security: remove .env from tracking (contains secrets)

This commit is contained in:
ZOMBIIIIIII
2026-05-11 18:15:21 +03:00
parent 295c3a9d6d
commit 64696b334c
26 changed files with 1840 additions and 128 deletions

View File

@@ -0,0 +1,121 @@
/**
* Wallet generation: BIP39 mnemonic + multi-chain address derivation.
* Server-side для custodial-флоу.
*
* Поддерживаемые chains (BIP44):
* ETH/BSC — m/44'/60'/0'/0/0 (secp256k1, EIP-55 checksum)
* BTC — m/84'/0'/0'/0/0 (P2WPKH bech32)
* TRX — m/44'/195'/0'/0/0 (secp256k1, base58check + prefix 0x41)
* SOL — m/44'/501'/0'/0' (ed25519)
*/
import { ethers } from 'ethers';
import { createHash } from 'crypto';
import bs58 from 'bs58';
import * as bip39 from 'bip39';
import { BIP32Factory } from 'bip32';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';
import { Keypair } from '@solana/web3.js';
import { derivePath } from 'ed25519-hd-key';
import type { ChainCode } from '../lib/address-validators';
const bip32 = BIP32Factory(ecc);
export const DERIVATION_PATHS: Record<ChainCode, string> = {
ETH: "m/44'/60'/0'/0/0",
BSC: "m/44'/60'/0'/0/0",
BTC: "m/84'/0'/0'/0/0",
TRX: "m/44'/195'/0'/0/0",
SOL: "m/44'/501'/0'/0'",
};
export const ALL_CHAINS: ChainCode[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL'];
export interface DerivedWallet {
chain: ChainCode;
address: string;
derivationPath: string;
}
/**
* Сгенерить 12-словную BIP39 мнемонику.
*/
export function generateMnemonic(): string {
return bip39.generateMnemonic(128);
}
/**
* Валидация существующей mnemonic (не используется в текущем флоу — оставлено на будущее).
*/
export function validateMnemonic(m: string): boolean {
return bip39.validateMnemonic(m);
}
/**
* Деривить адреса для всех chains из одной mnemonic.
*/
export async function deriveAllAddresses(mnemonic: string): Promise<DerivedWallet[]> {
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic');
}
const seed = await bip39.mnemonicToSeed(mnemonic);
const seedHex = seed.toString('hex');
// ETH (BSC использует тот же адрес)
const ethWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.ETH);
const ethAddress = ethers.utils.getAddress(ethWallet.address); // EIP-55 checksum
// BTC (P2WPKH bech32)
const btcRoot = bip32.fromSeed(seed);
const btcChild = btcRoot.derivePath(DERIVATION_PATHS.BTC);
if (!btcChild.publicKey) {
throw new Error('BTC derivation failed: no public key');
}
const btcPayment = bitcoin.payments.p2wpkh({
pubkey: Buffer.from(btcChild.publicKey),
network: bitcoin.networks.bitcoin,
});
if (!btcPayment.address) {
throw new Error('BTC derivation failed: no address');
}
// TRX (derive privkey same curve as ETH, convert pubkey → TRX base58check address)
const trxWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.TRX);
const trxAddress = ethAddressToTron(trxWallet.address);
// SOL (ed25519 derivation)
const { key: solKey } = derivePath(DERIVATION_PATHS.SOL, seedHex);
if (!solKey || solKey.length !== 32) {
throw new Error('SOL derivation produced invalid seed length');
}
const solKeypair = Keypair.fromSeed(solKey);
const solAddress = solKeypair.publicKey.toBase58();
return [
{ chain: 'ETH', address: ethAddress, derivationPath: DERIVATION_PATHS.ETH },
{ chain: 'BSC', address: ethAddress, derivationPath: DERIVATION_PATHS.BSC },
{ chain: 'BTC', address: btcPayment.address, derivationPath: DERIVATION_PATHS.BTC },
{ chain: 'TRX', address: trxAddress, derivationPath: DERIVATION_PATHS.TRX },
{ chain: 'SOL', address: solAddress, derivationPath: DERIVATION_PATHS.SOL },
];
}
/**
* ETH-style address (0x...) → TRX base58check (T...).
* TRX и ETH используют одну curve и одну keccak256-логику для получения 20-байтного хеша.
* Различие только в префиксе (0x41 vs ничего) и в кодировке (base58check vs hex).
*/
export function ethAddressToTron(ethAddr: string): string {
const hex = ethAddr.toLowerCase().replace(/^0x/, '');
if (hex.length !== 40) {
throw new Error('ethAddressToTron: invalid input length');
}
const bytes = Buffer.from(hex, 'hex');
const prefixed = Buffer.concat([Buffer.from([0x41]), bytes]); // 21 байт
const h1 = createHash('sha256').update(prefixed).digest();
const h2 = createHash('sha256').update(h1).digest();
const checksum = h2.subarray(0, 4);
return bs58.encode(new Uint8Array(Buffer.concat([prefixed, checksum])));
}