diff --git a/backend/services/identity-service/src/app.module.ts b/backend/services/identity-service/src/app.module.ts index bc475a0f..c7e7f5da 100644 --- a/backend/services/identity-service/src/app.module.ts +++ b/backend/services/identity-service/src/app.module.ts @@ -32,7 +32,6 @@ import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.se import { MpcEventConsumerService } from '@/infrastructure/kafka/mpc-event-consumer.service'; import { SmsService } from '@/infrastructure/external/sms/sms.service'; import { MpcClientService, MpcWalletService } from '@/infrastructure/external/mpc'; -import { BackupClientService, MpcShareStorageService } from '@/infrastructure/external/backup'; import { WalletGeneratorServiceImpl } from '@/infrastructure/external/blockchain/wallet-generator.service.impl'; // Shared @@ -56,8 +55,6 @@ import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard'; SmsService, MpcClientService, MpcWalletService, - BackupClientService, - MpcShareStorageService, // WalletGeneratorService 抽象类由 WalletGeneratorServiceImpl 实现 WalletGeneratorServiceImpl, { provide: WalletGeneratorService, useExisting: WalletGeneratorServiceImpl }, @@ -71,8 +68,6 @@ import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard'; SmsService, MpcClientService, MpcWalletService, - BackupClientService, - MpcShareStorageService, WalletGeneratorService, MPC_KEY_SHARE_REPOSITORY, ], diff --git a/backend/services/identity-service/src/application/services/user-application.service.referral.spec.ts b/backend/services/identity-service/src/application/services/user-application.service.referral.spec.ts index c35eebbc..94c09c43 100644 --- a/backend/services/identity-service/src/application/services/user-application.service.referral.spec.ts +++ b/backend/services/identity-service/src/application/services/user-application.service.referral.spec.ts @@ -13,7 +13,6 @@ import { RedisService } from '@/infrastructure/redis/redis.service'; import { SmsService } from '@/infrastructure/external/sms/sms.service'; import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; import { MpcWalletService } from '@/infrastructure/external/mpc'; -import { BackupClientService } from '@/infrastructure/external/backup'; describe('UserApplicationService - Referral APIs', () => { let service: UserApplicationService; @@ -131,10 +130,6 @@ describe('UserApplicationService - Referral APIs', () => { generateMpcWallet: jest.fn(), }; - const mockBackupClientService = { - storeBackupShare: jest.fn(), - }; - const module: TestingModule = await Test.createTestingModule({ providers: [ UserApplicationService, @@ -182,10 +177,6 @@ describe('UserApplicationService - Referral APIs', () => { provide: MpcWalletService, useValue: mockMpcWalletService, }, - { - provide: BackupClientService, - useValue: mockBackupClientService, - }, ], }).compile(); 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 b7557d85..b47aaedd 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 @@ -15,7 +15,6 @@ import { RedisService } from '@/infrastructure/redis/redis.service'; import { SmsService } from '@/infrastructure/external/sms/sms.service'; import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; import { MpcWalletService } from '@/infrastructure/external/mpc'; -import { BackupClientService } from '@/infrastructure/external/backup'; import { ApplicationError } from '@/shared/exceptions/domain.exception'; import { generateRandomIdentity } from '@/shared/utils'; import { @@ -43,7 +42,6 @@ export class UserApplicationService { private readonly validatorService: UserValidatorService, private readonly walletGenerator: WalletGeneratorService, private readonly mpcWalletService: MpcWalletService, - private readonly backupClient: BackupClientService, private readonly tokenService: TokenService, private readonly redisService: RedisService, private readonly smsService: SmsService, diff --git a/backend/services/identity-service/src/infrastructure/external/backup/index.ts b/backend/services/identity-service/src/infrastructure/external/backup/index.ts deleted file mode 100644 index 739369c2..00000000 --- a/backend/services/identity-service/src/infrastructure/external/backup/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './backup-client.service'; -export * from './mpc-share-storage.service'; diff --git a/backend/services/identity-service/src/infrastructure/external/backup/mpc-share-storage.service.ts b/backend/services/identity-service/src/infrastructure/external/backup/mpc-share-storage.service.ts deleted file mode 100644 index 0449403b..00000000 --- a/backend/services/identity-service/src/infrastructure/external/backup/mpc-share-storage.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * MPC Share Storage Service - * - * 封装 MPC 分片存储逻辑,提供简化接口给应用层 - */ - -import { Injectable, Logger } from '@nestjs/common'; -import { BackupClientService } from './backup-client.service'; - -export interface MpcStoreBackupShareParams { - userId: string; - shareData: string; - publicKey: string; - accountSequence?: number; -} - -export interface MpcRetrieveBackupShareParams { - userId: string; - publicKey: string; - recoveryToken: string; - deviceId?: string; -} - -export interface MpcBackupShareData { - encryptedShareData: string; - partyIndex: number; - publicKey: string; -} - -@Injectable() -export class MpcShareStorageService { - private readonly logger = new Logger(MpcShareStorageService.name); - - constructor(private readonly backupClient: BackupClientService) {} - - /** - * 存储备份分片 - * - * @param params 分片存储参数 - */ - async storeBackupShare(params: MpcStoreBackupShareParams): Promise { - this.logger.log(`Storing backup share for user=${params.userId}`); - - await this.backupClient.storeBackupShare({ - userId: params.userId, - accountSequence: params.accountSequence || 0, - publicKey: params.publicKey, - encryptedShareData: params.shareData, - }); - } - - /** - * 获取备份分片 (用于账户恢复) - * - * @param params 分片获取参数 - * @returns 备份分片数据或 null - */ - async retrieveBackupShare( - params: MpcRetrieveBackupShareParams, - ): Promise { - this.logger.log(`Retrieving backup share for user=${params.userId}`); - - return this.backupClient.retrieveBackupShare(params); - } - - /** - * 撤销备份分片 - * - * @param userId 用户ID - * @param publicKey MPC 公钥 - * @param reason 撤销原因 - */ - async revokeBackupShare( - userId: string, - publicKey: string, - reason: string, - ): Promise { - this.logger.log(`Revoking backup share for user=${userId}`); - - await this.backupClient.revokeBackupShare(userId, publicKey, reason); - } - - /** - * 检查备份服务是否可用 - */ - isEnabled(): boolean { - return this.backupClient.isEnabled(); - } -} diff --git a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts index 3c5a8cf9..a63130ef 100644 --- a/backend/services/identity-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/identity-service/src/infrastructure/infrastructure.module.ts @@ -11,7 +11,6 @@ import { SmsService } from './external/sms/sms.service'; import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl'; import { BlockchainQueryService } from './external/blockchain/blockchain-query.service'; import { MpcClientService, MpcWalletService } from './external/mpc'; -import { BackupClientService, MpcShareStorageService } from './external/backup'; import { MPC_KEY_SHARE_REPOSITORY } from '@/domain/repositories/mpc-key-share.repository.interface'; import { WalletGeneratorService } from '@/domain/services/wallet-generator.service'; @@ -44,8 +43,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi BlockchainQueryService, MpcClientService, MpcWalletService, - BackupClientService, - MpcShareStorageService, ], exports: [ PrismaService, @@ -64,8 +61,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi BlockchainQueryService, MpcClientService, MpcWalletService, - BackupClientService, - MpcShareStorageService, ], }) export class InfrastructureModule {} diff --git a/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts b/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts index 5b81a92e..aa57ddca 100644 --- a/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts +++ b/backend/services/mpc-service/src/application/event-handlers/keygen-requested.handler.ts @@ -13,6 +13,7 @@ import { MPC_CONSUME_TOPICS, KeygenRequestedPayload, } from '../../infrastructure/messaging/kafka/event-consumer.service'; +import { BackupClientService } from '../../infrastructure/external/backup'; import { KeygenStartedEvent } from '../../domain/events/keygen-started.event'; import { KeygenCompletedEvent } from '../../domain/events/keygen-completed.event'; import { SessionFailedEvent } from '../../domain/events/session-failed.event'; @@ -26,6 +27,7 @@ export class KeygenRequestedHandler implements OnModuleInit { private readonly eventConsumer: EventConsumerService, private readonly eventPublisher: EventPublisherService, private readonly mpcCoordinator: MPCCoordinatorService, + private readonly backupClient: BackupClientService, ) {} async onModuleInit() { @@ -72,14 +74,31 @@ export class KeygenRequestedHandler implements OnModuleInit { // Cache public key await this.mpcCoordinator.savePublicKeyCache(username, result.publicKey); - // Save delegate share if exists + // Save delegate share to backup-service if exists if (result.delegateShare) { + // 1. 保存到本地缓存(用于签名时快速访问) await this.mpcCoordinator.saveDelegateShare({ username, partyId: result.delegateShare.partyId, partyIndex: result.delegateShare.partyIndex, encryptedShare: result.delegateShare.encryptedShare, }); + + // 2. 存储到 backup-service(用于灾备恢复) + try { + await this.backupClient.storeBackupShare({ + userId, + username, + publicKey: result.publicKey, + partyId: result.delegateShare.partyId, + partyIndex: result.delegateShare.partyIndex, + encryptedShare: result.delegateShare.encryptedShare, + }); + this.logger.log(`Delegate share stored to backup-service: userId=${userId}`); + } catch (backupError) { + // 备份失败不阻塞主流程,但记录错误 + this.logger.error(`Failed to store delegate share to backup-service: userId=${userId}`, backupError); + } } // Publish success event diff --git a/backend/services/identity-service/src/infrastructure/external/backup/backup-client.service.ts b/backend/services/mpc-service/src/infrastructure/external/backup/backup-client.service.ts similarity index 54% rename from backend/services/identity-service/src/infrastructure/external/backup/backup-client.service.ts rename to backend/services/mpc-service/src/infrastructure/external/backup/backup-client.service.ts index 2401e27f..e6387115 100644 --- a/backend/services/identity-service/src/infrastructure/external/backup/backup-client.service.ts +++ b/backend/services/mpc-service/src/infrastructure/external/backup/backup-client.service.ts @@ -1,192 +1,155 @@ -/** - * Backup Client Service - * - * 与 backup-service 通信的客户端服务 - * 负责存储和获取 MPC Backup Share (Party 2) - * - * 安全要求: backup-service 必须部署在与 identity-service 不同的物理服务器上 - */ - -import { Injectable, Logger } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; -import { ConfigService } from '@nestjs/config'; -import { firstValueFrom } from 'rxjs'; -import * as jwt from 'jsonwebtoken'; - -export interface StoreBackupShareParams { - userId: string; - accountSequence: number; - publicKey: string; - encryptedShareData: string; -} - -export interface RetrieveBackupShareParams { - userId: string; - publicKey: string; - recoveryToken: string; - deviceId?: string; -} - -export interface BackupShareResult { - encryptedShareData: string; - partyIndex: number; - publicKey: string; -} - -@Injectable() -export class BackupClientService { - private readonly logger = new Logger(BackupClientService.name); - private readonly backupServiceUrl: string; - private readonly serviceJwtSecret: string; - private readonly enabled: boolean; - - constructor( - private readonly httpService: HttpService, - private readonly configService: ConfigService, - ) { - this.backupServiceUrl = this.configService.get('BACKUP_SERVICE_URL', 'http://localhost:3002'); - this.serviceJwtSecret = this.configService.get('SERVICE_JWT_SECRET', ''); - this.enabled = this.configService.get('BACKUP_SERVICE_ENABLED', 'false') === 'true'; - } - - /** - * 检查 backup-service 是否启用 - */ - isEnabled(): boolean { - return this.enabled && !!this.serviceJwtSecret; - } - - /** - * 存储备份分片到 backup-service - */ - async storeBackupShare(params: StoreBackupShareParams): Promise { - if (!this.isEnabled()) { - this.logger.warn('Backup service is disabled, skipping backup share storage'); - return; - } - - this.logger.log(`Storing backup share for user: ${params.userId}`); - - try { - const serviceToken = this.generateServiceToken(); - - await firstValueFrom( - this.httpService.post( - `${this.backupServiceUrl}/backup-share/store`, - { - userId: params.userId, - accountSequence: params.accountSequence, - publicKey: params.publicKey, - encryptedShareData: params.encryptedShareData, - }, - { - headers: { - 'Content-Type': 'application/json', - 'X-Service-Token': serviceToken, - }, - timeout: 30000, // 30秒超时 - }, - ), - ); - - this.logger.log(`Backup share stored successfully for user: ${params.userId}`); - } catch (error) { - this.logger.error(`Failed to store backup share for user: ${params.userId}`, error); - // 不抛出异常,允许账户创建继续 - // 可以通过补偿任务稍后重试 - } - } - - /** - * 从 backup-service 获取备份分片 (用于账户恢复) - */ - async retrieveBackupShare(params: RetrieveBackupShareParams): Promise { - if (!this.isEnabled()) { - this.logger.warn('Backup service is disabled'); - return null; - } - - this.logger.log(`Retrieving backup share for user: ${params.userId}`); - - try { - const serviceToken = this.generateServiceToken(); - - const response = await firstValueFrom( - this.httpService.post( - `${this.backupServiceUrl}/backup-share/retrieve`, - { - userId: params.userId, - publicKey: params.publicKey, - recoveryToken: params.recoveryToken, - deviceId: params.deviceId, - }, - { - headers: { - 'Content-Type': 'application/json', - 'X-Service-Token': serviceToken, - }, - timeout: 30000, - }, - ), - ); - - this.logger.log(`Backup share retrieved successfully for user: ${params.userId}`); - return response.data; - } catch (error) { - this.logger.error(`Failed to retrieve backup share for user: ${params.userId}`, error); - throw new Error(`Failed to retrieve backup share: ${error.message}`); - } - } - - /** - * 撤销备份分片 (用于密钥轮换或账户注销) - */ - async revokeBackupShare(userId: string, publicKey: string, reason: string): Promise { - if (!this.isEnabled()) { - this.logger.warn('Backup service is disabled'); - return; - } - - this.logger.log(`Revoking backup share for user: ${userId}, reason: ${reason}`); - - try { - const serviceToken = this.generateServiceToken(); - - await firstValueFrom( - this.httpService.post( - `${this.backupServiceUrl}/backup-share/revoke`, - { - userId, - publicKey, - reason, - }, - { - headers: { - 'Content-Type': 'application/json', - 'X-Service-Token': serviceToken, - }, - timeout: 30000, - }, - ), - ); - - this.logger.log(`Backup share revoked successfully for user: ${userId}`); - } catch (error) { - this.logger.error(`Failed to revoke backup share for user: ${userId}`, error); - } - } - - /** - * 生成服务间认证 JWT - */ - private generateServiceToken(): string { - return jwt.sign( - { - service: 'identity-service', - iat: Math.floor(Date.now() / 1000), - }, - this.serviceJwtSecret, - { expiresIn: '5m' }, - ); - } -} +/** + * Backup Client Service + * + * mpc-service 调用 backup-service 存储/获取 delegate share + * backup-service 负责安全存储 MPC 分片备份 + */ + +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { firstValueFrom } from 'rxjs'; +import * as jwt from 'jsonwebtoken'; + +export interface StoreBackupShareParams { + userId: string; + username: string; + publicKey: string; + partyId: string; + partyIndex: number; + encryptedShare: string; +} + +export interface RetrieveBackupShareParams { + userId: string; + publicKey: string; +} + +export interface BackupShareResult { + encryptedShareData: string; + partyIndex: number; + publicKey: string; +} + +@Injectable() +export class BackupClientService { + private readonly logger = new Logger(BackupClientService.name); + private readonly backupServiceUrl: string; + private readonly serviceJwtSecret: string; + private readonly enabled: boolean; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) { + this.backupServiceUrl = this.configService.get('BACKUP_SERVICE_URL', 'http://localhost:3002'); + this.serviceJwtSecret = this.configService.get('SERVICE_JWT_SECRET', ''); + this.enabled = this.configService.get('BACKUP_SERVICE_ENABLED', 'true') === 'true'; + + this.logger.log(`[INIT] BackupClientService initialized`); + this.logger.log(`[INIT] URL: ${this.backupServiceUrl}`); + this.logger.log(`[INIT] Enabled: ${this.enabled}`); + } + + /** + * 检查 backup-service 是否启用 + */ + isEnabled(): boolean { + return this.enabled && !!this.serviceJwtSecret; + } + + /** + * 存储 delegate share 到 backup-service + */ + async storeBackupShare(params: StoreBackupShareParams): Promise { + if (!this.isEnabled()) { + this.logger.warn('Backup service is disabled, skipping backup share storage'); + return; + } + + this.logger.log(`Storing backup share: userId=${params.userId}, username=${params.username}`); + + try { + const serviceToken = this.generateServiceToken(); + + await firstValueFrom( + this.httpService.post( + `${this.backupServiceUrl}/backup-share/store`, + { + userId: params.userId, + username: params.username, + publicKey: params.publicKey, + partyId: params.partyId, + partyIndex: params.partyIndex, + encryptedShareData: params.encryptedShare, + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-Service-Token': serviceToken, + }, + timeout: 30000, + }, + ), + ); + + this.logger.log(`Backup share stored successfully: userId=${params.userId}`); + } catch (error) { + this.logger.error(`Failed to store backup share: userId=${params.userId}`, error); + throw error; // 抛出异常,让调用方处理 + } + } + + /** + * 从 backup-service 获取 delegate share (用于签名) + */ + async retrieveBackupShare(params: RetrieveBackupShareParams): Promise { + if (!this.isEnabled()) { + this.logger.warn('Backup service is disabled'); + return null; + } + + this.logger.log(`Retrieving backup share: userId=${params.userId}`); + + try { + const serviceToken = this.generateServiceToken(); + + const response = await firstValueFrom( + this.httpService.post( + `${this.backupServiceUrl}/backup-share/retrieve`, + { + userId: params.userId, + publicKey: params.publicKey, + }, + { + headers: { + 'Content-Type': 'application/json', + 'X-Service-Token': serviceToken, + }, + timeout: 30000, + }, + ), + ); + + this.logger.log(`Backup share retrieved: userId=${params.userId}`); + return response.data; + } catch (error) { + this.logger.error(`Failed to retrieve backup share: userId=${params.userId}`, error); + return null; + } + } + + /** + * 生成服务间认证 JWT + */ + private generateServiceToken(): string { + return jwt.sign( + { + service: 'mpc-service', + iat: Math.floor(Date.now() / 1000), + }, + this.serviceJwtSecret, + { expiresIn: '5m' }, + ); + } +} diff --git a/backend/services/mpc-service/src/infrastructure/external/backup/index.ts b/backend/services/mpc-service/src/infrastructure/external/backup/index.ts new file mode 100644 index 00000000..2b849264 --- /dev/null +++ b/backend/services/mpc-service/src/infrastructure/external/backup/index.ts @@ -0,0 +1 @@ +export { BackupClientService } from './backup-client.service'; diff --git a/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts b/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts index ee62144a..350fe3ab 100644 --- a/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts +++ b/backend/services/mpc-service/src/infrastructure/infrastructure.module.ts @@ -4,10 +4,12 @@ * mpc-service 作为网关,需要: * - PrismaService 用于缓存公钥和 delegate share * - Kafka 事件发布和消费 + * - BackupClientService 用于存储 delegate share 到 backup-service */ import { Global, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { HttpModule } from '@nestjs/axios'; // Persistence import { PrismaService } from './persistence/prisma/prisma.service'; @@ -16,9 +18,18 @@ import { PrismaService } from './persistence/prisma/prisma.service'; import { EventPublisherService } from './messaging/kafka/event-publisher.service'; import { EventConsumerService } from './messaging/kafka/event-consumer.service'; +// External Services +import { BackupClientService } from './external/backup'; + @Global() @Module({ - imports: [ConfigModule], + imports: [ + ConfigModule, + HttpModule.register({ + timeout: 30000, + maxRedirects: 5, + }), + ], providers: [ // Prisma (用于缓存公钥和 delegate share) PrismaService, @@ -26,11 +37,15 @@ import { EventConsumerService } from './messaging/kafka/event-consumer.service'; // Kafka (事件发布和消费) EventPublisherService, EventConsumerService, + + // External Services + BackupClientService, ], exports: [ PrismaService, EventPublisherService, EventConsumerService, + BackupClientService, ], }) export class InfrastructureModule {}