elliptic 基于 bn.js,用纯 JavaScript 实现了 NIST 曲线和 secp256k1 上的 ECDSA,以及 Ed25519。它通过 crypto-browserify、较早的以太坊和比特币库,以及 Webpack 4 注入浏览器 bundle 的 Node.js polyfill 广泛传播,所以至今仍会出现在锁文件中。ethers v6 和 viem 则改为基于 @noble/curves 构建,它覆盖相同的曲线,默认设置更严格。
它的 API 有三处与大多数库不同,容易引发互操作问题。sign() 接受的是摘要而不是消息,并把收到的任何字节都当作摘要——本次检查对原始消息签名,得到的签名与对其 SHA-256 签名的结果不同。它返回带有 r、s 和 recoveryParam 的 Signature 对象,因此传输格式完全取决于你如何序列化:对 OpenSSL 风格的对端用 toDER(),要得到原始 r‖s 则把两个值都填充到 32 字节。此外,除非传入 canonical: true,否则 s 不会被规范化,而验证时 high-S 和 low-S 都接受。
nonce 遵循 RFC 6979——精确复现了 P-256 的测试向量——但安全公告 GHSA-848j-6mx2-7j84(CVE-2025-14505)指出,某个中间值带有前导零字节时会被截断,该公告在 6.6.1 中仍未关闭。本次检查复现了这个问题:在 P-521 搭配 SHA-512 时,2,048 个固定输入中有 9 个生成的 nonce 与 RFC 6979 不同;而 secp256k1 搭配 SHA-256、P-256 搭配 SHA-512 时,各自的 4,096 个输入中都没有出现差异。这些签名仍能验证通过,但如果用另一个 RFC 6979 实现以同一密钥对同一消息签名,得到的 nonce 之间存在关联,公告指出这可能导致密钥泄露。在 P-521 上应避免使用 elliptic;关联 nonce 为何危险,参见 nonce 重用检测工具。
更早的安全公告已在 6.5.7 到 6.6.1 中修复,其中包括接受 BER 编码签名的问题,以及对字符串而不是哈希签名时可提取密钥的缺陷;在本次检查中,6.6.1 拒绝了带有多余前导零的 DER 签名。密钥是十六进制字符串或字节数组——SEC1 点(压缩或未压缩)和原始标量——不支持 PEM、SPKI 或 JWK。
示例先计算哈希,再请求规范的 low-S 签名,并展示两种序列化形式。如果 elliptic 生成的签名在别处验证失败,可以在 DER ⇄ 原始格式转换器中比较它的两种编码。