在 P-256 和 secp256k1 之间,很少能自由选择:曲线通常是随生态系统一起定下来的。两者都是素数阶的 256 位 Weierstrass 曲线,安全强度约为 128 位,密钥和签名的大小完全相同;但它们的来历不同,支持它们的平台不同,叠加在上面的约定也不同,而在这两个世界之间搬运数据时最容易出错。
概览
| P-256(secp256r1、prime256v1) | secp256k1 | |
|---|---|---|
| 规范出处 | NIST SP 800-186 §3.2.1.3、SEC 2 §2.4.2 | SEC 2 §2.4.1;SP 800-186 附录 H.2 允许用于区块链 |
| 方程 | y² = x³ − 3x + b,素域 p = 2²⁵⁶ − 2²²⁴ + 2¹⁹² + 2⁹⁶ − 1 | y² = x³ + 7,素域 p = 2²⁵⁶ − 2³² − 977 |
| 参数来历 | b 由来源未加说明的种子经 SHA-1 派生 | 固定的最小系数(Koblitz 式) |
| 余因子、安全强度 | 1,约 128 位 | 1,约 128 位 |
| 密钥和签名 | 32 字节私钥,33/65 字节公钥,64 字节原始签名 | 相同 |
| OID | 1.2.840.10045.3.1.7 | 1.3.132.0.10 |
| JOSE / COSE | ES256 / −7 | ES256K / −47(RFC 8812) |
| 每秒签名 / 验证次数,Node.js 26(OpenSSL 3.6.5) | 约 68,800 / 27,200 | 约 6,500 / 7,200 |
| 每秒签名 / 验证次数,@noble/curves 2.3.0 | 约 3,800 / 870 | 约 3,800 / 1,180 |
| WebCrypto、TLS 1.3、WebAuthn | 支持 | 不支持 |
| Apple 的 Secure Enclave、PIV 卡、大多数 HSM | 支持 | 很少支持(AWS KMS 提供 ECC_SECG_P256K1) |
| 典型用户 | TLS 证书、通行密钥、JWT、设备身份 | 比特币、以太坊、Nostr、大多数 EVM 链 |
在 Apple M4 Max 的单个核心上测得。
来历与信任
P-256 由 NIST 于1999年发布,并在 FIPS 186-2(2000年)中标准化;SECG 也以 secp256r1 的名称将它标准化。它的素数具有特殊形式,可以快速约简;系数 b 则按照当时“可验证随机”流程的规定,由一个种子经 SHA-1 生成。这个种子的来源从未得到解释;Dual_EC_DRBG 事件曝光后,一些设计者因此不再信任 NIST 曲线。至今没有发现任何基于该种子的 P-256 攻击,它仍是实际部署中受到审视最多的曲线。
secp256k1 出自 SEC 2(2000年)。它的 a = 0、b = 7,参数里没有任何可以隐藏的东西;它的特殊结构还提供了一个可以高效计算的自同态(GLV 方法),能加速标量乘法。中本聪在2009年为比特币选用了它,这在当时并不常见,以太坊随后也采用了它。NIST SP 800-186(2023年)明确允许将 secp256k1 用于“区块链相关应用”。
两条曲线都是素数阶,因此不存在余因子带来的微妙问题;面对 Shor 算法,两者也同样会被攻破。
平台与库的支持情况
这才是真正的区别。所有替你管理密钥的东西都内置了 P-256:每个浏览器中的 WebCrypto、每个操作系统的 TLS 协议栈、Apple 的 Secure Enclave 和 CryptoKit、Android Keystore、TPM 2.0、PIV 智能卡,以及每一种云 KMS。通行密钥默认使用它,CA/Browser Forum 的《基线要求》也允许公开信任的证书使用它。
WebCrypto 和 TLS 1.3 中都没有 secp256k1(RFC 8422 早已弃用它在 TLS 1.2 中的码点),通用库也只是顺带支持它。因此,区块链软件依赖专门的实现:Bitcoin Core 的常量时间库 libsecp256k1,以及其他场合使用的 @noble/curves、Rust 的 k256 和 Go 的 go-ethereum/crypto。硬件钱包支持它;企业级 HSM 的支持情况不一,AWS KMS 则以 ECC_SECG_P256K1 密钥规格提供它,且只能用于签名。
性能
大小相同时,速度取决于实现质量,而不是曲线本身。OpenSSL 为 P-256 准备了手工调优的汇编代码,secp256k1 用的则是通用代码,因此 Node.js 26.10(OpenSSL 3.6.5)用 P-256 签名要快约十倍:在单个 Apple M4 Max 核心上,每秒约 68,800 次签名对 6,500 次。在纯 JavaScript 中,情况略有反转:@noble/curves 2.3.0 在两条曲线上的签名速度相同,而得益于自同态,secp256k1 的验证要快约三分之一。libsecp256k1 还要更快。由此得出的教训是:要对你将实际发布的库做基准测试,而不是对曲线做基准测试。
不同的约定
同样的 ECDSA 数学,在两个世界里配套的是不同的规则。
- 哈希。P-256 与 SHA-256 搭配(
ES256、ecdsa-with-SHA256)。比特币对双重 SHA-256 摘要签名;以太坊对 Keccak-256 摘要签名,而 Keccak-256 并不是 SHA3-256。 - 编码。X.509 和 TLS 使用 DER;JOSE 和 WebCrypto 使用原始的
r‖s;以太坊在末尾追加一个恢复字节v,总长 65 字节;比特币的签名消息则在前面加一个头部字节。 - 公钥恢复。以太坊从不发送公钥:恢复操作(
ecrecover)根据签名计算出公钥,然后比较地址。P-256 协议则直接传输公钥,或者用证书为公钥背书。 - low-S。每个 ECDSA 签名
(r, s)都有一个孪生签名(r, n − s)。比特币的中继策略和以太坊的交易规则(EIP-2)只接受 low-S 形式;通用的 P-256 验证器两种都接受。 - Schnorr。比特币的 Taproot 升级在 secp256k1 上引入了 64 字节的 BIP-340 Schnorr 签名和 32 字节的仅 x 坐标公钥,Nostr 也使用这种签名。P-256 上没有标准化的 Schnorr 方案。
用 Node.js 及其基于 OpenSSL 的 secp256k1 支持,很容易演示这种延展性:
import { generateKeyPairSync, sign, verify } from "node:crypto";
// Group order of secp256k1 (SEC 2 §2.4.1).
const n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "secp256k1" });
const message = Buffer.from("hello");
const raw = sign("sha256", message, { key: privateKey, dsaEncoding: "ieee-p1363" });
const r = raw.subarray(0, 32);
const s = BigInt("0x" + raw.subarray(32).toString("hex"));
const flipped = Buffer.concat([r, Buffer.from((n - s).toString(16).padStart(64, "0"), "hex")]);
const check = (sig) => verify("sha256", message, { key: publicKey, dsaEncoding: "ieee-p1363" }, sig);
console.log("(r, s) and (r, n - s) both verify:", check(raw) && check(flipped));
console.log("exactly one of them is low-S:", (s <= n / 2n) !== (n - s <= n / 2n));
(r, s) and (r, n - s) both verify: true
exactly one of them is low-S: true
OpenSSL 既不生成 low-S 签名,也不强制要求 low-S,因此这样生成的签名必须先规范化,比特币或以太坊才会接受。以太坊签名验证工具会标记 high-S 值。
如何选择
- Web、TLS、通行密钥、JWT、移动端密钥和设备密钥:P-256,因为这些平台在硬件中不提供其他曲线。
- 比特币、以太坊和 EVM 链、Nostr:secp256k1,并遵循相应链的哈希、编码、恢复和 low-S 规则。
- 连接两个世界(例如由通行密钥控制的智能合约钱包):让每条曲线各守其位。以太坊的 Fusaka 升级(2025年12月)加入了 EIP-7951,即位于地址
0x100、消耗 6,900 gas 的 P-256 验证预编译合约;许多 Rollup 也提供兼容的 RIP-7212 预编译合约,因此合约可以直接验证通行密钥签名。 - 可以自由选择的新协议:两者都不是首选;软件密钥可以考虑 Ed25519,由硬件保护的密钥可以考虑 P-256。
迁移说明
密钥无法在两条曲线之间转换;更换曲线意味着要换新密钥,在区块链中还意味着要换新地址。
- 不要在两条曲线之间复用密钥材料。一个 32 字节的秘密值在两条曲线上都是有效的私钥,但由此得到的公钥毫无关联;要把这当作容易出错的陷阱,而不是一项功能。
- 把 secp256k1 签名迁入 HSM 或 KMS 时,先确认存在该密钥类型;输出会是 DER,要转换成原始的
r‖s,规范化为 low-S,并通过尝试两种奇偶性自行算出恢复 ID。 - 在 EVM 合约中验证 P-256 签名时,使用经过验证的库,或者在链提供预编译合约时使用预编译合约;切勿在 Solidity 中手写模运算。
- 在 JOSE 中,从
ES256K切换到ES256会改变alg、crv和密钥本身;过渡期间同时发布两个密钥,并为它们分配不同的kid值。
参考资料
- NIST SP 800-186:椭圆曲线域参数(§3.2.1.3 和附录 H.2)
- SEC 2 v2:推荐的椭圆曲线域参数(§2.4.1、§2.4.2)
- FIPS 186-5:数字签名标准
- RFC 8812:JOSE 和 COSE 中的 ES256K 与 secp256k1
- BIP-340:secp256k1 上的 Schnorr 签名
- BIP-146:LOW_S 和 EIP-2
- AWS KMS 密钥规格参考