# 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**: ```yaml 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): ```yaml 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`): ```json { "session_type": "keygen", "threshold_n": 5, "threshold_t": 3, "expires_in": 600, "party_composition": { "persistent_count": 3, "delegate_count": 2 } } ``` **Response**: ```json { "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**: ```go // 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**: ```go // 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): ```go // 如果是 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`): ```bash curl -X GET \ https://delegate-party-1.example.com/api/v1/sessions/550e8400-e29b-41d4-a716-446655440000/user-share \ -H "Authorization: Bearer " ``` **成功响应**: ```json { "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" } ``` **错误响应** (已被获取或过期): ```json { "error": "Share not found or already retrieved", "note": "Shares can only be retrieved once and expire after 15 minutes" } ``` **Forbidden** (非 delegate party): ```json { "error": "This endpoint is only available for delegate parties", "role": "persistent" } ``` --- ## 安全特性 ### 1. 内存缓存(非持久化) ```go // share_cache.go type ShareCache struct { entries map[string]*ShareCacheEntry // session_id -> entry mu sync.RWMutex ttl time.Duration // 15 分钟 } ``` **特点**: - ✅ 只存在内存中(不写入磁盘) - ✅ 服务重启自动清空 - ✅ 15 分钟 TTL 自动过期 - ✅ 后台定时清理过期数据 ### 2. 一次性获取 ```go // 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 检查 ```go // 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 } ``` --- ## 测试指南 ### 端到端测试流程 ```bash # 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 完整部署示例 ```yaml # 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 获取失败 ```bash # 错误: 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 ```bash # 错误: This endpoint is only available for delegate parties # 原因: PARTY_ROLE 环境变量不是 "delegate" # 解决: kubectl set env deployment/mpc-delegate-party PARTY_ROLE=delegate ``` --- ## 总结 ✅ **已实现功能**: 1. ✅ Delegate Party Role 定义 2. ✅ PartyComposition API 支持 3. ✅ Share 不保存到数据库 4. ✅ 内存缓存(15 分钟 TTL) 5. ✅ HTTP API 一次性获取 6. ✅ 自动清理机制 ✅ **安全特性**: 1. ✅ 一次性获取 2. ✅ 自动过期 3. ✅ 重启清空 4. ✅ Role 权限检查 ✅ **符合国际标准**: - Fireblocks: Party-Driven + Delegate Share - ZenGo: Hybrid Custody - ING Bank: Threshold Signature --- **文档版本**: 1.0 **最后更新**: 2024-01-01 **作者**: Claude (Anthropic)