155 lines
4.3 KiB
TypeScript
155 lines
4.3 KiB
TypeScript
/**
|
|
* 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;
|
|
accountSequence: number;
|
|
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<string>('BACKUP_SERVICE_URL', 'http://localhost:3002');
|
|
this.serviceJwtSecret = this.configService.get<string>('SERVICE_JWT_SECRET', '');
|
|
this.enabled = this.configService.get<string>('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<void> {
|
|
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,
|
|
accountSequence: params.accountSequence,
|
|
publicKey: params.publicKey,
|
|
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<BackupShareResult | null> {
|
|
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<BackupShareResult>(
|
|
`${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' },
|
|
);
|
|
}
|
|
}
|