SPEC states the rule after its status line and describes the manifest's wai member as the envelope's wire version; its title, the four extension headers, the README and two code comments no longer call the standard itself v1.0. Text that names an envelope by its wire version is unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
30 KiB
WAI Extension: Hybrid Signatures (Ed25519 with ML-DSA-65)
Status: Draft. Sub-spec of WAI. Gives WAI's signed records a second, post-quantum signature beside their Ed25519 one, so a record stays verifiable while either algorithm holds: one suite, ML-DSA-65 (FIPS 204) beside Ed25519 (RFC 8032), combined as the composite
id-MLDSA65-Ed25519-SHA512ofdraft-ietf-lamps-pq-composite-sigs-19. It covers the key logs of jwp-receipts §7 (an entry's post-quantum key, a hybrid root, an anchor that requires hybrid), group receipts (the third version of the labelled payload of jwp-receipts §2.3), claims (jwp-receipts §2.4.3), and every JSON record an extension signs aswai:<domain>\x01 ‖ JCS(record)(§3.4), through one shared check. It adds a verifier policy that refuses a record signed classically only, and a confirmation step that refuses a classical-only record signed by a key its origin lists as hybrid. Nothing changes for a publisher that signs classically: its records keep their bytes and verify as before. No new transport. The reference implementation is thepq_sigmodule ofwai-rs(featurepq_signatures), with the readers ofjwp_delivery,claim_receipt,key_logandsynthesis; the corpus ispq-conformance/, and its second implementation ispq-conformance/pq_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision, each pinned to the version named: FIPS 204, Module-Lattice-Based Digital Signature Standard (August 2024), whose PDF's SHA-256 as fetched on 22 October 2026 was
57239b9f84c03227eda3ca0991204dc7764c79af9ce2e6824eda774918d46b6b;draft-ietf-lamps-pq-composite-sigs-19(the revision of 20 October 2026), whose text's SHA-256 as fetched that day wasb4ee04416efc26e7d8de7c740dc3848ffe8b8cc9dcd035ad9d4e33e18844ec83; RFC 8032; and the ACVP test vectors for FIPS 204's ML-DSA at the upstream commit975de31eb83d87039ec88934fdc47d8c312b892d(§10.1). A section number of one of those texts in this extension is that version's.
1. Scope
Every signature WAI defines is Ed25519 (SPEC §7 Signatures). A quantum computer large enough to compute discrete logarithms on Ed25519's curve recovers a private key from its public key, and its holder then signs anything under it: on the day such a computer exists, a record signed with Ed25519 alone no longer shows who signed it. Media provenance and delivery receipts are archival — a receipt signed today is asked to prove something years later — so the signature they carry has to outlast that day.
A hybrid signature is two signatures over the same bytes, one classical and one post-quantum, and a record verifies only if both do. While Ed25519 holds, the record is as strong as it was; once it does not, the record stands on the post-quantum half, provided that half's key is bound to the signer by something a forger cannot rewrite either (§6.1, §7).
In scope:
- the suite (§2) and the members that carry its post-quantum half (§3);
- signing and verifying, and why the classical half signs what it signs (§4);
- the verifier policy that refuses a classical-only record (§5);
- where hybrid signatures go: key logs, group receipts, claims, and the records of the
wai:<domain>\x01 ‖ JCSpattern (§6); - sizes and costs, measured (§9); the corpus and the vendored vectors (§10).
Not in scope, in this revision:
- the binary records of the live family (live events, rooms, takes and others), OER/2 receipts, the receipts of the quantum family, and §7.9's module grants and key statuses: each keeps Ed25519 alone until its own extension gives it a hybrid form. A key an origin's log lists in a hybrid entry signs none of them (§6.1, step 4a);
- hash functions: BLAKE3-256 and SHA-256 are unchanged. Generic quantum search lowers a 256-bit hash's preimage resistance to about 2^128 operations, and nothing here depends on less;
- confidentiality: key agreement for encrypted rooms is confidentiality's;
- a transport.
2. The suite
One suite:
| member | value |
|---|---|
pq_suite |
id-MLDSA65-Ed25519-SHA512 (draft-ietf-lamps-pq-composite-sigs-19 §6, OID 1.3.6.1.5.5.7.6.48) |
| post-quantum half | ML-DSA-65 (FIPS 204 §4): a 1952-byte public key (pkEncode, Algorithm 22), a 3309-byte signature (sigEncode, Algorithm 26) |
| classical half | Ed25519 (RFC 8032), the key a record's signer_pubkey already carries, verified by SPEC §7 Signatures |
| label | COMPSIG-MLDSA65-Ed25519-SHA512, in ASCII (draft §6) |
| prefix | CompositeAlgorithmSignatures2025, in ASCII (draft §2.2) |
| pre-hash | SHA-512 (FIPS 180-4) |
Why ML-DSA-65. The post-quantum half is the half that must still hold when the classical one does not, so its parameters are chosen for the life of the record, not for the life of the key. FIPS 204 §4 claims security category 3 for ML-DSA-65 and category 2 for ML-DSA-44; for records asked to prove something for decades, the margin of category 3 against advances in the cryptanalysis of module lattices is worth the 889 bytes per signature and 640 bytes per key it costs (FIPS 204 §4, Table 2; §9 measures what that is per record). ML-DSA-87 (category 5) adds 1318 bytes per signature more. The draft's §10.4 recommends this composite where the signature must be strongly unforgeable, and WAI's link hashes cover signature bytes (jwp-receipts §3 step 3, §2.4.3), so a second valid signature of the same bytes would be a second receipt (§7).
Why the composite. The draft's construction is one WAI can adopt as it stands: it prefixes
and pre-hashes the message once for both halves, binds both to the suite by the label, and has a
published analysis of what it gives when one half fails (draft §9.2). A WAI hybrid record carries
the composite's two component signatures in two members, sig and pq_sig; pq_sig ‖ sig is
the draft's composite signature value and pq_key ‖ signer_pubkey its composite public key
(draft §4), so a composite verifier can check a WAI record given the bytes it signs. A simpler
form — two signatures over the same domain-separated bytes — would also bind both halves where
the record's own members include the post-quantum key, as every JSON record's do (§3.2); the
composite adds that the classical half is over bytes no classical record of any family signs
(§4.3), which holds for the binary group receipt too, whose members are not a JSON object.
Keys. A hybrid key is an Ed25519 key and an ML-DSA-65 key generated apart, each from its own
32-byte seed (RFC 8032 §5.1.5; FIPS 204 Algorithm 6, ML-DSA.KeyGen_internal(ξ)), and used only
together (draft §3.1, §9.3): its Ed25519 key never signs an item with a classical signature only,
and its ML-DSA key never signs outside this suite. §6.1 makes the first of these checkable.
3. The hybrid members
3.1 In a JSON record
A hybrid JSON record carries three members beside its signer_pubkey and sig:
| member | type | meaning |
|---|---|---|
pq_suite |
string | The suite (§2): id-MLDSA65-Ed25519-SHA512. |
pq_key |
1952 B hex | The signer's ML-DSA-65 public key. In a key-log revision, whose signer is its root, the member is root_pq_key (§6.1). |
pq_sig |
3309 B hex | The ML-DSA-65 signature (§4). |
Byte strings are lowercase hex, as every byte string of jwp-receipts §2.4.3. A record carries all three or none: with none it is classical and is read and verified as its family defines; with all three it is hybrid. A reader checks them in this order, and refuses the record at its family's member step (§6) with the reason given:
- one or two of the three present:
pq_malformed; pq_suitenot a string:pq_malformed; a string naming no suite of §2:pq_suite_unknown;pq_key, thenpq_sig, not lowercase hex of exactly the suite's length:pq_malformed(FIPS 204 §3.6.2: a key or signature of another length is refused, never read).
A reader built without this extension does not know the members, and refuses a hybrid record as
its family refuses any member it does not define (malformed, unknown_member,
key_log_malformed, record_malformed), so a hybrid record is never read as a classical one.
3.2 What is signed
The message M of a hybrid record is the bytes its family's rule signs, computed with
pq_suite and the key member among the record's members and with sig and pq_sig left out:
for a claim, "moq-jwp:claim\x01" ‖ JCS(claim without "sig" and "pq_sig"); for a key-log
revision, "wai:key-set\x01" ‖ JCS(revision without "sig" and "pq_sig"); for a record of the
pattern of §3.4, domain ‖ JCS(record without "sig" and "pq_sig"). JCS is
jwp-receipts §2.4.3's canonical form. A record's link hash and identity are
unchanged in definition: they are over the whole record, both signatures and all three hybrid
members included.
3.3 In a binary record
The group receipt's binary form carries the post-quantum key inside its signing payload and the
post-quantum signature after sig, under the payload's third version, whose signing domain names
the suite (§6.2). It has no pq_suite member: moq-jwp:group-sign\x03 is this suite, and a
later suite takes a later version.
3.4 Records of the pattern wai:<domain>\x01 ‖ JCS
Many extensions sign their JSON records the same way: a record carries signer_pubkey and sig,
and sig is Ed25519 over domain ‖ JCS(record without "sig"), domain being the extension's
own wai:<name>\x01 (synthesis §2 is one). Any such record MAY carry the
three members of §3.1 beside sig, and then is checked by one shared check, the same for
every such extension:
- the hybrid members, as §3.1 reads them;
signer_pubkeyandsigpresent as 32 and 64 bytes of lowercase hex, or the record is the extension's own malformed record;- §4.2 over
M = domain ‖ JCS(record without "sig" and "pq_sig"), under the verifier's policy (§5): a key that fails is the extension's own bad-key reason, an Ed25519 half that fails its own bad-signature reason, and otherwise this extension's reasons (§8).
The extension's own member tables then apply to the record without its hybrid members, and its own checks to what it states. A classical record's message is the bytes its extension signs today, so a classical record reads exactly as before. An extension's verifier implements this section by calling the shared check where it checks a signature; the reference does so for synthesis records. A verifier of an extension that does not implement it refuses a hybrid record as malformed (§3.1).
4. Signing and verifying
4.1 Signing
Given M (§3.2), the signer computes the composite's message representative with an empty
application context (draft §2.2, §3.2 step 2):
M' = "CompositeAlgorithmSignatures2025" ‖ "COMPSIG-MLDSA65-Ed25519-SHA512" ‖ 0x00 ‖ SHA-512(M)
sig = Ed25519(ed_private_key, M') RFC 8032, 64 bytes
pq_sig = ML-DSA.Sign(ml_dsa_private_key, M', ctx = "COMPSIG-MLDSA65-Ed25519-SHA512")
FIPS 204 Algorithm 2, 3309 bytes
The ML-DSA half takes the label as its context string (draft §3.2 step 4), so FIPS 204's own domain separation (Algorithm 2 line 10) binds it to the suite. A signer MAY sign in FIPS 204's hedged or deterministic variant (§3.4); a verifier cannot tell them apart. The corpus signs deterministically, so it regenerates byte for byte; a signer on a platform exposed to fault or side-channel attacks SHOULD sign hedged (FIPS 204 §3.4, §3.6.1).
4.2 Verifying
A verifier checks an item's signature in this order and reports the first that fails:
signer_pubkeyis an Ed25519 key SPEC §7 Signatures steps 1 and 2 accept: otherwise the family's bad-key reason (bad_signer_key,bad_key,key_log_signature_invalid);- under the policy that requires hybrid (§5), an item with no post-quantum half:
hybrid_required, before its signature is examined; - the Ed25519 half under SPEC §7 Signatures steps 3 to 5, over
M'for a hybrid item and overMfor a classical one: otherwise the family's signature reason (signature_verify_failed,bad_signature,key_log_signature_invalid); - the post-quantum half: the key and the signature of the suite's lengths (
pq_malformed), andML-DSA.Verify(pq_key, M', pq_sig, ctx = label)(FIPS 204 Algorithm 3) true:pq_signature_invalidotherwise, a signature whose hint does not decode or whose response is out of range (Algorithm 27, Algorithm 8 line 13) among them.
The classical half is verified by WAI's strict rule, which refuses some signatures RFC 8032's permissive reading accepts and no honest signer makes; the composite's verification (draft §3.3) requires each component to verify under its own specification, which this is.
4.3 What stripping does
The Ed25519 half of a hybrid record signs M', never M. Remove the post-quantum half from a
hybrid record, and its sig verifies over no classical record's bytes: M' begins with the
composite's prefix and holds a digest, not a family's domain and members (draft §9.2.3, weak
non-separability). Remove only the members of §3.1 from a JSON record, and its M changes too,
since pq_suite and the key member are signed. Either way the stripped record is refused at its
signature step; neither way can a party that cannot forge Ed25519 turn a hybrid record into a
classical one.
A party that can forge Ed25519 does not need to strip anything: it makes a classical record outright. Against it, what holds is policy, not construction: §5 for a relying party that requires hybrid of every record, and §6.1 for any record signed by a key its origin lists as hybrid.
5. The policy
A verifier that implements this extension MUST offer the policy require hybrid: an item
with a classical signature only is refused (hybrid_required) at its family's signature step,
after its key is checked and before its signature is examined. A relying party that relies on
records for longer than it trusts Ed25519 SHOULD apply it to them. Without it, a verifier
accepts classical and hybrid items alike, and verifies both halves of a hybrid one.
The policy is the relying party's, and is not carried in any record. Its counterpart in a key log is an anchor that requires hybrid (§6.1).
6. Where hybrid signatures go
6.1 Key logs (jwp-receipts §7)
A hybrid entry. A key entry of jwp-receipts §7.2 MAY carry pq_suite and pq_key
(§3.1), together: the entry is then hybrid, and the origin states that every item its key
signs is hybrid with that ML-DSA key. Rule 1 adds that no two entries share a pq_key, none is
the revision's root_pq_key, and no entry's pair is the one next_root commits to (below).
Rule 6 adds that an entry's form never changes: an entry listed hybrid keeps its pq_suite and
pq_key, and one listed classical never gains them (key_log_rewrites_history). An origin that
moves a key to hybrid lists a successor, as for any change of authority (jwp-receipts §7.6).
A hybrid root. A revision MAY carry pq_suite, root_pq_key and pq_sig (§3.1): its
root is then hybrid, and both halves sign it, §4 over the M of §3.2. Rule 3 requires both to
verify (key_log_signature_invalid). A hybrid root's ML-DSA key is part of the root: rule 5's
"its root and next_root are its parent's" holds only if root_pq_key is the parent's too,
present or absent alike.
The hybrid commitment. next_root commits to a classical root as jwp-receipts §7.2 defines,
or to a hybrid one as
commitment_hybrid(K, P) = BLAKE3("wai:key-set-next\x02" ‖ K ‖ P)
over its Ed25519 key K and its ML-DSA key P. Rule 1 adds that next_root is not the
commitment to the revision's own root in either form. A rotation (rule 5) is a child whose root
is not its parent's and whose commitment, in the child's form, is the parent's next_root: a root
becomes hybrid only by a rotation its parent committed to in the hybrid form, and a hybrid root's
non-rotating children are signed by both its halves.
A classical root is no stronger than Ed25519: whoever can forge it signs revisions that add keys, hybrid ones among them, and a hybrid entry under such a root binds its ML-DSA key to the origin only as firmly as Ed25519 does. Hence:
An anchor that requires hybrid. A relying party that pins an origin (jwp-receipts §7.3) MAY record that it requires hybrid of it. Then:
- only a hybrid-signed revision descends from the anchor: at §7.4 step 3, a revision without the hybrid members is no child of anything, as a revision signed by a key its parent does not authorise is not. The pinned revision itself may be classical: it is trusted by provisioning, and its hybrid successor arrives by a rotation it committed to;
- the anchor moves only by pinning: a retrieval that holds it leaves it where it is, and the head walk follows its hybrid-signed descendants;
- an item with a classical signature only is unconfirmed (
hybrid_required) at step 4a.
Confirmation step 4a. After jwp-receipts §7.4 step 4 has found the entry and matched its key, in order:
- the entry is hybrid and the item carries a classical signature only: refused,
hybrid_downgrade. The log contradicts the item's claim to speak for the key: the origin states the key signs nothing classically, so the item is a forgery or the key was misused; - the item is hybrid and its ML-DSA key is not the entry's, or the entry is classical:
refused,
pq_key_mismatch; - the anchor requires hybrid and the item carries a classical signature only: unconfirmed,
hybrid_required. Nothing contradicts the item; the verifier lacks what its anchor requires.
A key status and a module grant (jwp-receipts §7.9) have no hybrid form in this revision: a status is signed by the root's Ed25519 key alone, and a grant by a key that §6.1 refuses if its entry is hybrid. An origin lists classical keys for grants until §7.9 gives them a hybrid form; §7.9's freshness bound limits how long a status matters.
6.2 Group receipts (jwp-receipts §2.3, §2.4.1, §2.7)
The payload's third version. A hybrid group is signed over
signing_payload_v3 = "moq-jwp:group-sign\x03"
|| the labelled payload's fields after its domain, in its order // §2.3
|| pq_key[1952] // the publisher's ML-DSA-65 key
by both halves (§4, M = the payload). A hybrid group is a labelled group: its class, coverage
and parent link are the labelled payload's, under every rule of jwp-receipts §2.4.1. Its JSON form
is the labelled group's members with sign_version 3 and two more, pq_key and pq_sig.
Read at jwp-receipts §3 step 1: sign_version 3 with neither of them is a version jwp-receipts
does not define (group_label_invalid); with one of them, or either of them in a group whose
sign_version is not 3, or either not lowercase hex of the suite's length, pq_malformed.
Verified at step 2 by §4.2.
Canonical bytes and link hash. canonical_bytes(G) is the payload, then sig, then
pq_sig, so a hybrid group's link hash (jwp-receipts §3 step 3) covers both signatures. The
binary form (jwp-receipts §2.7.1) of a hybrid group is its canonical bytes, read in that order:
the fields of the labelled form, the 1952-byte key, the 64-byte sig, the 3309-byte pq_sig. A
reader of jwp-receipts §2.7.4 that predates this extension finds a signing domain that is
neither of §2.3's and refuses the record (malformed).
Objects. Object receipts are unchanged: they are bound to a hybrid group through its root as to any other (§2.2, §3 step 5), so the post-quantum cost is paid once per group, not per object.
Confirmation. A hybrid group is an item whose ML-DSA key is its pq_key (step 4a above).
6.3 Claims (jwp-receipts §2.4.3, §3.1)
A claim MAY carry the three members of §3.1; a claim has no other optional members. At §3.1
step 1, after the subject type is admitted, a verifier reads them as §3.1 says (pq_malformed,
pq_suite_unknown), and the envelope's member check is of the claim without them. At step 3 it
verifies by §4.2: bad_signer_key, hybrid_required, signature_verify_failed,
pq_signature_invalid. The claim's link_hash is over the whole claim. A claim chain (§2.4.3) is
per signer_pubkey, hybrid or not.
6.4 Records of the pattern
As §3.4 says. The reference wires the shared check into synthesis's records; the
other extensions that sign as wai:<domain>\x01 ‖ JCS adopt it by the same call.
7. Security considerations
- What a hybrid signature gives. Under the composite, a classical adversary cannot forge a
record if either half is unforgeable, and a quantum adversary cannot if ML-DSA is (draft
§9.2.1). It does not say who holds the ML-DSA key. A forger who can forge Ed25519 generates an
ML-DSA key of its own and signs a hybrid record with it; only the key log tells the two apart
(§6.1, step 4a
pq_key_mismatch), and only a key log whose revisions from the anchor on are hybrid-signed tells them apart once Ed25519 is forged. A verifier that must check a record years from now keeps the revisions it confirmed it against (jwp-receipts §7.3) and pins an anchor that requires hybrid. - Records signed before. A record signed classically stays classical. Nothing here makes an old record hybrid, and once Ed25519 can be forged, a classical record proves nothing about its signer. An origin that wants its history to outlast that day registers its receipts with a transparency service while Ed25519 still holds (transparency §3), and re-signs what it still vouches for.
- Strong unforgeability and link hashes. WAI names receipts by link hashes over their signature bytes. Ed25519 under SPEC §7's strict rule has one signature per key and message; ML-DSA is designed to be strongly unforgeable (FIPS 204 §3.1), so no one but the signer can make a second valid ML-DSA half. A signer that signs hedged can make two hybrid records of the same members with different link hashes; that is the signer's own fork, which jwp-receipts §3 step 3 and §3.1 step 5 refuse as for any other.
- Key reuse. A hybrid key's Ed25519 half signing a classical item is the stripping attack draft §9.3 warns of; step 4a refuses it for every item confirmed under the key's origin. A key status (§6.1) is the one classical use left to a hybrid root, under its own signing domain.
- The implementation. The reference's ML-DSA implementation is a maintained, permissively licensed (Apache-2.0 OR MIT) pure-Rust one that has had no independent audit. Before anything is built on it, it is held to FIPS 204's published ACVP vectors and the composite to the draft's own vector (§10.1), and the corpus's second implementation verifies ML-DSA from FIPS 204's text.
- Side channels. Verification handles no secret. Signing does; see §4.1 on the hedged variant.
8. Reasons
The codes this extension adds. A family reports them at the step §6 names, beside its own codes,
which keep their meaning: a hybrid item whose Ed25519 key or half fails is refused with the
family's own reason; a key-log revision that breaks §6.1's additions to rule 1 is
key_log_malformed, to rule 3 key_log_signature_invalid, to rule 5
key_log_root_not_committed, to rule 6 key_log_rewrites_history.
| code | where | raised when |
|---|---|---|
pq_malformed |
the family's member step | one or two of the hybrid members present; pq_suite not a string; a key or signature not lowercase hex of the suite's length; a group's hybrid members outside its third version, or one of them missing in it |
pq_suite_unknown |
the family's member step | pq_suite names no suite of §2 |
pq_signature_invalid |
the family's signature step | ML-DSA.Verify over M' with the label as context is false (§4.2 step 4) |
hybrid_required |
the family's signature step; confirmation step 4a | under the policy of §5, an item with a classical signature only; under an anchor that requires hybrid, the same item at confirmation (unconfirmed) |
hybrid_downgrade |
confirmation step 4a | the entry is hybrid and the item carries a classical signature only (refused) |
pq_key_mismatch |
confirmation step 4a | the item's ML-DSA key is not the entry's, or the entry is classical (refused) |
9. Sizes and costs
Sizes are the corpus's own items (pq-conformance/vectors.json, sizes), the same on every
machine. Times are wai_pq_vectors bench, built with --release, on the reference's build host:
a 32-core arm64 workstation that other jobs shared during the runs (its load average between 15
and 24). Each time is the median of seven rounds over 64 distinct 512-byte messages, about a
claim's signing input — ML-DSA's signing loop repeats a number of times that depends on the
message (FIPS 204 §4, Table 1: 5.1 on average for ML-DSA-65), so one message would time one draw
of it. The range is that median across three runs.
| operation | time per operation |
|---|---|
| Ed25519 sign | 10.6–11.5 µs |
| Ed25519 verify, SPEC §7 strict | 34.0–34.9 µs |
| ML-DSA-65 sign, deterministic | 349–378 µs |
| ML-DSA-65 verify | 75–88 µs |
| hybrid sign, both halves | 323–366 µs |
| hybrid verify, both halves | 114–128 µs |
A hybrid record costs about thirty times an Ed25519 signature to sign and between three and four times to verify: some eight thousand hybrid verifications a second on one core.
| item | classical | hybrid | difference |
|---|---|---|---|
| group receipt, binary (jwp-receipts §2.7.1) | 291 B | 5552 B | +5261 B: the key, 1952, and the signature, 3309 |
a group's receipts with 3 objects (WJR1, §2.7.3) |
722 B | 5983 B | +5261 B |
| object receipt, binary | 141 B | 141 B | none |
| claim, JSON | 579 B | 11164 B | +10585 B: the key and signature in hex, and pq_suite |
| first key-log revision, JSON: classical root and one classical entry; hybrid root, one hybrid and one classical entry | 684 B | 15443 B | +14759 B: the root's key and signature and the hybrid entry's key, in hex, and one entry more |
| origin claim of synthesis, JSON | 557 B | 11142 B | +10585 B |
A receipts track (moq-streaming-format §4.1) carries one group's receipts per media group. A hybrid publisher's track grows by 5261 bytes a group, whatever the group holds: at one group a second, by 42 kbit/s. Beside a group of one object (376 bytes classical) that is fifteen times the track; of 30 objects (7409 bytes), 1.71 times; of 60 (16439 bytes), 1.32 times. A room's receipts are its tracks' receipts tracks, each a publisher's: the cost is per publishing track and group, never per subscriber, and a subscriber pays it only for the tracks it subscribes to. The second implementation, in pure Python, verifies an ML-DSA-65 signature in about 4 ms on an arm64 laptop (median of 45, Python 3.15).
10. Conformance
10.1 The vendored vectors
pq-conformance/acvp/ holds FIPS 204's ACVP test vectors for ML-DSA-65, derived by extract.py
from the internal projections at the upstream commit named above, each upstream file and each
derived file pinned by SHA-256 (pq-conformance/README.md): key generation (25 vectors), the
deterministic signature generation of the external interface with a context, of the internal
interface, and of the internal interface over an external μ (15 each), and signature verification
over the same three interfaces (15 each, four of every five modified). The reference reproduces
every key and every signature byte for byte and gives every verification its verdict; the
second implementation verifies every signature both files state. draft-composite-sigs-19.json
is the draft's Appendix E vector for this suite, which both verify, with no context and with the
draft's.
10.2 The corpus
pq-conformance/vectors.json, written by wai_pq_vectors gen from seeds that are one byte
repeated 32 times (jwp-receipts §7.8: every key in it is published and attests nothing). Each case
is a group receipt (JSON or a group's receipts in the binary form, its objects verified by
inclusion), a claim, a record of §3.4's pattern, or a key log anchored by pinning; a group or claim
may carry a confirmation against a pinned log, with or without the hybrid requirement. The negative
controls include: the post-quantum half stripped, made over other bytes, and with a bit flipped;
a classical item under the policy of §5, under a hybrid entry, and under an anchor that requires
hybrid; a classical revision under such an anchor; a hybrid entry later listed without its key; a
child signed by a hybrid root's Ed25519 key alone and with another ML-DSA key; another parameter
set, named (id-MLDSA44-Ed25519-SHA512, validly signed) and by length. Every code of §8 is given.
A conforming verifier gives every case its stated verdict. wai_pq_vectors verify regenerates
the file byte for byte and checks every verdict; pq_verify.py, written from this text and
sharing no code with the reference, checks the vendored vectors, every case, the sizes of §9 and
this section's table of codes, and then requires every mutated case to be refused and every one
of its own checks, dropped, to be caught by the corpus.
11. References
- FIPS 204, Module-Lattice-Based Digital Signature Standard, August 2024.
- FIPS 180-4, Secure Hash Standard.
draft-ietf-lamps-pq-composite-sigs-19, Composite ML-DSA for use in X.509 Public Key Infrastructure.- RFC 8032, Edwards-Curve Digital Signature Algorithm (EdDSA).
- RFC 8785, JSON Canonicalization Scheme (JCS).
- jwp-receipts, SPEC §7 Signatures.