cheatah
Module

p256

Benchmarks
Every measured table for p256 — with its stamp (commit, host, harness) and the commands that reproduce it — is on the p256 benchmarks page.

NIST P-256 (secp256r1) elliptic curve — ECDSA signature verification and deterministic (RFC 6979) signing, plus the DER/SPKI parsing needed to use it with TLS certificates and JWTs. From scratch, no external libraries.

This is the curve the rest of the world's TLS certificates and OAuth/JWT (ES256) are signed with. Adding it lets cheatah's tls client verify a real server's ecdsa_secp256r1_sha256 certificate (most of the public web), and lets applications sign an ES256 JWT.

import io
import hashlib
import p256

let privkey = hashlib.from_hex("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721")
let pubkey_xy = p256.public_from_private(privkey)
let h = hashlib.sha256_digest("the message")
let sig = p256.sign_raw(privkey, h)        # 64-byte r||s (RFC 6979, deterministic)
io.print(p256.verify_raw(pubkey_xy, h, sig))   # True

What's inside

  • Field/scalar arithmetic in Montgomery form over both P-256 moduli (the field prime p and the group order n); the Montgomery constants are derived from the modulus at startup, so there are no hand-transcribed magic numbers.

  • Points in Jacobian coordinates (a = -3).

  • Verify uses Strauss-Shamir (one doubling chain for u1·G + u2·Q).

  • Sign uses an RFC 6979 deterministic nonce (HMAC-SHA256 — no entropy source needed, never repeats a nonce) and a fixed-base comb for k·G.

  • Parsing: DER SEQUENCE{r,s} (the TLS/X.509 form), raw r||s (the JWT ES256 form), and the uncompressed EC point out of a certificate's SPKI.

Byte conventions: scalars/coordinates are 32 big-endian bytes; a public key point is the 64 bytes X||Y; a raw signature is the 64 bytes r||s.

Correctness & speed

Verified against the RFC 6979 Appendix A.2.5 P-256/SHA-256 test vector — the deterministic signature matches bit-for-bit, and verification round-trips it (stdlib/tests/p256_test.cpp). Micro-benchmarks (tests/benchmarks/p256_bench.cpp, release build, one core) are on the p256 benchmarks page — verification runs once per P-256 link in the certificate chain plus once for CertificateVerify — negligible next to a network round trip; signing is the per-message JWT path.

Security notes

ECDSA verification handles public data and stays branchy. Signing uses a deterministic nonce (RFC 6979), which removes the "repeated/biased `k`" failure mode, and multiplies the secret scalar with branch-free point ops and masked table selection. The limb arithmetic underneath is branch-free too: each modular add, subtract and multiply computes its conditional reduction unconditionally and selects the result with a mask, and the multi-limb compare is one full-width subtract rather than a scan that stops at the first differing limb — the scan's running time revealed how much of the private key matched the group order.

The differential in CheatahP256.ConstantTimeFieldOpsMatchReference checks that arithmetic against a plainly written reference, because the point-op self-check runs both of its sides through the same field operations and so cannot see an error in them.

Functions

fn bool verify_raw(const std::string &pubkey_xy, const std::string &msg_hash, const std::string &sig_raw) source#

Verify an ECDSA/P-256 signature given the raw 64-byte r||s form (the JWT ES256 layout).

Parameters
pubkey_xy

the public key (64 bytes X||Y).

msg_hash

the digest (truncated to 256 bits if longer).

sig_raw

the signature as 64 bytes r||s.

Returns

true iff valid.

Complexity

O(1) — two scalar multiplications, computed as one Strauss-Shamir double chain.

Allocation

none.

fn bool verify_der(const std::string &pubkey_xy, const std::string &msg_hash, const std::string &sig_der) source#

Verify an ECDSA/P-256 signature.

Parameters
pubkey_xy

the public key as 64 bytes (X||Y), big-endian.

msg_hash

the message digest (e.g. 32 bytes of SHA-256); if longer than 32 bytes it is truncated to the leftmost 256 bits (FIPS 186-4).

sig_der

the signature, DER-encoded SEQUENCE{INTEGER r, INTEGER s}.

Returns

true iff the signature is valid for pubkey_xy over msg_hash.

Complexity

O(1) — two scalar multiplications, computed as one Strauss-Shamir double chain.

Allocation

a temporary raw r||s signature string.

fn std::string rs_to_der(const std::string &sig_raw) source#

Encode a raw 64-byte r||s signature as the DER SEQUENCE{INTEGER r, INTEGER s} that TLS CertificateVerify and X.509 carry — the inverse of the parse inside verify_der, with minimal-form integers (stripped leading zeros, 0x00 sign byte when the top bit is set).

Composes with sign_raw to produce the wire form a TLS 1.3 server sends for ecdsa_secp256r1_sha256.

Parameters
sig_raw

the 64-byte r||s signature.

Returns

the DER bytes, or "" if sig_raw has the wrong length or a zero integer.

Complexity

O(1).

Allocation

the returned string plus the two integer temporaries.

fn std::string sign_raw(const std::string &privkey, const std::string &msg_hash) source#

Deterministic ECDSA/P-256 signing (RFC 6979 nonce, HMAC-SHA256).

Returns the raw 64-byte r||s signature — the form an ES256 JWT carries.

Parameters
privkey

the private scalar d as 32 big-endian bytes.

msg_hash

the digest to sign (truncated to 256 bits if longer).

Returns

the signature as 64 bytes r||s, or "" if privkey is not a valid scalar (wrong length, zero, or >= the group order n), or if all 64 RFC 6979 nonce candidates fail.

Complexity

O(1) — one fixed-base scalar multiplication per RFC 6979 candidate (almost always one).

Allocation

the returned signature plus RFC 6979 HMAC scratch strings.

fn std::string public_from_private(const std::string &privkey) source#

Derive the public key point from a private scalar: Q = d·G.

Parameters
privkey

the private scalar d as 32 big-endian bytes.

Returns

the public key as 64 bytes (X||Y), or "" if d is out of range.

Complexity

O(1) — one fixed-base scalar multiplication.

Allocation

the returned point.

fn std::string spki_ec_point(std::string_view spki_or_cert_der) source#

Extract the P-256 public key point from a certificate / SubjectPublicKeyInfo DER: finds the uncompressed EC point BIT STRING (03 42 00 04 X Y, the 66-byte length only a P-256 point has) and returns its 64 bytes (X||Y); the AlgorithmIdentifier OIDs are not inspected.

Returns "" if no such point is present.

Parameters
spki_or_cert_der

the certificate or SPKI DER bytes.

Returns

the 64-byte X||Y point, or "" if not a P-256 EC key.

Complexity

O(der length).

Allocation

the returned point.