diff --git a/wai/extensions/jwp-receipts.md b/wai/extensions/jwp-receipts.md index 0aeac8fc5..4b7c320b4 100644 --- a/wai/extensions/jwp-receipts.md +++ b/wai/extensions/jwp-receipts.md @@ -27,6 +27,14 @@ In scope: parent-receipt linkage for cross-hop preservation. - Verifier behaviour, relay preservation requirements, and conformance test vectors. +- A typed **claim** receipt (§2.4.3) so statements other than delivery — + energy, task fidelity, lawful basis, log inclusion, recovered binding — + ride the same chain without growing a field set per question. +- **Key discovery** (§7): where a signer's key is authoritatively + published, so a party who was never on the delivery path can do more + than check that a signature is internally consistent. +- **Binding recovery** (§8): a second, explicitly weaker binding class + for objects whose bytes changed in normal distribution. Out of scope: @@ -154,6 +162,60 @@ Required fields: | `origin` | string | Datacenter / origin label. | | `merkle_proof` | object | Inclusion proof: `{ "siblings": [32 B hex, …], "direction_bits": u64, "leaf_index": u64 }`. `direction_bits` bit `i` is `1` iff the current node at level `i` is the right child. | +#### 2.4.3. `kind: "claim"` (typed claim receipt) + +`group` and `object` answer one question — *what was delivered, by whom, +at what energy cost*. Three other bodies now issue per-object artifacts +answering different questions over the same objects: a lawful-basis +declaration bound to a media service (ABNT NBR 25608 §8.8), a +transparency-log inclusion proof (RFC 9942 COSE Receipts), and a +recovered-binding assertion (C2PA soft binding, §8). Each is a signed +statement about an object that is not a delivery statement. + +Rather than grow a field set per question, a `claim` receipt carries a +**class discriminator** and a class-scoped body, and links into the same +chain as `group` and `object`. + +Required fields: + +| field | type | meaning | +|---|---|---| +| `kind` | string | MUST equal `"claim"`. | +| `class` | string | Registered class identifier; see the table below. | +| `subject` | 32 B hex | `content_hash` (§2.1) of the object the claim is about. | +| `body` | object | Class-scoped payload. Its shape is fixed by `class` and by nothing else. | +| `signer_pubkey` | 32 B hex | Ed25519 verifying key per RFC 8032. | +| `signer_id` | string | Bound identity string. | +| `sig` | 64 B hex | Ed25519 signature over the canonical bytes of the receipt with `sig` removed, under domain `"moq-jwp:claim\x01"`. | +| `parent_receipt_hash` | 32 B hex or `null` | As §2.4.1. | + +Classes defined here: + +| `class` | body asserts | signer is | +|---|---|---| +| `energy` | a measured energy figure with its acquisition class, per [energy-measurement](energy-measurement.md) | whoever performed the measurement | +| `task-fidelity` | a claim tuple per [task-fidelity](task-fidelity.md) §2 | whoever ran the test | +| `lawful-basis` | the basis on which the sink's observations may be processed | the service operator | +| `log-inclusion` | inclusion of a prior receipt in an append-only log | the log operator | +| `binding-recovery` | a binding recovered after the hard binding was destroyed (§8) | the resolver | + +Rules: + +- A verifier **MUST** reject a `claim` whose `class` it does not know. + Ignoring an unknown claim is not the same as accepting the object + without it, and silently discarding a class a publisher thought + load-bearing is the failure this rule exists to prevent. +- The `class` **MUST NOT** be inferred from the body's shape. +- A `claim` **MUST NOT** restate a field that `group` or `object` + already carries for the same subject. Where both exist and disagree, + a verifier **MUST** reject rather than choose. +- The signer of a `claim` **MAY** differ from the object's publisher — + that is the point of the class. A verifier **MUST NOT** treat a + claim signature as evidence about delivery, or a delivery signature + as evidence about a claim. +- A `claim` carries the authority of its signer and no more. `class` + says what is being asserted; it never says the assertion is true. + ### 2.5. Worked example Manifest JSON (group receipt, formatted for readability — wire form @@ -465,3 +527,70 @@ multi-signer container; the v1.0 wire format remains a strict subset. `joules_total` are unit-of-energy fields, not unit-of-account fields. Conversion to currency, carbon, or grid-mix CO₂ is performed by downstream aggregators and is not normative here. + +## 7. Key discovery (REQUIRED for a standalone verifier) + +Every signature in this extension is verified against `signer_pubkey`, +carried in the receipt. That makes the receipt self-describing and the +signature checkable — but it does not make it *trustworthy*: a forger +supplies a key as easily as a signature. Until now WAI has presumed the +verifier already holds the right key by some means outside the +specification. For a party who was never on the delivery path, that +presumption fails, and every guarantee in §6 fails with it. + +A receipt **MUST** carry a `signer_locator` object naming where the +signer's key is authoritatively published: + +| field | type | meaning | +|---|---|---| +| `scheme` | string | `"cert-uri"`, `"trust-record"`, or `"did"`. | +| `value` | string | The URI, trust-record identifier, or DID. | + +This follows the design ITU-T H.274's `dsci` SEI uses for the same +problem — a certificate URI or an ISO/IEC 21617-1 trust-record URI — +rather than inventing a third shape. + +- A verifier **MUST NOT** treat `signer_pubkey` as authoritative merely + because a signature over it validates. It **MUST** resolve + `signer_locator` and confirm the key, or report the receipt as + **signature-valid, identity-unconfirmed**, which is a materially + weaker statement and **MUST NOT** be reported as verification. +- A verifier that cannot resolve the locator **MUST NOT** fail closed on + that ground alone if it holds the key by prior arrangement; it + **MUST** record which path it used. +- `signer_locator` **MUST** be inside the signed payload. A locator a + relay can rewrite is a locator an attacker can rewrite. + +## 8. Binding recovery (the second binding class) + +§2.1 binds a receipt to an object by BLAKE3 over the envelope bytes. +That is a **hard binding**, and it is destroyed by any change to those +bytes — a transcode, a remux, a platform re-encode. Those are not +attacks; they are what normal distribution does. + +WAI has exactly one binding class, so today a re-encode leaves no +recovery path at all: the receipt is intact, the object no longer +matches it, and nothing connects them. + +A second class is therefore defined, and it is deliberately weaker: + +- A publisher **MAY** additionally register a **soft binding** — a + derived descriptor computed from the samples rather than the bytes, + which survives re-encoding — together with a `resolution_endpoint` + at which a receipt may be recovered from that descriptor. +- The algorithm **MUST** be named from a public registry and **MUST + NOT** be described only as a vendor name. C2PA's soft-binding + algorithm registry is a usable list. +- A receipt recovered by soft binding **MUST** be reported as a + `claim` of class `binding-recovery`, never as a hard binding. +- **The ancestor rule.** A recovered receipt attests **the ancestor** + from which the object in hand was derived — not the object in hand. + A verifier **MUST** state this when reporting a recovered binding. + Any implementation that cannot express the distinction **MUST NOT** + implement soft binding at all; a recovery presented as a hard + binding is worse than no recovery, because it launders a + transformation nobody attested. + +Soft binding does not repair the hard binding, and this section does not +claim it does. It supplies a path where there was none, at a stated and +strictly lower grade.