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