RustCrypto 项目把 ECDSA 拆分为通用的 ecdsa crate 和每条曲线各一个 crate——p256、p384、p521、用于 secp256k1 的 k256 以及 Brainpool 系列 crate——它们都实现了 signature crate 中的 trait(Signer、Verifier、PrehashSigner)。这些 crate 是纯 Rust 实现,没有 C 依赖,因此通常是 WebAssembly 和嵌入式目标的首选。本次检查使用的是 ecdsa 0.17.0,以及 0.14.0 版的 p256、p384 和 k256。
签名是确定性的:对 RFC 6979 测试消息的 P-256 签名逐字节复现了 RFC 中的向量。Signature 类型是 64 字节的原始 r‖s 形式;DER 需要显式调用 to_der() 和 from_der(),解析了错误的形式时,会在任何验证发生之前返回错误。Signer trait 用曲线的默认哈希算法(P-256 为 SHA-256)对消息计算哈希;PrehashSigner::sign_prehash 接受摘要,对消息的 SHA-256 摘要给出了完全相同的签名。
low-S 的处理方式因曲线而异,同一套代码同时处理两类曲线时,这就是陷阱所在。NIST 曲线的 crate 保留计算得到的 s——64 个 P-256 签名中有 29 个是 high-S——其验证器对处于高、低两个半区的 s 都接受。k256 签名时会对 s 做规范化,其验证器拒绝了有效签名经镜像得到的 high-S 形式,这与比特币的规则一致。因此,由 OpenSSL、Java 或云 KMS 生成的 secp256k1 签名大约有一半会在 k256 中验证失败;应先对它调用 normalize_s(),示例中对 P-256 签名演示了这一做法。
密钥可以从 SPKI 和 PKCS#8(PEM 和 DER,需启用 pem 和 pkcs8 feature)、两种形式的 SEC1 点、SEC1 私钥和原始标量加载。Ed25519 位于单独的 ed25519-dalek crate 中,这里也没有后量子算法;在 Rust 中需要经过 FIPS 验证的原语或后量子原语时,可参阅 aws-lc-rs。
由于签名是确定性的,测试中签名发生变化在这里是真正的回归信号。可以用测试向量和 DER ⇄ 原始格式转换器与其他库对比。