std/signature/p256
std/signature/src/p256.trb
ECDSA over P-256 with SHA-256 (FIPS 186-5 section 6.4.2, the curve of SP 800-186 section 3.2.1.3): what JOSE and
WebAuthn call ES256. Only verification is here - P256PublicKey verifies, P256Signature is r and s - so nothing
it computes is secret, and it takes variable time.
A point is computed in projective coordinates (X : Y : Z) with the complete formulas of Renes, Costello and Batina
("Complete addition formulas for prime order elliptic curves", 2016, algorithms 4 and 6 for a = -3): they add any two
points - the point at infinity and a point to itself included - without a case, which leaves no special case for a
crafted key or signature to reach. The numbers are those of ./montgomery.
type P256PublicKey
type P256PublicKey with Show, Equals, Hash, TryFrom<String, SignatureError>
A P-256 public key: the point Q of the curve that verifies, read from the uncompressed encoding of SEC 1 section
2.3.3 - the byte 4, then x and y as 32 bytes each, big endian - or from the two coordinates (a JSON Web Key's and a
COSE key's x and y). Made only by factories that check that both coordinates are below p and that the point is
on the curve, so a key is always a point of the group: the cofactor of P-256 is 1, and every point of the curve
other than infinity, which has no encoding, is in the group of the base point.
Examples
const text = "0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6"
+ "7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299"
const key = P256PublicKey.tryFrom text
print key.isOk()
Pitfalls
- The compressed encoding of SEC 1 (33 bytes, starting with 2 or 3) is not read: it fails with
SignatureError.WrongLength. WebAuthn and JSON Web Keys give both coordinates.
fn fromBytes
static fn fromBytes(bytes: Bytes): Result<Self, SignatureError>
The key the 65 bytes of the uncompressed encoding stand for: 4, x, y.
Errors
SignatureError.WrongLengthfor other than 65 bytes, andSignatureError.NotAPointfor a first byte that is not 4, a coordinate that is not below p, or a point that is not on the curve.
fn fromCoordinates
static fn fromCoordinates(x: Bytes, y: Bytes): Result<Self, SignatureError>
The key of the two coordinates, 32 bytes each, big endian: how a JSON Web Key (RFC 7518 section 6.2.1) and a COSE key (RFC 9053 section 7.1.1) write it.
Errors
SignatureError.WrongLengthfor a coordinate of other than 32 bytes, andSignatureError.NotAPointas forP256PublicKey.fromBytes.
fn tryFrom
static fn tryFrom(value: String): Result<Self, SignatureError>
The key 130 hexadecimal digits of either case write: the uncompressed encoding.
Errors
SignatureError.NotHexadecimalorSignatureError.WrongLengthfor a text that is not 130 hexadecimal digits, andSignatureError.NotAPointas forP256PublicKey.fromBytes.
fn bytes
fn bytes(): Bytes
The 65 bytes of the uncompressed encoding: 4, x, y.
fn hex
fn hex(): String
The 65 bytes as 130 lowercase hexadecimal digits.
fn show
fn show(): String
The same as P256PublicKey.hex.
fn equals
fn equals(other: Self): Bool
Whether the two keys are the same 65 bytes.
fn hash
fn hash(): Int
The hash of the 65 bytes, so that a key is a key of a Map or a member of a Set by its bytes alone.
fn verifies
fn verifies(message: Bytes, signature: P256Signature): Bool
Whether signature is this key's ECDSA signature of message with SHA-256 (FIPS 186-5 section 6.4.2): with e the
SHA-256 of the message, w the inverse of s modulo n, u1 = e w and u2 = r w, whether the point u1 G + u2 Q is not
infinity and has an x that is r modulo n. The x is compared without a division: X / Z is r where X is r Z, or
r + n where that is below p.
It takes variable time - everything it reads is public - and about 6,000 multiplications modulo p.
Pitfalls
- A signature (r, s) verifies, and so does (r, n - s): ECDSA signatures are malleable, as FIPS 186-5, WebAuthn and OpenSSL accept both. A protocol that tells signatures apart by their bytes - a replay cache, a transaction id - needs its own rule, such as only ever storing the message.
extend String with From<P256PublicKey>
extend String with From<P256PublicKey>
A public key as its 130 lowercase hexadecimal digits: the way back of P256PublicKey.tryFrom.
fn from
static fn from(value: P256PublicKey): Self
type P256Signature
type P256Signature with Show, TryFrom<String, SignatureError>
A P-256 ECDSA signature: the two scalars r and s, each from 1 to n - 1. As bytes it is r and then s, 32 bytes each,
big endian - the form of IEEE P1363 that JOSE's ES256 uses (RFC 7518 section 3.4) - and as text the 128 lowercase
hexadecimal digits of those bytes; P256Signature.fromDer reads the DER that WebAuthn, X.509 and OpenSSL use.
Examples
const text = "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716"
+ "f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8"
print P256Signature.tryFrom(text).isOk()
fn fromBytes
static fn fromBytes(bytes: Bytes): Result<Self, SignatureError>
The signature 64 bytes are: r, then s.
Errors
SignatureError.WrongLengthfor other than 64 bytes, andSignatureError.ScalarOutOfRangefor an r or an s that is 0 or not below n.
fn fromDer
static fn fromDer(bytes: Bytes): Result<Self, SignatureError>
The signature its DER encoding is (RFC 3279 section 2.2.3): a SEQUENCE of the two INTEGERs r and s. Only the one strict encoding is read - lengths in their short form, each integer positive and in its fewest bytes, and nothing after the sequence - so that a signature has one encoding and a lenient reading leaves nothing to vary.
Errors
SignatureError.NotDerfor bytes that are not that encoding, andSignatureError.ScalarOutOfRangefor an r or an s that is 0 or not below n.
fn tryFrom
static fn tryFrom(value: String): Result<Self, SignatureError>
The signature 128 hexadecimal digits of either case write: r, then s.
Errors
SignatureError.NotHexadecimalorSignatureError.WrongLengthfor a text that is not 128 hexadecimal digits, andSignatureError.ScalarOutOfRangeas forP256Signature.fromBytes.
fn bytes
fn bytes(): Bytes
The 64 bytes: r, then s.
fn hex
fn hex(): String
The 64 bytes as 128 lowercase hexadecimal digits.
fn show
fn show(): String
The same as P256Signature.hex.
extend String with From<P256Signature>
extend String with From<P256Signature>
A signature as its 128 lowercase hexadecimal digits: the way back of P256Signature.tryFrom.
fn from
static fn from(value: P256Signature): Self