docs: update blockchain-service guide with address derivation responsibilities
- Add public key → address derivation as primary responsibility - Add AddressDerivationAdapter for EVM/Cosmos address derivation - Add WalletAddressCreated event definition - Add MPC event consumption (mpc.KeygenCompleted) - Add MpcKeygenCompletedHandler for processing keygen events - Add section 17: MPC integration event flow with diagrams - Update document version to 1.2.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
23043d5d79
commit
6150617c14
|
|
@ -6,12 +6,46 @@
|
|||
|
||||
blockchain-service 是 RWA 榴莲女皇平台的区块链基础设施服务,负责:
|
||||
|
||||
- **公钥→地址派生**:从 MPC 公钥派生多链钱包地址 (EVM/Cosmos)
|
||||
- **链上事件监听**:监听 ERC20 Transfer 事件,检测用户充值
|
||||
- **充值入账触发**:检测到充值后通知 wallet-service 入账
|
||||
- **余额查询**:查询链上 USDT/原生代币余额
|
||||
- **交易广播**:提交签名后的交易到链上
|
||||
- **地址管理**:管理平台充值地址池
|
||||
|
||||
### 1.4 与其他服务的关系
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ identity-service│ │ mpc-service │ │blockchain-service│
|
||||
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
|
||||
│ - 用户账户 │ │ - 密钥分片生成 │ │ - 公钥→地址派生 │
|
||||
│ - 设备绑定 │ │ - 签名协调 │ │ - 充值检测 │
|
||||
│ - KYC验证 │ │ - 分片存储 │ │ - 交易广播 │
|
||||
│ - 身份认证 │ │ - 阈值策略 │ │ - 余额查询 │
|
||||
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
||||
│ │ │
|
||||
│ MpcKeygenRequested │ KeygenCompleted │
|
||||
│──────────────────────>│ (with publicKey) │
|
||||
│ │──────────────────────>│
|
||||
│ │ │ derive address
|
||||
│ WalletAddressCreated │
|
||||
│<──────────────────────────────────────────────│
|
||||
│ (存储 userId ↔ walletAddress 关联) │
|
||||
│ │ │
|
||||
│ wallet-service │
|
||||
│ │ │
|
||||
│ WalletAddressCreated │
|
||||
│<──────────────────────────────────────────────│
|
||||
│ (存储钱包地址、管理余额) │
|
||||
```
|
||||
|
||||
**职责边界原则**:
|
||||
- **identity-service**:只关心用户身份,不处理区块链技术细节
|
||||
- **mpc-service**:只关心 MPC 协议,生成公钥后发布事件
|
||||
- **blockchain-service**:封装所有区块链技术,包括地址派生、链交互
|
||||
- **wallet-service**:管理用户钱包的业务逻辑,不直接与链交互
|
||||
|
||||
### 1.2 技术栈
|
||||
|
||||
| 组件 | 技术选型 |
|
||||
|
|
@ -119,7 +153,8 @@ blockchain-service/
|
|||
│ │ │ ├── deposit-detection.service.ts
|
||||
│ │ │ ├── balance-query.service.ts
|
||||
│ │ │ ├── transaction-broadcast.service.ts
|
||||
│ │ │ └── address-registry.service.ts
|
||||
│ │ │ ├── address-registry.service.ts
|
||||
│ │ │ └── address-derivation.service.ts # 公钥→地址派生
|
||||
│ │ ├── commands/
|
||||
│ │ │ ├── register-address/
|
||||
│ │ │ │ ├── register-address.command.ts
|
||||
|
|
@ -155,6 +190,7 @@ blockchain-service/
|
|||
│ │ │ ├── deposit-detected.event.ts
|
||||
│ │ │ ├── deposit-confirmed.event.ts
|
||||
│ │ │ ├── transaction-broadcasted.event.ts
|
||||
│ │ │ ├── wallet-address-created.event.ts # 地址派生完成事件
|
||||
│ │ │ └── index.ts
|
||||
│ │ ├── repositories/
|
||||
│ │ │ ├── deposit-transaction.repository.interface.ts
|
||||
|
|
@ -182,6 +218,7 @@ blockchain-service/
|
|||
│ │ │ ├── event-listener.service.ts
|
||||
│ │ │ ├── block-scanner.service.ts
|
||||
│ │ │ ├── transaction-sender.service.ts
|
||||
│ │ │ ├── address-derivation.adapter.ts # 地址派生适配器
|
||||
│ │ │ └── blockchain.module.ts
|
||||
│ │ ├── persistence/
|
||||
│ │ │ ├── prisma/
|
||||
|
|
@ -1137,6 +1174,51 @@ export { TxHash } from './tx-hash.vo';
|
|||
|
||||
### 4.4 领域事件
|
||||
|
||||
### 4.6 WalletAddressCreated 事件
|
||||
|
||||
```typescript
|
||||
// src/domain/events/wallet-address-created.event.ts
|
||||
|
||||
import { DomainEvent } from './domain-event.base';
|
||||
|
||||
export interface WalletAddressCreatedPayload {
|
||||
userId: string;
|
||||
username: string;
|
||||
publicKey: string; // 压缩公钥 (33 bytes hex)
|
||||
addresses: Array<{
|
||||
chainType: string; // BSC, KAVA, DST
|
||||
address: string; // 派生的地址
|
||||
addressType: string; // EVM 或 COSMOS
|
||||
}>;
|
||||
mpcSessionId: string; // MPC 会话 ID
|
||||
delegateShare?: {
|
||||
partyId: string;
|
||||
partyIndex: number;
|
||||
encryptedShare: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class WalletAddressCreatedEvent extends DomainEvent {
|
||||
constructor(public readonly payload: WalletAddressCreatedPayload) {
|
||||
super();
|
||||
}
|
||||
|
||||
get eventType(): string {
|
||||
return 'WalletAddressCreated';
|
||||
}
|
||||
|
||||
get aggregateId(): string {
|
||||
return this.payload.userId;
|
||||
}
|
||||
|
||||
get aggregateType(): string {
|
||||
return 'WalletAddress';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
```typescript
|
||||
// src/domain/events/deposit-detected.event.ts
|
||||
|
||||
|
|
@ -1484,7 +1566,127 @@ export class EventListenerService implements OnModuleInit, OnModuleDestroy {
|
|||
}
|
||||
```
|
||||
|
||||
### 5.3 区块扫描器(补扫服务)
|
||||
### 5.3 地址派生适配器
|
||||
|
||||
```typescript
|
||||
// src/infrastructure/blockchain/address-derivation.adapter.ts
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { createHash } from 'crypto';
|
||||
import { bech32 } from 'bech32';
|
||||
import { ethers } from 'ethers';
|
||||
import * as secp256k1 from 'secp256k1';
|
||||
|
||||
export enum AddressType {
|
||||
EVM = 'EVM',
|
||||
COSMOS = 'COSMOS',
|
||||
}
|
||||
|
||||
export interface DerivedAddress {
|
||||
chainType: string;
|
||||
address: string;
|
||||
addressType: AddressType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址派生适配器
|
||||
*
|
||||
* 从压缩的 secp256k1 公钥派生多链钱包地址
|
||||
* - EVM 链 (BSC, KAVA EVM): 使用 keccak256(uncompressed_pubkey[1:])[-20:]
|
||||
* - Cosmos 链 (KAVA, DST): 使用 bech32(ripemd160(sha256(compressed_pubkey)))
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddressDerivationAdapter {
|
||||
private readonly logger = new Logger(AddressDerivationAdapter.name);
|
||||
|
||||
/**
|
||||
* 从压缩公钥派生所有支持链的地址
|
||||
* @param compressedPubKeyHex 33 字节压缩公钥 (hex 格式,不含 0x 前缀)
|
||||
*/
|
||||
deriveAllAddresses(compressedPubKeyHex: string): DerivedAddress[] {
|
||||
const pubKeyBytes = Buffer.from(compressedPubKeyHex, 'hex');
|
||||
|
||||
if (pubKeyBytes.length !== 33) {
|
||||
throw new Error(`Invalid compressed public key length: ${pubKeyBytes.length}, expected 33`);
|
||||
}
|
||||
|
||||
const addresses: DerivedAddress[] = [];
|
||||
|
||||
// EVM 地址 (BSC, KAVA EVM)
|
||||
const evmAddress = this.deriveEvmAddress(pubKeyBytes);
|
||||
addresses.push({ chainType: 'BSC', address: evmAddress, addressType: AddressType.EVM });
|
||||
addresses.push({ chainType: 'KAVA', address: evmAddress, addressType: AddressType.EVM });
|
||||
|
||||
// Cosmos 地址 (KAVA Native 使用 kava 前缀,DST 使用 dst 前缀)
|
||||
const kavaCosmosAddress = this.deriveCosmosAddress(pubKeyBytes, 'kava');
|
||||
const dstAddress = this.deriveCosmosAddress(pubKeyBytes, 'dst');
|
||||
addresses.push({ chainType: 'KAVA_COSMOS', address: kavaCosmosAddress, addressType: AddressType.COSMOS });
|
||||
addresses.push({ chainType: 'DST', address: dstAddress, addressType: AddressType.COSMOS });
|
||||
|
||||
this.logger.log(`Derived ${addresses.length} addresses from public key`);
|
||||
return addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 派生 EVM 地址
|
||||
* 1. 解压公钥 (33 bytes → 65 bytes)
|
||||
* 2. 取非压缩公钥去掉前缀 (64 bytes)
|
||||
* 3. keccak256 哈希后取后 20 bytes
|
||||
*/
|
||||
private deriveEvmAddress(compressedPubKey: Buffer): string {
|
||||
// 使用 secp256k1 解压公钥
|
||||
const uncompressedPubKey = Buffer.from(
|
||||
secp256k1.publicKeyConvert(compressedPubKey, false)
|
||||
);
|
||||
|
||||
// 去掉 0x04 前缀,取 64 bytes
|
||||
const pubKeyWithoutPrefix = uncompressedPubKey.slice(1);
|
||||
|
||||
// keccak256 哈希后取后 20 bytes
|
||||
const hash = ethers.keccak256(pubKeyWithoutPrefix);
|
||||
const address = '0x' + hash.slice(-40);
|
||||
|
||||
return ethers.getAddress(address); // 返回 checksum 格式
|
||||
}
|
||||
|
||||
/**
|
||||
* 派生 Cosmos 地址
|
||||
* 1. SHA256(compressed_pubkey)
|
||||
* 2. RIPEMD160(sha256_result)
|
||||
* 3. bech32 编码
|
||||
*/
|
||||
private deriveCosmosAddress(compressedPubKey: Buffer, prefix: string): string {
|
||||
// SHA256
|
||||
const sha256Hash = createHash('sha256').update(compressedPubKey).digest();
|
||||
|
||||
// RIPEMD160
|
||||
const ripemd160Hash = createHash('ripemd160').update(sha256Hash).digest();
|
||||
|
||||
// Bech32 编码
|
||||
const words = bech32.toWords(ripemd160Hash);
|
||||
return bech32.encode(prefix, words);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证地址格式
|
||||
*/
|
||||
validateAddress(chainType: string, address: string): boolean {
|
||||
switch (chainType) {
|
||||
case 'BSC':
|
||||
case 'KAVA':
|
||||
return ethers.isAddress(address);
|
||||
case 'KAVA_COSMOS':
|
||||
return /^kava1[a-z0-9]{38}$/.test(address);
|
||||
case 'DST':
|
||||
return /^dst1[a-z0-9]{38}$/.test(address);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 区块扫描器(补扫服务)
|
||||
|
||||
```typescript
|
||||
// src/infrastructure/blockchain/block-scanner.service.ts
|
||||
|
|
@ -1986,16 +2188,126 @@ export class BalanceQueryService {
|
|||
// src/infrastructure/kafka/event-publisher.service.ts
|
||||
|
||||
export const BLOCKCHAIN_TOPICS = {
|
||||
// 地址派生完成事件 (发给 identity-service, wallet-service)
|
||||
WALLET_ADDRESS_CREATED: 'blockchain.WalletAddressCreated',
|
||||
|
||||
// 充值相关事件
|
||||
DEPOSIT_DETECTED: 'blockchain.DepositDetected',
|
||||
DEPOSIT_CONFIRMED: 'blockchain.DepositConfirmed',
|
||||
|
||||
// 交易相关事件
|
||||
TRANSACTION_BROADCASTED: 'blockchain.TransactionBroadcasted',
|
||||
TRANSACTION_CONFIRMED: 'blockchain.TransactionConfirmed',
|
||||
|
||||
// 区块扫描事件
|
||||
BLOCK_SCANNED: 'blockchain.BlockScanned',
|
||||
} as const;
|
||||
```
|
||||
|
||||
### 7.2 消费的事件
|
||||
|
||||
```typescript
|
||||
// src/infrastructure/kafka/mpc-event-consumer.service.ts
|
||||
|
||||
// 消费 MPC 服务发布的事件
|
||||
export const MPC_CONSUME_TOPICS = {
|
||||
KEYGEN_COMPLETED: 'mpc.KeygenCompleted',
|
||||
SESSION_FAILED: 'mpc.SessionFailed',
|
||||
} as const;
|
||||
|
||||
export interface KeygenCompletedPayload {
|
||||
sessionId: string;
|
||||
partyId: string;
|
||||
publicKey: string; // 33 bytes 压缩公钥 (hex)
|
||||
shareId: string;
|
||||
threshold: string; // "2-of-3"
|
||||
extraPayload?: {
|
||||
userId: string;
|
||||
username: string;
|
||||
delegateShare?: {
|
||||
partyId: string;
|
||||
partyIndex: number;
|
||||
encryptedShare: string;
|
||||
};
|
||||
serverParties?: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 MPC Keygen 完成事件处理
|
||||
|
||||
```typescript
|
||||
// src/application/event-handlers/mpc-keygen-completed.handler.ts
|
||||
|
||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
||||
import { MpcEventConsumerService, KeygenCompletedPayload } from '@/infrastructure/kafka/mpc-event-consumer.service';
|
||||
import { AddressDerivationAdapter } from '@/infrastructure/blockchain/address-derivation.adapter';
|
||||
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
||||
import { WalletAddressCreatedEvent } from '@/domain/events';
|
||||
|
||||
@Injectable()
|
||||
export class MpcKeygenCompletedHandler implements OnModuleInit {
|
||||
private readonly logger = new Logger(MpcKeygenCompletedHandler.name);
|
||||
|
||||
constructor(
|
||||
private readonly mpcEventConsumer: MpcEventConsumerService,
|
||||
private readonly addressDerivation: AddressDerivationAdapter,
|
||||
private readonly eventPublisher: EventPublisherService,
|
||||
) {}
|
||||
|
||||
async onModuleInit() {
|
||||
this.mpcEventConsumer.onKeygenCompleted(this.handleKeygenCompleted.bind(this));
|
||||
this.logger.log('Registered KeygenCompleted event handler');
|
||||
}
|
||||
|
||||
private async handleKeygenCompleted(payload: KeygenCompletedPayload): Promise<void> {
|
||||
const userId = payload.extraPayload?.userId;
|
||||
const username = payload.extraPayload?.username;
|
||||
const publicKey = payload.publicKey;
|
||||
|
||||
if (!userId || !username || !publicKey) {
|
||||
this.logger.error('Missing required fields in KeygenCompleted payload');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`[DERIVE] Processing keygen completion for userId=${userId}`);
|
||||
this.logger.log(`[DERIVE] PublicKey: ${publicKey.substring(0, 20)}...`);
|
||||
|
||||
try {
|
||||
// 从公钥派生所有链的地址
|
||||
const addresses = this.addressDerivation.deriveAllAddresses(publicKey);
|
||||
|
||||
this.logger.log(`[DERIVE] Derived ${addresses.length} addresses:`);
|
||||
addresses.forEach(addr => {
|
||||
this.logger.log(`[DERIVE] ${addr.chainType}: ${addr.address}`);
|
||||
});
|
||||
|
||||
// 发布 WalletAddressCreated 事件
|
||||
const event = new WalletAddressCreatedEvent({
|
||||
userId,
|
||||
username,
|
||||
publicKey,
|
||||
addresses: addresses.map(a => ({
|
||||
chainType: a.chainType,
|
||||
address: a.address,
|
||||
addressType: a.addressType,
|
||||
})),
|
||||
mpcSessionId: payload.sessionId,
|
||||
delegateShare: payload.extraPayload?.delegateShare,
|
||||
});
|
||||
|
||||
await this.eventPublisher.publish(event);
|
||||
this.logger.log(`[DERIVE] Published WalletAddressCreated event for userId=${userId}`);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`[DERIVE] Failed to derive addresses: ${error.message}`, error.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 消费的 Identity/Wallet 事件
|
||||
|
||||
```typescript
|
||||
// src/infrastructure/kafka/event-consumer.controller.ts
|
||||
|
||||
|
|
@ -2380,6 +2692,99 @@ constructor(
|
|||
|
||||
---
|
||||
|
||||
*文档版本: 1.1.0*
|
||||
*最后更新: 2025-12-03*
|
||||
*变更说明: 添加模块化设计、聚合根基类、完整值对象定义、架构兼容性说明*
|
||||
## 17. MPC 集成事件流
|
||||
|
||||
### 17.1 完整事件流程图
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MPC 钱包创建事件流 │
|
||||
├──────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [1] 用户创建账户 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ identity-service│ │
|
||||
│ │ │ ────────────────────────────────────────┐ │
|
||||
│ │ 发布事件: │ │ │
|
||||
│ │ mpc.KeygenRequested │ │
|
||||
│ └─────────────────┘ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ [2] MPC 密钥生成 │ mpc-service │ │
|
||||
│ │ │ │
|
||||
│ ┌────────────────────────────────────────────│ 消费事件: │ │
|
||||
│ │ │ mpc.KeygenRequested │
|
||||
│ │ │ │ │
|
||||
│ │ [3] 生成公钥 │ 发布事件: │ │
|
||||
│ │ │ │ mpc.KeygenStarted │
|
||||
│ │ │ │ mpc.KeygenCompleted │
|
||||
│ │ ▼ └─────────────────┘ │
|
||||
│ │ publicKey: 33 bytes │ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ blockchain-service │ │
|
||||
│ │ │ │
|
||||
│ │ [4] 消费: mpc.KeygenCompleted │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ AddressDerivationAdapter │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ publicKey ──┬──> deriveEvmAddress() ──> 0x1234... │ │ │
|
||||
│ │ │ │ (BSC, KAVA) │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ └──> deriveCosmosAddress() ──> kava1... │ │ │
|
||||
│ │ │ dst1... │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ │ │
|
||||
│ │ [5] 发布: blockchain.WalletAddressCreated │ │
|
||||
│ │ │ │ │
|
||||
│ └───────┼──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ├──────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ identity-service│ │ wallet-service │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ [6] 存储关联: │ │ [7] 存储钱包: │ │
|
||||
│ │ userId ↔ address│ │ 地址、余额管理 │ │
|
||||
│ └─────────────────┘ └─────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 17.2 事件 Topic 汇总
|
||||
|
||||
| Topic | 发布者 | 消费者 | 描述 |
|
||||
|-------|--------|--------|------|
|
||||
| `mpc.KeygenRequested` | identity-service | mpc-service | 请求生成 MPC 密钥 |
|
||||
| `mpc.KeygenStarted` | mpc-service | identity-service | 密钥生成开始 |
|
||||
| `mpc.KeygenCompleted` | mpc-service | blockchain-service | 密钥生成完成,包含公钥 |
|
||||
| `mpc.SessionFailed` | mpc-service | identity-service, blockchain-service | 会话失败 |
|
||||
| `blockchain.WalletAddressCreated` | blockchain-service | identity-service, wallet-service | 地址派生完成 |
|
||||
|
||||
### 17.3 关键设计决策
|
||||
|
||||
1. **职责分离**:
|
||||
- mpc-service 只负责 MPC 协议,不了解区块链地址格式
|
||||
- blockchain-service 封装所有区块链技术细节
|
||||
- identity-service 不直接处理公钥→地址转换
|
||||
|
||||
2. **多链扩展性**:
|
||||
- 新增链类型只需在 `AddressDerivationAdapter` 添加派生逻辑
|
||||
- 事件格式保持不变,下游服务无需修改
|
||||
|
||||
3. **事件溯源**:
|
||||
- 所有状态变更通过事件传递
|
||||
- 支持事件重放和故障恢复
|
||||
|
||||
---
|
||||
|
||||
*文档版本: 1.2.0*
|
||||
*最后更新: 2025-12-06*
|
||||
*变更说明: 添加公钥→地址派生职责、MPC 事件集成、AddressDerivationAdapter、WalletAddressCreated 事件*
|
||||
|
|
|
|||
Loading…
Reference in New Issue