rwadurian/backend/mpc-system/docs/06-tss-protocol.md

454 lines
14 KiB
Markdown
Raw 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.

# MPC 分布式签名系统 - TSS 协议详解
## 1. 概述
本系统使用 **门限签名方案 (Threshold Signature Scheme, TSS)** 实现分布式密钥管理和签名。基于 [bnb-chain/tss-lib](https://github.com/bnb-chain/tss-lib) 库,采用 GG20 协议。
### 1.1 核心概念
| 术语 | 定义 |
|------|------|
| t-of-n | t+1 个参与方中的任意组合可以签名,需要 n 个参与方共同生成密钥 |
| DKG | 分布式密钥生成 (Distributed Key Generation) |
| TSS | 门限签名方案 (Threshold Signature Scheme) |
| Party | MPC 协议中的参与方 |
| Share | 密钥分片,每个 Party 持有一份 |
### 1.2 安全属性
- **无单点故障**: 私钥从未以完整形式存在
- **门限安全**: 需要 t+1 个分片才能签名
- **抗合谋**: t 个恶意方无法伪造签名
- **可审计**: 每次签名可追踪参与方
## 2. 阈值参数说明
### 2.1 tss-lib 参数约定
在 tss-lib 中,`threshold` 参数定义如下:
- `threshold = t` 表示需要 **t+1** 个签名者
- 例如: `threshold=1` 需要 2 个签名者
### 2.2 常见阈值方案
| 方案 | tss-lib threshold | 总参与方 (n) | 签名者数 (t+1) | 应用场景 |
|------|-------------------|-------------|---------------|---------|
| 2-of-3 | 1 | 3 | 2 | 个人钱包 + 设备 + 恢复 |
| 3-of-5 | 2 | 5 | 3 | 企业多签 |
| 4-of-7 | 3 | 7 | 4 | 机构托管 |
| 5-of-9 | 4 | 9 | 5 | 大型组织 |
### 2.3 阈值选择建议
```
安全性 vs 可用性权衡:
高安全性 ◄────────────────────────► 高可用性
5-of-9 4-of-7 3-of-5 2-of-3
建议:
- 个人用户: 2-of-3 (设备 + 服务器 + 恢复)
- 小型企业: 3-of-5 (3 管理员 + 1 服务器 + 1 恢复)
- 大型企业: 4-of-7 或更高
```
## 3. 密钥生成协议 (Keygen)
### 3.1 协议流程
```
Round 1: 承诺分发
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Party 0 │ │ Party 1 │ │ Party 2 │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
│ 生成随机多项式 │ │
│ 计算承诺 Ci │ │
│ │ │
│◄─────────────────┼──────────────────┤ 广播承诺
├──────────────────►◄─────────────────┤
│ │ │
Round 2: 秘密分享
│ │ │
│ 计算 Shamir 分片│ │
│ 发送 share_ij │ │
│ │ │
│──────────────────► │ 点对点
│ ◄──────────────────│
◄──────────────────│ │
│ │──────────────────►
│ │ │
Round 3: 验证与聚合
│ │ │
│ 验证收到的分片 │ │
│ 计算最终密钥分片 │ │
│ 计算公钥 PK │ │
│ │ │
▼ ▼ ▼
Share_0 Share_1 Share_2
│ │ │
└──────────────────┼──────────────────┘
公钥 PK (相同)
```
### 3.2 代码实现
```go
// pkg/tss/keygen.go
func RunLocalKeygen(threshold, totalParties int) ([]*LocalKeygenResult, error) {
// 验证参数
if threshold < 1 || threshold > totalParties {
return nil, ErrInvalidThreshold
}
// 创建 Party IDs
partyIDs := make([]*tss.PartyID, totalParties)
for i := 0; i < totalParties; i++ {
partyIDs[i] = tss.NewPartyID(
fmt.Sprintf("party-%d", i),
fmt.Sprintf("party-%d", i),
big.NewInt(int64(i+1)),
)
}
sortedPartyIDs := tss.SortPartyIDs(partyIDs)
peerCtx := tss.NewPeerContext(sortedPartyIDs)
// 创建各方的通道和 Party 实例
outChs := make([]chan tss.Message, totalParties)
endChs := make([]chan *keygen.LocalPartySaveData, totalParties)
parties := make([]tss.Party, totalParties)
for i := 0; i < totalParties; i++ {
outChs[i] = make(chan tss.Message, totalParties*10)
endChs[i] = make(chan *keygen.LocalPartySaveData, 1)
params := tss.NewParameters(
tss.S256(), // secp256k1 曲线
peerCtx,
sortedPartyIDs[i],
totalParties,
threshold,
)
parties[i] = keygen.NewLocalParty(params, outChs[i], endChs[i])
}
// 启动所有 Party
for i := 0; i < totalParties; i++ {
go parties[i].Start()
}
// 消息路由
go routeMessages(parties, outChs, sortedPartyIDs)
// 收集结果
results := make([]*LocalKeygenResult, totalParties)
for i := 0; i < totalParties; i++ {
saveData := <-endChs[i]
results[i] = &LocalKeygenResult{
SaveData: saveData,
PublicKey: saveData.ECDSAPub.ToECDSAPubKey(),
PartyIndex: i,
}
}
return results, nil
}
```
### 3.3 SaveData 结构
每个 Party 保存的数据:
```go
type LocalPartySaveData struct {
// 本方的私钥分片 (xi)
Xi *big.Int
// 所有方的公钥分片 (Xi = xi * G)
BigXj []*crypto.ECPoint
// 组公钥
ECDSAPub *crypto.ECPoint
// Paillier 密钥对 (用于同态加密)
PaillierSK *paillier.PrivateKey
PaillierPKs []*paillier.PublicKey
// 其他预计算数据...
}
```
## 4. 签名协议 (Signing)
### 4.1 协议流程
```
签名协议 (GG20 - 6 轮):
Round 1: 承诺生成
┌────────────┐ ┌────────────┐
│ Party 0 │ │ Party 1 │
└─────┬──────┘ └─────┬──────┘
│ │
│ 生成随机 ki │
│ 计算 γi = ki*G │
│ 广播 C(γi) │
│ │
│◄────────────────►│
│ │
Round 2: Paillier 加密
│ │
│ 加密 ki │
│ MtA 协议开始 │
│ │
│◄────────────────►│
│ │
Round 3: MtA 响应
│ │
│ 计算乘法三元组 │
│ │
│◄────────────────►│
│ │
Round 4: Delta 分享
│ │
│ 计算 δi │
│ 广播 │
│ │
│◄────────────────►│
│ │
Round 5: 重构与验证
│ │
│ 重构 δ = Σδi │
│ 计算 R = δ^-1*Γ │
│ 计算 r = Rx │
│ │
│◄────────────────►│
│ │
Round 6: 签名聚合
│ │
│ 计算 si = ... │
│ 广播 si │
│ │
│◄────────────────►│
│ │
▼ ▼
最终签名 (r, s)
```
### 4.2 代码实现
```go
// pkg/tss/signing.go
func RunLocalSigning(
threshold int,
keygenResults []*LocalKeygenResult,
messageHash []byte,
) (*LocalSigningResult, error) {
signerCount := len(keygenResults)
if signerCount < threshold+1 {
return nil, ErrInvalidSignerCount
}
// 创建 Party IDs (必须使用原始索引)
partyIDs := make([]*tss.PartyID, signerCount)
for i, result := range keygenResults {
idx := result.PartyIndex
partyIDs[i] = tss.NewPartyID(
fmt.Sprintf("party-%d", idx),
fmt.Sprintf("party-%d", idx),
big.NewInt(int64(idx+1)),
)
}
sortedPartyIDs := tss.SortPartyIDs(partyIDs)
peerCtx := tss.NewPeerContext(sortedPartyIDs)
// 转换消息哈希
msgHash := new(big.Int).SetBytes(messageHash)
// 创建签名方
outChs := make([]chan tss.Message, signerCount)
endChs := make([]chan *common.SignatureData, signerCount)
parties := make([]tss.Party, signerCount)
for i := 0; i < signerCount; i++ {
outChs[i] = make(chan tss.Message, signerCount*10)
endChs[i] = make(chan *common.SignatureData, 1)
params := tss.NewParameters(tss.S256(), peerCtx, sortedPartyIDs[i], signerCount, threshold)
parties[i] = signing.NewLocalParty(msgHash, params, *keygenResults[i].SaveData, outChs[i], endChs[i])
}
// 启动并路由消息
for i := 0; i < signerCount; i++ {
go parties[i].Start()
}
go routeSignMessages(parties, outChs, sortedPartyIDs)
// 收集签名结果
signData := <-endChs[0]
return &LocalSigningResult{
R: new(big.Int).SetBytes(signData.R),
S: new(big.Int).SetBytes(signData.S),
RecoveryID: int(signData.SignatureRecovery[0]),
}, nil
}
```
### 4.3 签名验证
```go
// 验证签名
import "crypto/ecdsa"
func VerifySignature(publicKey *ecdsa.PublicKey, messageHash []byte, r, s *big.Int) bool {
return ecdsa.Verify(publicKey, messageHash, r, s)
}
// 示例
message := []byte("Hello MPC!")
hash := sha256.Sum256(message)
valid := ecdsa.Verify(publicKey, hash[:], signResult.R, signResult.S)
```
## 5. 消息路由
### 5.1 消息类型
| 类型 | 说明 | 方向 |
|------|------|------|
| Broadcast | 发送给所有其他方 | 1 → n-1 |
| P2P | 点对点消息 | 1 → 1 |
### 5.2 消息结构
```go
type MPCMessage struct {
SessionID string // 会话 ID
FromParty string // 发送方
ToParties []string // 接收方 (空=广播)
Round int // 协议轮次
Payload []byte // 加密的协议消息
IsBroadcast bool // 是否广播
Timestamp int64
}
```
### 5.3 消息路由实现
```go
func routeMessages(
parties []tss.Party,
outChs []chan tss.Message,
sortedPartyIDs []*tss.PartyID,
) {
signerCount := len(parties)
for idx := 0; idx < signerCount; idx++ {
go func(i int) {
for msg := range outChs[i] {
if msg.IsBroadcast() {
// 广播给所有其他方
for j := 0; j < signerCount; j++ {
if j != i {
updateParty(parties[j], msg)
}
}
} else {
// 点对点发送
for _, dest := range msg.GetTo() {
for j := 0; j < signerCount; j++ {
if sortedPartyIDs[j].Id == dest.Id {
updateParty(parties[j], msg)
break
}
}
}
}
}
}(idx)
}
}
```
## 6. 子集签名 (Subset Signing)
### 6.1 原理
在 t-of-n 方案中,任意 t+1 个 Party 的子集都可以生成有效签名。关键是使用原始的 Party 索引。
### 6.2 示例: 2-of-3 的所有组合
```go
// 3 方生成密钥
keygenResults, _ := tss.RunLocalKeygen(1, 3) // threshold=1, n=3
// 任意 2 方可签名:
// 组合 1: Party 0 + Party 1
signers1 := []*tss.LocalKeygenResult{keygenResults[0], keygenResults[1]}
sig1, _ := tss.RunLocalSigning(1, signers1, messageHash)
// 组合 2: Party 0 + Party 2
signers2 := []*tss.LocalKeygenResult{keygenResults[0], keygenResults[2]}
sig2, _ := tss.RunLocalSigning(1, signers2, messageHash)
// 组合 3: Party 1 + Party 2
signers3 := []*tss.LocalKeygenResult{keygenResults[1], keygenResults[2]}
sig3, _ := tss.RunLocalSigning(1, signers3, messageHash)
// 所有签名都对同一公钥有效!
ecdsa.Verify(publicKey, messageHash, sig1.R, sig1.S) // true
ecdsa.Verify(publicKey, messageHash, sig2.R, sig2.S) // true
ecdsa.Verify(publicKey, messageHash, sig3.R, sig3.S) // true
```
### 6.3 注意事项
1. **Party 索引必须一致**: 签名时使用 keygen 时的原始索引
2. **不能混用不同 keygen 的分片**: 每个账户对应唯一的一组分片
3. **阈值验证**: 签名者数量 >= threshold + 1
## 7. 性能考虑
### 7.1 测试基准
| 操作 | 2-of-3 | 3-of-5 | 4-of-7 |
|------|--------|--------|--------|
| Keygen | ~93s | ~198s | ~221s |
| Signing | ~80s | ~120s | ~150s |
### 7.2 优化建议
1. **预计算**: 部分 Keygen 数据可预计算
2. **并行执行**: 多个签名请求可并行处理
3. **消息压缩**: 大消息进行压缩传输
4. **连接池**: 复用 Party 间的连接
## 8. 故障恢复
### 8.1 Keygen 失败
如果 Keygen 过程中某个 Party 离线:
- 协议超时失败
- 需要全部重新开始
- 建议设置合理的超时时间
### 8.2 Signing 失败
如果签名过程中 Party 离线:
- 当前签名失败
- 可以选择其他 Party 子集重试
- 密钥分片不受影响
### 8.3 密钥分片丢失
如果某个 Party 的分片丢失:
- 如果丢失数量 < n - t: 仍可签名
- 如果丢失数量 >= n - t: 无法签名,需要重新 Keygen
- 建议: 加密备份分片到安全存储