83 KiB
83 KiB
MPC分布式签名系统 - 完整技术规范
真正的去中心化MPC架构:对等参与、零信任、Share物理隔离
目录
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 核心领域模型代码
// 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
}
// 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 应用层用例实现
// 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
}
// 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
}
// 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参与方)
// 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
}
// 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)
}
}
}
// 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
-- 会话表
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
-- 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
-- 账户表
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
-- 审核工作流表
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 审计日志表(所有服务共享)
-- 审计日志表
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(核心实现)
// 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)
// 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
#!/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定义
// 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<string, string> 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;
}
// 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(开发环境)
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部署(生产环境)
# 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
.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 通信安全
// 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 认证与授权
// 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)
}
快速开始
# 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