Skip to content
ecdsa.com

P-256 vs secp256k1: two 256-bit curves, two ecosystems

The NIST curve runs the web, passkeys and secure hardware; the Koblitz curve runs Bitcoin, Ethereum and Nostr. Same size, same strength, different rules around them.

P-256 vs secp256k1 is rarely a free choice: the curve usually comes with the ecosystem. Both are 256-bit prime-order Weierstrass curves with about 128-bit security and identical key and signature sizes, but they differ in origin, in which platforms support them and in the conventions layered on top, and moving data between the two worlds is where bugs happen.

At a glance

P-256 (secp256r1, prime256v1)secp256k1
Specified inNIST SP 800-186 §3.2.1.3, SEC 2 §2.4.2SEC 2 §2.4.1; allowed by SP 800-186 App. H.2 for blockchain use
Equationy² = x³ − 3x + b over p = 2²⁵⁶ − 2²²⁴ + 2¹⁹² + 2⁹⁶ − 1y² = x³ + 7 over p = 2²⁵⁶ − 2³² − 977
Parameter originb derived from an unexplained SHA-1 seedFixed, minimal coefficients (Koblitz-style)
Cofactor, security1, about 128 bits1, about 128 bits
Keys and signatures32-byte private key, 33/65-byte public key, 64-byte raw signatureSame
OID1.2.840.10045.3.1.71.3.132.0.10
JOSE / COSEES256 / −7ES256K / −47 (RFC 8812)
Node.js 26 (OpenSSL 3.6.5), sign / verify per secondabout 68,800 / 27,200about 6,500 / 7,200
@noble/curves 2.3.0, sign / verify per secondabout 3,800 / 870about 3,800 / 1,180
WebCrypto, TLS 1.3, WebAuthnYesNo
Apple Secure Enclave, PIV cards, most HSMsYesRarely (AWS KMS offers ECC_SECG_P256K1)
Typical usersTLS certificates, passkeys, JWTs, device identityBitcoin, Ethereum, Nostr, most EVM chains

Measured on one core of an Apple M4 Max.

Origins and trust

P-256 was published by NIST in 1999 and standardized in FIPS 186-2 (2000), and by SECG as secp256r1. Its prime has a special shape that enables fast reduction, and its coefficient b was generated from a seed through SHA-1, as the "verifiably random" procedure of the time prescribed. The seed's origin was never explained, which after the Dual_EC_DRBG revelations led some designers to distrust NIST curves. No attack on P-256 based on that seed has ever been found, and it remains the most scrutinized curve in deployment.

secp256k1 comes from SEC 2 (2000). With a = 0 and b = 7 there is nothing to hide, and its special structure provides an efficiently computable endomorphism (the GLV method) that speeds up scalar multiplication. Satoshi Nakamoto chose it for Bitcoin in 2009, an unusual choice at the time, and Ethereum followed. NIST SP 800-186 (2023) explicitly allows secp256k1 "for blockchain-related applications".

Both curves have prime order, so there are no cofactor subtleties, and both fall equally to Shor's algorithm.

Platform and library support

This is the real difference. P-256 is built into everything that manages keys for you: WebCrypto in every browser, the TLS stacks of every operating system, Apple's Secure Enclave and CryptoKit, Android Keystore, TPM 2.0, PIV smart cards, and every cloud KMS. Passkeys use it by default, and the CA/Browser Forum Baseline Requirements allow it for publicly trusted certificates.

secp256k1 is absent from WebCrypto and from TLS 1.3 (RFC 8422 had already deprecated its TLS 1.2 codepoint), and general-purpose libraries treat it as an afterthought. Blockchain software therefore relies on dedicated implementations: Bitcoin Core's constant-time libsecp256k1, and @noble/curves, Rust's k256 and Go's go-ethereum/crypto elsewhere. Hardware wallets support it; enterprise HSMs vary, and AWS KMS offers it as the ECC_SECG_P256K1 key spec for signing only.

Performance

With equal sizes, speed depends on implementation quality rather than the curve. OpenSSL has hand-tuned assembly for P-256 and generic code for secp256k1, so Node.js 26.10 (OpenSSL 3.6.5) signs about ten times faster with P-256: about 68,800 against 6,500 signatures per second on one Apple M4 Max core. In pure JavaScript the picture flips slightly: @noble/curves 2.3.0 signs at the same rate on both curves and verifies secp256k1 about a third faster, thanks to the endomorphism. libsecp256k1 is faster still. The lesson: benchmark the library you will ship, not the curve.

Conventions that differ

The same ECDSA math travels with different rules in each world.

  • Hashing. P-256 pairs with SHA-256 (ES256, ecdsa-with-SHA256). Bitcoin signs double SHA-256 digests; Ethereum signs Keccak-256 digests, which are not SHA3-256.
  • Encoding. X.509 and TLS use DER; JOSE and WebCrypto use raw r‖s; Ethereum appends a recovery byte v for 65 bytes total, and Bitcoin's signed messages put a header byte in front.
  • Public key recovery. Ethereum never sends public keys: recovery (ecrecover) computes the key from the signature and the address is compared. P-256 protocols transmit or certify the key instead.
  • Low-S. Every ECDSA signature (r, s) has a twin (r, n − s). Bitcoin relay policy and Ethereum transaction rules (EIP-2) accept only the low-S form; generic P-256 verifiers accept both.
  • Schnorr. Bitcoin's Taproot added 64-byte BIP-340 Schnorr signatures with 32-byte x-only keys on secp256k1, also used by Nostr. There is no standard Schnorr scheme on P-256.

The malleability is easy to demonstrate with Node.js and its OpenSSL-backed secp256k1 support:

Node.js 26: both (r, s) and (r, n − s) verify on 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));
Output
(r, s) and (r, n - s) both verify: true
exactly one of them is low-S: true

OpenSSL neither produces nor enforces low-S, so signatures made this way must be normalized before Bitcoin or Ethereum will accept them. The Ethereum signature verifier flags high-S values.

When to choose which

  • Web, TLS, passkeys, JWTs, mobile and device keys: P-256, because the platforms offer nothing else in hardware.
  • Bitcoin, Ethereum and EVM chains, Nostr: secp256k1, with the chain's hashing, encoding, recovery and low-S rules.
  • Bridging the two (for example a smart-contract wallet controlled by a passkey): keep each curve in its own lane. Ethereum's Fusaka upgrade (December 2025) added EIP-7951, a P-256 verification precompile at address 0x100 costing 6,900 gas, and many rollups offer the compatible RIP-7212 precompile, so contracts can verify passkey signatures directly.
  • A new protocol with free choice: neither; consider Ed25519 for software keys or P-256 for hardware-backed keys.

Migration notes

Keys cannot be converted between the curves; changing curves means new keys and, in blockchains, new addresses.

  1. Do not reuse key material across curves. A 32-byte secret is a valid private key on both curves, but the resulting public keys are unrelated; treat that as a footgun, not a feature.
  2. When moving secp256k1 signing into an HSM or KMS, confirm the key type exists, expect DER output, convert to raw r‖s, normalize to low-S and compute the recovery id yourself by trying both parities.
  3. When verifying P-256 signatures in an EVM contract, use a verified library or a precompile where the chain offers one; never hand-roll modular arithmetic in Solidity.
  4. In JOSE, switching from ES256K to ES256 changes alg, crv and the key; publish both keys with distinct kid values during the transition.

References

All comparisons