fix(mpc-service): 修复 MPC 会话流程,先创建会话再加入

问题:mpc-service 尝试用 identity-service 生成的 SHA256 哈希作为
joinToken 加入会话,但 session-coordinator 期望的是由它自己
CreateSession 接口生成的 JWT token。

修复:
- coordinator-client.ts: 添加 createSession() 方法
- participate-keygen.handler.ts: 先创建会话获取 JWT,再加入
- participate-signing.handler.ts: 同上
- rotate-share.handler.ts: 同上(使用 keygen 类型)

流程变更:
1. CreateSession -> 获取 sessionId + JWT joinToken
2. JoinSession 使用 JWT token 加入会话

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Developer 2025-12-03 21:21:22 -08:00
parent 467206fd61
commit e51edc2ce4
4 changed files with 132 additions and 13 deletions

View File

@ -163,11 +163,31 @@ export class ParticipateInKeygenHandler {
private async joinSession(command: ParticipateInKeygenCommand): Promise<SessionInfo> {
try {
return await this.coordinatorClient.joinSession({
sessionId: command.sessionId,
partyId: command.partyId,
joinToken: command.joinToken,
// First, create the session via coordinator to get a valid JWT token
// The session-coordinator expects us to create a session first
this.logger.log('Creating MPC session via coordinator...');
const createResponse = await this.coordinatorClient.createSession({
sessionType: 'keygen',
thresholdN: 3, // Default 2-of-3 MPC
thresholdT: 2,
createdBy: command.partyId,
expiresIn: 600, // 10 minutes
});
this.logger.log(`Session created: ${createResponse.sessionId}, now joining...`);
// Now join using the valid JWT token from the coordinator
const sessionInfo = await this.coordinatorClient.joinSession({
sessionId: createResponse.sessionId,
partyId: command.partyId,
joinToken: createResponse.joinToken, // Use the JWT from createSession
});
// Return session info with the original session ID for consistency
return {
...sessionInfo,
sessionId: createResponse.sessionId,
joinToken: createResponse.joinToken,
};
} catch (error) {
throw new ApplicationError(
`Failed to join session: ${error.message}`,

View File

@ -167,11 +167,33 @@ export class ParticipateInSigningHandler {
private async joinSession(command: ParticipateInSigningCommand): Promise<SessionInfo> {
try {
return await this.coordinatorClient.joinSession({
sessionId: command.sessionId,
partyId: command.partyId,
joinToken: command.joinToken,
// First, create the session via coordinator to get a valid JWT token
this.logger.log('Creating MPC signing session via coordinator...');
const createResponse = await this.coordinatorClient.createSession({
sessionType: 'sign',
thresholdN: 3, // Default 2-of-3 MPC
thresholdT: 2,
createdBy: command.partyId,
messageHash: command.messageHash,
expiresIn: 300, // 5 minutes for signing
});
this.logger.log(`Signing session created: ${createResponse.sessionId}, now joining...`);
// Now join using the valid JWT token from the coordinator
const sessionInfo = await this.coordinatorClient.joinSession({
sessionId: createResponse.sessionId,
partyId: command.partyId,
joinToken: createResponse.joinToken,
});
// Return session info with correct IDs and public key from command
return {
...sessionInfo,
sessionId: createResponse.sessionId,
joinToken: createResponse.joinToken,
publicKey: command.publicKey, // Preserve public key from command
messageHash: command.messageHash,
};
} catch (error) {
throw new ApplicationError(
`Failed to join signing session: ${error.message}`,

View File

@ -174,11 +174,31 @@ export class RotateShareHandler {
private async joinSession(command: RotateShareCommand): Promise<SessionInfo> {
try {
return await this.coordinatorClient.joinSession({
sessionId: command.sessionId,
partyId: command.partyId,
joinToken: command.joinToken,
// First, create the session via coordinator to get a valid JWT token
// Key refresh uses 'keygen' session type (coordinator doesn't have 'refresh' type)
this.logger.log('Creating MPC refresh session via coordinator...');
const createResponse = await this.coordinatorClient.createSession({
sessionType: 'keygen', // Use keygen type for key refresh
thresholdN: 3,
thresholdT: 2,
createdBy: command.partyId,
expiresIn: 600, // 10 minutes
});
this.logger.log(`Refresh session created: ${createResponse.sessionId}, now joining...`);
// Now join using the valid JWT token from the coordinator
const sessionInfo = await this.coordinatorClient.joinSession({
sessionId: createResponse.sessionId,
partyId: command.partyId,
joinToken: createResponse.joinToken,
});
return {
...sessionInfo,
sessionId: createResponse.sessionId,
joinToken: createResponse.joinToken,
publicKey: command.publicKey,
};
} catch (error) {
throw new ApplicationError(
`Failed to join rotation session: ${error.message}`,

View File

@ -2,12 +2,32 @@
* MPC Coordinator Client
*
* Client for communicating with the external MPC Session Coordinator.
*
* Flow:
* 1. CreateSession - Creates a new MPC session and returns a JWT joinToken
* 2. JoinSession - Parties join using the JWT token
*/
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, { AxiosInstance, AxiosError } from 'axios';
export interface CreateSessionRequest {
sessionType: 'keygen' | 'sign';
thresholdN: number;
thresholdT: number;
createdBy: string;
messageHash?: string;
expiresIn?: number; // seconds
}
export interface CreateSessionResponse {
sessionId: string;
joinToken: string; // JWT token for joining
status: string;
expiresAt?: number;
}
export interface JoinSessionRequest {
sessionId: string;
partyId: string;
@ -22,6 +42,7 @@ export interface SessionInfo {
participants: Array<{ partyId: string; partyIndex: number }>;
publicKey?: string;
messageHash?: string;
joinToken?: string; // JWT token for this session
}
export interface ReportCompletionRequest {
@ -89,7 +110,42 @@ export class MPCCoordinatorClient implements OnModuleInit {
}
/**
* Join an MPC session
* Create an MPC session
*
* This must be called first to get a valid JWT joinToken
*/
async createSession(request: CreateSessionRequest): Promise<CreateSessionResponse> {
this.logger.log(`Creating session: type=${request.sessionType}, ${request.thresholdT}-of-${request.thresholdN}`);
try {
const response = await this.client.post('/api/v1/sessions', {
sessionType: request.sessionType,
thresholdN: request.thresholdN,
thresholdT: request.thresholdT,
createdBy: request.createdBy,
messageHash: request.messageHash,
expiresIn: request.expiresIn || 600, // Default 10 minutes
});
this.logger.log(`Session created: ${response.data.sessionId}`);
return {
sessionId: response.data.sessionId,
joinToken: response.data.joinToken,
status: response.data.status,
expiresAt: response.data.expiresAt,
};
} catch (error) {
const message = this.getErrorMessage(error);
this.logger.error(`Failed to create session: ${message}`);
throw new Error(`Failed to create MPC session: ${message}`);
}
}
/**
* Join an MPC session using a JWT token
*
* The joinToken must be obtained from createSession()
*/
async joinSession(request: JoinSessionRequest): Promise<SessionInfo> {
this.logger.log(`Joining session: ${request.sessionId}`);
@ -114,6 +170,7 @@ export class MPCCoordinatorClient implements OnModuleInit {
})) || [],
publicKey: undefined,
messageHash: undefined,
joinToken: request.joinToken,
};
} catch (error) {
const message = this.getErrorMessage(error);