The P-256 cipher suite: ECVRF-P256-SHA256-TAI and ECDSA/P-256 - #34
Draft
OR13 wants to merge 1 commit into
Draft
Conversation
… and it is the only one that exercises VRF.Np != 80
Implements `KT_128_SHA256_P256`: ECVRF-P256-SHA256-TAI (§11.7) and ECDSA/P-256 over SHA-256
(§11.3, §11.4). Both agree with katie on the first run. The coverage table loses its only
out-of-scope row: 35 verified against the peer, 2 implemented without an oracle, 3 not implemented.
The VRF is the substantial half, and it has two oracles that are independent of each other. RFC
9381 Appendix B.1's three vectors are in the unit tests and settle every choice the module makes —
the suite string, the three domain separators, the big-endian integers, the SEC1 0x02 tag in
encode_to_curve, the absence of truncation in proof_to_hash. Getting any one wrong fails all three.
katie's own proofs are the second, through vrf-p256.json, and they pin what RFC 9381 says nothing
about: that alpha_string is the encoded VrfInput.
Verification only, and the module says so rather than stubbing a prover. A VRF proof is produced by
a log and consumed by everyone else, and RFC 9381 §5.4.2.1 derives P-256's nonce with RFC 6979,
which is a signing concern a verifier has no use for. What a client must be able to do — take an
81-byte proof from a BinaryLadderStep and recover the search key it commits to — is there in full,
and the report row says "proving is a log's operation and is not" rather than implying otherwise.
`vrf.rs` becomes `vrf/{mod,edwards25519,p256}.rs`, mirroring both RFC 9381's structure and the
peer's `crypto/vrf/{edwards25519,p256}`. Error and Output are shared — a search key is 32 bytes
either way, since §17.1 truncates edwards25519's 64-byte beta_string and takes P-256's whole 32 —
and everything else differs: curve, hash, integer byte order, encoded sizes. The keys and proofs are
deliberately not unified behind an enum: a Configuration fixes the suite for the whole log, so a
caller knows which it holds, and a sum type would only move that knowledge to run time and invite
reading an 81-byte proof as an 80-byte one.
Signatures were the easy half but have their own trap, now documented: the two suites differ in how
the *key* is encoded and §11.2 does not say. The peer emits an Ed25519 key as 32 raw bytes and a
P-256 signature key uncompressed at 65, while the same Configuration carries a P-256 VRF key
compressed at 33. Rather than fix a length, the P-256 path accepts whatever SEC1 admits, and
tree-head-p256.json pins what the peer sends.
Two new vector files, generated by parameterising the existing generators rather than copying them,
so a difference between the suites' files is attributable to the suite and nothing else.
tree-head-p256.json cannot be reproducible, because Go's crypto/ecdsa draws a nonce per signature —
and here that is a feature: CI regenerates and re-checks, so every run verifies signatures nobody
has seen, and a verifier that happened to work for one nonce would fail. Added to the exclusion list
with that reasoning attached.
What P-256 does *not* buy, recorded so the table is not read as more than it is: both suites use
SHA-256 and identical commitment fixed bytes, so commitment.json, log-tree.json, prefix-tree.json,
log-append.json, prefix-mutation.json and the pure-math families would be byte-identical under it.
The new coverage is the VRF, the signatures, the suite code in every Configuration, and VRF.Np = 81
— which the response decoders take as context and have only ever run at 80. Threading 81 end to end
through a live SearchResponse is the remaining piece, tracked in #27.
7071 checks over 817 cases in 21 suites, 0 disagreements. 225 reverse-direction artifacts, 0
disagreements. Coverage floor holds at 97.67% lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #26 — the first two commits are that PR's; review only
eacdc23. Closes the last row a peer could verify and wasn't.Implements
KT_128_SHA256_P256: ECVRF-P256-SHA256-TAI (§11.7) and ECDSA/P-256 over SHA-256 (§11.3, §11.4). Both agree with katie on the first run. The coverage table loses its only out of scope row: 35 verified against the peer · 2 implemented without an oracle · 3 not implemented.The VRF, and its two independent oracles
RFC 9381 Appendix B.1's three vectors are in the unit tests. They settle every choice the module makes — suite string, all three domain separators, the big-endian integers, the SEC1
0x02tag inencode_to_curve, the absence of truncation inproof_to_hash. Get any one wrong and all three fail. katie's own proofs are the second oracle, viavrf-p256.json, and they pin what RFC 9381 says nothing about: thatalpha_stringis the encodedVrfInput.Also tested: all 81 single-byte mutations of a valid proof, cross-message and cross-key rejection (using the RFC's own two-messages-one-key vectors, which is sharper than random tampering since both proofs are individually valid), and
s >= qrejected rather than reduced.Verification only, and the module says so rather than stubbing a prover. A VRF proof is produced by a log and consumed by everyone else, and RFC 9381 §5.4.2.1 derives P-256's nonce with RFC 6979 — a signing concern a verifier has no use for. What a client must do, take an 81-byte proof from a
BinaryLadderStepand recover the search key, is there in full; the report row reads "proving is a log's operation and is not".Structure
vrf.rs→vrf/{mod,edwards25519,p256}.rs, mirroring RFC 9381 and the peer'scrypto/vrf/{edwards25519,p256}.ErrorandOutputare shared (a search key is 32 bytes either way — §17.1 truncates edwards25519's 64-bytebeta_stringand takes P-256's whole 32); curve, hash, integer byte order and every encoded length differ.Keys and proofs are deliberately not unified behind an enum: a
Configurationfixes the suite for the whole log, so a caller knows which it holds. A sum type would move that to run time and invite reading an 81-byte proof as an 80-byte one.The signature trap, now documented
The two suites differ in how the key is encoded and §11.2 doesn't say. The peer emits an Ed25519 key as 32 raw bytes and a P-256 signature key uncompressed (65), while the same
Configurationcarries a P-256 VRF key compressed (33). Rather than fix a length, the P-256 path accepts whatever SEC1 admits;tree-head-p256.jsonpins what the peer sends.Vectors
Two new files, made by parameterising the existing generators rather than copying them, so any difference between the suites' files is attributable to the suite.
tree-head-p256.jsoncan't be reproducible — Go'scrypto/ecdsadraws a nonce per signature — and here that's a feature: CI regenerates and re-checks, so every run verifies signatures nobody has seen, and a verifier that worked for only one nonce would fail.What P-256 does not buy
Recorded so the table isn't read as more than it is: both suites use SHA-256 and identical commitment fixed bytes, so
commitment.json,log-tree.json,prefix-tree.json,log-append.json,prefix-mutation.jsonand the pure-math families would be byte-identical under it. The new coverage is the VRF, the signatures, the suite code in everyConfiguration, andVRF.Np = 81. Threading 81 end-to-end through a liveSearchResponseis the remaining piece, tracked in #27.7071 checks over 817 cases in 21 suites · 225 reverse-direction artifacts · 0 disagreements · coverage 97.67% lines.
🤖 Generated with Claude Code