You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RFC 6979 conformance
--------------------
ama_nistp_ecdsa_sign normalised `s` to the low representative
unconditionally, so it failed RFC 6979's own Appendix A.2.5/A.2.6/A.2.7
vectors on every case whose natural `s` came out high — roughly half —
while the header advertised "deterministic per RFC 6979". `r` matched
everywhere, so the nonce derivation was correct and the divergence was
invisible to every test that existed.
It was invisible for a second, worse reason: the "independent" reference
in tests/test_nistp_curves.py normalised too, because it was written
alongside the C code rather than from the specification. Two
implementations that share an assumption do not check each other.
Signing now emits RFC 6979's `s` verbatim. All 18 in-scope vectors from
the RFC are vendored under tests/kat/rfc6979/ and replayed, including the
RFC's printed public keys. _ref_sign takes the policy as a parameter
instead of baking one in.
INVARIANT-34 — low-`s` is a property of the sign/verify pair
------------------------------------------------------------
Normalising on the signer while verifying permissively — what shipped —
prevents nothing: the twin of an AMA signature still verifies under AMA.
It only costs conformance. The pair is the control; either half alone is
a costume.
NIST prime curves now default to neither half; secp256k1 keeps both
(INVARIANT-28 unchanged). New ama_nistp_ecdsa_sign_ex / _sign_raw_ex make
every combination of {deterministic, hedged} x {DER, raw} x {RFC s, low s}
reachable through one entry point, with unknown flag bits rejected. The
previous API made hedged+raw raise purely because a fourth function had
not been written.
INVARIANT-35 — a selector must never resolve weaker than it was asked
---------------------------------------------------------------------
INVARIANT-7 covers availability; nothing covered selection until the
library grew nine selectable levels across three families. A selector that
maps an unrecognised value onto a neighbour produces working code, valid
signatures and successful handshakes at a level nobody chose, and never
surfaces.
tests/test_selector_strictness.py derives its selector list from the
modules rather than a literal, drives each with plausible near-misses
including every other family's valid values, and asserts the C side
returns 0/NULL rather than another set's size.
It found one on its first run: ama_nist_curve_t was 0/1/2, so `0` — what
an uninitialised field holds — meant P-256. Renumbered to 256/384/521,
which also no longer collides with the ML-KEM or ML-DSA selector spaces.
Also
----
NativeBackendUnavailableError is now the single type for "native backend
missing", replacing 36 bare RuntimeError raises. PQCUnavailableError is a
subclass, so every existing except-clause is unaffected; asserted directly
rather than assumed.
ctest 56/56, pytest 2854 passed, Wycheproof 4263/0 failures, all gates green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtRczQn3XzfaikLDrxJLZZ
0 commit comments