# MPC分布式签名系统 - 完整技术规范 > **真正的去中心化MPC架构**:对等参与、零信任、Share物理隔离 ## 目录 1. [系统概述](#1-系统概述) 2. [核心架构](#2-核心架构) 3. [技术栈](#3-技术栈) 4. [领域模型设计](#4-领域模型设计) 5. [核心服务实现](#5-核心服务实现) 6. [数据库设计](#6-数据库设计) 7. [客户端SDK](#7-客户端sdk) 8. [API接口](#8-api接口) 9. [部署方案](#9-部署方案) 10. [安全设计](#10-安全设计) --- ## 1. 系统概述 ### 1.1 核心理念 **真正的分布式MPC签名**: - ✅ 私钥**从未在任何地方完整存在** - ✅ 所有参与方(Party)地位**完全对等** - ✅ 客户端和服务器都运行**完整的tss-lib** - ✅ Coordinator只负责**协调,不参与计算** - ✅ Share**物理隔离**存储,互不可见 ### 1.2 业务场景 | 场景 | 阈值方案 | 参与方 | |------|---------|--------| | 账号注册 | 2-of-3 | 用户设备 + 服务器 + 恢复密钥 | | 多人审核 | 3-of-5 | 5个审核员,需3人同意 | | 高安全审批 | 4-of-7 | 7个高管,需4人同意 | | 数据签名 | 2-of-3 | 应用服务器 + HSM + 备份 | ### 1.3 关键特性 - 🔐 **零信任架构**:无需信任任何单一节点 - 🚀 **跨平台支持**:Android、iOS、PC、Server - 📱 **硬件安全**:Android KeyStore、Secure Enclave、HSM - ⚡ **高可用**:任意t个Party即可完成签名 - 🔄 **可恢复**:通过MPC协议安全恢复丢失的share - 🏗️ **微服务架构**:DDD + Hexagonal + 独立部署 --- ## 2. 核心架构 ### 2.1 整体架构图 ``` ┌─────────────────────────── MPC 参与方层(对等架构)───────────────────────────┐ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ │ Party 1 │ │ Party 2 │ │ Party 3 │ │ │ │ (用户手机) │ │ (服务器节点) │ │ (恢复密钥) │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ │ │ │ tss-lib │ │ │ │ tss-lib │ │ │ │ tss-lib │ │ │ │ │ │ (Go Mobile) │ │ │ │ (Go Native) │ │ │ │ (Go Native) │ │ │ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────▼───────┐ │ │ ┌──────▼───────┐ │ │ ┌──────▼───────┐ │ │ │ │ │ Share 1 │ │ │ │ Share 2 │ │ │ │ Share 3 │ │ │ │ │ │ (KeyStore) │ │ │ │ (HSM/PG) │ │ │ │ (Cold Store) │ │ │ │ │ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │ │ │ └──────────┬───────┘ └──────────┬───────┘ └──────────┬───────┘ │ │ │ │ │ │ │ └─────────────────────────┼─────────────────────────┘ │ │ │ │ │ P2P MPC 消息交换(端到端加密) │ │ │ │ └────────────────────────────────────────┼───────────────────────────────────┘ │ │ ┌────────────────────────────────────────▼───────────────────────────────────┐ │ 协调服务层(不参与MPC计算) │ │ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ Session Coordinator │ │ Message Router │ │ │ │ │ │ │ │ │ │ • 创建MPC会话 │ │ • P2P消息中继 │ │ │ │ • 管理参与方列表 │◄────────────►│ • 消息持久化 │ │ │ │ • 会话状态追踪 │ │ • 离线消息缓存 │ │ │ │ • 超时控制 │ │ • 消息去重排序 │ │ │ │ • 参与方认证 │ │ │ │ │ │ │ │ ❌ 不解密MPC消息 │ │ │ │ ❌ 不存储Share │ │ ❌ 不参与MPC计算 │ │ │ │ ❌ 不参与MPC计算 │ │ │ │ │ └──────────────────────┘ └──────────────────────┘ │ │ │ └────────────────────────────────────────┬───────────────────────────────────┘ │ │ ┌────────────────────────────────────────▼───────────────────────────────────┐ │ 业务服务层 │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Account │ │ Audit │ │ Data │ │ │ │ Service │ │ Service │ │ Integrity │ │ │ │ │ │ │ │ Service │ │ │ │ • 用户管理 │ │ • 审核工作流 │ │ • 数据签名 │ │ │ │ • 账号创建 │ │ • 多签管理 │ │ • 签名验证 │ │ │ │ • 恢复流程 │ │ • 审批追踪 │ │ • 防篡改 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └────────────────────────────────────────┬───────────────────────────────────┘ │ ┌────────────────────────────────────────▼───────────────────────────────────┐ │ 基础设施层 │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ PostgreSQL │ │ Redis │ │ RabbitMQ │ │ Consul │ │ │ │ │ │ │ │ │ │ │ │ │ │ • 会话状态 │ │ • 临时缓存 │ │ • 消息队列 │ │ • 服务发现 │ │ │ │ • 元数据 │ │ • 分布式锁 │ │ • 事件总线 │ │ • 配置中心 │ │ │ │ • 审计日志 │ │ │ │ │ │ │ │ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### 2.2 MPC消息流(2-of-3 账号创建) ``` 时序图:用户注册账号(2-of-3 Keygen) ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ Android │ │ Server │ │Recovery │ │Coordinator│ │ Party │ │ Party │ │ Party │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬─────┘ │ │ │ │ │ 1. Request Create Account │ │ ├──────────────────────────────────────────────────────────>│ │ │ │ │ │ 2. Create Keygen Session (3 parties, t=2) │ │<──────────────────────────────────────────────────────────┤ │ SessionID: abc-123 │ │ │ JoinTokens: {party1, party2, party3}│ │ │ │ │ │ │ 3. Join Session │ │ │ ├──────────────────────────────────────────────────────────>│ │ │ │ ✓ Party1 Joined │ │ │ 4. Join Session │ │ │ ├──────────────────────────────────────>│ │ │ │ ✓ Party2 Joined │ │ │ │ 5. Join Session │ │ │ ├──────────────────>│ │ │ │ ✓ Party3 Joined │ │ │ │ │ │ 6. 所有Party就绪,开始 TSS Keygen Protocol │ │ │ │ │ │ Round 1: 生成随机commitment │ │ ├──────────────────►│◄─────────────────►│ │ │ (通过Message Router中继消息) │ │ │ │ │ │ │ Round 2: Decommitment & Secret Share │ │ ├──────────────────►│◄─────────────────►│ │ │ │ │ │ │ Round 3: VSS Verification │ │ ├──────────────────►│◄─────────────────►│ │ │ │ │ │ │ 7. Keygen完成,各方获得自己的Share │ │ │ ✓ Share1 │ ✓ Share2 │ ✓ Share3 │ │ (存KeyStore) │ (存HSM/DB) │ (离线存储) │ │ │ │ │ │ 8. 上报完成状态,返回群公钥 │ │ ├──────────────────────────────────────────────────────────>│ │ │ │ │ │ 9. PublicKey: 0x1a2b3c... │ │ │<──────────────────────────────────────────────────────────┤ │ │ │ │ 注意: - Coordinator只负责会话管理,不参与MPC计算 - 各Party直接通过Message Router交换加密消息 - 无任何节点知道完整私钥 - 各Party的Share完全物理隔离 ``` ### 2.3 架构设计原则 | 原则 | 说明 | 实现方式 | |------|------|---------| | **对等参与** | 所有Party地位平等,无主从关系 | 客户端和服务器都运行tss-lib | | **零信任** | 不信任任何单一节点 | 需要t个Party协同才能签名 | | **物理隔离** | Share分布在不同物理位置 | Android KeyStore / HSM / Cold Storage | | **协调不计算** | Coordinator只管理流程 | Session Coordinator不参与MPC | | **端到端加密** | MPC消息加密传输 | Message Router不解密消息内容 | | **可审计** | 所有操作可追溯 | 完整的审计日志 | --- ## 3. 技术栈 ### 3.1 核心技术选型 | 组件 | 技术 | 版本 | 说明 | |------|------|------|------| | MPC库 | Binance tss-lib | latest | ECDSA阈值签名 | | 后端语言 | Go | 1.21+ | 高性能、并发友好 | | 移动端 | Go Mobile + Kotlin/Swift | - | 跨平台MPC实现 | | 数据库 | PostgreSQL | 15+ | 关系型存储 | | 缓存 | Redis | 7+ | 会话缓存、分布式锁 | | 消息队列 | RabbitMQ | 3.12+ | 异步消息、事件总线 | | 服务发现 | Consul | 1.16+ | 服务注册、健康检查 | | API协议 | gRPC + REST | - | 高性能RPC | | 容器化 | Docker + K8s | - | 微服务部署 | ### 3.2 安全组件 | 组件 | 用途 | 实现 | |------|------|------| | Android KeyStore | 移动端Share存储 | 硬件级加密 | | Secure Enclave | iOS Share存储 | 硬件级加密 | | HSM | 服务器Share存储 | 硬件安全模块 | | TLS 1.3 | 通信加密 | 强制启用 | | JWT | 身份认证 | Token-based | | AES-256-GCM | 数据加密 | Share加密 | --- ## 4. 领域模型设计 ### 4.1 Session Coordinator Service(DDD+Hexagonal) ``` session-coordinator/ ├── domain/ # 领域层(核心业务逻辑) │ ├── entities/ │ │ ├── mpc_session.go # MPC会话实体 │ │ ├── participant.go # 参与方实体 │ │ └── session_message.go # 会话消息实体 │ ├── value_objects/ │ │ ├── session_id.go │ │ ├── party_id.go │ │ ├── threshold.go │ │ └── session_status.go │ ├── aggregates/ │ │ └── session_aggregate.go # 会话聚合根 │ ├── repositories/ # 仓储接口(端口) │ │ ├── session_repository.go │ │ └── message_repository.go │ └── services/ # 领域服务 │ ├── session_coordinator.go # 会话协调器 │ └── message_router.go # 消息路由器 │ ├── application/ # 应用层(用例编排) │ ├── use_cases/ │ │ ├── create_session.go # 创建会话用例 │ │ ├── join_session.go # 加入会话用例 │ │ ├── get_session_status.go # 查询会话状态 │ │ ├── route_message.go # 路由消息用例 │ │ └── close_session.go # 关闭会话用例 │ └── ports/ # 端口定义 │ ├── input/ │ │ └── session_management_port.go │ └── output/ │ ├── session_storage_port.go │ ├── message_broker_port.go │ └── notification_port.go │ ├── adapters/ # 适配器层(技术实现) │ ├── input/ # 入站适配器 │ │ ├── grpc/ │ │ │ ├── session_grpc_handler.go │ │ │ └── message_grpc_handler.go │ │ └── http/ │ │ └── session_http_handler.go │ └── output/ # 出站适配器 │ ├── postgres/ │ │ ├── session_postgres_repo.go │ │ └── message_postgres_repo.go │ ├── redis/ │ │ └── session_cache_adapter.go │ └── rabbitmq/ │ └── event_publisher_adapter.go │ └── pkg/ # 通用包 ├── crypto/ ├── errors/ └── utils/ ``` ### 4.2 核心领域模型代码 ```go // domain/entities/mpc_session.go package entities import ( "time" "github.com/google/uuid" ) // MPCSession 代表一个MPC会话 // Coordinator只管理会话元数据,不参与MPC计算 type MPCSession struct { ID uuid.UUID SessionType SessionType // keygen 或 sign ThresholdN int // 总参与方数 ThresholdT int // 所需参与方数 Participants []Participant Status SessionStatus MessageHash []byte // Sign会话使用 PublicKey []byte // Keygen完成后的群公钥 CreatedBy string CreatedAt time.Time UpdatedAt time.Time ExpiresAt time.Time CompletedAt *time.Time } type SessionType string const ( SessionTypeKeygen SessionType = "keygen" SessionTypeSign SessionType = "sign" ) type SessionStatus string const ( SessionCreated SessionStatus = "created" SessionInProgress SessionStatus = "in_progress" SessionCompleted SessionStatus = "completed" SessionFailed SessionStatus = "failed" SessionExpired SessionStatus = "expired" ) // Participant 参与方 type Participant struct { PartyID string PartyIndex int Status ParticipantStatus DeviceInfo DeviceInfo PublicKey []byte // Party的身份公钥(用于认证) JoinedAt time.Time CompletedAt *time.Time } type ParticipantStatus string const ( ParticipantInvited ParticipantStatus = "invited" ParticipantJoined ParticipantStatus = "joined" ParticipantReady ParticipantStatus = "ready" ParticipantCompleted ParticipantStatus = "completed" ParticipantFailed ParticipantStatus = "failed" ) type DeviceInfo struct { DeviceType string // android, ios, pc, server DeviceID string Platform string AppVersion string } // SessionMessage MPC消息(加密,Coordinator不解密) type SessionMessage struct { ID uuid.UUID SessionID uuid.UUID FromParty string ToParties []string // nil表示广播 RoundNumber int MessageType string Payload []byte // 加密的MPC协议消息 CreatedAt time.Time DeliveredAt *time.Time } // 业务方法 func (s *MPCSession) CanStart() bool { // 检查是否所有参与方都已加入 joinedCount := 0 for _, p := range s.Participants { if p.Status == ParticipantJoined || p.Status == ParticipantReady { joinedCount++ } } return joinedCount == s.ThresholdN } func (s *MPCSession) AddParticipant(p Participant) error { if len(s.Participants) >= s.ThresholdN { return errors.New("session is full") } s.Participants = append(s.Participants, p) return nil } func (s *MPCSession) UpdateParticipantStatus(partyID string, status ParticipantStatus) error { for i, p := range s.Participants { if p.PartyID == partyID { s.Participants[i].Status = status if status == ParticipantCompleted { now := time.Now() s.Participants[i].CompletedAt = &now } return nil } } return errors.New("participant not found") } func (s *MPCSession) IsExpired() bool { return time.Now().After(s.ExpiresAt) } func (s *MPCSession) AllCompleted() bool { for _, p := range s.Participants { if p.Status != ParticipantCompleted { return false } } return true } ``` ```go // domain/repositories/session_repository.go package repositories import ( "context" "github.com/google/uuid" "yourorg/mpc/domain/entities" ) // SessionRepository 会话仓储接口(端口) type SessionRepository interface { Save(ctx context.Context, session *entities.MPCSession) error FindByID(ctx context.Context, id uuid.UUID) (*entities.MPCSession, error) FindByStatus(ctx context.Context, status entities.SessionStatus) ([]*entities.MPCSession, error) Update(ctx context.Context, session *entities.MPCSession) error Delete(ctx context.Context, id uuid.UUID) error } // MessageRepository 消息仓储接口 type MessageRepository interface { SaveMessage(ctx context.Context, msg *entities.SessionMessage) error GetMessages(ctx context.Context, sessionID uuid.UUID, partyID string, afterTime time.Time) ([]*entities.SessionMessage, error) MarkDelivered(ctx context.Context, messageID uuid.UUID) error } ``` ### 4.3 应用层用例实现 ```go // application/use_cases/create_session.go package use_cases import ( "context" "time" "github.com/google/uuid" "yourorg/mpc/domain/entities" "yourorg/mpc/domain/repositories" ) type CreateSessionInput struct { InitiatorID string SessionType string // "keygen" or "sign" ThresholdN int ThresholdT int Participants []ParticipantInfo MessageHash []byte // Sign会话需要 ExpiresIn time.Duration } type ParticipantInfo struct { PartyID string DeviceInfo entities.DeviceInfo } type CreateSessionOutput struct { SessionID uuid.UUID JoinTokens map[string]string // PartyID -> JoinToken ExpiresAt time.Time } type CreateSessionUseCase struct { sessionRepo repositories.SessionRepository tokenGen TokenGenerator eventPublisher EventPublisher } func NewCreateSessionUseCase( sessionRepo repositories.SessionRepository, tokenGen TokenGenerator, eventPublisher EventPublisher, ) *CreateSessionUseCase { return &CreateSessionUseCase{ sessionRepo: sessionRepo, tokenGen: tokenGen, eventPublisher: eventPublisher, } } func (uc *CreateSessionUseCase) Execute( ctx context.Context, input CreateSessionInput, ) (*CreateSessionOutput, error) { // 1. 验证输入 if input.ThresholdT > input.ThresholdN { return nil, errors.New("threshold t cannot exceed n") } if len(input.Participants) != input.ThresholdN { return nil, errors.New("participant count must equal n") } // 2. 创建会话实体 session := &entities.MPCSession{ ID: uuid.New(), SessionType: entities.SessionType(input.SessionType), ThresholdN: input.ThresholdN, ThresholdT: input.ThresholdT, Status: entities.SessionCreated, MessageHash: input.MessageHash, CreatedBy: input.InitiatorID, CreatedAt: time.Now(), UpdatedAt: time.Now(), ExpiresAt: time.Now().Add(input.ExpiresIn), } // 3. 添加参与方并生成加入令牌 tokens := make(map[string]string) for i, pInfo := range input.Participants { participant := entities.Participant{ PartyID: pInfo.PartyID, PartyIndex: i, Status: entities.ParticipantInvited, DeviceInfo: pInfo.DeviceInfo, JoinedAt: time.Now(), } if err := session.AddParticipant(participant); err != nil { return nil, err } // 生成安全的加入令牌(JWT) token, err := uc.tokenGen.Generate(session.ID, pInfo.PartyID, input.ExpiresIn) if err != nil { return nil, err } tokens[pInfo.PartyID] = token } // 4. 保存会话 if err := uc.sessionRepo.Save(ctx, session); err != nil { return nil, err } // 5. 发布会话创建事件 event := SessionCreatedEvent{ SessionID: session.ID, SessionType: string(session.SessionType), ThresholdN: session.ThresholdN, ThresholdT: session.ThresholdT, Participants: extractPartyIDs(input.Participants), CreatedAt: session.CreatedAt, } if err := uc.eventPublisher.Publish(ctx, "mpc.session.created", event); err != nil { // Log error but don't fail the operation log.Error("failed to publish event", "error", err) } return &CreateSessionOutput{ SessionID: session.ID, JoinTokens: tokens, ExpiresAt: session.ExpiresAt, }, nil } ``` ```go // application/use_cases/join_session.go package use_cases type JoinSessionInput struct { SessionID uuid.UUID PartyID string JoinToken string DeviceInfo entities.DeviceInfo } type JoinSessionOutput struct { Success bool SessionInfo SessionInfo OtherParties []PartyInfo } type JoinSessionUseCase struct { sessionRepo repositories.SessionRepository tokenValidator TokenValidator eventPublisher EventPublisher } func (uc *JoinSessionUseCase) Execute( ctx context.Context, input JoinSessionInput, ) (*JoinSessionOutput, error) { // 1. 验证令牌 claims, err := uc.tokenValidator.Validate(input.JoinToken) if err != nil { return nil, errors.New("invalid join token") } if claims.SessionID != input.SessionID || claims.PartyID != input.PartyID { return nil, errors.New("token mismatch") } // 2. 加载会话 session, err := uc.sessionRepo.FindByID(ctx, input.SessionID) if err != nil { return nil, err } // 3. 检查会话状态 if session.IsExpired() { return nil, errors.New("session expired") } if session.Status != entities.SessionCreated { return nil, errors.New("session already started or completed") } // 4. 更新参与方状态 if err := session.UpdateParticipantStatus(input.PartyID, entities.ParticipantJoined); err != nil { return nil, err } // 5. 如果所有人都加入,开始会话 if session.CanStart() { session.Status = entities.SessionInProgress session.UpdatedAt = time.Now() } // 6. 保存更新 if err := uc.sessionRepo.Update(ctx, session); err != nil { return nil, err } // 7. 发布加入事件 event := ParticipantJoinedEvent{ SessionID: session.ID, PartyID: input.PartyID, JoinedAt: time.Now(), } uc.eventPublisher.Publish(ctx, "mpc.participant.joined", event) // 8. 构建返回信息 return &JoinSessionOutput{ Success: true, SessionInfo: SessionInfo{ SessionID: session.ID, SessionType: string(session.SessionType), ThresholdN: session.ThresholdN, ThresholdT: session.ThresholdT, MessageHash: session.MessageHash, Status: string(session.Status), }, OtherParties: buildPartyInfoList(session.Participants, input.PartyID), }, nil } ``` ```go // application/use_cases/route_message.go package use_cases type RouteMessageInput struct { SessionID uuid.UUID FromParty string ToParties []string // nil表示广播 RoundNumber int MessageType string Payload []byte // 加密的MPC消息 } type RouteMessageUseCase struct { sessionRepo repositories.SessionRepository messageRepo repositories.MessageRepository messageQueue MessageQueue } func (uc *RouteMessageUseCase) Execute( ctx context.Context, input RouteMessageInput, ) error { // 1. 验证会话存在 session, err := uc.sessionRepo.FindByID(ctx, input.SessionID) if err != nil { return err } if session.Status != entities.SessionInProgress { return errors.New("session not in progress") } // 2. 验证发送方是参与方 if !session.IsParticipant(input.FromParty) { return errors.New("sender is not a participant") } // 3. 创建消息实体 msg := &entities.SessionMessage{ ID: uuid.New(), SessionID: input.SessionID, FromParty: input.FromParty, ToParties: input.ToParties, RoundNumber: input.RoundNumber, MessageType: input.MessageType, Payload: input.Payload, // 不解密,直接转发 CreatedAt: time.Now(), } // 4. 持久化消息(用于离线场景) if err := uc.messageRepo.SaveMessage(ctx, msg); err != nil { return err } // 5. 通过消息队列路由到目标Party if input.ToParties == nil { // 广播到所有其他参与方 for _, p := range session.Participants { if p.PartyID != input.FromParty { uc.messageQueue.Send(ctx, p.PartyID, msg) } } } else { // 单播到指定Party for _, toParty := range input.ToParties { uc.messageQueue.Send(ctx, toParty, msg) } } return nil } ``` --- ## 5. 核心服务实现 ### 5.1 Server Party Service(服务器作为MPC参与方) ```go // server-party-service/domain/entities/party_key_share.go package entities type PartyKeyShare struct { ID uuid.UUID PartyID string PartyIndex int SessionID uuid.UUID ThresholdN int ThresholdT int ShareData []byte // 加密的tss-lib LocalPartySaveData PublicKey []byte // 群公钥 CreatedAt time.Time LastUsedAt *time.Time } ``` ```go // server-party-service/application/use_cases/participate_in_keygen.go package use_cases import ( "context" "math/big" "github.com/binance-chain/tss-lib/tss" "github.com/binance-chain/tss-lib/ecdsa/keygen" ) type ParticipateInKeygenInput struct { SessionID uuid.UUID PartyID string JoinToken string } type ParticipateInKeygenOutput struct { Success bool KeyShare *entities.PartyKeyShare PublicKey []byte } type ParticipateInKeygenUseCase struct { keyShareRepo repositories.KeyShareRepository sessionClient SessionCoordinatorClient messageRouter MessageRouterClient crypto CryptoService } func (uc *ParticipateInKeygenUseCase) Execute( ctx context.Context, input ParticipateInKeygenInput, ) (*ParticipateInKeygenOutput, error) { // 1. 加入会话(通过Coordinator) sessionInfo, err := uc.sessionClient.JoinSession(ctx, &JoinSessionRequest{ SessionID: input.SessionID, PartyID: input.PartyID, JoinToken: input.JoinToken, }) if err != nil { return nil, err } // 2. 获取参与方列表,构建TSS参数 parties := make([]*tss.PartyID, len(sessionInfo.Participants)) for i, p := range sessionInfo.Participants { parties[i] = tss.NewPartyID( p.PartyID, p.PartyID, big.NewInt(int64(p.PartyIndex)), ) } // 3. 找到自己的Party var selfPartyID *tss.PartyID for _, p := range parties { if p.Id == input.PartyID { selfPartyID = p break } } // 4. 创建TSS参数 tssCtx := tss.NewPeerContext(parties) params := tss.NewParameters( tss.S256(), tssCtx, selfPartyID, len(parties), sessionInfo.ThresholdT, ) // 5. 创建通信通道 outCh := make(chan tss.Message, len(parties)*10) endCh := make(chan keygen.LocalPartySaveData, 1) errCh := make(chan *tss.Error, 1) // 6. 创建TSS Keygen Party party := keygen.NewLocalParty(params, outCh, endCh).(*keygen.LocalParty) // 7. 启动消息路由goroutine go uc.routeOutgoingMessages(ctx, input.SessionID, input.PartyID, outCh) go uc.handleIncomingMessages(ctx, input.SessionID, input.PartyID, party) go uc.handleErrors(ctx, errCh) // 8. 启动Party go func() { if err := party.Start(); err != nil { errCh <- err } }() // 9. 等待Keygen完成 select { case saveData := <-endCh: // 10. Keygen成功,加密并保存Share encryptedShare, err := uc.crypto.EncryptShare(saveData, input.PartyID) if err != nil { return nil, err } keyShare := &entities.PartyKeyShare{ ID: uuid.New(), PartyID: input.PartyID, PartyIndex: getPartyIndex(sessionInfo.Participants, input.PartyID), SessionID: input.SessionID, ThresholdN: len(parties), ThresholdT: sessionInfo.ThresholdT, ShareData: encryptedShare, PublicKey: saveData.ECDSAPub.Bytes(), CreatedAt: time.Now(), } if err := uc.keyShareRepo.Save(ctx, keyShare); err != nil { return nil, err } // 11. 通知Coordinator完成 uc.sessionClient.ReportCompletion(ctx, &ReportCompletionRequest{ SessionID: input.SessionID, PartyID: input.PartyID, PublicKey: keyShare.PublicKey, }) return &ParticipateInKeygenOutput{ Success: true, KeyShare: keyShare, PublicKey: keyShare.PublicKey, }, nil case err := <-errCh: return nil, fmt.Errorf("keygen failed: %v", err) case <-time.After(10 * time.Minute): return nil, errors.New("keygen timeout") } } // routeOutgoingMessages 路由Party发出的消息 func (uc *ParticipateInKeygenUseCase) routeOutgoingMessages( ctx context.Context, sessionID uuid.UUID, partyID string, outCh <-chan tss.Message, ) { for { select { case msg := <-outCh: // 序列化TSS消息 msgBytes, err := msg.WireBytes() if err != nil { log.Error("failed to serialize message", "error", err) continue } // 确定接收方 var toParties []string if msg.IsBroadcast() { toParties = nil // 广播 } else { for _, to := range msg.GetTo() { toParties = append(toParties, to.Id) } } // 通过Message Router发送 _, err = uc.messageRouter.RouteMessage(ctx, &RouteMessageRequest{ SessionID: sessionID, FromParty: partyID, ToParties: toParties, RoundNumber: int(msg.GetRound()), MessageType: msg.Type(), Payload: msgBytes, }) if err != nil { log.Error("failed to route message", "error", err) } case <-ctx.Done(): return } } } // handleIncomingMessages 处理收到的消息并传递给Party func (uc *ParticipateInKeygenUseCase) handleIncomingMessages( ctx context.Context, sessionID uuid.UUID, partyID string, party tss.Party, ) { // 订阅自己的消息 stream, err := uc.messageRouter.SubscribeMessages(ctx, &SubscribeMessagesRequest{ SessionID: sessionID, PartyID: partyID, }) if err != nil { log.Error("failed to subscribe messages", "error", err) return } for { msg, err := stream.Recv() if err != nil { if err == io.EOF { return } log.Error("failed to receive message", "error", err) continue } // 反序列化并传递给Party // 注意:tss-lib会自动处理消息的验证和状态更新 if _, err := party.UpdateFromBytes(msg.Payload, msg.FromParty, msg.IsBroadcast); err != nil { log.Error("failed to update party from message", "error", err) } } } ``` ```go // server-party-service/application/use_cases/participate_in_signing.go package use_cases import ( "github.com/binance-chain/tss-lib/ecdsa/signing" ) type ParticipateInSigningInput struct { SessionID uuid.UUID PartyID string JoinToken string MessageHash []byte } type ParticipateInSigningOutput struct { Success bool Signature []byte R *big.Int S *big.Int } type ParticipateInSigningUseCase struct { keyShareRepo repositories.KeyShareRepository sessionClient SessionCoordinatorClient messageRouter MessageRouterClient crypto CryptoService } func (uc *ParticipateInSigningUseCase) Execute( ctx context.Context, input ParticipateInSigningInput, ) (*ParticipateInSigningOutput, error) { // 1. 加入签名会话 sessionInfo, err := uc.sessionClient.JoinSession(ctx, &JoinSessionRequest{ SessionID: input.SessionID, PartyID: input.PartyID, JoinToken: input.JoinToken, }) if err != nil { return nil, err } // 2. 加载自己的KeyShare keyShare, err := uc.keyShareRepo.FindBySessionAndParty(ctx, sessionInfo.KeygenSessionID, input.PartyID) if err != nil { return nil, errors.New("key share not found") } // 3. 解密Share saveData, err := uc.crypto.DecryptShare(keyShare.ShareData, input.PartyID) if err != nil { return nil, err } // 4. 构建TSS参数(与Keygen类似) parties := buildPartyList(sessionInfo.Participants) selfPartyID := findSelfParty(parties, input.PartyID) tssCtx := tss.NewPeerContext(parties) params := tss.NewParameters( tss.S256(), tssCtx, selfPartyID, len(parties), sessionInfo.ThresholdT, ) // 5. 创建通信通道 outCh := make(chan tss.Message, len(parties)*10) endCh := make(chan *common.SignatureData, 1) // 6. 创建TSS Signing Party msgHash := new(big.Int).SetBytes(input.MessageHash) party := signing.NewLocalParty(msgHash, params, saveData, outCh, endCh).(*signing.LocalParty) // 7. 启动消息路由 go uc.routeSigningMessages(ctx, input.SessionID, input.PartyID, outCh) go uc.handleSigningMessages(ctx, input.SessionID, input.PartyID, party) // 8. 启动Party go func() { if err := party.Start(); err != nil { log.Error("signing party error", "error", err) } }() // 9. 等待签名完成 select { case signData := <-endCh: // 签名成功 signature := append(signData.R, signData.S...) // 更新KeyShare的最后使用时间 now := time.Now() keyShare.LastUsedAt = &now uc.keyShareRepo.Update(ctx, keyShare) // 通知Coordinator完成 uc.sessionClient.ReportCompletion(ctx, &ReportCompletionRequest{ SessionID: input.SessionID, PartyID: input.PartyID, Signature: signature, }) return &ParticipateInSigningOutput{ Success: true, Signature: signature, R: signData.R, S: signData.S, }, nil case <-time.After(5 * time.Minute): return nil, errors.New("signing timeout") } } ``` --- ## 6. 数据库设计 ### 6.1 Session Coordinator Schema ```sql -- 会话表 CREATE TABLE mpc_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_type VARCHAR(20) NOT NULL, -- 'keygen' or 'sign' threshold_n INTEGER NOT NULL, threshold_t INTEGER NOT NULL, status VARCHAR(20) NOT NULL, message_hash BYTEA, -- Sign会话使用 public_key BYTEA, -- Keygen完成后的群公钥 created_by VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), expires_at TIMESTAMP NOT NULL, completed_at TIMESTAMP, CONSTRAINT chk_threshold CHECK (threshold_t <= threshold_n AND threshold_t > 0), CONSTRAINT chk_session_type CHECK (session_type IN ('keygen', 'sign')), CONSTRAINT chk_status CHECK (status IN ('created', 'in_progress', 'completed', 'failed', 'expired')) ); CREATE INDEX idx_mpc_sessions_status ON mpc_sessions(status); CREATE INDEX idx_mpc_sessions_created_at ON mpc_sessions(created_at); CREATE INDEX idx_mpc_sessions_expires_at ON mpc_sessions(expires_at); -- 参与方表 CREATE TABLE participants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL REFERENCES mpc_sessions(id) ON DELETE CASCADE, party_id VARCHAR(255) NOT NULL, party_index INTEGER NOT NULL, status VARCHAR(20) NOT NULL, device_type VARCHAR(50), device_id VARCHAR(255), platform VARCHAR(50), app_version VARCHAR(50), public_key BYTEA, -- Party身份公钥(用于认证) joined_at TIMESTAMP NOT NULL DEFAULT NOW(), completed_at TIMESTAMP, CONSTRAINT chk_participant_status CHECK (status IN ('invited', 'joined', 'ready', 'completed', 'failed')), UNIQUE(session_id, party_id), UNIQUE(session_id, party_index) ); CREATE INDEX idx_participants_session_id ON participants(session_id); CREATE INDEX idx_participants_party_id ON participants(party_id); CREATE INDEX idx_participants_status ON participants(status); -- MPC消息表(用于离线消息缓存) CREATE TABLE mpc_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL REFERENCES mpc_sessions(id) ON DELETE CASCADE, from_party VARCHAR(255) NOT NULL, to_parties TEXT[], -- NULL表示广播 round_number INTEGER NOT NULL, message_type VARCHAR(50) NOT NULL, payload BYTEA NOT NULL, -- 加密的MPC消息 created_at TIMESTAMP NOT NULL DEFAULT NOW(), delivered_at TIMESTAMP, CONSTRAINT chk_round_number CHECK (round_number >= 0) ); CREATE INDEX idx_mpc_messages_session_id ON mpc_messages(session_id); CREATE INDEX idx_mpc_messages_to_parties ON mpc_messages USING GIN(to_parties); CREATE INDEX idx_mpc_messages_delivered_at ON mpc_messages(delivered_at) WHERE delivered_at IS NULL; CREATE INDEX idx_mpc_messages_created_at ON mpc_messages(created_at); ``` ### 6.2 Server Party Service Schema ```sql -- Party密钥分片表(Server Party自己的Share) CREATE TABLE party_key_shares ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), party_id VARCHAR(255) NOT NULL, party_index INTEGER NOT NULL, session_id UUID NOT NULL, -- Keygen会话ID threshold_n INTEGER NOT NULL, threshold_t INTEGER NOT NULL, share_data BYTEA NOT NULL, -- 加密的tss-lib LocalPartySaveData public_key BYTEA NOT NULL, -- 群公钥 created_at TIMESTAMP NOT NULL DEFAULT NOW(), last_used_at TIMESTAMP, CONSTRAINT chk_threshold CHECK (threshold_t <= threshold_n) ); CREATE INDEX idx_party_key_shares_party_id ON party_key_shares(party_id); CREATE INDEX idx_party_key_shares_session_id ON party_key_shares(session_id); CREATE INDEX idx_party_key_shares_public_key ON party_key_shares(public_key); CREATE UNIQUE INDEX idx_party_key_shares_unique ON party_key_shares(party_id, session_id); ``` ### 6.3 Account Service Schema ```sql -- 账户表 CREATE TABLE accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, phone VARCHAR(50), public_key BYTEA NOT NULL, -- MPC群公钥 keygen_session_id UUID NOT NULL, -- 关联的Keygen会话 threshold_n INTEGER NOT NULL, threshold_t INTEGER NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), last_login_at TIMESTAMP, CONSTRAINT chk_status CHECK (status IN ('active', 'suspended', 'locked', 'recovering')) ); CREATE INDEX idx_accounts_username ON accounts(username); CREATE INDEX idx_accounts_email ON accounts(email); CREATE INDEX idx_accounts_public_key ON accounts(public_key); CREATE INDEX idx_accounts_status ON accounts(status); -- 账户Share映射表(记录各个Share的位置,不存储Share内容) CREATE TABLE account_shares ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, share_type VARCHAR(20) NOT NULL, -- 'user_device', 'server', 'recovery' party_id VARCHAR(255) NOT NULL, party_index INTEGER NOT NULL, device_type VARCHAR(50), device_id VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT NOW(), last_used_at TIMESTAMP, is_active BOOLEAN DEFAULT TRUE, CONSTRAINT chk_share_type CHECK (share_type IN ('user_device', 'server', 'recovery')), UNIQUE(account_id, share_type, is_active) ); CREATE INDEX idx_account_shares_account_id ON account_shares(account_id); CREATE INDEX idx_account_shares_party_id ON account_shares(party_id); -- 账户恢复记录表 CREATE TABLE account_recovery_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID NOT NULL REFERENCES accounts(id), recovery_type VARCHAR(20) NOT NULL, -- 'device_lost', 'share_rotation' old_share_type VARCHAR(20), new_keygen_session_id UUID, status VARCHAR(20) NOT NULL, requested_at TIMESTAMP NOT NULL DEFAULT NOW(), completed_at TIMESTAMP, CONSTRAINT chk_recovery_status CHECK (status IN ('requested', 'in_progress', 'completed', 'failed')) ); CREATE INDEX idx_account_recovery_account_id ON account_recovery_sessions(account_id); CREATE INDEX idx_account_recovery_status ON account_recovery_sessions(status); ``` ### 6.4 Audit Service Schema ```sql -- 审核工作流表 CREATE TABLE audit_workflows ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workflow_name VARCHAR(255) NOT NULL, workflow_type VARCHAR(50) NOT NULL, data_hash BYTEA NOT NULL, threshold_n INTEGER NOT NULL, threshold_t INTEGER NOT NULL, sign_session_id UUID, -- 关联的签名会话 signature BYTEA, status VARCHAR(20) NOT NULL, created_by VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), expires_at TIMESTAMP, completed_at TIMESTAMP, metadata JSONB, CONSTRAINT chk_status CHECK (status IN ('pending', 'in_progress', 'approved', 'rejected', 'expired')) ); CREATE INDEX idx_audit_workflows_status ON audit_workflows(status); CREATE INDEX idx_audit_workflows_created_at ON audit_workflows(created_at); CREATE INDEX idx_audit_workflows_workflow_type ON audit_workflows(workflow_type); -- 审批人表 CREATE TABLE audit_approvers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workflow_id UUID NOT NULL REFERENCES audit_workflows(id) ON DELETE CASCADE, approver_id VARCHAR(255) NOT NULL, party_id VARCHAR(255) NOT NULL, party_index INTEGER NOT NULL, status VARCHAR(20) NOT NULL, approved_at TIMESTAMP, comments TEXT, CONSTRAINT chk_approver_status CHECK (status IN ('pending', 'approved', 'rejected')), UNIQUE(workflow_id, approver_id) ); CREATE INDEX idx_audit_approvers_workflow_id ON audit_approvers(workflow_id); CREATE INDEX idx_audit_approvers_approver_id ON audit_approvers(approver_id); CREATE INDEX idx_audit_approvers_status ON audit_approvers(status); ``` ### 6.5 审计日志表(所有服务共享) ```sql -- 审计日志表 CREATE TABLE audit_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), service_name VARCHAR(100) NOT NULL, action_type VARCHAR(100) NOT NULL, user_id VARCHAR(255), resource_type VARCHAR(100), resource_id VARCHAR(255), session_id UUID, ip_address INET, user_agent TEXT, request_data JSONB, response_data JSONB, status VARCHAR(20) NOT NULL, error_message TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW(), CONSTRAINT chk_audit_status CHECK (status IN ('success', 'failure', 'pending')) ); CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at); CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id); CREATE INDEX idx_audit_logs_session_id ON audit_logs(session_id); CREATE INDEX idx_audit_logs_action_type ON audit_logs(action_type); CREATE INDEX idx_audit_logs_service_name ON audit_logs(service_name); ``` --- ## 7. 客户端SDK ### 7.1 Go SDK(核心实现) ```go // sdk/mpc_client.go package mpcsdk import ( "context" "crypto/ecdsa" "crypto/elliptic" "math/big" "github.com/binance-chain/tss-lib/tss" "github.com/binance-chain/tss-lib/ecdsa/keygen" "github.com/binance-chain/tss-lib/ecdsa/signing" ) // MPCClient 是MPC客户端SDK type MPCClient struct { config *Config coordinatorClient CoordinatorClient messageRouter MessageRouterClient localStorage LocalStorage crypto CryptoService } type Config struct { CoordinatorEndpoint string MessageRouterEndpoint string PartyID string Timeout time.Duration } func NewMPCClient(config *Config) *MPCClient { return &MPCClient{ config: config, coordinatorClient: NewCoordinatorClient(config.CoordinatorEndpoint), messageRouter: NewMessageRouterClient(config.MessageRouterEndpoint), localStorage: NewLocalStorage(), crypto: NewCryptoService(), } } // CreateAccount 创建账号(2-of-3 Keygen) func (c *MPCClient) CreateAccount( ctx context.Context, username string, ) (*Account, error) { // 1. 请求创建Keygen会话 createResp, err := c.coordinatorClient.CreateSession(ctx, &CreateSessionRequest{ SessionType: "keygen", ThresholdN: 3, ThresholdT: 2, Participants: []ParticipantInfo{ {PartyID: username + "-device", DeviceInfo: getDeviceInfo()}, {PartyID: username + "-server", DeviceInfo: DeviceInfo{DeviceType: "server"}}, {PartyID: username + "-recovery", DeviceInfo: DeviceInfo{DeviceType: "recovery"}}, }, ExpiresIn: 10 * time.Minute, }) if err != nil { return nil, err } // 2. 参与Keygen(作为user-device party) keyShare, publicKey, err := c.participateInKeygen( ctx, createResp.SessionID, username+"-device", createResp.JoinTokens[username+"-device"], ) if err != nil { return nil, err } // 3. 保存KeyShare到本地安全存储 if err := c.localStorage.SaveKeyShare(keyShare); err != nil { return nil, err } // 4. 返回账户信息 return &Account{ ID: uuid.New().String(), Username: username, PublicKey: publicKey, KeyShareID: keyShare.ID, ThresholdN: 3, ThresholdT: 2, }, nil } // participateInKeygen 参与Keygen协议 func (c *MPCClient) participateInKeygen( ctx context.Context, sessionID uuid.UUID, partyID string, joinToken string, ) (*KeyShare, []byte, error) { // 1. 加入会话 joinResp, err := c.coordinatorClient.JoinSession(ctx, &JoinSessionRequest{ SessionID: sessionID, PartyID: partyID, JoinToken: joinToken, DeviceInfo: getDeviceInfo(), }) if err != nil { return nil, nil, err } // 2. 构建TSS Party列表 parties := make([]*tss.PartyID, len(joinResp.OtherParties)+1) for i, p := range joinResp.OtherParties { parties[i] = tss.NewPartyID( p.PartyID, p.PartyID, big.NewInt(int64(p.PartyIndex)), ) } // 添加自己 selfIndex := findSelfIndex(joinResp.SessionInfo, partyID) selfPartyID := tss.NewPartyID(partyID, partyID, big.NewInt(int64(selfIndex))) parties[selfIndex] = selfPartyID // 3. 创建TSS参数 tssCtx := tss.NewPeerContext(parties) params := tss.NewParameters( tss.S256(), tssCtx, selfPartyID, joinResp.SessionInfo.ThresholdN, joinResp.SessionInfo.ThresholdT, ) // 4. 创建通信通道 outCh := make(chan tss.Message, len(parties)*10) endCh := make(chan keygen.LocalPartySaveData, 1) // 5. 创建TSS Keygen Party party := keygen.NewLocalParty(params, outCh, endCh).(*keygen.LocalParty) // 6. 启动消息处理 ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) defer cancel() errCh := make(chan error, 1) go c.handleOutgoingMessages(ctx, sessionID, partyID, outCh, errCh) go c.handleIncomingMessages(ctx, sessionID, partyID, party, errCh) // 7. 启动Party go func() { if err := party.Start(); err != nil { errCh <- err } }() // 8. 等待完成或超时 select { case saveData := <-endCh: // Keygen成功 encryptedShare, err := c.crypto.EncryptShare(saveData.Bytes(), partyID) if err != nil { return nil, nil, err } keyShare := &KeyShare{ ID: uuid.New(), PartyID: partyID, SessionID: sessionID, ShareData: encryptedShare, PublicKey: saveData.ECDSAPub.Bytes(), ThresholdN: joinResp.SessionInfo.ThresholdN, ThresholdT: joinResp.SessionInfo.ThresholdT, CreatedAt: time.Now(), } // 通知Coordinator完成 c.coordinatorClient.ReportCompletion(ctx, &ReportCompletionRequest{ SessionID: sessionID, PartyID: partyID, PublicKey: keyShare.PublicKey, }) return keyShare, keyShare.PublicKey, nil case err := <-errCh: return nil, nil, fmt.Errorf("keygen failed: %v", err) case <-ctx.Done(): return nil, nil, errors.New("keygen timeout") } } // SignMessage 使用MPC签名消息 func (c *MPCClient) SignMessage( ctx context.Context, account *Account, messageHash []byte, ) ([]byte, error) { // 1. 加载本地KeyShare keyShare, err := c.localStorage.LoadKeyShare(account.KeyShareID) if err != nil { return nil, err } // 2. 请求创建Sign会话(2-of-3,使用device+server) createResp, err := c.coordinatorClient.CreateSession(ctx, &CreateSessionRequest{ SessionType: "sign", ThresholdN: 2, ThresholdT: 2, Participants: []ParticipantInfo{ {PartyID: account.Username + "-device", DeviceInfo: getDeviceInfo()}, {PartyID: account.Username + "-server", DeviceInfo: DeviceInfo{DeviceType: "server"}}, }, MessageHash: messageHash, ExpiresIn: 5 * time.Minute, }) if err != nil { return nil, err } // 3. 参与Signing signature, err := c.participateInSigning( ctx, createResp.SessionID, account.Username+"-device", keyShare, messageHash, createResp.JoinTokens[account.Username+"-device"], ) if err != nil { return nil, err } return signature, nil } // participateInSigning 参与Signing协议 func (c *MPCClient) participateInSigning( ctx context.Context, sessionID uuid.UUID, partyID string, keyShare *KeyShare, messageHash []byte, joinToken string, ) ([]byte, error) { // 1. 加入会话 joinResp, err := c.coordinatorClient.JoinSession(ctx, &JoinSessionRequest{ SessionID: sessionID, PartyID: partyID, JoinToken: joinToken, }) if err != nil { return nil, err } // 2. 解密KeyShare saveDataBytes, err := c.crypto.DecryptShare(keyShare.ShareData, partyID) if err != nil { return nil, err } var saveData keygen.LocalPartySaveData if err := saveData.UnmarshalBinary(saveDataBytes); err != nil { return nil, err } // 3. 构建TSS参数 parties := buildPartyList(joinResp.OtherParties, partyID, findSelfIndex(joinResp.SessionInfo, partyID)) selfPartyID := parties[findSelfIndex(joinResp.SessionInfo, partyID)] tssCtx := tss.NewPeerContext(parties) params := tss.NewParameters( tss.S256(), tssCtx, selfPartyID, len(parties), joinResp.SessionInfo.ThresholdT, ) // 4. 创建通信通道 outCh := make(chan tss.Message, len(parties)*10) endCh := make(chan *common.SignatureData, 1) // 5. 创建TSS Signing Party msgHash := new(big.Int).SetBytes(messageHash) party := signing.NewLocalParty(msgHash, params, saveData, outCh, endCh).(*signing.LocalParty) // 6. 启动消息处理 ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() errCh := make(chan error, 1) go c.handleOutgoingMessages(ctx, sessionID, partyID, outCh, errCh) go c.handleIncomingMessages(ctx, sessionID, partyID, party, errCh) // 7. 启动Party go func() { if err := party.Start(); err != nil { errCh <- err } }() // 8. 等待完成 select { case signData := <-endCh: // 签名成功 signature := append(signData.R, signData.S...) // 通知Coordinator完成 c.coordinatorClient.ReportCompletion(ctx, &ReportCompletionRequest{ SessionID: sessionID, PartyID: partyID, Signature: signature, }) return signature, nil case err := <-errCh: return nil, fmt.Errorf("signing failed: %v", err) case <-ctx.Done(): return nil, errors.New("signing timeout") } } // handleOutgoingMessages 处理Party发出的消息 func (c *MPCClient) handleOutgoingMessages( ctx context.Context, sessionID uuid.UUID, partyID string, outCh <-chan tss.Message, errCh chan<- error, ) { for { select { case msg := <-outCh: msgBytes, err := msg.WireBytes() if err != nil { errCh <- err return } var toParties []string if !msg.IsBroadcast() { for _, to := range msg.GetTo() { toParties = append(toParties, to.Id) } } _, err = c.messageRouter.RouteMessage(ctx, &RouteMessageRequest{ SessionID: sessionID, FromParty: partyID, ToParties: toParties, RoundNumber: int(msg.GetRound()), Payload: msgBytes, }) if err != nil { errCh <- err return } case <-ctx.Done(): return } } } // handleIncomingMessages 处理接收到的消息 func (c *MPCClient) handleIncomingMessages( ctx context.Context, sessionID uuid.UUID, partyID string, party tss.Party, errCh chan<- error, ) { stream, err := c.messageRouter.SubscribeMessages(ctx, &SubscribeMessagesRequest{ SessionID: sessionID, PartyID: partyID, }) if err != nil { errCh <- err return } for { msg, err := stream.Recv() if err != nil { if err == io.EOF { return } errCh <- err return } if _, err := party.UpdateFromBytes(msg.Payload, msg.FromParty, msg.IsBroadcast); err != nil { log.Error("failed to update party", "error", err) } } } // VerifySignature 验证ECDSA签名 func (c *MPCClient) VerifySignature( messageHash []byte, signature []byte, publicKey []byte, ) (bool, error) { // 解析公钥 x, y := elliptic.Unmarshal(elliptic.P256(), publicKey) if x == nil { return false, errors.New("invalid public key") } pubKey := &ecdsa.PublicKey{ Curve: elliptic.P256(), X: x, Y: y, } // 解析签名 (r, s) if len(signature) != 64 { return false, errors.New("invalid signature length") } r := new(big.Int).SetBytes(signature[:32]) s := new(big.Int).SetBytes(signature[32:]) // 验证 msgHashInt := new(big.Int).SetBytes(messageHash) valid := ecdsa.Verify(pubKey, msgHashInt.Bytes(), r, s) return valid, nil } ``` ### 7.2 Android SDK(Kotlin + Go Mobile) ```kotlin // android-sdk/src/main/java/com/yourorg/mpcsdk/MPCAndroidClient.kt package com.yourorg.mpcsdk import android.content.Context import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import androidx.biometric.BiometricPrompt import androidx.fragment.app.FragmentActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import mpcsdk.Mpcsdk // Generated by gomobile bind import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.GCMParameterSpec import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** * MPC Android客户端SDK * 在Android设备上运行完整的tss-lib */ class MPCAndroidClient( private val context: Context, private val config: MPCConfig ) { private val goMPCClient: Mpcsdk.MPCClient private val keyStore: KeyStore private val secureStorage: SecureStorage init { // 初始化Go MPC客户端 val goConfig = Mpcsdk.NewConfig() goConfig.coordinatorEndpoint = config.coordinatorEndpoint goConfig.messageRouterEndpoint = config.messageRouterEndpoint goConfig.timeout = config.timeout goMPCClient = Mpcsdk.NewMPCClient(goConfig) // 初始化Android KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) // 初始化安全存储 secureStorage = SecureStorage(context, keyStore) } /** * 创建账号(2-of-3 Keygen) */ suspend fun createAccount(username: String): Account = withContext(Dispatchers.IO) { try { // 调用Go SDK执行Keygen val goAccount = goMPCClient.createAccount(username) // 安全存储KeyShare到Android KeyStore secureStorage.saveKeyShare( keyShareID = goAccount.keyShareID, shareData = goAccount.shareData ) // 返回账户信息 Account( id = goAccount.id, username = goAccount.username, publicKey = goAccount.publicKey, keyShareID = goAccount.keyShareID, thresholdN = goAccount.thresholdN.toInt(), thresholdT = goAccount.thresholdT.toInt() ) } catch (e: Exception) { throw MPCException("Failed to create account: ${e.message}", e) } } /** * 签名消息(需要生物识别认证) */ suspend fun signMessage( activity: FragmentActivity, account: Account, messageHash: ByteArray ): ByteArray = withContext(Dispatchers.IO) { // 1. 生物识别认证 authenticateUser(activity) // 2. 从安全存储加载KeyShare val shareData = secureStorage.loadKeyShare(account.keyShareID) // 3. 调用Go SDK执行Signing try { goMPCClient.signMessage( account.toGoAccount(shareData), messageHash ) } catch (e: Exception) { throw MPCException("Failed to sign message: ${e.message}", e) } } /** * 验证签名 */ fun verifySignature( messageHash: ByteArray, signature: ByteArray, publicKey: ByteArray ): Boolean { return goMPCClient.verifySignature(messageHash, signature, publicKey) } /** * 生物识别认证 */ private suspend fun authenticateUser(activity: FragmentActivity) { return suspendCancellableCoroutine { continuation -> val biometricPrompt = BiometricPrompt( activity, ContextCompat.getMainExecutor(context), object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult ) { continuation.resume(Unit) } override fun onAuthenticationFailed() { continuation.resumeWithException( MPCException("Biometric authentication failed") ) } override fun onAuthenticationError( errorCode: Int, errString: CharSequence ) { continuation.resumeWithException( MPCException("Authentication error: $errString") ) } } ) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("MPC Signature Required") .setSubtitle("Authenticate to sign with your key share") .setNegativeButtonText("Cancel") .build() biometricPrompt.authenticate(promptInfo) } } } /** * 安全存储(使用Android KeyStore) */ class SecureStorage( private val context: Context, private val keyStore: KeyStore ) { private val prefs = context.getSharedPreferences("mpc_shares", Context.MODE_PRIVATE) fun saveKeyShare(keyShareID: String, shareData: ByteArray) { // 1. 获取或创建AES密钥 val secretKey = getOrCreateSecretKey() // 2. 加密Share数据 val cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, secretKey) val encryptedData = cipher.doFinal(shareData) val iv = cipher.iv // 3. 存储到SharedPreferences prefs.edit() .putString("share_$keyShareID", Base64.encodeToString(encryptedData, Base64.DEFAULT)) .putString("iv_$keyShareID", Base64.encodeToString(iv, Base64.DEFAULT)) .apply() } fun loadKeyShare(keyShareID: String): ByteArray { // 1. 从SharedPreferences加载 val encryptedDataStr = prefs.getString("share_$keyShareID", null) ?: throw MPCException("Key share not found") val ivStr = prefs.getString("iv_$keyShareID", null) ?: throw MPCException("IV not found") val encryptedData = Base64.decode(encryptedDataStr, Base64.DEFAULT) val iv = Base64.decode(ivStr, Base64.DEFAULT) // 2. 解密 val secretKey = getOrCreateSecretKey() val cipher = Cipher.getInstance(TRANSFORMATION) val spec = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) return cipher.doFinal(encryptedData) } private fun getOrCreateSecretKey(): SecretKey { val keyAlias = "mpc_share_key" return if (keyStore.containsAlias(keyAlias)) { (keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry).secretKey } else { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore" ) val spec = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .setUserAuthenticationValidityDurationSeconds(30) .build() keyGenerator.init(spec) keyGenerator.generateKey() } } companion object { private const val TRANSFORMATION = "AES/GCM/NoPadding" } } // 数据类 data class MPCConfig( val coordinatorEndpoint: String, val messageRouterEndpoint: String, val timeout: Long = 60000 ) data class Account( val id: String, val username: String, val publicKey: ByteArray, val keyShareID: String, val thresholdN: Int, val thresholdT: Int ) class MPCException(message: String, cause: Throwable? = null) : Exception(message, cause) ``` ### 7.3 编译移动SDK ```bash #!/bin/bash # build-mobile-sdk.sh # 1. 安装gomobile go install golang.org/x/mobile/cmd/gomobile@latest gomobile init # 2. 编译Android SDK echo "Building Android SDK..." cd sdk/go gomobile bind -target=android -o ../android/libs/mpcsdk.aar . # 3. 编译iOS SDK echo "Building iOS SDK..." gomobile bind -target=ios -o ../ios/Mpcsdk.xcframework . echo "Mobile SDKs built successfully!" ``` --- ## 8. API接口 ### 8.1 gRPC API定义 ```protobuf // api/proto/session_coordinator.proto syntax = "proto3"; package mpc.coordinator.v1; option go_package = "github.com/yourorg/mpc-system/api/grpc/coordinator/v1;coordinator"; service SessionCoordinator { // 会话管理 rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); rpc JoinSession(JoinSessionRequest) returns (JoinSessionResponse); rpc GetSessionStatus(GetSessionStatusRequest) returns (GetSessionStatusResponse); rpc ReportCompletion(ReportCompletionRequest) returns (ReportCompletionResponse); rpc CloseSession(CloseSessionRequest) returns (CloseSessionResponse); } message CreateSessionRequest { string session_type = 1; // "keygen" or "sign" int32 threshold_n = 2; int32 threshold_t = 3; repeated ParticipantInfo participants = 4; bytes message_hash = 5; // For sign sessions int64 expires_in_seconds = 6; } message ParticipantInfo { string party_id = 1; DeviceInfo device_info = 2; } message DeviceInfo { string device_type = 1; // android, ios, pc, server string device_id = 2; string platform = 3; string app_version = 4; } message CreateSessionResponse { string session_id = 1; map join_tokens = 2; // party_id -> join_token int64 expires_at = 3; } message JoinSessionRequest { string session_id = 1; string party_id = 2; string join_token = 3; DeviceInfo device_info = 4; } message JoinSessionResponse { bool success = 1; SessionInfo session_info = 2; repeated PartyInfo other_parties = 3; } message SessionInfo { string session_id = 1; string session_type = 2; int32 threshold_n = 3; int32 threshold_t = 4; bytes message_hash = 5; string status = 6; } message PartyInfo { string party_id = 1; int32 party_index = 2; DeviceInfo device_info = 3; } message GetSessionStatusRequest { string session_id = 1; } message GetSessionStatusResponse { string status = 1; int32 completed_parties = 2; int32 total_parties = 3; bytes public_key = 4; // For completed keygen bytes signature = 5; // For completed sign } message ReportCompletionRequest { string session_id = 1; string party_id = 2; bytes public_key = 3; // For keygen bytes signature = 4; // For sign } message ReportCompletionResponse { bool success = 1; bool all_completed = 2; } message CloseSessionRequest { string session_id = 1; } message CloseSessionResponse { bool success = 1; } ``` ```protobuf // api/proto/message_router.proto syntax = "proto3"; package mpc.router.v1; option go_package = "github.com/yourorg/mpc-system/api/grpc/router/v1;router"; service MessageRouter { // 消息路由 rpc RouteMessage(RouteMessageRequest) returns (RouteMessageResponse); rpc SubscribeMessages(SubscribeMessagesRequest) returns (stream MPCMessage); } message RouteMessageRequest { string session_id = 1; string from_party = 2; repeated string to_parties = 3; // empty for broadcast int32 round_number = 4; string message_type = 5; bytes payload = 6; // Encrypted MPC message } message RouteMessageResponse { bool success = 1; } message SubscribeMessagesRequest { string session_id = 1; string party_id = 2; } message MPCMessage { string message_id = 1; string from_party = 2; bool is_broadcast = 3; int32 round_number = 4; bytes payload = 5; int64 created_at = 6; } ``` --- ## 9. 部署方案 ### 9.1 Docker Compose(开发环境) ```yaml version: '3.8' services: # PostgreSQL postgres: image: postgres:15-alpine environment: POSTGRES_DB: mpc_system POSTGRES_USER: mpc_user POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data - ./migrations:/docker-entrypoint-initdb.d healthcheck: test: ["CMD-SHELL", "pg_isready -U mpc_user"] interval: 10s timeout: 5s retries: 5 # Redis redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis-data:/data command: redis-server --appendonly yes # RabbitMQ rabbitmq: image: rabbitmq:3-management-alpine ports: - "5672:5672" - "15672:15672" environment: RABBITMQ_DEFAULT_USER: mpc_user RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} volumes: - rabbitmq-data:/var/lib/rabbitmq # Consul consul: image: consul:latest ports: - "8500:8500" command: agent -server -ui -bootstrap-expect=1 -client=0.0.0.0 volumes: - consul-data:/consul/data # Session Coordinator Service session-coordinator: build: context: ./services/session-coordinator ports: - "50051:50051" # gRPC - "8080:8080" # HTTP environment: DATABASE_URL: postgres://mpc_user:${POSTGRES_PASSWORD}@postgres:5432/mpc_system REDIS_URL: redis://redis:6379/0 RABBITMQ_URL: amqp://mpc_user:${RABBITMQ_PASSWORD}@rabbitmq:5672/ CONSUL_URL: consul:8500 depends_on: postgres: condition: service_healthy redis: condition: service_started rabbitmq: condition: service_started # Message Router Service message-router: build: context: ./services/message-router ports: - "50052:50051" - "8081:8080" environment: DATABASE_URL: postgres://mpc_user:${POSTGRES_PASSWORD}@postgres:5432/mpc_system RABBITMQ_URL: amqp://mpc_user:${RABBITMQ_PASSWORD}@rabbitmq:5672/ REDIS_URL: redis://redis:6379/1 depends_on: - postgres - rabbitmq - redis # Server Party Service server-party: build: context: ./services/server-party ports: - "50053:50051" - "8082:8080" environment: DATABASE_URL: postgres://mpc_user:${POSTGRES_PASSWORD}@postgres:5432/mpc_system COORDINATOR_URL: session-coordinator:50051 ROUTER_URL: message-router:50051 HSM_CONFIG: ${HSM_CONFIG} depends_on: - postgres - session-coordinator - message-router # Account Service account-service: build: context: ./services/account ports: - "50054:50051" - "8083:8080" environment: DATABASE_URL: postgres://mpc_user:${POSTGRES_PASSWORD}@postgres:5432/mpc_system COORDINATOR_URL: session-coordinator:50051 depends_on: - postgres - session-coordinator volumes: postgres-data: redis-data: rabbitmq-data: consul-data: ``` ### 9.2 Kubernetes部署(生产环境) ```yaml # k8s/session-coordinator-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: session-coordinator namespace: mpc-system spec: replicas: 3 selector: matchLabels: app: session-coordinator template: metadata: labels: app: session-coordinator spec: containers: - name: session-coordinator image: yourorg/session-coordinator:latest ports: - containerPort: 50051 name: grpc - containerPort: 8080 name: http env: - name: DATABASE_URL valueFrom: secretKeyRef: name: database-credentials key: url - name: REDIS_URL value: redis://redis:6379/0 - name: RABBITMQ_URL valueFrom: secretKeyRef: name: rabbitmq-credentials key: url resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: session-coordinator namespace: mpc-system spec: selector: app: session-coordinator ports: - name: grpc port: 50051 targetPort: 50051 - name: http port: 8080 targetPort: 8080 type: ClusterIP --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: session-coordinator-hpa namespace: mpc-system spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: session-coordinator minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 ``` ### 9.3 Makefile ```makefile .PHONY: help proto build test docker-build docker-up deploy-k8s help: ## Show this help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' proto: ## Generate protobuf code @echo "Generating protobuf..." protoc --go_out=. --go-grpc_out=. api/proto/*.proto build: ## Build all services @echo "Building services..." cd services/session-coordinator && go build -o ../../bin/session-coordinator cmd/server/main.go cd services/message-router && go build -o ../../bin/message-router cmd/server/main.go cd services/server-party && go build -o ../../bin/server-party cmd/server/main.go cd services/account && go build -o ../../bin/account cmd/server/main.go test: ## Run tests go test -v ./... docker-build: ## Build Docker images docker-compose build docker-up: ## Start all services docker-compose up -d docker-down: ## Stop all services docker-compose down build-android-sdk: ## Build Android SDK @echo "Building Android SDK..." cd sdk/go && gomobile bind -target=android -o ../android/libs/mpcsdk.aar . build-ios-sdk: ## Build iOS SDK @echo "Building iOS SDK..." cd sdk/go && gomobile bind -target=ios -o ../ios/Mpcsdk.xcframework . deploy-k8s: ## Deploy to Kubernetes kubectl apply -f k8s/ ``` --- ## 10. 安全设计 ### 10.1 Share存储安全 | Party类型 | 存储位置 | 加密方式 | 访问控制 | |----------|---------|---------|---------| | Android客户端 | Android KeyStore | AES-256-GCM(硬件支持) | 生物识别/PIN | | iOS客户端 | Secure Enclave | 硬件加密 | Face ID/Touch ID | | PC客户端 | OS Keychain | 系统级加密 | 用户密码 | | 服务器 | HSM或PostgreSQL | AES-256-GCM | IAM + 审计 | | 恢复密钥 | 冷存储 | 离线加密 | 物理隔离 | ### 10.2 通信安全 ```go // TLS 1.3配置 func setupTLS() (*tls.Config, error) { cert, err := tls.LoadX509KeyPair("server.crt", "server.key") if err != nil { return nil, err } return &tls.Config{ Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS13, CipherSuites: []uint16{ tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, }, }, nil } ``` ### 10.3 认证与授权 ```go // JWT认证 type JWTAuth struct { secretKey []byte issuer string } func (a *JWTAuth) GenerateToken(partyID string, sessionID uuid.UUID, expiresIn time.Duration) (string, error) { claims := jwt.MapClaims{ "party_id": partyID, "session_id": sessionID.String(), "iss": a.issuer, "iat": time.Now().Unix(), "exp": time.Now().Add(expiresIn).Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(a.secretKey) } ``` --- ## 快速开始 ```bash # 1. Clone项目 git clone https://github.com/yourorg/mpc-distributed-signature-system.git cd mpc-distributed-signature-system # 2. 配置环境变量 cp .env.example .env # 编辑 .env 文件 # 3. 生成protobuf代码 make proto # 4. 启动所有服务 make docker-up # 5. 编译Android SDK make build-android-sdk # 6. 运行测试 make test ``` --- ## 总结 这是一份**真正的去中心化MPC分布式签名系统**完整技术规范,核心特点: ✅ **对等参与**:客户端和服务器都运行tss-lib,地位平等 ✅ **零信任架构**:无需信任任何单一节点 ✅ **Share物理隔离**:各Party的share完全独立存储 ✅ **Coordinator不参与计算**:只负责协调,不参与MPC ✅ **跨平台支持**:Android、iOS、PC、Server ✅ **DDD+Hexagonal架构**:清晰的领域模型和六边形设计 ✅ **生产级实现**:完整的数据库设计、部署方案、安全措施 可直接用于Claude Code自动化开发。 --- **版本**: 2.0(修正版) **最后更新**: 2024-11-27 **作者**: Your Organization