BoundedAuthorityProtocol.Es256 (Bounded Authority Protocol v1.0.0)

Copy Markdown View Source

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

acceptable_public_key?(arg1)

(since 0.9.0)
@spec acceptable_public_key?(binary()) :: boolean()

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.

canonical_signature?(arg1)

(since 0.9.0)
@spec canonical_signature?(binary()) :: boolean()

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.

der_to_raw(arg1)

(since 0.9.0)
@spec der_to_raw(binary()) :: {:ok, binary()} | {:error, :invalid}

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}.

normalize_low_s(arg1)

(since 0.9.0)
@spec normalize_low_s(binary()) :: {:ok, binary()} | {: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.

verify_strict(message, signature, public_key)

(since 0.9.0)
@spec verify_strict(binary(), binary(), binary()) :: boolean()

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.