From 6150617c147ae829011fadc6db17cfc300a84939 Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 6 Dec 2025 20:02:50 -0800 Subject: [PATCH] docs: update blockchain-service guide with address derivation responsibilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../blockchain-service/DEVELOPMENT_GUIDE.md | 415 +++++++++++++++++- 1 file changed, 410 insertions(+), 5 deletions(-) diff --git a/backend/services/blockchain-service/DEVELOPMENT_GUIDE.md b/backend/services/blockchain-service/DEVELOPMENT_GUIDE.md index dbbdfd1b..401f57ec 100644 --- a/backend/services/blockchain-service/DEVELOPMENT_GUIDE.md +++ b/backend/services/blockchain-service/DEVELOPMENT_GUIDE.md @@ -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 { + 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 ไบ‹ไปถ*