rwadurian/backend/services/blockchain-service/src/infrastructure/blockchain/address-derivation.adapter.ts

189 lines
5.3 KiB
TypeScript

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;
}
}
}