251 lines
8.1 KiB
TypeScript
251 lines
8.1 KiB
TypeScript
/**
|
||
* MPC Wallet Service
|
||
*
|
||
* 使用 MPC 2-of-3 协议生成三链钱包地址
|
||
* 并对地址进行签名验证
|
||
*
|
||
* 调用路径: identity-service → mpc-service → mpc-system
|
||
*/
|
||
|
||
import { Injectable, Logger } from '@nestjs/common';
|
||
import { createHash } from 'crypto';
|
||
import { MpcClientService } from './mpc-client.service';
|
||
|
||
export interface MpcWalletGenerationParams {
|
||
userId: string;
|
||
username: string; // 用户名 (用于 MPC keygen)
|
||
deviceId: string;
|
||
}
|
||
|
||
export interface ChainWalletInfo {
|
||
chainType: 'KAVA' | 'DST' | 'BSC';
|
||
address: string;
|
||
publicKey: string;
|
||
addressDigest: string;
|
||
signature: string; // 64 bytes hex (R + S)
|
||
}
|
||
|
||
export interface MpcWalletGenerationResult {
|
||
publicKey: string; // MPC 公钥
|
||
delegateShare: string; // delegate share (加密的用户分片)
|
||
serverParties: string[]; // 服务器 party IDs
|
||
wallets: ChainWalletInfo[]; // 三条链的钱包信息
|
||
sessionId: string; // MPC 会话ID
|
||
}
|
||
|
||
@Injectable()
|
||
export class MpcWalletService {
|
||
private readonly logger = new Logger(MpcWalletService.name);
|
||
|
||
// 三条链的地址生成配置
|
||
private readonly chainConfigs = {
|
||
BSC: {
|
||
name: 'Binance Smart Chain',
|
||
prefix: '0x',
|
||
derivationPath: "m/44'/60'/0'/0/0", // EVM 兼容链
|
||
addressType: 'evm' as const,
|
||
},
|
||
KAVA: {
|
||
name: 'Kava EVM',
|
||
prefix: '0x',
|
||
derivationPath: "m/44'/60'/0'/0/0", // Kava EVM 使用以太坊兼容地址
|
||
addressType: 'evm' as const,
|
||
},
|
||
DST: {
|
||
name: 'Durian Star Token',
|
||
prefix: 'dst', // Cosmos Bech32 前缀
|
||
derivationPath: "m/44'/118'/0'/0/0", // Cosmos 标准路径
|
||
addressType: 'cosmos' as const,
|
||
},
|
||
};
|
||
|
||
constructor(
|
||
private readonly mpcClient: MpcClientService,
|
||
) {}
|
||
|
||
/**
|
||
* 使用 MPC 2-of-3 生成三链钱包
|
||
*
|
||
* 流程:
|
||
* 1. 生成 MPC 密钥 (2-of-3)
|
||
* 2. 从公钥派生三条链的地址
|
||
* 3. 计算地址摘要
|
||
* 4. 使用 MPC 签名对摘要进行签名
|
||
* 5. 返回完整的钱包信息
|
||
*/
|
||
async generateMpcWallet(params: MpcWalletGenerationParams): Promise<MpcWalletGenerationResult> {
|
||
this.logger.log(`Generating MPC wallet for user=${params.userId}, username=${params.username}`);
|
||
|
||
// Step 1: 生成 MPC 密钥
|
||
const keygenResult = await this.mpcClient.executeKeygen({
|
||
sessionId: this.mpcClient.generateSessionId(),
|
||
username: params.username,
|
||
threshold: 1, // t in t-of-n (2-of-3 means t=1)
|
||
totalParties: 3,
|
||
requireDelegate: true,
|
||
});
|
||
|
||
this.logger.log(`MPC keygen completed: publicKey=${keygenResult.publicKey}`);
|
||
|
||
// Step 2: 从公钥派生三条链的地址
|
||
const walletAddresses = await this.deriveChainAddresses(keygenResult.publicKey);
|
||
|
||
// Step 3: 计算地址摘要
|
||
const addressDigest = this.computeAddressDigest(walletAddresses);
|
||
|
||
// Step 4: 使用 MPC 签名对摘要进行签名
|
||
const signingResult = await this.mpcClient.executeSigning({
|
||
username: params.username,
|
||
messageHash: addressDigest,
|
||
});
|
||
|
||
this.logger.log(`MPC signing completed: signature=${signingResult.signature.slice(0, 16)}...`);
|
||
|
||
// Step 5: 构建钱包信息
|
||
const wallets: ChainWalletInfo[] = walletAddresses.map((wa) => ({
|
||
chainType: wa.chainType as 'KAVA' | 'DST' | 'BSC',
|
||
address: wa.address,
|
||
publicKey: keygenResult.publicKey,
|
||
addressDigest: this.computeSingleAddressDigest(wa.address, wa.chainType),
|
||
signature: signingResult.signature,
|
||
}));
|
||
|
||
return {
|
||
publicKey: keygenResult.publicKey,
|
||
delegateShare: keygenResult.delegateShare.encryptedShare,
|
||
serverParties: keygenResult.serverParties,
|
||
wallets,
|
||
sessionId: keygenResult.sessionId,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 验证钱包地址签名
|
||
*
|
||
* 用于检测地址是否被篡改
|
||
*
|
||
* @param address 钱包地址
|
||
* @param chainType 链类型
|
||
* @param publicKey 公钥 (hex)
|
||
* @param signature 签名 (64 bytes hex: R + S)
|
||
*/
|
||
async verifyWalletSignature(
|
||
address: string,
|
||
chainType: string,
|
||
publicKey: string,
|
||
signature: string,
|
||
): Promise<boolean> {
|
||
try {
|
||
const { ethers } = await import('ethers');
|
||
|
||
// 签名格式: R (32 bytes) + S (32 bytes) = 64 bytes hex
|
||
if (signature.length !== 128) {
|
||
this.logger.error(`Invalid signature length: ${signature.length}, expected 128`);
|
||
return false;
|
||
}
|
||
|
||
const r = '0x' + signature.slice(0, 64);
|
||
const s = '0x' + signature.slice(64, 128);
|
||
|
||
// 计算地址摘要
|
||
const digest = this.computeSingleAddressDigest(address, chainType);
|
||
const digestBytes = Buffer.from(digest, 'hex');
|
||
|
||
// 尝试两种 recovery id
|
||
for (const v of [27, 28]) {
|
||
try {
|
||
const sig = ethers.Signature.from({ r, s, v });
|
||
const recoveredPubKey = ethers.SigningKey.recoverPublicKey(digestBytes, sig);
|
||
const compressedRecovered = ethers.SigningKey.computePublicKey(recoveredPubKey, true);
|
||
|
||
if (compressedRecovered.slice(2).toLowerCase() === publicKey.toLowerCase()) {
|
||
return true;
|
||
}
|
||
} catch {
|
||
// 尝试下一个 v 值
|
||
}
|
||
}
|
||
|
||
return false;
|
||
} catch (error) {
|
||
this.logger.error(`Signature verification failed: ${error.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从 MPC 公钥派生三条链的地址
|
||
*
|
||
* - BSC/KAVA: EVM 地址 (keccak256)
|
||
* - DST: Cosmos Bech32 地址 (ripemd160(sha256))
|
||
*/
|
||
private async deriveChainAddresses(publicKey: string): Promise<{ chainType: string; address: string }[]> {
|
||
const { ethers } = await import('ethers');
|
||
const { bech32 } = await import('bech32');
|
||
|
||
// MPC 公钥 (压缩格式,33 bytes)
|
||
const pubKeyHex = publicKey.startsWith('0x') ? publicKey : '0x' + publicKey;
|
||
const compressedPubKeyBytes = Buffer.from(pubKeyHex.replace('0x', ''), 'hex');
|
||
|
||
// 解压公钥 (如果是压缩格式)
|
||
let uncompressedPubKey: string;
|
||
if (pubKeyHex.length === 68) {
|
||
// 压缩格式 (33 bytes = 66 hex chars + 0x)
|
||
uncompressedPubKey = ethers.SigningKey.computePublicKey(pubKeyHex, false);
|
||
} else {
|
||
uncompressedPubKey = pubKeyHex;
|
||
}
|
||
|
||
// ===== EVM 地址派生 (BSC, KAVA) =====
|
||
// 地址 = keccak256(公钥[1:])[12:]
|
||
const pubKeyBytes = Buffer.from(uncompressedPubKey.slice(4), 'hex'); // 去掉 0x04 前缀
|
||
const addressHash = ethers.keccak256(pubKeyBytes);
|
||
const evmAddress = ethers.getAddress('0x' + addressHash.slice(-40));
|
||
|
||
// ===== Cosmos 地址派生 (DST) =====
|
||
// 地址 = bech32(prefix, ripemd160(sha256(compressed_pubkey)))
|
||
const sha256Hash = createHash('sha256').update(compressedPubKeyBytes).digest();
|
||
const ripemd160Hash = createHash('ripemd160').update(sha256Hash).digest();
|
||
const dstAddress = bech32.encode(this.chainConfigs.DST.prefix, bech32.toWords(ripemd160Hash));
|
||
|
||
return [
|
||
{ chainType: 'BSC', address: evmAddress },
|
||
{ chainType: 'KAVA', address: evmAddress },
|
||
{ chainType: 'DST', address: dstAddress },
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 计算三个地址的联合摘要
|
||
*
|
||
* digest = SHA256(BSC地址 + KAVA地址 + DST地址)
|
||
*/
|
||
private computeAddressDigest(addresses: { chainType: string; address: string }[]): string {
|
||
// 按链类型排序以确保一致性
|
||
const sortedAddresses = [...addresses].sort((a, b) =>
|
||
a.chainType.localeCompare(b.chainType),
|
||
);
|
||
|
||
// 拼接地址
|
||
const concatenated = sortedAddresses.map((a) => a.address.toLowerCase()).join('');
|
||
|
||
// 计算 SHA256 摘要
|
||
return createHash('sha256').update(concatenated).digest('hex');
|
||
}
|
||
|
||
/**
|
||
* 计算单个地址的摘要
|
||
*/
|
||
private computeSingleAddressDigest(address: string, chainType: string): string {
|
||
const message = `${chainType}:${address.toLowerCase()}`;
|
||
return createHash('sha256').update(message).digest('hex');
|
||
}
|
||
|
||
/**
|
||
* 获取所有支持的链类型
|
||
*/
|
||
getSupportedChains(): string[] {
|
||
return Object.keys(this.chainConfigs);
|
||
}
|
||
}
|