12 KiB
12 KiB
Delegate Party Implementation Guide
概述
Delegate Party 是一种特殊类型的 Server-Party,用于生成用户端 key shares 并返回给用户,而不保存到服务器数据库中。
这是 混合托管(Hybrid Custody) 模式的核心实现,类似于 Fireblocks 和 ZenGo 的架构。
架构设计
三种 Party Role
| Role | 存储位置 | 用途 | 示例 |
|---|---|---|---|
| persistent | 服务器数据库 | 永久存储 share | 托管钱包、企业密钥 |
| delegate | 不保存,返回给用户 | 用户自己持有 share | 移动钱包、个人密钥 |
| temporary | 临时(不实现) | 一次性操作 | 临时签名 |
MPC Session 组合示例
Keygen Session (3-of-5)
├── Persistent Party 1 → Database (share_1) ✅ 服务器托管
├── Persistent Party 2 → Database (share_2) ✅ 服务器托管
├── Persistent Party 3 → Database (share_3) ✅ 服务器托管
├── Delegate Party 1 → User Device (share_4) 📱 用户自持
└── Delegate Party 2 → User Device (share_5) 📱 用户自持
签名时需要任意 3 个 shares:
- 方案 A: 3 个服务器 shares(完全托管)
- 方案 B: 2 个服务器 + 1 个用户(用户参与)
- 方案 C: 1 个服务器 + 2 个用户(用户主导)
完整工作流程
1. 部署 Delegate Parties
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mpc-delegate-party
namespace: mpc-system
spec:
replicas: 2
selector:
matchLabels:
app: mpc-server-party
party-role: delegate
template:
metadata:
labels:
app: mpc-server-party
party-role: delegate # ← Party Discovery 使用
spec:
containers:
- name: delegate-party
image: rwadurian/mpc-server-party:latest
env:
- name: PARTY_ROLE
value: "delegate" # ← 关键环境变量
- name: MESSAGE_ROUTER_ADDR
value: "mpc-message-router:9092"
- name: SESSION_COORDINATOR_ADDR
value: "mpc-session-coordinator:9091"
ports:
- containerPort: 8080
name: http
Docker Compose (Development):
services:
delegate-party-1:
image: rwadurian/mpc-server-party:latest
container_name: delegate-party-1
environment:
PARTY_ROLE: "delegate"
MESSAGE_ROUTER_ADDR: "mpc-message-router:9092"
SESSION_COORDINATOR_ADDR: "mpc-session-coordinator:9091"
ports:
- "8081:8080"
2. 创建 Session(指定 Party Composition)
API Request (POST /api/v1/sessions/create):
{
"session_type": "keygen",
"threshold_n": 5,
"threshold_t": 3,
"expires_in": 600,
"party_composition": {
"persistent_count": 3,
"delegate_count": 2
}
}
Response:
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"join_tokens": {
"mpc-server-party-1": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"mpc-server-party-2": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"mpc-server-party-3": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"mpc-delegate-party-0": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"mpc-delegate-party-1": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"expires_at": "2024-01-01T12:10:00Z"
}
3. Keygen 协议执行
Session Coordinator 发布 SessionEvent:
1. Session Coordinator
└─ PublishSessionCreated() → Message Router
2. Message Router 广播到所有 selected parties:
├─ → mpc-server-party-1 (persistent)
├─ → mpc-server-party-2 (persistent)
├─ → mpc-server-party-3 (persistent)
├─ → mpc-delegate-party-0 (delegate)
└─ → mpc-delegate-party-1 (delegate)
3. All Parties 收到 SessionEvent:
├─ 提取自己的 JoinToken
├─ 调用 JoinSession()
└─ 执行 GG20 Keygen 协议
4. Keygen 完成后的处理
Persistent Parties:
// participate_keygen.go:165-173
case "persistent":
// 保存 share 到数据库
if err := uc.keyShareRepo.Save(ctx, keyShare); err != nil {
return nil, ErrShareSaveFailed
}
logger.Info("Share saved to database (persistent party)",
zap.String("party_id", input.PartyID),
zap.String("session_id", input.SessionID.String()))
Delegate Parties:
// participate_keygen.go:175-181
case "delegate":
// 不保存到数据库,返回给用户
shareForUser = encryptedShare
logger.Info("Share NOT saved, will be returned to user (delegate party)",
zap.String("party_id", input.PartyID),
zap.String("session_id", input.SessionID.String()),
zap.Int("share_size", len(shareForUser)))
存入内存缓存 (main.go:328-340):
// 如果是 delegate party,将 share 存入缓存
if output.ShareForUser != nil && len(output.ShareForUser) > 0 {
globalShareCache.Store(
sessionID,
req.PartyID,
output.ShareForUser,
output.PublicKey,
)
logger.Info("Share stored in cache for user retrieval (delegate party)")
}
5. 用户获取 Share
API Request (GET /api/v1/sessions/{session_id}/user-share):
curl -X GET \
https://delegate-party-1.example.com/api/v1/sessions/550e8400-e29b-41d4-a716-446655440000/user-share \
-H "Authorization: Bearer <optional_jwt_token>"
成功响应:
{
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"party_id": "mpc-delegate-party-0",
"share": "0123456789abcdef...", // hex-encoded encrypted share
"public_key": "04a1b2c3d4e5f6...", // hex-encoded public key
"note": "This share has been deleted from memory and cannot be retrieved again"
}
错误响应 (已被获取或过期):
{
"error": "Share not found or already retrieved",
"note": "Shares can only be retrieved once and expire after 15 minutes"
}
Forbidden (非 delegate party):
{
"error": "This endpoint is only available for delegate parties",
"role": "persistent"
}
安全特性
1. 内存缓存(非持久化)
// share_cache.go
type ShareCache struct {
entries map[string]*ShareCacheEntry // session_id -> entry
mu sync.RWMutex
ttl time.Duration // 15 分钟
}
特点:
- ✅ 只存在内存中(不写入磁盘)
- ✅ 服务重启自动清空
- ✅ 15 分钟 TTL 自动过期
- ✅ 后台定时清理过期数据
2. 一次性获取
// GetAndDelete - 获取后立即删除
func (c *ShareCache) GetAndDelete(sessionID uuid.UUID) (*ShareCacheEntry, bool) {
c.mu.Lock()
defer c.mu.Unlock()
entry, exists := c.entries[sessionID.String()]
if !exists {
return nil, false
}
// 立即删除(原子操作)
delete(c.entries, sessionID.String())
return entry, true
}
特点:
- ✅ 只能获取一次
- ✅ 获取后立即从内存删除
- ✅ 原子操作(线程安全)
3. Role 检查
// main.go:454-461
// 检查 PARTY_ROLE 环境变量
partyRole := os.Getenv("PARTY_ROLE")
if partyRole != "delegate" {
c.JSON(http.StatusForbidden, gin.H{
"error": "This endpoint is only available for delegate parties",
})
return
}
测试指南
端到端测试流程
# 1. 创建 Session
curl -X POST http://localhost:9091/api/v1/sessions/create \
-H "Content-Type: application/json" \
-d '{
"session_type": "keygen",
"threshold_n": 5,
"threshold_t": 3,
"party_composition": {
"persistent_count": 3,
"delegate_count": 2
}
}'
# Response:
# {
# "session_id": "550e8400-e29b-41d4-a716-446655440000",
# "join_tokens": { ... }
# }
# 2. 等待 Keygen 完成(约 30-60 秒)
# Parties 会自动接收 SessionEvent 并执行 Keygen
# 3. 检查日志
docker logs mpc-delegate-party-0 | grep "Share stored in cache"
# 输出:
# INFO Share stored in cache for user retrieval (delegate party)
# session_id=550e8400-e29b-41d4-a716-446655440000
# party_id=mpc-delegate-party-0
# share_size=256
# 4. 用户获取 Share(第一次)
curl -X GET http://localhost:8081/api/v1/sessions/550e8400-e29b-41d4-a716-446655440000/user-share
# Response:
# {
# "session_id": "550e8400-e29b-41d4-a716-446655440000",
# "party_id": "mpc-delegate-party-0",
# "share": "0a1b2c3d...",
# "public_key": "04a1b2c3...",
# "note": "This share has been deleted from memory..."
# }
# 5. 再次尝试获取(应该失败)
curl -X GET http://localhost:8081/api/v1/sessions/550e8400-e29b-41d4-a716-446655440000/user-share
# Response:
# {
# "error": "Share not found or already retrieved",
# "note": "Shares can only be retrieved once..."
# }
# 6. 验证持久化 Party(对比)
curl -X GET http://localhost:8080/api/v1/shares/mpc-server-party-1
# Response (Persistent Party 有数据库记录):
# {
# "party_id": "mpc-server-party-1",
# "count": 1,
# "shares": [...]
# }
对比总结
Persistent vs Delegate
| 特性 | Persistent Party | Delegate Party |
|---|---|---|
| 存储位置 | PostgreSQL 数据库 | 内存缓存(临时) |
| 持久化 | ✅ 永久保存 | ❌ 不保存 |
| 获取方式 | 数据库查询(多次) | HTTP API(一次) |
| 安全性 | 服务器托管 | 用户自持 |
| TTL | 无限期 | 15 分钟 |
| 重启后 | 数据保留 | 数据丢失 ✅ |
国际标准对比
| 方案 | Persistent Count | Delegate Count | 模式 |
|---|---|---|---|
| Fireblocks | 2 | 1 | 2-of-3(1 个用户) |
| ZenGo | 1 | 1 | 2-of-2(1 个用户) |
| Coinbase | 3 | 0 | 3-of-5(全托管) |
| 我们的系统 | 灵活配置 | 灵活配置 | 任意组合 |
Kubernetes 完整部署示例
# k8s/delegate-party-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: mpc-delegate-party
namespace: mpc-system
spec:
type: LoadBalancer # 或 Ingress
ports:
- port: 443
targetPort: 8080
name: https
selector:
app: mpc-server-party
party-role: delegate
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mpc-delegate-party
namespace: mpc-system
spec:
replicas: 2
selector:
matchLabels:
app: mpc-server-party
party-role: delegate
template:
metadata:
labels:
app: mpc-server-party
party-role: delegate
spec:
containers:
- name: delegate-party
image: rwadurian/mpc-server-party:latest
env:
- name: PARTY_ROLE
value: "delegate"
- name: MESSAGE_ROUTER_ADDR
value: "mpc-message-router:9092"
- name: SESSION_COORDINATOR_ADDR
value: "mpc-session-coordinator:9091"
- name: MPC_CRYPTO_MASTER_KEY
valueFrom:
secretKeyRef:
name: mpc-secrets
key: crypto-master-key
ports:
- containerPort: 8080
name: http
resources:
requests:
memory: "256Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "1000m"
故障排查
问题 1: Share 获取失败
# 错误: Share not found or already retrieved
# 可能原因:
1. Share 已经被获取过(一次性)
2. Share 已过期(15 分钟)
3. Keygen 还未完成
4. PARTY_ROLE 不是 "delegate"
# 排查步骤:
# 检查缓存大小
curl http://localhost:8081/health
# 查看日志
docker logs mpc-delegate-party-0 --tail 100
问题 2: Endpoint 返回 403 Forbidden
# 错误: This endpoint is only available for delegate parties
# 原因: PARTY_ROLE 环境变量不是 "delegate"
# 解决:
kubectl set env deployment/mpc-delegate-party PARTY_ROLE=delegate
总结
✅ 已实现功能:
- ✅ Delegate Party Role 定义
- ✅ PartyComposition API 支持
- ✅ Share 不保存到数据库
- ✅ 内存缓存(15 分钟 TTL)
- ✅ HTTP API 一次性获取
- ✅ 自动清理机制
✅ 安全特性:
- ✅ 一次性获取
- ✅ 自动过期
- ✅ 重启清空
- ✅ Role 权限检查
✅ 符合国际标准:
- Fireblocks: Party-Driven + Delegate Share
- ZenGo: Hybrid Custody
- ING Bank: Threshold Signature
文档版本: 1.0 最后更新: 2024-01-01 作者: Claude (Anthropic)