rwadurian/backend/services/identity-service/src/infrastructure/external/mpc/mpc-wallet.service.ts

251 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
}
}