跳到主要内容
ecdsa.com

ECDSA vs Schnorr 签名:Taproot 带来了哪些变化

Schnorr 是更早、更简单、更易于证明安全性的设计;ECDSA 之所以存在,很大程度上是因为一项专利。BIP-340 于2021年把 Schnorr 引入比特币,而 EdDSA 把它带到了其他所有领域。

如今,ECDSA 与 Schnorr 之争主要是比特币领域的问题,因为 Taproot(BIP 340~342,2021年11月激活)在 ECDSA 之外,于同一条曲线 secp256k1 上新增了 Schnorr 签名。两种方案使用相同的密钥和相同的困难问题,但 Schnorr 的线性结构让多重签名、门限签名和批量验证变得水到渠成,而 ECDSA 的结构让它们实现起来很别扭。

概览

ECDSA(secp256k1)Schnorr(BIP-340,secp256k1)
签名方程s = k⁻¹·(z + r·d) mod ns = k + e·d mod n,e = H(R ‖ P ‖ m)
公钥压缩形式 33 字节(未压缩形式 65 字节)32 字节,仅含 x 坐标(x-only)
签名紧凑格式 64 字节;比特币脚本中的 DER 格式为 70~72 字节64 字节
延展性(r, n − s) 同样有效;比特币强制要求 low-S按 BIP-340 验证时不存在
公钥恢复可以(以太坊的 ecrecover)不可以
密钥聚合需要多方计算协议原生支持(MuSig2,BIP-327)
门限签名复杂的协议(GG18/20、CGGMP)FROST(RFC 9591)
批量验证不支持支持
安全性证明仅限通用群模型随机预言机模型,离散对数假设
标准FIPS 186-5、SEC 1BIP-340;EdDSA(RFC 8032)是 Schnorr 的一种变体
使用场景比特币传统输出和 SegWit v0 输出、以太坊、大多数 EVM 链比特币 Taproot、Nostr、FROST 部署

两者的区别

两者签名时都使用一个秘密 nonce k 及其对应的点 R = k·G。ECDSA 通过 k 的模逆,把哈希 z、私钥 d 和 r(R 的 x 坐标)混合在一起。Schnorr 则把承诺值、公钥和消息一起哈希成挑战值 e,并以 s = k + e·d 作为响应,验证者通过 s·G = R + e·P 进行检查。

这一个设计选择决定了其余的一切差异:

  • 线性。Schnorr 签名可以相加。多个签名者可以把各自的 nonce 和部分响应合并成一个签名,该签名可以用他们公钥之和来验证。MuSig2(BIP-327)通过两轮交互完成 n-of-n 签名;FROST(RFC 9591)实现 t-of-n 门限签名。在 ECDSA 中,k 的逆破坏了线性,因此门限 ECDSA 需要繁重的多方协议;钱包和托管机构都在使用这些协议,但它们出现过严重的漏洞。
  • 批量验证。许多 Schnorr 签名可以一起检查,比逐个检查更快,这对区块验证很重要。ECDSA 没有这样的捷径。
  • 对公钥的承诺。由于 e 的哈希输入包含公钥,Schnorr 签名无法换到另一个公钥下复用,也无法从签名中恢复出公钥。ECDSA 支持公钥恢复,以太坊依赖它来节省字节;使用 Schnorr 时,你必须传输公钥或事先知道公钥。
  • 延展性。ECDSA 的 (r, s) 和 (r, n − s) 都能通过验证,因此比特币强制要求 low-S 和严格 DER 编码。BIP-340 规定了唯一的编码,因此第三方无法修改一个有效的签名。
  • 历史。Claus Schnorr 于1989年提出的方案申请了专利,DSA(以及后来的 ECDSA)就是为了绕开这项专利而设计的;该专利于2008年到期,那时 ECDSA 早已根深蒂固。EdDSA(2011年)是 Edwards 曲线上的一种确定性 Schnorr 变体。

Taproot 在实践中带来的变化

Taproot 输出承诺的是单个 32 字节的 x-only 公钥。通过密钥路径花费只需要一个 64 字节的 Schnorr 签名,无论这个公钥属于一个人,还是多个公钥的 MuSig2 聚合,因此 2-of-2 或 3-of-3 的多签花费在链上看起来和普通的单签名花费没有区别。其他花费条件放在一棵脚本 Merkle 树中,只有在使用时才会公开。在 SegWit v0 中,等价的单密钥花费要在见证数据中携带一个最长 72 字节的 DER ECDSA 签名、一个 sighash 字节和一个 33 字节的公钥。

