From 289691dc3c4e6d26a6505b080a70179960d193ac Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 6 Dec 2025 18:42:48 -0800 Subject: [PATCH] fix: align mpc-service migration with schema and fix identity-service compile errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update mpc-service migration to match new gateway mode schema (mpc_wallets, mpc_shares) - Remove old MySQL migrations (party_shares, session_states, share_backups) - Fix MpcSignature type to use string format (64 bytes hex: R + S) - Add persistence layer conversion functions for DB compatibility - Fix method names in domain services (checkDeviceNotRegistered, generateNextUserSequence) - Update wallet generator interface to use delegateShare instead of clientShareData 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../identity-service/package-lock.json | 1 + .../auto-create-account.handler.ts | 9 +- .../src/application/commands/index.ts | 2 +- .../services/user-application.service.ts | 37 ++--- .../domain/entities/wallet-address.entity.ts | 52 ++++--- .../src/domain/services/index.ts | 6 +- .../services/wallet-generator.service.ts | 8 +- .../external/mpc/mpc-wallet.service.ts | 147 +++++++----------- .../entities/wallet-address.entity.ts | 23 +++ .../mappers/user-account.mapper.ts | 5 +- .../user-account.repository.impl.ts | 32 ++-- .../001_create_party_shares_table.sql | 30 ---- .../002_create_session_states_table.sql | 33 ---- .../003_create_share_backups_table.sql | 20 --- .../20241204000000_init/migration.sql | 127 ++++++--------- .../prisma/migrations/migration_lock.toml | 6 +- 16 files changed, 198 insertions(+), 340 deletions(-) delete mode 100644 backend/services/mpc-service/database/migrations/001_create_party_shares_table.sql delete mode 100644 backend/services/mpc-service/database/migrations/002_create_session_states_table.sql delete mode 100644 backend/services/mpc-service/database/migrations/003_create_share_backups_table.sql diff --git a/backend/services/identity-service/package-lock.json b/backend/services/identity-service/package-lock.json index 5594416a..81d34618 100644 --- a/backend/services/identity-service/package-lock.json +++ b/backend/services/identity-service/package-lock.json @@ -259,6 +259,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", diff --git a/backend/services/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts b/backend/services/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts index 0432e23b..c7cfef06 100644 --- a/backend/services/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts +++ b/backend/services/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts @@ -53,6 +53,7 @@ export class AutoCreateAccountHandler { this.logger.log(`Generating MPC wallet for user=${account.userId.toString()}`); const mpcResult = await this.walletGenerator.generateMpcWalletSystem({ userId: account.userId.toString(), + username: accountSequence.value.toString(), // 使用账户序列号作为用户名 deviceId: command.deviceId, }); @@ -62,11 +63,11 @@ export class AutoCreateAccountHandler { mpcResult.wallets, ); - // 保存备份分片到备份服务 - this.logger.log(`Storing backup share for user=${account.userId.toString()}`); + // 保存 delegate share 到备份服务 (用于恢复) + this.logger.log(`Storing delegate share for user=${account.userId.toString()}`); await this.mpcShareStorage.storeBackupShare({ userId: account.userId.toString(), - shareData: mpcResult.backupShareData, + shareData: mpcResult.delegateShare, publicKey: mpcResult.publicKey, }); @@ -90,7 +91,7 @@ export class AutoCreateAccountHandler { accountSequence: account.accountSequence.value, referralCode: account.referralCode.value, mnemonic: '', // MPC 模式下不再使用助记词 - clientShareData: mpcResult.clientShareData, // 客户端需安全存储此分片 + delegateShare: mpcResult.delegateShare, // delegate share (客户端需安全存储) publicKey: mpcResult.publicKey, walletAddresses: { kava: wallets.get(ChainType.KAVA)!.address, diff --git a/backend/services/identity-service/src/application/commands/index.ts b/backend/services/identity-service/src/application/commands/index.ts index 660bbe82..fcdf75f5 100644 --- a/backend/services/identity-service/src/application/commands/index.ts +++ b/backend/services/identity-service/src/application/commands/index.ts @@ -151,7 +151,7 @@ export interface AutoCreateAccountResult { accountSequence: number; referralCode: string; mnemonic: string; // 兼容字段,MPC模式下为空 - clientShareData?: string; // MPC 客户端分片数据 (需安全存储) + delegateShare?: string; // MPC delegate share (客户端需安全存储) publicKey?: string; // MPC 公钥 walletAddresses: { kava: string; dst: string; bsc: string }; accessToken: string; diff --git a/backend/services/identity-service/src/application/services/user-application.service.ts b/backend/services/identity-service/src/application/services/user-application.service.ts index 935e960d..335ca351 100644 --- a/backend/services/identity-service/src/application/services/user-application.service.ts +++ b/backend/services/identity-service/src/application/services/user-application.service.ts @@ -59,8 +59,8 @@ export class UserApplicationService { async autoCreateAccount(command: AutoCreateAccountCommand): Promise { this.logger.log(`Creating account with MPC 2-of-3 for device: ${command.deviceId}`); - // 1. 验证设备ID - const deviceValidation = await this.validatorService.validateDeviceId(command.deviceId); + // 1. 验证设备ID (检查设备是否已创建过账户) + const deviceValidation = await this.validatorService.checkDeviceNotRegistered(command.deviceId); if (!deviceValidation.isValid) throw new ApplicationError(deviceValidation.errorMessage!); // 2. 验证邀请码 @@ -90,6 +90,7 @@ export class UserApplicationService { this.logger.log(`Generating MPC wallet for account sequence: ${accountSequence.value}`); const mpcResult = await this.mpcWalletService.generateMpcWallet({ userId: account.userId.toString(), + username: accountSequence.value.toString(), // 使用账户序列号作为用户名 deviceId: command.deviceId, }); @@ -115,37 +116,26 @@ export class UserApplicationService { await this.userRepository.save(account); await this.userRepository.saveWallets(account.userId, Array.from(wallets.values())); - // 9. 保存服务端 MPC 分片到数据库 (用于后续签名) - await this.mpcKeyShareRepository.saveServerShare({ - userId: account.userId.value, - publicKey: mpcResult.publicKey, - partyIndex: 0, // SERVER = party 0 - threshold: 2, - totalParties: 3, - encryptedShareData: mpcResult.serverShareData, - }); - this.logger.log(`Server MPC share saved for user: ${account.userId.toString()}`); - - // 10. 保存备份 MPC 分片到 backup-service (异地服务器) - // 注意: backup-service 必须部署在不同物理服务器,否则 MPC 安全性失效 - if (mpcResult.backupShareData) { + // 9. 保存 delegate share 到 backup-service (用于恢复) + // 注意: delegate share 由 mpc-service 代理生成,用户设备也应安全存储一份 + if (mpcResult.delegateShare) { await this.backupClient.storeBackupShare({ userId: account.userId.toString(), accountSequence: account.accountSequence.value, publicKey: mpcResult.publicKey, - encryptedShareData: mpcResult.backupShareData, + encryptedShareData: mpcResult.delegateShare, }); - this.logger.log(`Backup MPC share sent to backup-service for user: ${account.userId.toString()}`); + this.logger.log(`Delegate share sent to backup-service for user: ${account.userId.toString()}`); } - // 11. 生成 Token + // 10. 生成 Token const tokens = await this.tokenService.generateTokenPair({ userId: account.userId.toString(), accountSequence: account.accountSequence.value, deviceId: command.deviceId, }); - // 12. 发布领域事件 + // 11. 发布领域事件 await this.eventPublisher.publishAll(account.domainEvents); account.clearDomainEvents(); @@ -155,10 +145,9 @@ export class UserApplicationService { userId: account.userId.toString(), accountSequence: account.accountSequence.value, referralCode: account.referralCode.value, - // MPC 模式下返回客户端分片数据 (而不是助记词) - mnemonic: '', // MPC 模式不使用助记词,返回空字符串 - clientShareData: mpcResult.clientShareData, // 客户端需要安全存储的分片数据 - publicKey: mpcResult.publicKey, // MPC 公钥 + mnemonic: '', // MPC 模式不使用助记词 + delegateShare: mpcResult.delegateShare, // delegate share (客户端需安全存储) + publicKey: mpcResult.publicKey, walletAddresses: { kava: wallets.get(ChainType.KAVA)!.address, dst: wallets.get(ChainType.DST)!.address, diff --git a/backend/services/identity-service/src/domain/entities/wallet-address.entity.ts b/backend/services/identity-service/src/domain/entities/wallet-address.entity.ts index fc8c1088..304ea361 100644 --- a/backend/services/identity-service/src/domain/entities/wallet-address.entity.ts +++ b/backend/services/identity-service/src/domain/entities/wallet-address.entity.ts @@ -15,12 +15,9 @@ import { /** * MPC 签名信息 + * 64 bytes hex (R 32 bytes + S 32 bytes) */ -export interface MpcSignature { - r: string; - s: string; - v: number; -} +export type MpcSignature = string; export class WalletAddress { private readonly _addressId: AddressId; @@ -104,9 +101,7 @@ export class WalletAddress { address: string; publicKey: string; addressDigest: string; - mpcSignatureR: string; - mpcSignatureS: string; - mpcSignatureV: number; + mpcSignature: string; // 64 bytes hex status: AddressStatus; boundAt: Date; }): WalletAddress { @@ -117,11 +112,7 @@ export class WalletAddress { params.address, params.publicKey, params.addressDigest, - { - r: params.mpcSignatureR, - s: params.mpcSignatureS, - v: params.mpcSignatureV, - }, + params.mpcSignature, params.status, params.boundAt, ); @@ -149,18 +140,31 @@ export class WalletAddress { return false; } - // 验证签名 + // 签名格式: R (32 bytes) + S (32 bytes) = 64 bytes hex = 128 chars + if (this._mpcSignature.length !== 128) { + return false; + } + + const r = '0x' + this._mpcSignature.slice(0, 64); + const s = '0x' + this._mpcSignature.slice(64, 128); const digestBytes = Buffer.from(this._addressDigest, 'hex'); - const sig = ethers.Signature.from({ - r: '0x' + this._mpcSignature.r, - s: '0x' + this._mpcSignature.s, - v: this._mpcSignature.v + 27, - }); - const recoveredPubKey = ethers.SigningKey.recoverPublicKey(digestBytes, sig); - const compressedRecovered = ethers.SigningKey.computePublicKey(recoveredPubKey, true); + // 尝试两种 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); - return compressedRecovered.slice(2).toLowerCase() === this._publicKey.toLowerCase(); + if (compressedRecovered.slice(2).toLowerCase() === this._publicKey.toLowerCase()) { + return true; + } + } catch { + // 尝试下一个 v 值 + } + } + + return false; } catch { return false; } @@ -197,7 +201,7 @@ export class WalletAddress { params.address, '', '', - { r: '', s: '', v: 0 }, + '', // empty signature AddressStatus.ACTIVE, new Date(), ); @@ -220,7 +224,7 @@ export class WalletAddress { address, '', '', - { r: '', s: '', v: 0 }, + '', // empty signature AddressStatus.ACTIVE, new Date(), ); diff --git a/backend/services/identity-service/src/domain/services/index.ts b/backend/services/identity-service/src/domain/services/index.ts index 1d5af093..4318b9b3 100644 --- a/backend/services/identity-service/src/domain/services/index.ts +++ b/backend/services/identity-service/src/domain/services/index.ts @@ -34,7 +34,7 @@ export class AccountSequenceGeneratorService { private readonly repository: UserAccountRepository, ) {} - async generateNext(): Promise { + async generateNextUserSequence(): Promise { return this.repository.getNextAccountSequence(); } } @@ -53,9 +53,9 @@ export class UserValidatorService { return ValidationResult.success(); } - async validateDeviceId(deviceId: string): Promise { + async checkDeviceNotRegistered(deviceId: string): Promise { const existing = await this.repository.findByDeviceId(deviceId); - if (existing) return ValidationResult.failure('该设备已创建账户'); + if (existing) return ValidationResult.failure('该设备已创建过账户'); return ValidationResult.success(); } diff --git a/backend/services/identity-service/src/domain/services/wallet-generator.service.ts b/backend/services/identity-service/src/domain/services/wallet-generator.service.ts index 3b7154b6..2003c1e9 100644 --- a/backend/services/identity-service/src/domain/services/wallet-generator.service.ts +++ b/backend/services/identity-service/src/domain/services/wallet-generator.service.ts @@ -6,6 +6,7 @@ import { ChainType, Mnemonic, UserId } from '@/domain/value-objects'; */ export interface MpcWalletGenerationParams { userId: string; + username: string; // 用户名 (用于 MPC keygen) deviceId: string; } @@ -17,7 +18,7 @@ export interface ChainWalletInfo { address: string; publicKey: string; addressDigest: string; - signature: MpcSignature; + signature: MpcSignature; // 64 bytes hex (R + S) } /** @@ -25,9 +26,8 @@ export interface ChainWalletInfo { */ export interface MpcWalletGenerationResult { publicKey: string; - serverShareData: string; - clientShareData: string; - backupShareData: string; + delegateShare: string; // delegate share (加密的用户分片) + serverParties: string[]; // 服务器 party IDs wallets: ChainWalletInfo[]; sessionId: string; } diff --git a/backend/services/identity-service/src/infrastructure/external/mpc/mpc-wallet.service.ts b/backend/services/identity-service/src/infrastructure/external/mpc/mpc-wallet.service.ts index e1dadcba..59e9c04b 100644 --- a/backend/services/identity-service/src/infrastructure/external/mpc/mpc-wallet.service.ts +++ b/backend/services/identity-service/src/infrastructure/external/mpc/mpc-wallet.service.ts @@ -3,15 +3,17 @@ * * 使用 MPC 2-of-3 协议生成三链钱包地址 * 并对地址进行签名验证 + * + * 调用路径: identity-service → mpc-service → mpc-system */ import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; import { createHash } from 'crypto'; -import { MpcClientService, KeygenResult, SigningResult } from './mpc-client.service'; +import { MpcClientService } from './mpc-client.service'; export interface MpcWalletGenerationParams { userId: string; + username: string; // 用户名 (用于 MPC keygen) deviceId: string; } @@ -20,18 +22,13 @@ export interface ChainWalletInfo { address: string; publicKey: string; addressDigest: string; - signature: { - r: string; - s: string; - v: number; - }; + signature: string; // 64 bytes hex (R + S) } export interface MpcWalletGenerationResult { publicKey: string; // MPC 公钥 - serverShareData: string; // 服务端分片 (加密后存储) - clientShareData: string; // 客户端分片 (返回给用户设备) - backupShareData: string; // 备份分片 (存储在备份服务) + delegateShare: string; // delegate share (加密的用户分片) + serverParties: string[]; // 服务器 party IDs wallets: ChainWalletInfo[]; // 三条链的钱包信息 sessionId: string; // MPC 会话ID } @@ -39,7 +36,6 @@ export interface MpcWalletGenerationResult { @Injectable() export class MpcWalletService { private readonly logger = new Logger(MpcWalletService.name); - private readonly useLocalMpc: boolean; // 三条链的地址生成配置 private readonly chainConfigs = { @@ -65,11 +61,7 @@ export class MpcWalletService { constructor( private readonly mpcClient: MpcClientService, - private readonly configService: ConfigService, - ) { - // 开发环境使用本地模拟 MPC - this.useLocalMpc = this.configService.get('MPC_MODE', 'local') === 'local'; - } + ) {} /** * 使用 MPC 2-of-3 生成三链钱包 @@ -82,11 +74,16 @@ export class MpcWalletService { * 5. 返回完整的钱包信息 */ async generateMpcWallet(params: MpcWalletGenerationParams): Promise { - this.logger.log(`Generating MPC wallet for user=${params.userId}, device=${params.deviceId}`); + this.logger.log(`Generating MPC wallet for user=${params.userId}, username=${params.username}`); // Step 1: 生成 MPC 密钥 - const sessionId = this.mpcClient.generateSessionId(); - const keygenResult = await this.executeKeygen(sessionId, params); + 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}`); @@ -97,14 +94,12 @@ export class MpcWalletService { const addressDigest = this.computeAddressDigest(walletAddresses); // Step 4: 使用 MPC 签名对摘要进行签名 - const signingSessionId = this.mpcClient.generateSessionId(); - const signingResult = await this.executeSigning( - signingSessionId, - keygenResult.publicKey, - addressDigest, - ); + const signingResult = await this.mpcClient.executeSigning({ + username: params.username, + messageHash: addressDigest, + }); - this.logger.log(`MPC signing completed: r=${signingResult.signature.r}`); + this.logger.log(`MPC signing completed: signature=${signingResult.signature.slice(0, 16)}...`); // Step 5: 构建钱包信息 const wallets: ChainWalletInfo[] = walletAddresses.map((wa) => ({ @@ -115,18 +110,12 @@ export class MpcWalletService { signature: signingResult.signature, })); - // 提取各方分片 - const serverShare = keygenResult.partyShares.find((s) => s.partyIndex === 0); - const clientShare = keygenResult.partyShares.find((s) => s.partyIndex === 1); - const backupShare = keygenResult.partyShares.find((s) => s.partyIndex === 2); - return { publicKey: keygenResult.publicKey, - serverShareData: serverShare?.encryptedShareData || '', - clientShareData: clientShare?.encryptedShareData || '', - backupShareData: backupShare?.encryptedShareData || '', + delegateShare: keygenResult.delegateShare.encryptedShare, + serverParties: keygenResult.serverParties, wallets, - sessionId, + sessionId: keygenResult.sessionId, }; } @@ -134,86 +123,56 @@ export class MpcWalletService { * 验证钱包地址签名 * * 用于检测地址是否被篡改 + * + * @param address 钱包地址 + * @param chainType 链类型 + * @param publicKey 公钥 (hex) + * @param signature 签名 (64 bytes hex: R + S) */ async verifyWalletSignature( address: string, chainType: string, publicKey: string, - signature: { r: string; s: string; v: number }, + signature: string, ): Promise { 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'); - // 重建签名 - const sig = ethers.Signature.from({ - r: '0x' + signature.r, - s: '0x' + signature.s, - v: signature.v + 27, - }); + // 尝试两种 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); - // 从签名恢复公钥 - const recoveredPubKey = ethers.SigningKey.recoverPublicKey(digestBytes, sig); + if (compressedRecovered.slice(2).toLowerCase() === publicKey.toLowerCase()) { + return true; + } + } catch { + // 尝试下一个 v 值 + } + } - // 压缩公钥并比较 - const compressedRecovered = ethers.SigningKey.computePublicKey(recoveredPubKey, true); - - return compressedRecovered.slice(2).toLowerCase() === publicKey.toLowerCase(); + return false; } catch (error) { this.logger.error(`Signature verification failed: ${error.message}`); return false; } } - /** - * 执行 MPC 密钥生成 - */ - private async executeKeygen(sessionId: string, params: MpcWalletGenerationParams): Promise { - const request = { - sessionId, - threshold: 2, - totalParties: 3, - parties: [ - { partyId: `server-${params.userId}`, partyIndex: 0, partyType: 'SERVER' as const }, - { partyId: `client-${params.deviceId}`, partyIndex: 1, partyType: 'CLIENT' as const }, - { partyId: `backup-${params.userId}`, partyIndex: 2, partyType: 'BACKUP' as const }, - ], - }; - - if (this.useLocalMpc) { - return this.mpcClient.executeLocalKeygen(request); - } - return this.mpcClient.executeKeygen(request); - } - - /** - * 执行 MPC 签名 - */ - private async executeSigning( - sessionId: string, - publicKey: string, - messageHash: string, - ): Promise { - const request = { - sessionId, - publicKey, - messageHash, - threshold: 2, - signerParties: [ - { partyId: 'server', partyIndex: 0, partyType: 'SERVER' as const }, - { partyId: 'backup', partyIndex: 2, partyType: 'BACKUP' as const }, - ], - }; - - if (this.useLocalMpc) { - return this.mpcClient.executeLocalSigning(request); - } - return this.mpcClient.executeSigning(request); - } - /** * 从 MPC 公钥派生三条链的地址 * diff --git a/backend/services/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts b/backend/services/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts index 0541251b..e608d020 100644 --- a/backend/services/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts +++ b/backend/services/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts @@ -5,9 +5,32 @@ export interface WalletAddressEntity { address: string; publicKey: string; addressDigest: string; + // 数据库存储格式 (兼容旧格式) mpcSignatureR: string; mpcSignatureS: string; mpcSignatureV: number; status: string; boundAt: Date; } + +/** + * 将数据库签名格式转换为应用层格式 + * 数据库: {r, s, v} -> 应用: string (64 bytes hex) + */ +export function toMpcSignatureString(entity: WalletAddressEntity): string { + // 兼容旧格式: r + s + return (entity.mpcSignatureR || '') + (entity.mpcSignatureS || ''); +} + +/** + * 将应用层签名格式转换为数据库格式 + * 应用: string (64 bytes hex) -> 数据库: {r, s, v} + */ +export function fromMpcSignatureString(signature: string): { r: string; s: string; v: number } { + // 签名格式: R (32 bytes = 64 hex) + S (32 bytes = 64 hex) + return { + r: signature.slice(0, 64) || '', + s: signature.slice(64, 128) || '', + v: 0, // 默认 v=0,实际验证时尝试 27 和 28 + }; +} diff --git a/backend/services/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts b/backend/services/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts index 519bac5a..87d3a262 100644 --- a/backend/services/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts +++ b/backend/services/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts @@ -3,6 +3,7 @@ import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggre import { WalletAddress } from '@/domain/entities/wallet-address.entity'; import { DeviceInfo, KYCInfo, KYCStatus, AccountStatus, ChainType, AddressStatus } from '@/domain/value-objects'; import { UserAccountEntity } from '../entities/user-account.entity'; +import { toMpcSignatureString } from '../entities/wallet-address.entity'; @Injectable() export class UserAccountMapper { @@ -19,9 +20,7 @@ export class UserAccountMapper { address: w.address, publicKey: w.publicKey, addressDigest: w.addressDigest, - mpcSignatureR: w.mpcSignatureR, - mpcSignatureS: w.mpcSignatureS, - mpcSignatureV: w.mpcSignatureV, + mpcSignature: toMpcSignatureString(w), // 64 bytes hex (r + s) status: w.status as AddressStatus, boundAt: w.boundAt, }), diff --git a/backend/services/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts b/backend/services/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts index cceed99f..58693f63 100644 --- a/backend/services/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts +++ b/backend/services/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts @@ -9,6 +9,7 @@ import { UserId, AccountSequence, PhoneNumber, ReferralCode, ChainType, AccountStatus, KYCStatus, DeviceInfo, KYCInfo, AddressStatus, } from '@/domain/value-objects'; +import { toMpcSignatureString, fromMpcSignatureString } from '../entities/wallet-address.entity'; @Injectable() export class UserAccountRepositoryImpl implements UserAccountRepository { @@ -88,18 +89,21 @@ export class UserAccountRepositoryImpl implements UserAccountRepository { async saveWallets(userId: UserId, wallets: WalletAddress[]): Promise { await this.prisma.walletAddress.createMany({ - data: wallets.map((w) => ({ - userId: BigInt(userId.value), - chainType: w.chainType, - address: w.address, - publicKey: w.publicKey, - addressDigest: w.addressDigest, - mpcSignatureR: w.mpcSignature.r, - mpcSignatureS: w.mpcSignature.s, - mpcSignatureV: w.mpcSignature.v, - status: w.status, - boundAt: w.boundAt, - })), + data: wallets.map((w) => { + const sig = fromMpcSignatureString(w.mpcSignature); + return { + userId: BigInt(userId.value), + chainType: w.chainType, + address: w.address, + publicKey: w.publicKey, + addressDigest: w.addressDigest, + mpcSignatureR: sig.r, + mpcSignatureS: sig.s, + mpcSignatureV: sig.v, + status: w.status, + boundAt: w.boundAt, + }; + }), skipDuplicates: true, }); } @@ -213,9 +217,7 @@ export class UserAccountRepositoryImpl implements UserAccountRepository { address: w.address, publicKey: w.publicKey || '', addressDigest: w.addressDigest || '', - mpcSignatureR: w.mpcSignatureR || '', - mpcSignatureS: w.mpcSignatureS || '', - mpcSignatureV: w.mpcSignatureV || 0, + mpcSignature: toMpcSignatureString(w), // 64 bytes hex (r + s) status: w.status as AddressStatus, boundAt: w.boundAt, }), diff --git a/backend/services/mpc-service/database/migrations/001_create_party_shares_table.sql b/backend/services/mpc-service/database/migrations/001_create_party_shares_table.sql deleted file mode 100644 index b920f879..00000000 --- a/backend/services/mpc-service/database/migrations/001_create_party_shares_table.sql +++ /dev/null @@ -1,30 +0,0 @@ --- ============================================================================= --- Migration: Create party_shares table --- ============================================================================= - -CREATE TABLE IF NOT EXISTS `party_shares` ( - `id` VARCHAR(255) NOT NULL, - `party_id` VARCHAR(255) NOT NULL COMMENT 'Party identifier (format: {userId}-server)', - `session_id` VARCHAR(255) NOT NULL COMMENT 'MPC session ID that created this share', - `share_type` VARCHAR(20) NOT NULL COMMENT 'Type: wallet, admin, recovery', - `share_data` TEXT NOT NULL COMMENT 'Encrypted share data (JSON: {data, iv, authTag})', - `public_key` TEXT NOT NULL COMMENT 'Group public key (hex)', - `threshold_n` INT NOT NULL COMMENT 'Total number of parties', - `threshold_t` INT NOT NULL COMMENT 'Minimum required signers', - `status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'Status: active, rotated, revoked', - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `last_used_at` TIMESTAMP NULL, - - PRIMARY KEY (`id`), - UNIQUE KEY `uk_party_session` (`party_id`, `session_id`), - INDEX `idx_party_id` (`party_id`), - INDEX `idx_session_id` (`session_id`), - INDEX `idx_status` (`status`), - INDEX `idx_public_key` (`public_key`(255)), - - CONSTRAINT `chk_share_type` CHECK (`share_type` IN ('wallet', 'admin', 'recovery')), - CONSTRAINT `chk_status` CHECK (`status` IN ('active', 'rotated', 'revoked')), - CONSTRAINT `chk_threshold` CHECK (`threshold_t` <= `threshold_n` AND `threshold_t` >= 2) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci -COMMENT='MPC party shares - stores encrypted key shares'; diff --git a/backend/services/mpc-service/database/migrations/002_create_session_states_table.sql b/backend/services/mpc-service/database/migrations/002_create_session_states_table.sql deleted file mode 100644 index 8314e8bd..00000000 --- a/backend/services/mpc-service/database/migrations/002_create_session_states_table.sql +++ /dev/null @@ -1,33 +0,0 @@ --- ============================================================================= --- Migration: Create session_states table --- ============================================================================= - -CREATE TABLE IF NOT EXISTS `session_states` ( - `id` VARCHAR(255) NOT NULL, - `session_id` VARCHAR(255) NOT NULL COMMENT 'MPC session ID', - `party_id` VARCHAR(255) NOT NULL COMMENT 'This party identifier', - `party_index` INT NOT NULL COMMENT 'Party index in the session', - `session_type` VARCHAR(20) NOT NULL COMMENT 'Type: keygen, sign, refresh', - `participants` TEXT NOT NULL COMMENT 'JSON array of participants', - `threshold_n` INT NOT NULL COMMENT 'Total parties', - `threshold_t` INT NOT NULL COMMENT 'Required signers', - `status` VARCHAR(20) NOT NULL COMMENT 'Status: pending, in_progress, completed, failed, timeout', - `current_round` INT NOT NULL DEFAULT 0 COMMENT 'Current protocol round', - `error_message` TEXT NULL COMMENT 'Error message if failed', - `public_key` TEXT NULL COMMENT 'Group public key (for keygen)', - `message_hash` VARCHAR(66) NULL COMMENT 'Message hash (for signing)', - `signature` TEXT NULL COMMENT 'Final signature (for signing)', - `started_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `completed_at` TIMESTAMP NULL, - - PRIMARY KEY (`id`), - UNIQUE KEY `uk_session_party` (`session_id`, `party_id`), - INDEX `idx_session_id` (`session_id`), - INDEX `idx_party_id` (`party_id`), - INDEX `idx_status` (`status`), - INDEX `idx_started_at` (`started_at`), - - CONSTRAINT `chk_session_type` CHECK (`session_type` IN ('keygen', 'sign', 'refresh')), - CONSTRAINT `chk_session_status` CHECK (`status` IN ('pending', 'in_progress', 'completed', 'failed', 'timeout', 'cancelled')) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci -COMMENT='MPC session states - tracks party participation in sessions'; diff --git a/backend/services/mpc-service/database/migrations/003_create_share_backups_table.sql b/backend/services/mpc-service/database/migrations/003_create_share_backups_table.sql deleted file mode 100644 index 85a2f0aa..00000000 --- a/backend/services/mpc-service/database/migrations/003_create_share_backups_table.sql +++ /dev/null @@ -1,20 +0,0 @@ --- ============================================================================= --- Migration: Create share_backups table --- ============================================================================= - -CREATE TABLE IF NOT EXISTS `share_backups` ( - `id` VARCHAR(255) NOT NULL, - `share_id` VARCHAR(255) NOT NULL COMMENT 'Reference to party_shares.id', - `backup_data` TEXT NOT NULL COMMENT 'Encrypted backup data', - `backup_type` VARCHAR(20) NOT NULL COMMENT 'Type: manual, auto, recovery', - `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - `created_by` VARCHAR(255) NULL COMMENT 'User/system that created the backup', - - PRIMARY KEY (`id`), - INDEX `idx_share_id` (`share_id`), - INDEX `idx_created_at` (`created_at`), - INDEX `idx_backup_type` (`backup_type`), - - CONSTRAINT `chk_backup_type` CHECK (`backup_type` IN ('manual', 'auto', 'recovery')) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci -COMMENT='Share backups for disaster recovery'; diff --git a/backend/services/mpc-service/prisma/migrations/20241204000000_init/migration.sql b/backend/services/mpc-service/prisma/migrations/20241204000000_init/migration.sql index 89f3d132..4debe56d 100644 --- a/backend/services/mpc-service/prisma/migrations/20241204000000_init/migration.sql +++ b/backend/services/mpc-service/prisma/migrations/20241204000000_init/migration.sql @@ -1,82 +1,45 @@ --- CreateTable -CREATE TABLE "party_shares" ( - "id" VARCHAR(255) NOT NULL, - "party_id" VARCHAR(255) NOT NULL, - "session_id" VARCHAR(255) NOT NULL, - "share_type" VARCHAR(20) NOT NULL, - "share_data" TEXT NOT NULL, - "public_key" TEXT NOT NULL, - "threshold_n" INTEGER NOT NULL, - "threshold_t" INTEGER NOT NULL, - "status" VARCHAR(20) NOT NULL DEFAULT 'active', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL, - "last_used_at" TIMESTAMP(3), - - CONSTRAINT "party_shares_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "session_states" ( - "id" VARCHAR(255) NOT NULL, - "session_id" VARCHAR(255) NOT NULL, - "party_id" VARCHAR(255) NOT NULL, - "party_index" INTEGER NOT NULL, - "session_type" VARCHAR(20) NOT NULL, - "participants" TEXT NOT NULL, - "threshold_n" INTEGER NOT NULL, - "threshold_t" INTEGER NOT NULL, - "status" VARCHAR(20) NOT NULL, - "current_round" INTEGER NOT NULL DEFAULT 0, - "error_message" TEXT, - "public_key" TEXT, - "message_hash" VARCHAR(66), - "signature" TEXT, - "started_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "completed_at" TIMESTAMP(3), - - CONSTRAINT "session_states_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "share_backups" ( - "id" VARCHAR(255) NOT NULL, - "share_id" VARCHAR(255) NOT NULL, - "backup_data" TEXT NOT NULL, - "backup_type" VARCHAR(20) NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "created_by" VARCHAR(255), - - CONSTRAINT "share_backups_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE INDEX "idx_ps_party_id" ON "party_shares"("party_id"); - --- CreateIndex -CREATE INDEX "idx_ps_session_id" ON "party_shares"("session_id"); - --- CreateIndex -CREATE INDEX "idx_ps_status" ON "party_shares"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "party_shares_party_id_session_id_key" ON "party_shares"("party_id", "session_id"); - --- CreateIndex -CREATE INDEX "idx_ss_session_id" ON "session_states"("session_id"); - --- CreateIndex -CREATE INDEX "idx_ss_party_id" ON "session_states"("party_id"); - --- CreateIndex -CREATE INDEX "idx_ss_status" ON "session_states"("status"); - --- CreateIndex -CREATE UNIQUE INDEX "session_states_session_id_party_id_key" ON "session_states"("session_id", "party_id"); - --- CreateIndex -CREATE INDEX "idx_share_id" ON "share_backups"("share_id"); - --- CreateIndex -CREATE INDEX "idx_created_at" ON "share_backups"("created_at"); - +-- ============================================================================= +-- MPC Service - Database Migration +-- ============================================================================= +-- +-- mpc-service 作为 MPC 服务网关: +-- 1. 缓存 username + publicKey 的映射关系 (mpc_wallets) +-- 2. 存储 delegate share (mpc_shares) +-- +-- ============================================================================= + +-- CreateTable +CREATE TABLE "mpc_wallets" ( + "id" TEXT NOT NULL, + "username" VARCHAR(255) NOT NULL, + "public_key" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "mpc_wallets_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "mpc_shares" ( + "id" TEXT NOT NULL, + "username" VARCHAR(255) NOT NULL, + "party_id" VARCHAR(255) NOT NULL, + "party_index" INTEGER NOT NULL, + "encrypted_share" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "mpc_shares_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "mpc_wallets_username_key" ON "mpc_wallets"("username"); + +-- CreateIndex +CREATE INDEX "idx_mw_username" ON "mpc_wallets"("username"); + +-- CreateIndex +CREATE INDEX "idx_ms_username" ON "mpc_shares"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "uq_ms_username" ON "mpc_shares"("username"); diff --git a/backend/services/mpc-service/prisma/migrations/migration_lock.toml b/backend/services/mpc-service/prisma/migrations/migration_lock.toml index f7db42dd..99e4f200 100644 --- a/backend/services/mpc-service/prisma/migrations/migration_lock.toml +++ b/backend/services/mpc-service/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql"