102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
/**
|
|
* Wallet generation: BIP39 mnemonic + multi-chain address derivation.
|
|
* Server-side для custodial-флоу.
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
export function generateMnemonic(): string {
|
|
return bip39.generateMnemonic(128);
|
|
}
|
|
|
|
export function validateMnemonic(m: string): boolean {
|
|
return bip39.validateMnemonic(m);
|
|
}
|
|
|
|
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 shares)
|
|
const ethWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.ETH);
|
|
const ethAddress = ethers.utils.getAddress(ethWallet.address);
|
|
|
|
// BTC P2WPKH bech32
|
|
const btcRoot = bip32.fromSeed(seed);
|
|
const btcChild = btcRoot.derivePath(DERIVATION_PATHS.BTC);
|
|
if (!btcChild.publicKey) throw new Error('BTC derivation failed');
|
|
const btcPayment = bitcoin.payments.p2wpkh({
|
|
pubkey: Buffer.from(btcChild.publicKey),
|
|
network: bitcoin.networks.bitcoin,
|
|
});
|
|
if (!btcPayment.address) throw new Error('BTC payment derivation failed');
|
|
|
|
// TRX (same secp256k1 + keccak256 as ETH, different encoding)
|
|
const trxWallet = ethers.Wallet.fromMnemonic(mnemonic, DERIVATION_PATHS.TRX);
|
|
const trxAddress = ethAddressToTron(trxWallet.address);
|
|
|
|
// SOL (ed25519)
|
|
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 address (0x...) → TRX base58check (T...).
|
|
* Используют одну curve и keccak256-derivation; различается только prefix (0x41) + encoding.
|
|
*/
|
|
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]);
|
|
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])));
|
|
}
|