Module crrl::p256

source ·
Expand description

NIST P-256 curve implementation.

This module implements generic group operations on the NIST P-256 elliptic curve, a short Weierstraß curve with equation y^2 = x^3 - 3*x + b for a given constant b. This curve is standardized in FIPS 186-4 as well as in other standards such as SEC 2 or ANSI X9:62. It is also known under the names “secp256r1” and “prime256v1”.

The curve has prime order. “Scalars” are integers modulo that prime order, and are implemented by the Scalar structure. This structure supports the usual arithmetic operators (+, -, *, /, and the compound assignments +=, -=, *= and /=).

A point on the curve is represented by the Point structure. The additive arithmetic operators can be applied on Point instances (+, -, +=, -=); multiplications by an integer (u64 type) or by a scalar (Scalar type) are also supported with the * and *= operators. Point doublings can be performed with the double() function (which is somewhat faster than general addition), and additional optimizations are obtained in the context of multiple successive doublings by calling the xdouble() function. All these operations are implemented with fully constant-time code and are complete, i.e. they work with all points, even when adding a point with itself or when operations involve the curve point-at-infinity (the neutral element for the curve as a group).

Scalars can be encoded over 32 bytes, using unsigned little-endian convention) and decoded back. Encoding is always canonical, and decoding always verifies that the value is indeed in the canonical range. Take care that many standards related to P-256 tend to use big-endian for encoding scalars (and often use a variable-length encoding, e.g. an ASN.1 INTEGER).

Points can be encoded in compressed (33 bytes) or uncompressed (65 bytes) formats. These formats internally use big-endian. The nominal encoding of the point-at-infinity is a single byte of value 0x00; the encode_compressed() and encode_uncompressed() functions cannot produce that specific encoding (since they produce fixed-length outputs), and instead yield a sequence of 33 or 65 zeros in that case. Point decoding accepts compressed and uncompressed formats, and also the one-byte encoding of the point-at-infinity, but they do not accept a sequence of 33 or 65 zeros as a valid input. Thus, point decoding is stricly standards-conforming. All decoding operations enforce canonicality of encoding, and verify that the point is indeed on the curve.

The PrivateKey structure represents a private key for the ECDSA signature algorithm; it is basically a wrapper around a private scalar value. The PrivateKey::encode() and PrivateKey::decode() functions encode a private key to exactly 32 bytes, and decode it back, this time using unsigned big-endian, as per SEC 1 encoding rules (which represents private keys with the ASN.1 OCTET STRING type). The PrivateKey::from_seed() allows generating a private key from a source seed, which is presumed to have been obtained from a cryptographically secure random source.

The PublicKey structure represents a public key for the ECDSA signature algorithm; it is a wrapper around a Point. It has its own decode(), encode_compressed() and encode_uncompressed() which only wrap around the corresponding Point functions, except that decode() explicitly rejects the point-at-infinity: an ECDSA public key is never the identity point.

ECDSA signatures are generated with PrivateKey::sign_hash(), and verified with PublicKey::verify_hash(). The signature process is deterministic, using the SHA-256 function, following the description in RFC 6979. The caller is provides the pre-hashed message (normally, this hashing uses SHA-256, but the functions accept hash values of any length). In this implementation, the ECDSA signatures follow the non-ASN.1 format: the two r and s halves of the signature are encoded in unsigned big-endian format and concatenated, in that order. When generating a signature, exactly 32 bytes are used for each of r and s, so the signature has length 64 bytes exactly. When verifying a signature, any input size is accepted provided that it is even (so that it is unambiguous where r stops and s starts), and that the two r and s values are still in the proper range (i.e. lower than the curve order).

Truncated Signatures

The PublicKey::verify_trunc_hash() function supports truncated signatures: a 64-byte signature is provided, but the last few bits are considered to have been reused for encoding other data, and thus are ignored. The truncation requires that the original signature is first processed through PrivateKey::prepare_truncate(); this utility function does not use the private key itself, but it modifies the encoding format of the s part of the signature so that truncation removes the high-order bits of the value, instead of the low-order bits.

The verification function then tries to recompute the complete, original, untruncated signature. This process is safe since neither truncation nor reconstruction involve usage of the private key, and the original signature is obtained as an outcome of the process. Up to 32 bits (i.e. four whole bytes) can be rebuilt by this implementation, which corresponds to shrinking the signature encoding size from 64 down to 60 bytes.

Signature reconstruction cost increases with the number of ignored bits (asymptotically, cost doubles for every 2 removed bits, so 32-bit truncation sboud be about 16 times more expensive than 24-bit truncation); when 32 bits are ignored, the verification cost is about 300 to 450 times the cost of verifying an untruncated signature.

Truncated signature verification on curve P-256 requires dynamic memory allocation; it is not available if this library is compiled without the std or alloc feature (default compilation uses std and thus std::vec::Vec; without std but with alloc, alloc::vec::Vec is used).

Structs

  • A point on the short Weierstraß curve P-256.
  • A P-256 private key simply wraps around a scalar.
  • A P-256 public key simply wraps around a curve point.

Type Definitions

  • Integers modulo the curve order n (a 256-bit prime).