# 什么是门限签名(TSS)？

[区块链技术](https://www.infoq.cn/topic/Blockchain)与签名[算法](https://www.infoq.cn/topic/Algorithm)缔结颇深，从安全、效率到流通，签名算法都在影响着区块链网络的特性及稳定。个人及机构对账户密钥管理的需求逐渐强烈，也催生出一批相关应用，对于用户而言，管理签名其实就是管理密钥。而在多链的情况下，多签或许不是密钥管理的最佳选择——用多签通过合约的方式来管理密钥，使用成本高，[安全风险](https://www.infoq.cn/topic/Security)高。除了多签技术之外，在区块链世界中逐渐兴起的门限签名技术也是一种重要的共识工具。

&#x20;

门限签名（Threshold Signature Scheme，TSS）是数字签名的一个重要分支，它是一种基于安全多方计算（Secure Multi-Party Computation，MPC）的密码学技术，也是 MPC 密钥管理的重要研究方向。

&#x20;

门限签名特点是一个签名一定是由一个私钥产生，然而这个私钥不会被任何人完整掌握，而是会以某种方式分成很多碎片，这些碎片可以被多人同时持有，然后通过 MPC 协议，保证这些碎片不需要全部被拼起来就可以直接产生一个合法的签名。

&#x20;

![图 1 区块链上的 MPC 门限签名技术](https://static001.geekbang.org/infoq/c5/c56eea8b820b95341fa8bf6cbe95dd00.jpeg)

在与区块链的结合应用中，门限签名的优势在于签名的生成是通过链下的 MPC 协议产生的，其结果是更加安全，避免了合约被黑客攻击的风险。因为门限签名与合约模块是完全解耦的，合约不需要理解签名的协议，它只要确认签名的有效性，这与传统的合约验签模式完全一致的。此外，合约的设计策略可以更加灵活，因为除了验签外的大部分流程都搬到了链下，使用方可以根据场景制定自己的碎片管理策略。

另一个重要的应用场景是密钥管理。基于 MPC 的密钥管理，一方面可以安全地存储密钥，单一或者小批量碎片的丢失，不会对该密钥的安全性有任何影响；另一方面是让个人或者企业能够更方便、更安全、满足业务逻辑地使用密钥。

### 门限签名的算法原理与落地实践

目前针对门限签名的研究机构数量在递增，但已达到产品级标准的不多，本次我们以 Open TSS 为例详解门限签名。

Open TSS 由 LatticeX Foundation 发起和支持，理论依据来源于发表在顶级密码会议（Asiacrypt 2021）的论文 [DMZ+21](https://eprint.iacr.org/2022/297)，目前 Open TSS 最新协议代码库正式开源上线，代码采用安全高效的 Rust 实现，支持一站式 ECDSA MPC 密钥生成(Keygen)、MPC 签名(Sign)。

![](https://static001.geekbang.org/infoq/57/5784c8414e6f56930d890265ffa150d6.jpeg)

<br>

可以看到当前版本（0.1.2）支持 [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)，其他算法如 EdDSA、BLS 等将很快被整合。下面我们介绍基于 \[DMZ+21] 的多方 ECDSA。

#### 多方 ECDSA

ECDSA 被广泛用于密码货币，如 BTC、Ethereum（secp256k1 曲线）等。

多方 ECDSA 协议（{ *t*, *n* }-门限签名方案），它允许 *n* 个参与方联合生成一个共同的公共验证密钥，以及 *n* 份相应的秘密签名密钥，并允许任何至少由 *t* + 1 个参与方组成的子集安全地分布式签署一个给定的消息，而 *t* 个或更少的参与方组成的集合则不能进行签名。

本库中的多方 ECDSA 协议是基于类群实现的。它目前包括两个协议：

* 密钥生成，用于创建秘密分片。
* 签名，用于使用秘密分片来生成签名。这可以分为两个阶段，离线和在线。
* 离线阶段与要签名的信息无关，可以提前计算。
* 只需将信息（和离线阶段的输出）传递给在线阶段，就可以很快得到签名。

#### 功能使用

目前，ECDSA 由两个功能模块组成，包括密钥生成、签名，签名功能分为离线阶段与在线阶段。

在使用上，对于上面的每个功能，只需要三个步骤。这里假设 (*t*, *n*) = (1, 3)，参与方的 id 为 1， 2， 3，分别以 P1，P2，P3 表示。

#### 密钥生成

**第 1 步**。新建一个 KeyGenPhase 对象。

{% code lineNumbers="true" %}

```
let partyid = "1".to_string(); // P2, P3 are similar. let params = Parameters {
threshold: 1,
share_count: 3,
};
let party_ids = vec!["1".to_string(), "2".to_string(), "3".to_string()];
let mut keygen = KeyGenPhase::new(partyid, params, &Some(party_ids)).unwrap();
```

{% endcode %}

**第 2 步**。通过调用 process\_begin 开始，它返回下一轮要发送的信息。

{% code lineNumbers="true" %}

```
let sending_msg: SendingMessages = keygen.process_begin().unwrap();
```

{% endcode %}

根据 SendingMessages 的类型（广播，P2P 等）和内容，我们可以将索引（from）和消息（msg）一起打包发送给其他参与者。

{% code lineNumbers="true" %}

```
match sending_msg { SendingMessages::BroadcastMessage(msg) => {
// broadcast the msg to all(including self).
}
SendingMessages::P2pMessage(msg) => {
// send according to the k,v in the msg. k is the index which v will to be sent to.
}
SendingMessages::SubsetMessage(msg) => {
// send according to the k in the party_ids or subset(used in sign phase). k is the in
}
_ => {}
}
```

{% endcode %}

**第 3 步**: 通过 msg\_handler 处理消息。

当收到消息后，会得到 recv\_from 和 recv\_msg，然后把它们传给 msg\_handler，它返回一个结果或下一轮要发送的消息。

{% code lineNumbers="true" %}

```
loop {
// let (recv_from, recv_msg) = According to the last round of SendingMessages let recv_from = "".to_string();
let recv_msg = vec![0u8];
let sending_msg = keygen.msg_handler(recv_from, &recv_msg).unwrap(); match sending_msg {
SendingMessages::KeyGenSuccessWithResult(msg) => {
// got the keygen result break;
}
_ => {
// other sending messages, ref Step 2.
}
}
}
```

{% endcode %}

一旦收到 SendingMessages::KeyGenSuccessWithResult ，就表示此阶段完成。这里是一个密钥生成的样例：

{% code lineNumbers="true" %}

```
{
"index": "1", "participants": [
"1",
"2",
"3"
],
"pubkey": {
"pk": [ "10ec64d0a73c134c53ed764e86743397bab3bb06bdbbd638321b87eda9c6614e", "6b7df1b8b41c41fc69fef0d87fc8ee9d01c021936d3b44cd62883894cd60de14"
],
"share_pks": {
"1": [
"7a39ace81396d9c65dfb8f4c8ebdf3d5850447e129edbac052558b483b01ba52", "d942c292f40e65715f722b1db87d0ceaa122f9d6457eacffbd021653b0a6f65"
], "2": [
"6b31a24d2705971d18fffbdc2edbf4e97d01c2b4aea75df2a01566f03c269804", "5f94b59a0a97d604e356ca21c27b64c0f5dfc4e8315e4be8179c5292a8b6d015"
], "3": [
"79a7c9632cbfd98f890d9d4670ac301fda42db178b9b8ec2a2860e44488130da", "af7d69f73529d8235ae6dc9f896bd81830777ff9667d9ab1fc5b37599c712378"
]
}
},
"privkey": {
"cl_sk": "1b557b69c49c0715403f618907a051c012adc57e9ea6b17aa912d68b6056b1d24b5c10a36269ac03 "ec_sk": "a23ae304a46c36bbf52e1373daa4446dda3f3b1b721a77c84a2ec86f54e6970c",
"share_sk": "f869bd11e46d036cdd81ad9940c9d510d24114bba12edfa626a966677058ff5a"
}
}
```

{% endcode %}

#### 签名-离线阶段

**第 1 步**。与密钥生成类似，新建一个 SignPhase 对象。

{% code lineNumbers="true" %}

```
let partyid = "1".to_string(); // P2, P3 are similar. let params = Parameters {
threshold: 1,
share_count: 3,
};
let subset = vec!["1".to_string(), "2".to_string()]; // The set of parties that involved in si let keygen_result = "".to_string(); // The output of KeyGen
let mut signoffline = SignPhase::new(partyid, params, &subset, &keygen_result).unwrap();
```

{% endcode %}

这里的 subset 就是参与签名的各方集合，是所有参与密钥生成的一个子集。

**第 2 步、第 3 步**：与密钥生成一样。当收到 SendingMessages::SignOfflineSuccessWithResult ，就表示此阶段完成。

#### 签名-在线阶段

**第 1 步**。类似的，新建一个 SignPhaseOnline 对象。

{% code lineNumbers="true" %}

```
let offline_result = "".to_string(); // The output of SignOffline
let message_bytes = vec![0u8; 32]; // The hash value of the message to be signed, 32 bytes. let 
mut signonline = SignPhaseOnline::new(&offline_result, message_bytes).unwrap();
```

{% endcode %}

**第 2 步、第 3 步**：与密钥生成、签名离线阶段一样。当收到 SendingMessages::SignOnlineSuccessWithResult ，就表示此阶段完成。

* 从安全角度考虑，离线阶段的结果只能使用一次。
* 整个在线阶段只需要几毫秒即可完成。

<br>

这里是一个签名的样例：

{% code lineNumbers="true" %}

```
{
"s": "14af6f72d8bd26faccd75ff092544d15a3dce5d97e897773b515cd70ab0453e7", "r": "3687024517eb44de2cfaa6166866c9bd2587090317a4d12521b571c7509319b4",
"recid": 0
}
```

{% endcode %}

复制代码

一份[本地代码](https://github.com/LatticeX-Foundation/opentss/multi_party_ecdsa/src/protocols/multi_party/dmz21/local.rs)显示了如何使用这些功能。参考[这里](https://github.com/LatticeX-Foundation/opentss/docs/ECDSA.md)了解更详细的使用说明。

#### 性能表现

我们来看看 OpenTSS 的性能如何，与最先进的协议进行比较。为了进行公平的比较，我们用 Rust 实现了两个多方 ECDSA，包括我们的协议和 \[[CCL+20](https://eprint.iacr.org/2020/084.pdf)] 中的协议。椭圆曲线是 secp256k1，类群的判别式的位长选择为 1827，这确保了我们的协议具有 128 位的安全性。运行时间是在 Intel(R) Core(TM) i7-9700K @ 3.6GHz 的单核上测量的。

![](https://static001.geekbang.org/infoq/f1/f1b9eafde795277e822f2368a38d7ee6.jpeg)

如上表（来自论文第 7 节）所示，进行了具体比较。与基本上基于 \[[GG18](https://eprint.iacr.org/2019/114.pdf)] 的 \[CCL+20] 相比，可以看到 OpenTSS 的多方 ECDSA 协议的改进是非常明显的。它主要体现在密钥生成阶段。

在计算复杂度方面，OpenTSS 的多方 ECDSA 协议比 \[CCL+20] 中的协议在 *κ* = 40(*κ* = 128) 时的密钥生成阶段快 4 倍（12 倍），这在理论（详见论文）和具体方面都可以看到。OpenTSS 构造的签名阶段比 \[CCL+20] 中的略好，在具体方面大约快 10%。

在通信方面，由于 OpenTSS 消除了昂贵的交互式设置阶段的需要，OpenTSS 的协议在密钥生成阶段优于 \[CCL+20] 中的协议，其差异根据参与方的数量 *n* 和门限 *t* 而变化。虽然在签名阶段通信开销稍大，但 OpenTSS 的解决方案仍然是同一数量级的。

### 总结

可以看出，门限签名协议将成为众多机构持续投入研究的方向、为开源社区做贡献、在不远的将来，相信以 Open TSS 为代表的产品将向区块链安全技术服务商的方向发展，为区块链钱包、托管企业提供底层架构服务。

Open TSS 项目地址：[https://github.com/LatticeX-Foundation/opentss](https://github.com/LatticeX-Foundation/opentss%E3%80%82)[。](https://github.com/LatticeX-Foundation/opentss%E3%80%82)<br>

**参考文献**

1\. [Promise Σ-protocol: How to Construct Efficient Threshold ECDSTSSA from Encryptions Based on Class Groups.](https://eprint.iacr.org/2022/297)<br>

<br>
