ES256 (ECDSA P-256 with SHA-256) signature canonicality, public-key acceptance, strict
verification, and producer utilities for the BAP3-ES256-SHA256 suite.
This module is the single owner of the ES256 scalar and point rules for every ES256 surface of the protocol. It is pure: no clock, randomness, process state, or I/O. It accepts public keys only; no function here takes, holds, or produces private key material. No function raises.
Wire form
A signature is the RFC 7518 §3.4 raw form r || s: exactly 64 bytes, two fixed-width 32-byte
unsigned big-endian integers. A public key is the 65-byte uncompressed SEC1 point
0x04 || x || y.
Canonical signature
canonical_signature?/1 is the suite's signature rule (spec/bap-v3.md § 3.2):
0 < r < n and 0 < s <= floor(n / 2), n the P-256 group order. Every verification surface
applies it before the backend, and every producer surface (compact assembly, anchored-export
encoding) refuses a signature that fails it (erratum 3).
Producer utilities
Normalization is the signer's duty. normalize_low_s/1 maps a high-S signature (r, s) to
its low-S twin (r, n - s); der_to_raw/1 converts a strict DER ECDSA-Sig-Value (the form
most HSM, KMS, and secret-store signers return) to the raw wire form. A producer runs
der_to_raw/1, then normalize_low_s/1, before compact assembly. The protocol's own producer
surfaces never normalize: they refuse.
A true result proves only that the bytes satisfy the rule. It never selects trusted keys or
grants authority.
Summary
Functions
Returns whether a 65-byte uncompressed SEC1 public key 0x04 || x || y may enter a
verification input: both coordinates are field elements (< p) and the point is on the
curve. Any other input returns false.
Returns whether a raw 64-byte signature r || s is canonical: 0 < r < n and
0 < s <= floor(n / 2). Any other input, including non-binary or wrong-size input, returns
false.
Converts a strict DER ECDSA-Sig-Value (SEQUENCE { INTEGER r, INTEGER s }) to the raw
64-byte r || s form.
Maps a raw 64-byte signature to its low-S form: (r, n - s) when s > floor(n / 2), and the
same bytes otherwise. Idempotent.
Verifies an ES256 signature strictly: the key must be acceptable, the signature canonical,
and then the ECDSA equation must hold (checked by the :crypto backend over a minimal DER
encoding of r and s). Any other input returns false; the function never raises.
Functions
Returns whether a 65-byte uncompressed SEC1 public key 0x04 || x || y may enter a
verification input: both coordinates are field elements (< p) and the point is on the
curve. Any other input returns false.
Returns whether a raw 64-byte signature r || s is canonical: 0 < r < n and
0 < s <= floor(n / 2). Any other input, including non-binary or wrong-size input, returns
false.
Converts a strict DER ECDSA-Sig-Value (SEQUENCE { INTEGER r, INTEGER s }) to the raw
64-byte r || s form.
The encoding must be exact DER: single-byte lengths that match the content exactly (a valid
signature's content is at most 70 bytes, so a long-form length never matches), minimal
non-negative INTEGER encodings (a leading 0x00 only when the next byte has its high bit
set), and no trailing bytes. Both scalars must lie in 0 < r < n and 0 < s < n. The output
is not low-S normalized; pass it to normalize_low_s/1. Any other input returns
{:error, :invalid}.
Maps a raw 64-byte signature to its low-S form: (r, n - s) when s > floor(n / 2), and the
same bytes otherwise. Idempotent.
Returns {:error, :invalid} unless 0 < r < n and 0 < s < n. This is a producer utility
for the signer; the protocol's producer surfaces never apply it.
Verifies an ES256 signature strictly: the key must be acceptable, the signature canonical,
and then the ECDSA equation must hold (checked by the :crypto backend over a minimal DER
encoding of r and s). Any other input returns false; the function never raises.
The backend runs only on a binary message, a canonical signature, and an on-curve key with
coordinates below p (P-256 has prime order, so every such point is a valid public key):
the inputs on which a backend raises never reach it.