# WAI Extension: Hybrid Signatures (Ed25519 with ML-DSA-65) > Status: Draft. Sub-spec of WAI v1.0. 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-SHA512` of `draft-ietf-lamps-pq-composite-sigs-19`. It covers the key > logs of [jwp-receipts](jwp-receipts.md) §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 as > `wai:\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 the `pq_sig` module of `wai-rs` (feature > `pq_signatures`), with the readers of `jwp_delivery`, `claim_receipt`, `key_log` and > `synthesis`; the corpus is [`pq-conformance/`](../pq-conformance/), and its second > implementation is `pq-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 was `b4ee04416efc26e7d8de7c740dc3848ffe8b8cc9dcd035ad9d4e33e18844ec83`; > RFC 8032; and the ACVP test vectors for FIPS 204's ML-DSA at the upstream commit > `975de31eb83d87039ec88934fdc47d8c312b892d` (§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:\x01 ‖ JCS` pattern (§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](confidentiality.md)'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](jwp-receipts.md) §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: 1. one or two of the three present: `pq_malformed`; 2. `pq_suite` not a string: `pq_malformed`; a string naming no suite of §2: `pq_suite_unknown`; 3. `pq_key`, then `pq_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](jwp-receipts.md) §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:\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:\x01` ([synthesis](synthesis.md) §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: 1. the hybrid members, as §3.1 reads them; 2. `signer_pubkey` and `sig` present as 32 and 64 bytes of lowercase hex, or the record is the extension's own malformed record; 3. §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](synthesis.md) 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: 1. `signer_pubkey` is 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`); 2. under the policy that requires hybrid (§5), an item with no post-quantum half: `hybrid_required`, before its signature is examined; 3. the Ed25519 half under SPEC §7 *Signatures* steps 3 to 5, over `M'` for a hybrid item and over `M` for a classical one: otherwise the family's signature reason (`signature_verify_failed`, `bad_signature`, `key_log_signature_invalid`); 4. the post-quantum half: the key and the signature of the suite's lengths (`pq_malformed`), and `ML-DSA.Verify(pq_key, M', pq_sig, ctx = label)` (FIPS 204 Algorithm 3) true: `pq_signature_invalid` otherwise, 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](jwp-receipts.md) §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](jwp-receipts.md) §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](jwp-receipts.md) §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](synthesis.md)'s records; the other extensions that sign as `wai:\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](transparency.md) §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](moq-streaming-format.md) §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](jwp-receipts.md), SPEC §7 *Signatures*.