From 0311ecf498b6d6e663b1f6cf39e6fe7e7c547e3c Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 8 Dec 2025 00:44:47 -0800 Subject: [PATCH] fix(mnemonic): use hash verification instead of address derivation for account recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mnemonic recovery now uses stored hash comparison via blockchain-service instead of trying to derive addresses from mnemonic (which never matched because MPC wallet addresses are not derived from mnemonics). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../api/controllers/internal.controller.ts | 16 +++++++- .../src/api/dto/request/index.ts | 1 + .../dto/request/verify-mnemonic-hash.dto.ts | 18 +++++++++ .../recover-by-mnemonic.handler.ts | 36 +++++++++-------- .../blockchain/blockchain-client.service.ts | 39 +++++++++++++++++++ 5 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 backend/services/blockchain-service/src/api/dto/request/verify-mnemonic-hash.dto.ts diff --git a/backend/services/blockchain-service/src/api/controllers/internal.controller.ts b/backend/services/blockchain-service/src/api/controllers/internal.controller.ts index df0f3482..4f2bf144 100644 --- a/backend/services/blockchain-service/src/api/controllers/internal.controller.ts +++ b/backend/services/blockchain-service/src/api/controllers/internal.controller.ts @@ -1,8 +1,8 @@ import { Controller, Post, Body, Get, Param } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { AddressDerivationService } from '@/application/services/address-derivation.service'; -import { MnemonicDerivationAdapter } from '@/infrastructure/blockchain'; -import { DeriveAddressDto, VerifyMnemonicDto } from '../dto/request'; +import { MnemonicDerivationAdapter, RecoveryMnemonicAdapter } from '@/infrastructure/blockchain'; +import { DeriveAddressDto, VerifyMnemonicDto, VerifyMnemonicHashDto } from '../dto/request'; import { DeriveAddressResponseDto } from '../dto/response'; /** @@ -15,6 +15,7 @@ export class InternalController { constructor( private readonly addressDerivationService: AddressDerivationService, private readonly mnemonicDerivation: MnemonicDerivationAdapter, + private readonly recoveryMnemonic: RecoveryMnemonicAdapter, ) {} @Post('derive-address') @@ -74,4 +75,15 @@ export class InternalController { })), }; } + + @Post('verify-mnemonic-hash') + @ApiOperation({ summary: '验证助记词哈希是否匹配' }) + @ApiResponse({ status: 200, description: '验证结果' }) + async verifyMnemonicHash(@Body() dto: VerifyMnemonicHashDto) { + const result = this.recoveryMnemonic.verifyMnemonic(dto.mnemonic, dto.expectedHash); + return { + valid: result.valid, + message: result.message, + }; + } } diff --git a/backend/services/blockchain-service/src/api/dto/request/index.ts b/backend/services/blockchain-service/src/api/dto/request/index.ts index 7ef0e722..5296a133 100644 --- a/backend/services/blockchain-service/src/api/dto/request/index.ts +++ b/backend/services/blockchain-service/src/api/dto/request/index.ts @@ -1,3 +1,4 @@ export * from './query-balance.dto'; export * from './derive-address.dto'; export * from './verify-mnemonic.dto'; +export * from './verify-mnemonic-hash.dto'; diff --git a/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic-hash.dto.ts b/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic-hash.dto.ts new file mode 100644 index 00000000..37cf2f4c --- /dev/null +++ b/backend/services/blockchain-service/src/api/dto/request/verify-mnemonic-hash.dto.ts @@ -0,0 +1,18 @@ +import { IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class VerifyMnemonicHashDto { + @ApiProperty({ + description: '助记词 (12个单词,空格分隔)', + example: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + }) + @IsString() + mnemonic: string; + + @ApiProperty({ + description: '期望的助记词哈希', + example: 'a1b2c3d4e5f6...', + }) + @IsString() + expectedHash: string; +} diff --git a/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts b/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts index 0887bee8..233be383 100644 --- a/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts +++ b/backend/services/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts @@ -1,9 +1,10 @@ import { Injectable, Inject, Logger } from '@nestjs/common'; import { RecoverByMnemonicCommand } from './recover-by-mnemonic.command'; import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { AccountSequence, ChainType } from '@/domain/value-objects'; +import { AccountSequence } from '@/domain/value-objects'; import { TokenService } from '@/application/services/token.service'; import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; +import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service'; import { BlockchainClientService } from '@/infrastructure/external/blockchain/blockchain-client.service'; import { ApplicationError } from '@/shared/exceptions/domain.exception'; import { RecoverAccountResult } from '../index'; @@ -15,9 +16,10 @@ export class RecoverByMnemonicHandler { constructor( @Inject(USER_ACCOUNT_REPOSITORY) private readonly userRepository: UserAccountRepository, - private readonly blockchainClient: BlockchainClientService, + private readonly prisma: PrismaService, private readonly tokenService: TokenService, private readonly eventPublisher: EventPublisherService, + private readonly blockchainClient: BlockchainClientService, ) {} async execute(command: RecoverByMnemonicCommand): Promise { @@ -26,26 +28,28 @@ export class RecoverByMnemonicHandler { if (!account) throw new ApplicationError('账户序列号不存在'); if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - // 获取账户的钱包地址用于验证 - const expectedAddresses: Array<{ chainType: string; address: string }> = []; - const kavaWallet = account.getWalletAddress(ChainType.KAVA); - if (kavaWallet) { - expectedAddresses.push({ chainType: 'KAVA', address: kavaWallet.address }); + // 从 recovery_mnemonics 表获取存储的助记词哈希 + const recoveryMnemonic = await this.prisma.recoveryMnemonic.findFirst({ + where: { + userId: account.userId.value, + status: 'ACTIVE', + }, + }); + + if (!recoveryMnemonic) { + this.logger.error(`No recovery mnemonic found for account ${command.accountSequence}`); + throw new ApplicationError('账户未设置恢复助记词'); } - if (expectedAddresses.length === 0) { - throw new ApplicationError('账户没有关联的钱包地址'); - } - - // 调用 blockchain-service 验证助记词 - this.logger.log(`Verifying mnemonic for account ${command.accountSequence}`); - const verifyResult = await this.blockchainClient.verifyMnemonic({ + // 调用 blockchain-service 验证助记词哈希 + this.logger.log(`Verifying mnemonic hash for account ${command.accountSequence}`); + const verifyResult = await this.blockchainClient.verifyMnemonicHash({ mnemonic: command.mnemonic, - expectedAddresses, + expectedHash: recoveryMnemonic.mnemonicHash, }); if (!verifyResult.valid) { - this.logger.warn(`Mnemonic verification failed for account ${command.accountSequence}`); + this.logger.warn(`Mnemonic hash mismatch for account ${command.accountSequence}`); throw new ApplicationError('助记词错误'); } diff --git a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts index e16493a2..8b096e14 100644 --- a/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts +++ b/backend/services/identity-service/src/infrastructure/external/blockchain/blockchain-client.service.ts @@ -25,6 +25,16 @@ export interface VerifyMnemonicResult { mismatchedAddresses: string[]; } +export interface VerifyMnemonicHashParams { + mnemonic: string; + expectedHash: string; +} + +export interface VerifyMnemonicHashResult { + valid: boolean; + message?: string; +} + export interface DerivedAddress { chainType: string; address: string; @@ -76,6 +86,35 @@ export class BlockchainClientService { } } + /** + * 验证助记词哈希是否匹配(用于账户恢复) + */ + async verifyMnemonicHash(params: VerifyMnemonicHashParams): Promise { + this.logger.log(`Verifying mnemonic hash`); + + try { + const response = await firstValueFrom( + this.httpService.post( + `${this.blockchainServiceUrl}/internal/verify-mnemonic-hash`, + { + mnemonic: params.mnemonic, + expectedHash: params.expectedHash, + }, + { + headers: { 'Content-Type': 'application/json' }, + timeout: 30000, + }, + ), + ); + + this.logger.log(`Mnemonic hash verification result: valid=${response.data.valid}`); + return response.data; + } catch (error) { + this.logger.error('Failed to verify mnemonic hash', error); + throw error; + } + } + /** * 从助记词派生所有链的钱包地址 */