secp256k1 的协议定位与签名机制
从 ECDSA 到 Schnorr 的账户层接口演化
Scope. 本文比较 secp256k1 上的 ECDSA 与 BIP-340 Schnorr:验证关系、编码、nonce、malleability、domain separation,以及 Bitcoin/Ethereum 的消费方式。
账户层消费普通离散对数群上的签名关系。secp256k1 的长期地位来自既有账户语义、成熟实现和硬件生态;签名机制再决定 nonce、编码与组合性质。1 2 3 4
为什么账户层长期绑定 secp256k1
账户层要让公开账户标识稳定绑定一个可验证签名接口,因此消费普通离散对数群上的签名关系。
secp256k1 同时满足几项现实条件:
- 它有稳定的标准参数来源
- 它在 Bitcoin 和 Ethereum 里长期部署
- 它拥有高质量实现和成熟审计历史
- 它足以支撑账户层所需的签名验证接口
账户层需要实现成熟、常数时间且接口清晰的 prime-order subgroup;secp256k1 的既有部署已经满足这一要求。1
ECDSA:历史主力接口
ECDSA 是 secp256k1 账户系统的历史主力接口,钱包、交易格式和恢复 API 长期围绕它组织。
最小签名与验证关系
设私钥为 \(d\),公钥为
\[ P = dG. \]对消息哈希 \(z\),签名者选取 nonce \(k\),并计算
\[ R = kG, \qquad r = x(R) \bmod n, \]再定义
\[ s = k^{-1}(z + rd) \bmod n. \]这就是 ECDSA 的最小签名关系。验证时,给定 \((r,s)\),验证者计算
\[ u_1 = z s^{-1}, \qquad u_2 = r s^{-1}, \]然后检查
\[ r \stackrel{?}= x(u_1 G + u_2 P) \bmod n. \]验证者只需要普通群上的标量乘法和点加法。
为什么 ECDSA 对 nonce 异常敏感
ECDSA 的工程负担,很大一部分都来自 nonce。
从
\[ s = k^{-1}(z + rd) \]出发,可改写为
\[ k = s^{-1}(z + rd) \bmod n. \]这意味着只要 \(k\) 有重复、偏置或部分泄漏,私钥 \(d\) 就可能被直接恢复或转化为后续的 hidden-number style 问题。也就是说,ECDSA 的安全边界不只取决于离散对数假设,还强烈依赖 nonce 生成和实现侧信道。3
RFC 6979 用 deterministic procedure 收紧 nonce 生成,降低弱随机源和重复 nonce 风险;它不改变 verifier 方程,也不消除 side channel 或 fault injection。
可塑性与 low-s normalization
ECDSA 还有一个工程上很重要、但在数学笔记里经常被一笔带过的点:可塑性。
因为在群阶模 \(n\) 下,若 \((r,s)\) 是有效签名,那么 \((r, n-s)\) 也会对应同一验证关系。这意味着如果协议层不加额外约束,同一个签名语义可能有两个编码表示。
账户系统因此增加 low-s normalization,稳定 transaction identity 与签名编码。Ethereum 的 EIP-2 使 high-s transaction signatures 无效,同时保留 ecrecover precompile 对 high-s 的兼容行为;调用合约若需要 canonical signature,必须自行执行 low-s policy。5
Schnorr:同一条曲线上的接口升级
Schnorr 在同一条 secp256k1 上采用线性签名关系;BIP-340 同时固定了 x-only keys、编码和 tagged hashes。2
最小验证关系
Schnorr 签名在最小形式下可写成:
\[ R = kG, \qquad e = H(R \| P \| m), \qquad s = k + ed \bmod n. \]验证时检查
\[ sG \stackrel{?}= R + eP. \]该验证器直接检查线性群等式,省去了 ECDSA 验证中的模逆与最终 \(x\)-coordinate 比较。
线性、批量验证与多签组合
一旦验证关系变成
\[ sG = R + eP, \]很多高层协议就自然多了。线性结构意味着多个公钥和多个 nonce 的组合可以更直接地进入多签和批量验证逻辑。BIP-340 里就明确把 batch verification 作为正式接口的一部分,而这正是账户层协议升级最重要的收益之一。2
线性关系让同一条曲线更适合:
- 多签和门限签名的上层构造
- 更直接的 batch verification
- 更清晰的编码和验证接口
接口变化体现在 malleability、batch verification 和 multisignature composition,而底层曲线保持不变。
BIP-340 的 nonce 与 domain separation
BIP-340 对 nonce 生成和 domain separation 另有完整约束。
尤其要注意两点:
- 它使用 synthetic nonce design,没有照搬 RFC 6979
- 它把 challenge 和消息处理放进更明确的 tagged hash / domain separation 语境
ECDSA 与 BIP-340 不能共用一套含糊的 nonce、编码或消息预处理规则。
BIP-340 的字节级验证边界
BIP-340 public key 是 32-byte x-only encoding。key generation 若得到奇数 \(Y(P)\),会使用 \(n-d\) 作为 secret scalar,使对应 public point 采用偶数 \(Y\)。verifier 先要求公钥整数小于 \(p\),再用 lift_x 恢复偶数 \(Y\) 的曲线点;不存在这样的点就拒绝。签名固定为 64 bytes:
verifier 检查 \(r
\[ e=\mathsf{taggedHash}(\texttt{BIP0340/challenge},r\,\|\,pk\,\|\,m)\bmod n. \]
随后重建
\[ R=sG-eP \]并要求 \(R\ne\mathcal O\)、\(Y(R)\) 为偶数且 \(x(R)=r\)。signer 的 aux、nonce 与 challenge 使用不同 tags;把普通 SHA-256 拼接替代 tagged hash 会改变协议。
Bitcoin 与 Ethereum 如何不同地消费 secp256k1
如果只写 ECDSA 和 Schnorr 的公式,这篇文章还不够工程。更关键的是:相同曲线,在 Bitcoin 和 Ethereum 里被消费成了不同的账户层接口。
Bitcoin:曲线不变,接口升级
Bitcoin 的演化可以概括成一句话:底层仍是 secp256k1,但签名接口从历史上的 ECDSA 走向了 BIP-340 Schnorr。
Bitcoin 保留 secp256k1,并通过 BIP-340/Taproot 升级签名接口;curve continuity 与 protocol upgrade 同时成立。
Ethereum:EOA、ECDSA 与 ecrecover
Ethereum 的账户层消费方式不同。对 externally owned accounts 来说,执行层长期围绕 ECDSA/secp256k1 组织,并通过 ecrecover 暴露公钥恢复相关接口。Yellow Paper 在相关附录里把 ECDSARECOVER 作为执行环境的一部分来定义。6
Ethereum 对 secp256k1 的消费集中于账户恢复和 ECDSA transaction signatures;主流程尚未采用 BIP-340 Schnorr。
所以“Bitcoin 和 Ethereum 都使用 secp256k1”这句话虽然没错,但工程上太粗。更准确的说法是:
- Bitcoin:同一曲线下,主签名接口在升级
- Ethereum:同一曲线下,账户恢复和 ECDSA 接口仍是主路径
曲线相同,protocol consumption 不同。
工程实现对接
下面把差异落到实现边界。
libsecp256k1 的工程哲学
账户层的高频风险位于标量算术、常数时间、nonce、编码和 API 边界。
libsecp256k1 的长期价值,在于它把账户层容易出错的部分当作一等问题来处理:
- 常数时间标量运算
- 明确的 signing / verification / key handling 边界
- 对 malformed inputs 和边界条件的严格处理
- 为上层协议保留稳定接口,隔离底层细节
账户系统应优先复用经过审计的 secp256k1 implementation,并缩小自定义标量代码与业务代码的接触面。
RFC 6979、synthetic nonce 与 side-channel
这一节最容易被写糊。更准确的区分是:
- ECDSA 工程上常依赖 RFC 6979 把 nonce 生成收紧
- BIP-340 Schnorr 有自己的 synthetic nonce 处理语境
二者都在回答同一个工程问题:nonce 不应成为可恢复私钥的脆弱入口。但它们的具体处理方式并不相同,不能把“deterministic nonce”当成一个无差别总标签。
此外,即使 nonce 方案设计正确,side-channel 和 fault injection 仍然可能在真实设备上打开额外泄漏面。这也是为什么账户层实现一定要把 constant-time 和 signing environment isolation 当作一等公民。
审计时该检查什么
如果要把这一篇压成审计清单,我会优先看下面几项:
- nonce 生成是否和协议规范一致
- 是否存在重复签名状态、弱随机数或跨上下文复用
- 是否有 low-s normalization 和签名编码约束
- 是否错误混用了 ECDSA 与 Schnorr 的消息处理和 domain separation
- 是否直接暴露了不必要的底层 secp256k1 接口给业务代码
对账户层来说,这些问题比“曲线参数有没有抄错”更现实。
总结
secp256k1 是 Web3 账户层长期消费的签名曲线。
在这条曲线上:
- ECDSA 是历史主力接口
- Schnorr 是同一曲线上的协议接口升级
- Bitcoin 和 Ethereum 使用了相同曲线,但并没有消费成同一种签名协议形态
账户层先绑定普通离散对数群接口;曲线稳定后,签名机制、nonce、编码、实现质量和执行环境接口决定实际安全边界。
下一篇再沿这条线往下走,就会自然进入安全性分析:一旦 nonce、side-channel 或 trace 泄漏进入系统,ECDSA 为什么会落到 HNP、EC-HNP 与 EHNP 这类问题。
-
SEC Group, SEC 2: Recommended Elliptic Curve Domain Parameters, Version 2.0. https://www.secg.org/sec2-v2.pdf ↩︎ ↩︎
-
Pieter Wuille et al., BIP-340: Schnorr Signatures for secp256k1. https://bips.xyz/0340 ↩︎ ↩︎ ↩︎
-
Thomas Pornin, RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). https://www.rfc-editor.org/rfc/rfc6979.html ↩︎ ↩︎
-
bitcoin-core/secp256k1repository. https://github.com/bitcoin-core/secp256k1 ↩︎ -
EIP-2: Homestead Hard-fork Changes, https://eips.ethereum.org/EIPS/eip-2. ↩︎
-
Gavin Wood, Ethereum: A Secure Decentralised Generalised Transaction Ledger (Yellow Paper). https://ethereum.github.io/yellowpaper/paper.pdf ↩︎