import * as crypto from 'crypto'; import { bech32 } from 'bech32'; // ============================================================================= // 链配置 // ============================================================================= export interface ChainConfig { name: string; prefix: string; coinType: number; curve: 'secp256k1' | 'ed25519'; derivationPath: string; } export const CHAIN_CONFIGS: Record = { kava: { name: 'Kava', prefix: 'kava', coinType: 459, curve: 'secp256k1', derivationPath: "m/44'/459'/0'/0/0", }, cosmos: { name: 'Cosmos Hub', prefix: 'cosmos', coinType: 118, curve: 'secp256k1', derivationPath: "m/44'/118'/0'/0/0", }, osmosis: { name: 'Osmosis', prefix: 'osmo', coinType: 118, curve: 'secp256k1', derivationPath: "m/44'/118'/0'/0/0", }, ethereum: { name: 'Ethereum', prefix: '0x', coinType: 60, curve: 'secp256k1', derivationPath: "m/44'/60'/0'/0/0", }, }; // ============================================================================= // 地址派生工具 // ============================================================================= /** * 从公钥派生 Bech32 地址 (Cosmos 系列) * * 流程: * 1. 公钥 → SHA256 → RIPEMD160 → 20字节地址 * 2. 20字节地址 → Bech32 编码 */ export function deriveCosmosAddress(publicKeyHex: string, prefix: string): string { // 移除可能的 0x 前缀 const cleanHex = publicKeyHex.startsWith('0x') ? publicKeyHex.slice(2) : publicKeyHex; const publicKeyBytes = Buffer.from(cleanHex, 'hex'); // 对于 secp256k1,需要压缩公钥 (33 bytes) // 如果是未压缩公钥 (65 bytes),需要先压缩 let compressedKey: Buffer = publicKeyBytes; if (publicKeyBytes.length === 65) { compressedKey = compressSecp256k1PublicKey(publicKeyBytes); } else if (publicKeyBytes.length === 64) { // 没有前缀的未压缩公钥 const uncompressed = Buffer.concat([Buffer.from([0x04]), publicKeyBytes]); compressedKey = compressSecp256k1PublicKey(uncompressed); } // SHA256 → RIPEMD160 const sha256Hash = crypto.createHash('sha256').update(compressedKey).digest(); const ripemd160Hash = crypto.createHash('ripemd160').update(sha256Hash).digest(); // Bech32 编码 const words = bech32.toWords(ripemd160Hash); const address = bech32.encode(prefix, words); return address; } /** * 从公钥派生以太坊地址 * * 流程: * 1. 未压缩公钥 (去掉 04 前缀) → Keccak256 → 取后 20 字节 */ export function deriveEthereumAddress(publicKeyHex: string): string { const cleanHex = publicKeyHex.startsWith('0x') ? publicKeyHex.slice(2) : publicKeyHex; const publicKeyBytes = Buffer.from(cleanHex, 'hex'); // 需要未压缩公钥的 x, y 坐标 (64 bytes) let uncompressedKey: Buffer; if (publicKeyBytes.length === 33) { // 压缩公钥,需要解压 uncompressedKey = decompressSecp256k1PublicKey(publicKeyBytes); } else if (publicKeyBytes.length === 65) { // 未压缩公钥,去掉 04 前缀 uncompressedKey = publicKeyBytes.slice(1) as Buffer; } else if (publicKeyBytes.length === 64) { uncompressedKey = publicKeyBytes; } else { throw new Error(`Invalid public key length: ${publicKeyBytes.length}`); } // Keccak256 (使用 keccak256 而不是 sha3-256) const { keccak_256 } = require('@noble/hashes/sha3'); const hash = keccak_256(uncompressedKey); // 取后 20 字节 const addressBytes = hash.slice(-20); const address = '0x' + Buffer.from(addressBytes).toString('hex'); return checksumAddress(address); } /** * 从 Ed25519 公钥派生地址 (用于某些链) */ export function deriveEd25519Address(publicKeyHex: string, prefix: string): string { const cleanHex = publicKeyHex.startsWith('0x') ? publicKeyHex.slice(2) : publicKeyHex; const publicKeyBytes = Buffer.from(cleanHex, 'hex'); // SHA256 → RIPEMD160 const sha256Hash = crypto.createHash('sha256').update(publicKeyBytes).digest(); const ripemd160Hash = crypto.createHash('ripemd160').update(sha256Hash).digest(); // Bech32 编码 const words = bech32.toWords(ripemd160Hash); const address = bech32.encode(prefix, words); return address; } /** * 压缩 secp256k1 公钥 */ function compressSecp256k1PublicKey(uncompressed: Buffer): Buffer { if (uncompressed.length !== 65 || uncompressed[0] !== 0x04) { throw new Error('Invalid uncompressed public key'); } const x = uncompressed.slice(1, 33); const y = uncompressed.slice(33, 65); // 判断 y 是奇数还是偶数 const prefix = y[31] % 2 === 0 ? 0x02 : 0x03; return Buffer.concat([Buffer.from([prefix]), x]); } /** * 解压缩 secp256k1 公钥 * 使用椭圆曲线数学: y² = x³ + 7 (mod p) */ function decompressSecp256k1PublicKey(compressed: Buffer): Buffer { if (compressed.length !== 33) { throw new Error('Invalid compressed public key'); } // secp256k1 曲线参数 const p = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'); const prefix = compressed[0]; const x = BigInt('0x' + compressed.slice(1).toString('hex')); // 计算 y² = x³ + 7 (mod p) const xCubed = modPow(x, 3n, p); const ySquared = (xCubed + 7n) % p; // 计算平方根 (p ≡ 3 mod 4, 所以 y = ySquared^((p+1)/4) mod p) let y = modPow(ySquared, (p + 1n) / 4n, p); // 根据前缀选择正确的 y 值 const isYOdd = y % 2n === 1n; const shouldBeOdd = prefix === 0x03; if (isYOdd !== shouldBeOdd) { y = p - y; } // 转换为 Buffer (64 bytes: x || y) const xBuffer = Buffer.from(x.toString(16).padStart(64, '0'), 'hex'); const yBuffer = Buffer.from(y.toString(16).padStart(64, '0'), 'hex'); return Buffer.concat([xBuffer, yBuffer]); } /** * 模幂运算 */ function modPow(base: bigint, exponent: bigint, modulus: bigint): bigint { let result = 1n; base = base % modulus; while (exponent > 0n) { if (exponent % 2n === 1n) { result = (result * base) % modulus; } exponent = exponent / 2n; base = (base * base) % modulus; } return result; } /** * EIP-55 校验和地址 */ function checksumAddress(address: string): string { const { keccak_256 } = require('@noble/hashes/sha3'); const addr = address.toLowerCase().replace('0x', ''); const hash = Buffer.from(keccak_256(Buffer.from(addr, 'utf8'))).toString('hex'); let result = '0x'; for (let i = 0; i < addr.length; i++) { if (parseInt(hash[i], 16) >= 8) { result += addr[i].toUpperCase(); } else { result += addr[i]; } } return result; } // ============================================================================= // 地址派生服务 // ============================================================================= export interface DerivedAddress { chain: string; chainName: string; prefix: string; address: string; derivationPath: string; publicKeyHex: string; } /** * 地址派生服务 * * 注意:TSS keygen 生成的是聚合公钥,不是派生公钥。 * 对于需要不同链的地址,我们直接使用聚合公钥派生地址, * 而不是像 HD 钱包那样从种子派生。 * * 这意味着: * - 所有链使用相同的公钥 * - 不同链的地址只是编码方式不同 * - 这是 TSS 钱包的标准做法 */ export class AddressDerivationService { /** * 从 TSS 聚合公钥派生指定链的地址 */ deriveAddress(publicKeyHex: string, chain: string): DerivedAddress { const config = CHAIN_CONFIGS[chain]; if (!config) { throw new Error(`Unsupported chain: ${chain}`); } let address: string; if (chain === 'ethereum') { address = deriveEthereumAddress(publicKeyHex); } else if (config.curve === 'ed25519') { address = deriveEd25519Address(publicKeyHex, config.prefix); } else { // Cosmos 系列 (kava, cosmos, osmosis 等) address = deriveCosmosAddress(publicKeyHex, config.prefix); } return { chain, chainName: config.name, prefix: config.prefix, address, derivationPath: config.derivationPath, publicKeyHex, }; } /** * 派生所有支持的链地址 */ deriveAllAddresses(publicKeyHex: string): DerivedAddress[] { const addresses: DerivedAddress[] = []; for (const chain of Object.keys(CHAIN_CONFIGS)) { try { const derived = this.deriveAddress(publicKeyHex, chain); addresses.push(derived); } catch (err) { console.error(`Failed to derive ${chain} address:`, err); } } return addresses; } /** * 验证地址格式 */ validateAddress(address: string, chain: string): boolean { const config = CHAIN_CONFIGS[chain]; if (!config) { return false; } if (chain === 'ethereum') { return /^0x[a-fA-F0-9]{40}$/.test(address); } try { const decoded = bech32.decode(address); return decoded.prefix === config.prefix; } catch { return false; } } /** * 获取支持的链列表 */ getSupportedChains(): ChainConfig[] { return Object.values(CHAIN_CONFIGS); } } // 导出单例 export const addressDerivationService = new AddressDerivationService();