下面的脚本在 Node.js 26.10 上用 @noble/curves 2.3.0 运行,展示了同一私钥下两种签名的大小差异。

@noble/curves:用同一个 secp256k1 密钥生成 ECDSA 和 BIP-340 Schnorr 签名
import { secp256k1, schnorr } from "@noble/curves/secp256k1.js";
import { sha256 } from "@noble/hashes/sha2.js";
import { utf8ToBytes } from "@noble/hashes/utils.js";

const secretKey = secp256k1.utils.randomSecretKey();
const msg = sha256(utf8ToBytes("pay 0.01 BTC to bc1p..."));

// ECDSA: 33-byte compressed public key, 64-byte compact (r, s) signature.
const ecdsaPub = secp256k1.getPublicKey(secretKey, true);
const ecdsaSig = secp256k1.sign(msg, secretKey, { prehash: false });

// BIP-340 Schnorr: 32-byte x-only public key, 64-byte (R.x, s) signature.
const schnorrPub = schnorr.getPublicKey(secretKey);
const schnorrSig = schnorr.sign(msg, secretKey);

console.log("ECDSA   public key:", ecdsaPub.length, "bytes, signature:", ecdsaSig.length, "bytes, valid:", secp256k1.verify(ecdsaSig, msg, ecdsaPub, { prehash: false }));
console.log("Schnorr public key:", schnorrPub.length, "bytes, signature:", schnorrSig.length, "bytes, valid:", schnorr.verify(schnorrSig, msg, schnorrPub));
输出
ECDSA   public key: 33 bytes, signature: 64 bytes, valid: true
Schnorr public key: 32 bytes, signature: 64 bytes, valid: true

ECDSA 的紧凑格式同样是 64 字节,但比特币脚本中的 ECDSA 签名采用 DER 编码,以太坊则会多加一个恢复字节;而 Schnorr 签名就以这种形式原样上链。

共同的陷阱

两种方案都容不下糟糕的 nonce:对两条消息重复使用同一个 k,或者反复泄露它的几位,在两种方案中都会暴露私钥。BIP-340 建议由私钥和消息派生 nonce,并混入 32 字节新鲜的辅助随机数。多方 Schnorr 还多了一条规则:MuSig2 的 nonce 绝不能仅凭消息确定性地派生,因为控制了另一个参与方的攻击者可以让同一个 nonce 为两个不同的聚合挑战签名。应使用经过审计的实现,例如 libsecp256k1 的 MuSig2 模块,而不要自己组合密码原语。

如何选择

  • 新的比特币钱包和输出:使用 Schnorr 签名的 Taproot 密钥路径花费;参与方能够相互协调时,多方密钥用 MuSig2,备用方案用脚本路径。
  • 比特币消息签名:Taproot 地址使用 BIP-322,因为较早的 BIP-137 格式只支持 ECDSA。
  • 以太坊和 EVM 链:ECDSA,因为交易、ecrecover 和钱包都以 ECDSA 为前提;智能账户可以在合约代码中验证其他方案。
  • Nostr:BIP-340 Schnorr,这是 NIP-01 的要求。
  • 门限托管:验证者接受 Schnorr 或 EdDSA 签名时用 FROST;只有目标链要求 ECDSA 时才用门限 ECDSA。
  • 非区块链系统:Ed25519 提供了 Schnorr 式设计,同时具备标准(RFC 8032)、库支持和确定性 nonce。

迁移说明

在比特币上,同一个私钥可以生成两种签名,但需要哪一种由输出决定:把资金从 P2WPKH 或 P2PKH 输出转移到 P2TR 输出,这就是迁移。

  1. 升级钱包软件和硬件,换成支持 Taproot(BIP-86 派生路径 m/86'/0'/0')和 PSBT Taproot 字段的版本。
  2. 正确使用 x-only 公钥:BIP-340 公钥去掉了表示奇偶性的那个字节,而且公钥会用脚本树承诺进行调整(tweak);不要复用 ECDSA 的公钥编码。
  3. 把消息签名切换到 BIP-322,让 Taproot 地址也能证明所有权。
  4. 对于多签,在 MuSig2 密钥路径聚合(隐私性最好、成本最低,但需要交互)和 Tapscript OP_CHECKSIGADD 脚本路径(无需交互,使用时会暴露策略)之间做出选择。
  5. 继续验证 ECDSA,以支持较早的输出;共识规则会无限期地同时支持两者。

参考资料

全部对比