rwadurian/backend/mpc-system/DELEGATE_PARTY_GUIDE.md

523 lines
12 KiB
Markdown
Raw Permalink 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.

# 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 <optional_jwt_token>"
```
**成功响应**:
```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-31 个用户) |
| **ZenGo** | 1 | 1 | 2-of-21 个用户) |
| **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)