# 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-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)
