fix: align mpc-service migration with schema and fix identity-service compile errors

- 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-06 18:42:48 -08:00
parent ba91a89b16
commit 289691dc3c
16 changed files with 198 additions and 340 deletions

View File

@ -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",

View File

@ -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,

View File

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

View File

@ -59,8 +59,8 @@ export class UserApplicationService {
async autoCreateAccount(command: AutoCreateAccountCommand): Promise<AutoCreateAccountResult> {
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,

View File

@ -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(),
);

View File

@ -34,7 +34,7 @@ export class AccountSequenceGeneratorService {
private readonly repository: UserAccountRepository,
) {}
async generateNext(): Promise<AccountSequence> {
async generateNextUserSequence(): Promise<AccountSequence> {
return this.repository.getNextAccountSequence();
}
}
@ -53,9 +53,9 @@ export class UserValidatorService {
return ValidationResult.success();
}
async validateDeviceId(deviceId: string): Promise<ValidationResult> {
async checkDeviceNotRegistered(deviceId: string): Promise<ValidationResult> {
const existing = await this.repository.findByDeviceId(deviceId);
if (existing) return ValidationResult.failure('该设备已创建账户');
if (existing) return ValidationResult.failure('该设备已创建账户');
return ValidationResult.success();
}

View File

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

View File

@ -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<string>('MPC_MODE', 'local') === 'local';
}
) {}
/**
* 使 MPC 2-of-3
@ -82,11 +74,16 @@ export class MpcWalletService {
* 5.
*/
async generateMpcWallet(params: MpcWalletGenerationParams): Promise<MpcWalletGenerationResult> {
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<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');
// 重建签名
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<KeygenResult> {
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<SigningResult> {
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
*

View File

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

View File

@ -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,
}),

View File

@ -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<void> {
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,
}),

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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");

View File

@ -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"