import { Injectable, Logger } from '@nestjs/common'; import { keccak256, getBytes, sha256, ripemd160 } from 'ethers'; import { bech32 } from 'bech32'; import { EvmAddress } from '@/domain/value-objects'; import { ChainTypeEnum } from '@/domain/enums'; export interface DerivedAddress { chainType: ChainTypeEnum; address: string; } /** * 地址派生适配器 * 从 MPC 公钥派生多链钱包地址 */ @Injectable() export class AddressDerivationAdapter { private readonly logger = new Logger(AddressDerivationAdapter.name); /** * 从压缩公钥派生 EVM 地址 * * @param compressedPublicKey 压缩格式的公钥 (33 bytes, 0x02/0x03 开头) * @returns EVM 地址 */ deriveEvmAddress(compressedPublicKey: string): string { // 移除 0x 前缀 const pubKeyHex = compressedPublicKey.replace('0x', ''); // 验证压缩公钥格式 if (pubKeyHex.length !== 66) { throw new Error(`Invalid compressed public key length: ${pubKeyHex.length}, expected 66`); } const prefix = pubKeyHex.slice(0, 2); if (prefix !== '02' && prefix !== '03') { throw new Error(`Invalid compressed public key prefix: ${prefix}, expected 02 or 03`); } // 解压缩公钥 const uncompressedPubKey = this.decompressPublicKey(pubKeyHex); // 移除 04 前缀(非压缩公钥标识) const pubKeyWithoutPrefix = uncompressedPubKey.slice(2); // Keccak256 哈希 const hash = keccak256(getBytes('0x' + pubKeyWithoutPrefix)); // 取最后 20 bytes 作为地址 const address = '0x' + hash.slice(-40); return address; } /** * 解压缩公钥 * * 使用 secp256k1 曲线解压缩 */ private decompressPublicKey(compressedPubKeyHex: string): string { const p = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'); const prefix = parseInt(compressedPubKeyHex.slice(0, 2), 16); const x = BigInt('0x' + compressedPubKeyHex.slice(2)); // y² = x³ + 7 (mod p) const ySquared = (x ** 3n + 7n) % p; // 计算模平方根 let y = this.modPow(ySquared, (p + 1n) / 4n, p); // 根据前缀选择 y 的奇偶性 const isYOdd = y % 2n === 1n; const shouldBeOdd = prefix === 0x03; if (isYOdd !== shouldBeOdd) { y = p - y; } // 返回非压缩格式 (04 + x + y) const xHex = x.toString(16).padStart(64, '0'); const yHex = y.toString(16).padStart(64, '0'); return '04' + xHex + yHex; } /** * 模幂运算 */ private modPow(base: bigint, exp: bigint, mod: bigint): bigint { let result = 1n; base = base % mod; while (exp > 0n) { if (exp % 2n === 1n) { result = (result * base) % mod; } exp = exp / 2n; base = (base * base) % mod; } return result; } /** * 从压缩公钥派生 Cosmos 地址 (bech32 格式) * * @param compressedPublicKey 压缩格式的公钥 (33 bytes, 0x02/0x03 开头) * @param prefix bech32 地址前缀 (如 'kava', 'dst') * @returns bech32 格式的地址 */ deriveCosmosAddress(compressedPublicKey: string, prefix: string): string { // 移除 0x 前缀 const pubKeyHex = compressedPublicKey.replace('0x', ''); // 验证压缩公钥格式 if (pubKeyHex.length !== 66) { throw new Error(`Invalid compressed public key length: ${pubKeyHex.length}, expected 66`); } // SHA256 哈希 const pubKeyBytes = getBytes('0x' + pubKeyHex); const sha256Hash = sha256(pubKeyBytes); // RIPEMD160 哈希 (得到 20 bytes) const ripemd160Hash = ripemd160(sha256Hash); // 转换为 5-bit words 用于 bech32 编码 const hashBytes = getBytes(ripemd160Hash); const words = bech32.toWords(Buffer.from(hashBytes)); // bech32 编码 const address = bech32.encode(prefix, words); this.logger.debug(`Derived Cosmos address with prefix '${prefix}': ${address}`); return address; } /** * 从公钥派生所有支持链的地址 */ deriveAllAddresses(compressedPublicKey: string): DerivedAddress[] { const addresses: DerivedAddress[] = []; this.logger.log(`[DERIVE] Starting address derivation for public key: ${compressedPublicKey.slice(0, 20)}...`); // EVM 链共用同一个地址 const evmAddress = this.deriveEvmAddress(compressedPublicKey); this.logger.log(`[DERIVE] EVM address derived: ${evmAddress}`); // KAVA (EVM 格式 - 0x...) - Kava EVM 兼容链 addresses.push({ chainType: ChainTypeEnum.KAVA, address: evmAddress, }); this.logger.log(`[DERIVE] KAVA address (EVM): ${evmAddress}`); // DST (Cosmos bech32 格式 - dst1...) const dstAddress = this.deriveCosmosAddress(compressedPublicKey, 'dst'); addresses.push({ chainType: ChainTypeEnum.DST, address: dstAddress, }); this.logger.log(`[DERIVE] DST address (Cosmos): ${dstAddress}`); // BSC (EVM 格式 - 0x...) addresses.push({ chainType: ChainTypeEnum.BSC, address: evmAddress, }); this.logger.log(`[DERIVE] BSC address (EVM): ${evmAddress}`); this.logger.log(`[DERIVE] Successfully derived ${addresses.length} addresses from public key`); return addresses; } /** * 验证地址格式 */ validateEvmAddress(address: string): boolean { try { EvmAddress.create(address); return true; } catch { return false; } } }