rwadurian/backend/mpc-system/MPC-Distributed-Signature-S...

2585 lines
83 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 ServiceDDD+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 SDKKotlin + 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<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;
}
```
```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