Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 7s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 10s
WAI neural sink gates / does this change touch it (pull_request) Successful in 12s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 20s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m46s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 51s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m15s
wai-web wasm32 / does this change touch it (pull_request) Successful in 25s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 31s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m28s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 4m42s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m17s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 27s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 9m42s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m20s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 1m48s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m57s
wai-pack / wai-pack · linux (pull_request) Successful in 1m30s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m7s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m54s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m45s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 17s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 27s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 6m31s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m58s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 15m30s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 6m57s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 27m40s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 48m12s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 47m28s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 5m50s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 7m15s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 15s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Failing after 6m40s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m12s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 2m58s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 48m45s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 40s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m14s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m14s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m55s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 13m33s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 22s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 46s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 4m29s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m31s
wai-pack / wai-pack · macos (pull_request) Successful in 1m6s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m17s
Reconciling is part of the delivery service a distributor operates over WAI. The section still says what a reconciliation discloses and to whom, keeps the fresh-key option, and points a distributor that a regime asks for a lawful basis to the lawful-basis claim (jwp-receipts §2.4.6). Co-Authored-By: Claude Opus 5.5 <[email protected]>
568 lines
36 KiB
Markdown
568 lines
36 KiB
Markdown
# WAI Extension: Bearer Receipts
|
||
|
||
> Status: Draft. Registers the record `wai.bearer.receipts`: one receipt on every bearer. A
|
||
> publisher's group and object receipts ([jwp-receipts](jwp-receipts.md) §2.4.1, §2.4.2) are
|
||
> the same bytes whether a group travels over a two-way relay chain, IP multicast or a
|
||
> broadcast bearer with no return path. What a bearer changes is what can honestly be said
|
||
> about delivery, and this extension says it beside the receipt, in two claim classes of
|
||
> jwp-receipts: a publisher's **transmission** claim (§4, jwp-receipts §2.4.14), which declares
|
||
> the bearer a group was sent over and attests transmission, never delivery; and a sink's
|
||
> **reconciliation** claim (§5, jwp-receipts §2.4.15), signed later and out of band, which
|
||
> attests the objects the sink received by a Merkle root under the group's own tree. A
|
||
> verifier derives from them what a relying party may state about a group (§6), an upper
|
||
> bound on each receiver's share of a shared transmission's energy (§7), and confirms keys
|
||
> on a bearer with no return path under anchors provisioned in advance (§8). §9 maps the
|
||
> carriage onto the bearers. WAI defines no transport, here as elsewhere
|
||
> ([moq-streaming-format](moq-streaming-format.md)). The reference implementation is the `bearer_receipts` module of `wai-rs` (feature
|
||
> `bearer_receipts`), with the two classes' bodies in `claim_receipt::bearer`; the corpus is
|
||
> [`live-conformance/bearers/`](../live-conformance/bearers/), and its second implementation
|
||
> is `live-conformance/bearer_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision, each at the edition named: ETSI TS 103 769 V1.2.1 (2024-11);
|
||
> ETSI TS 103 770 V1.2.1 (2024-09); ATSC A/331:2026-04, A/333:2026-04 and A/360:2026-08; 3GPP
|
||
> TS 26.346 V19.3.0, TS 26.517 V19.3.0 and TS 26.502 V19.6.0; RFC 6726;
|
||
> `draft-ramadan-moq-multicast-00` and `draft-ietf-moq-transport-22`, Internet-Drafts;
|
||
> ETSI TS 102 796 V1.8.1 (2026-09) and ETSI TS 102 809 V1.3.1. A clause number of one of
|
||
> them in this text is that edition's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the bearer classes (§2); why the bearer is declared beside the receipt and not in
|
||
it (§3); the transmission claim (§4) and the reconciliation claim (§5), their bodies, rules
|
||
and context checks; the verdict a verifier derives for one group from its receipts and those
|
||
claims (§6); the upper bound on a receiver's share of a shared transmission's energy (§7);
|
||
verification with no return path (§8); and the mappings onto bearers (§9).
|
||
|
||
Out of scope: a transport or a bearer; a reception-reporting protocol — a reconciliation
|
||
travels on whatever path a sink later has, as a receipt file (http-delivery §8.1) or inside a
|
||
bearer's own reporting (§9); a pre-emption authority ([unidirectional-delivery](unidirectional-delivery.md)
|
||
§3 registers none, and nor does this extension); and any statement of rendering. No receipt
|
||
over a bearer with no return path can prove rendering, and nothing here attempts it.
|
||
|
||
Relationship to the core spec: `wai.bearer.receipts` is a **record** ([SPEC](../SPEC.md)
|
||
§5): the transmission and reconciliation claims are signed records a WAI tool emits and
|
||
verifies. It names no payload format and is not dispatchable.
|
||
|
||
## 2. Terms
|
||
|
||
- **Bearer**: what carries a group's objects from a publisher to sinks. Its **class** is one of:
|
||
- `bidirectional`: each sink is reached over a path that can carry its statements back,
|
||
such as a relay chain or unicast HTTP;
|
||
- `multicast`: one transmission reaches every sink that joined it, with no per-sink path on
|
||
the bearer itself; a sink may have another path;
|
||
- `unidirectional`: one transmission, and no path back from any sink.
|
||
- **Bearer instance**: one transmission a publisher made, named by an absolute URI, its
|
||
`bearer_id`: a multicast session, a broadcast service, a relay chain's origin. WAI reads
|
||
nothing from it and compares it byte for byte.
|
||
- **Transmission**: putting a group's objects and their receipts onto a bearer instance.
|
||
**Delivery**: a sink receiving them. A transmission is not a delivery, on any bearer.
|
||
- **Sink**: a receiver that verifies what it received under [jwp-receipts](jwp-receipts.md)
|
||
§3. **Relying party**: anyone else who reads a group's receipts and claims.
|
||
- **Group**, **link hash**, **leaf**, **root**: as jwp-receipts §2.2, §2.4.1 and §3 step 3.
|
||
`delivered-verified` and `inclusion-verified`: as jwp-receipts §3.
|
||
|
||
## 3. One receipt on every bearer
|
||
|
||
The group receipt signs its objects' root, its coordinates, its times and its publisher
|
||
(jwp-receipts §2.3). It names no bearer, and this extension adds no member to it. The bearer
|
||
is declared beside the receipt, in a transmission claim signed by the group's own key and
|
||
naming the group by its link hash (§4). Three facts fix that form:
|
||
|
||
- **A group is not tied to one bearer.** A group may be sent over a relay chain and a
|
||
broadcast at once, and carried again later by another. A bearer inside the signed payload
|
||
would make one group per bearer, each with its own link hash, and the chain of a track
|
||
(jwp-receipts §3 step 3) could link through only one of them.
|
||
- **The bearer is a fact about a transmission, not about the bytes.** The receipt attests the
|
||
bytes; a declaration attests what the publisher did with them, and is a statement of its own
|
||
with its own signature.
|
||
- **Every receipt already issued keeps verifying.** A group carried before this extension, or
|
||
carried by a party that does not use it, verifies exactly as before; what is missing is a
|
||
declaration, which is reported as missing (§6.5), never inferred.
|
||
|
||
So the "MUST declare the bearer" of [unidirectional-delivery](unidirectional-delivery.md) §4
|
||
is met by a signed declaration that changes no byte of the receipt it declares. A sink that
|
||
holds a group's objects and receipts verifies them under jwp-receipts §3 the same way on
|
||
every bearer and reaches the same verdicts (`delivered-verified` for each object whose bytes
|
||
it holds): the corpus carries one group, byte for byte, on all three classes (§11). What
|
||
differs between bearers is what anyone other than that sink may state about delivery (§6),
|
||
and how a receiver's share of the transmission's energy is bounded (§7).
|
||
|
||
## 4. The transmission claim (`class: "transmission"`)
|
||
|
||
A publisher that sends a group over a bearer **MAY** declare the transmission in a
|
||
`transmission` claim, one per bearer instance, and **MUST** where
|
||
[unidirectional-delivery](unidirectional-delivery.md) §4 requires it. Its `subject_type` is
|
||
`receipt` and its `subject` the group receipt's link hash (jwp-receipts §3 step 3). It is
|
||
signed by the key that signed the group. It attests that the publisher transmitted the group
|
||
over the bearer instance it names, and nothing about what any sink received.
|
||
|
||
### 4.1 Body
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `bearer` | string | The bearer's class: `bidirectional`, `multicast` or `unidirectional` (§2). |
|
||
| `bearer_id` | absolute URI | The bearer instance (§2). |
|
||
| `energy` | object or `null` | The energy the bearer spent transmitting the group on this instance, every repetition included, written as jwp-receipts §2.4.12 writes a session's `energy`: `{ "joules_micro", "energy_provenance", "energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref" }`. `null` when the transmission was not metered, never a figure of 0 ([energy-measurement](energy-measurement.md) §2). It is not the group's `joules_total`, which is the energy of producing the objects. |
|
||
| `attests` | string | **MUST** be `"transmitted"`. Any string passes step 2; another value is refused at step 4. |
|
||
|
||
`bearer` is read at jwp-receipts §3.1 step 2 against its enumeration (`field_type`), and
|
||
`bearer_id` as an absolute URI.
|
||
|
||
### 4.2 Rules
|
||
|
||
Checked at jwp-receipts §3.1 step 4, in this order:
|
||
|
||
1. `attests` **MUST** be `"transmitted"` (`transmission_not_transmitted`): a declaration that
|
||
says it attests anything more is refused, never read as a weaker one.
|
||
2. Where `energy` is not `null`, its `joules_micro` **MUST NOT** be 0
|
||
(`energy_zero_figure`), and its class is admitted as jwp-receipts §2.4.5 rule 2 admits one
|
||
(`energy_unknown_acquisition`, `energy_legacy_acquisition`,
|
||
`energy_acquisition_evidence`).
|
||
|
||
### 4.3 Against the group
|
||
|
||
Checked at jwp-receipts §3.1 step 6 against the group receipt the verifier holds, which has
|
||
passed jwp-receipts §3 steps 1 and 2 (`context_invalid` otherwise; another receipt is
|
||
`context_mismatch`). In order:
|
||
|
||
1. the group's link hash is the claim's `subject` (`transmission_subject_mismatch`): a
|
||
declaration over another group declares nothing about this one;
|
||
2. the claim's `signer_pubkey` is the group's (`transmission_signer_mismatch`): only the
|
||
publisher declares its own transmission.
|
||
|
||
A transmission claim checked without the group stops at step 5 and is not counted by §6.
|
||
|
||
### 4.4 Authority
|
||
|
||
Confirmed under jwp-receipts §7, a transmission claim needs the authority `transmission`,
|
||
which is not pinned-only and is not `delivery`. Its energy figure, where `energy` is not
|
||
`null`, is a second item, as a session's is (jwp-receipts §7.5): it needs `transmission` and
|
||
the energy authority of its class, so a hardware-class figure is confirmed only under a pinned
|
||
anchor, and a verifier reports the declaration's confirmation and its figure's side by side.
|
||
|
||
## 5. The reconciliation claim (`class: "reconciliation"`)
|
||
|
||
A sink that received objects of a group **MAY** later sign which ones, in a `reconciliation`
|
||
claim. Its `subject_type` is `receipt` and its `subject` the group receipt's link hash. It is
|
||
signed by the sink, never by the publisher. It is not a hop: it is never a group's parent,
|
||
never in the publisher's chain, and nothing in the publisher's chain changes when it arrives.
|
||
|
||
### 5.1 Body
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `bearer` | string | The class of the bearer the sink received over (§2). |
|
||
| `bearer_id` | absolute URI | The bearer instance it received over: the `bearer_id` of the publisher's transmission claim for that instance. |
|
||
| `leaf_count` | integer | The group's `leaf_count`. |
|
||
| `received` | hex | A bitmap of ⌈`leaf_count` / 8⌉ bytes. Object *i* is bit `0x80 >> (i mod 8)` of byte ⌊*i* / 8⌋, so the hexadecimal reads left to right in object order; the bit is set exactly when the sink accepted object *i* under jwp-receipts §3 steps 1–6 over its bytes. |
|
||
| `received_root` | 32 B hex | The root jwp-receipts §2.2 computes over the leaves of the received objects, in increasing `leaf_index`, each leaf in the form its object receipt takes (legacy or labelled). With every bit set it is the group's `root_hash`. |
|
||
| `attests` | string | **MUST** be `"received"`. Any string passes step 2; another value is refused at step 4. |
|
||
|
||
The root is a commitment under the group's own tree, so it is checked against the
|
||
publisher's root without the objects' bytes (§5.3).
|
||
|
||
### 5.2 Rules
|
||
|
||
Checked at jwp-receipts §3.1 step 4, in this order:
|
||
|
||
1. `attests` **MUST** be `"received"` (`reconciliation_not_received`).
|
||
2. `leaf_count` is at least 1, `received` holds exactly ⌈`leaf_count` / 8⌉ bytes, no bit at
|
||
or past `leaf_count` is set, and at least one bit is set (`reconciliation_bitmap_malformed`).
|
||
A sink that received nothing has nothing to reconcile.
|
||
|
||
### 5.3 Against the group and its objects
|
||
|
||
Checked at jwp-receipts §3.1 step 6 against the group receipt the verifier holds, which has
|
||
passed jwp-receipts §3 steps 1 and 2 (`context_invalid`, `context_mismatch` as §4.3), and the
|
||
object receipts it holds under that group, each of which has passed jwp-receipts §3 step 5 —
|
||
verified by inclusion at least. In order:
|
||
|
||
1. the group's link hash is the claim's `subject` (`reconciliation_subject_mismatch`);
|
||
2. the claim's `signer_pubkey` is not the group's (`reconciliation_by_publisher`): a
|
||
publisher's statement that it received its own objects is not a reconciliation;
|
||
3. `leaf_count` is the group's (`reconciliation_leaf_count_mismatch`);
|
||
4. with every bit set, `received_root` is the group's `root_hash`; otherwise the verifier
|
||
holds an object receipt at every set bit (`reconciliation_objects_not_held`, recoverable
|
||
by obtaining them, never accepted silently) and `received_root` is the root over their
|
||
leaves (`reconciliation_root_mismatch`). A root over a leaf that is not the group's at its
|
||
position — a hash not in the group — is refused here.
|
||
|
||
A reconciliation checked without the group stops at step 5 and is not counted by §6.
|
||
|
||
### 5.4 What it attests
|
||
|
||
An accepted reconciliation establishes that its signer says it received the objects its
|
||
bitmap names, with the bytes their receipts commit to. It does not establish that the signer
|
||
held those bytes: content hashes travel with the receipts, so a party that saw only the
|
||
receipts can sign one. It never attests presentation, rendering or timeliness. Like a
|
||
`binding-recovery` record (jwp-receipts §2.6.3), it attests less than a hop would, and says so
|
||
in-band (`attests`).
|
||
|
||
### 5.5 Authority
|
||
|
||
Confirmed under jwp-receipts §7, a reconciliation needs the authority `reconciliation`, which
|
||
is not pinned-only and is not `delivery`. A sink signs under an origin's key where its origin
|
||
speaks for it, and under a `did:key` otherwise, which is unconfirmed (jwp-receipts §7.4
|
||
step 1), as a sink's `reconstruct-session` claim is.
|
||
|
||
## 6. The bearer verdict
|
||
|
||
What a verifier may state about one group, from its receipts and the claims about it.
|
||
|
||
### 6.1 Inputs
|
||
|
||
The group receipt; the object receipts the verifier holds under it, each with the object's
|
||
envelope bytes where it holds them; the transmission and reconciliation claims it holds about
|
||
the group, each signer's in the order of its chain; optionally, for a verifier that is itself
|
||
a sink, the bearer class and instance it received the group over (§6.5); optionally, a
|
||
statement offered about the group (§6.4); and optionally, for a relying party that counts
|
||
receivers only by keys it has confirmed, the set of those keys (§7).
|
||
|
||
### 6.2 Steps
|
||
|
||
1. **The group.** jwp-receipts §3 steps 1 and 2. A group that fails is refused with that
|
||
step's reason, and nothing below is derived from it. (Its chain is jwp-receipts §3 step
|
||
3's, and unchanged.)
|
||
2. **The objects.** Each object receipt is checked against the group: with the envelope, by
|
||
jwp-receipts §3 steps 4–6 (`delivered-verified`); without it, by step 5
|
||
(`inclusion-verified`); or refused with its step's reason. These verdicts do not depend on
|
||
the bearer, the declarations or the reconciliations.
|
||
3. **The claims.** Each claim, in order, under jwp-receipts §3.1 steps 1–5, each signer's
|
||
previous claim being the last of its claims held before it here; then step 6, a
|
||
transmission claim by §4.3 and a reconciliation by §5.3 against the object receipts that
|
||
passed step 2. A claim of another class is `context_mismatch` at step 6. A claim refused at
|
||
any step takes no further part.
|
||
4. **The declarations.** The accepted transmission claims, by `bearer_id`. Two with one
|
||
`bearer_id` and different bodies set aside every declaration of that instance
|
||
(`bearer_contradicted`): a publisher that contradicts itself about one transmission has
|
||
declared neither. Two with the same body are one declaration.
|
||
5. **The reconciliations**, each accepted one in order. It is set aside, and counted nowhere,
|
||
at the first of these that holds: step 4 set aside the declarations of its `bearer_id`
|
||
(`bearer_contradicted`); no declaration has its `bearer_id` (`bearer_not_declared`); the
|
||
declaration names another `bearer` (`bearer_contradicted`); the verifier counts only
|
||
confirmed keys and its key is not among them (`sink_not_confirmed`). Otherwise it is
|
||
**counted** for its bearer instance. A second counted reconciliation of one key for one
|
||
instance adds no sink (`reconciliation_duplicate_sink`): its bits are joined to the first's.
|
||
6. **The counts.** `sinks`: the distinct keys with a counted reconciliation; `whole`: those
|
||
whose joined bits, over every instance, cover every object of the group; for each object,
|
||
the number of distinct keys whose joined bits hold it; and for each declaration, the
|
||
distinct keys counted for its instance.
|
||
|
||
### 6.3 Status
|
||
|
||
- **`published`**: no declaration stands. The group verifies; nothing is stated about where
|
||
it went.
|
||
- **`transmitted`**: at least one declaration stands, and no reconciliation is counted. The
|
||
publisher sent it over the bearers declared; nothing is stated about any sink.
|
||
- **`reconciled`**: at least one reconciliation is counted. `sinks` keys say they received
|
||
objects of the group, out of band and on their own authority, and `whole` of them say they
|
||
received every object.
|
||
|
||
A verifier **MUST NOT** report a group, or anything read from it, as delivered to anyone but
|
||
itself on the strength of the publisher's receipts and declarations: the receipts attest
|
||
publication, the declarations transmission. `delivered-verified` stays the verdict of a sink
|
||
over bytes it holds (jwp-receipts §3).
|
||
|
||
### 6.4 Statements offered about a group
|
||
|
||
A verifier **MAY** be asked to check a statement made about the group:
|
||
|
||
- **transmitted over a bearer class**: holds when a declaration of that class stands, and is
|
||
otherwise refused (`bearer_not_declared`);
|
||
- **delivered to *k* sinks** (*k* at least 1): holds when `whole` is at least *k*. With `whole`
|
||
0 it is refused (`delivery_not_reconciled`), whatever was declared: a transmitted-only group
|
||
presented as delivered is refused here. With `whole` from 1 to *k* − 1 it is refused
|
||
(`delivery_exceeds_reconciled`).
|
||
|
||
### 6.5 A sink's own bearer
|
||
|
||
A sink knows the bearer instance it received over. Beside its object verdicts, and never
|
||
changing them, it reports the group's declaration for that instance: `declared` when one
|
||
stands with the sink's class; `bearer_contradicted` when one stands with another class, or
|
||
step 4 set the instance's declarations aside; and `bearer_not_declared` when none names the
|
||
instance. A group received over a unidirectional bearer with no declaration is
|
||
[unidirectional-delivery](unidirectional-delivery.md) §4's missing declaration.
|
||
|
||
## 7. Energy per receiver on a shared transmission
|
||
|
||
A transmission's energy is spent once, whoever receives it. On a multicast or broadcast
|
||
bearer the number of receivers is not known, and **unknown is not zero**: no figure divided by
|
||
an assumed audience is a measurement of anyone's share. What a verifier states instead is an
|
||
upper bound, for each declaration of §6.2 step 4 whose `energy` is not `null`:
|
||
|
||
```
|
||
per_receiver_upper_bound = ⌈ joules_micro / max(1, n) ⌉
|
||
```
|
||
|
||
where `n` is the number of distinct keys counted for that instance (§6.2 step 6).
|
||
|
||
- **It bounds an allocation, not a reading.** Dividing one transmission's figure among its
|
||
receivers is an allocation of that figure (as energy-steering §3.3 says of a hop shared by
|
||
pathways); no receiver's own energy was read. A verifier **MUST** report it as a bound, named
|
||
so, and **MUST NOT** report it as any receiver's measured energy.
|
||
- **Why it is an upper bound.** Every receiver of the transmission shares the one figure. If
|
||
the counted keys are distinct receivers, at least `n` receivers shared it, and an equal share
|
||
is at most `joules_micro / n`; with no reconciliation, a receiver's share is at most the whole
|
||
figure. Rounding up keeps it a bound in integer microjoules. Each reconciliation counted for
|
||
the instance tightens it, and none raises it.
|
||
- **Its class is the figure's.** The bound is stated with the transmission figure's
|
||
acquisition class and evidence (energy-measurement §2); the division adds no measurement and
|
||
changes no relative uncertainty. Bounds of two instances, or of two classes, are never added.
|
||
- **No figure, no bound.** Where `energy` is `null` the verifier states no bound at all, never
|
||
a bound of 0 (energy-measurement §2).
|
||
- **Keys are not receivers.** A receiver that reconciles under several keys is counted once
|
||
for each and lowers the bound below its share. The count is a count of keys, and a verifier
|
||
reports it as one. A relying party that needs a bound per receiver counts only keys it has
|
||
confirmed (jwp-receipts §7.4) with the authority `reconciliation` under an origin it relies
|
||
on to give that authority to one key per receiver, and reports that it did (§6.2 step 5,
|
||
`sink_not_confirmed`).
|
||
- It is not the group's production energy: `joules_total` (jwp-receipts §2.4.1) and its
|
||
objects' figures are read as jwp-receipts reads them, and never added to a transmission's.
|
||
|
||
## 8. Verification without a return path
|
||
|
||
### 8.1 Anchors
|
||
|
||
A sink on a bearer with no return path cannot retrieve a key log from an origin's location
|
||
(jwp-receipts §7.3, http-delivery §8.4): first-use retrieval is unavailable to it. It confirms
|
||
under anchors it already holds — logs provisioned in advance by its relying party (pinned), or
|
||
logs it retrieved earlier over another path (fetched, with the time it retrieved them). The
|
||
pinned-only authorities, and any authority registered for pre-emption, are confirmed only
|
||
under a pinned anchor that has not moved (jwp-receipts §7.4 step 6a), on this bearer as on any
|
||
other.
|
||
|
||
### 8.2 Logs that arrive on the bearer
|
||
|
||
A key log that reaches a sink over the bearer — in a file carousel, a delivery session, a
|
||
multicast track — is neither pinned nor fetched, whatever it holds. The sink:
|
||
|
||
- reads it as jwp-receipts §7.3 reads a log, and holds its revisions, each checked against its
|
||
parent when §7.4 step 3 walks them, so a revocation or a rotation its origin sends over the
|
||
bearer reaches a sink that has the origin anchored;
|
||
- **MUST NOT** anchor an origin from it (`anchor_on_bearer`): an item under an origin the sink
|
||
has not otherwise anchored stays unconfirmed (`origin_not_anchored`). A trust anchor
|
||
delivered over the bearer it authorises is not a trust anchor
|
||
([unidirectional-delivery](unidirectional-delivery.md) §3), for receipts as for pre-emption.
|
||
|
||
A revision on the bearer that its anchored line does not authorise — another root, a log
|
||
written by whoever controls the bearer — is no child of the trusted line (jwp-receipts §7.2
|
||
rule 7) and confirms nothing.
|
||
|
||
### 8.3 The procedure
|
||
|
||
Given the item to confirm (a group receipt with its object receipts, or a claim), the logs the
|
||
sink was provisioned with and the logs that arrived on the bearer:
|
||
|
||
1. verify the item — the group under jwp-receipts §3 steps 1 and 2 and each object under step
|
||
5; a claim under §3.1 steps 1–4 — and stop with its reason if it fails;
|
||
2. anchor each provisioned log as pinned (jwp-receipts §7.3), holding its revisions; a log
|
||
that does not read anchors nothing, with its reason;
|
||
3. read each log that arrived on the bearer as jwp-receipts §7.3 reads one and hold its
|
||
revisions; report it `held` where its origin is anchored by step 2, and `anchor_on_bearer`
|
||
otherwise. A log that does not read is held in no part, with its reason;
|
||
4. confirm the item under jwp-receipts §7.4 against every revision held and the anchors of
|
||
step 2, and, for a transmission claim with an energy figure, its figure as a second item
|
||
(§4.4).
|
||
|
||
The procedure touches no network and no clock; sinks holding the same bytes reach the same
|
||
verdicts, offline, on every platform. A confirmation describes the log as the sink holds it:
|
||
a revocation that has not reached it is not in it, and the head's `revision_hash` it reports
|
||
says how current that was.
|
||
|
||
## 9. Bearer mappings
|
||
|
||
### 9.1 What WAI fixes on every bearer (normative)
|
||
|
||
Where a bearer carries files or objects, WAI controls these bytes:
|
||
|
||
- **Envelopes.** Each object is one WAI envelope, carried whole as one file, object or segment
|
||
of the bearer; its bytes are the ones jwp-receipts §2.1 hashes. The bearer's own framing —
|
||
packets, forward error correction, an object's encoding for transport — is outside them, and
|
||
a sink hashes the envelope it reconstructs.
|
||
- **Receipts and claims.** A group receipt and every claim — transmission and reconciliation
|
||
claims among them — travel as the receipt files of [http-delivery](http-delivery.md) §8.1,
|
||
under the same name relative to the objects' location (`rcpt/<link hash>.json`), with the
|
||
same bytes and the same limits, and a sink reads each as §8.1 reads one. An object's receipts
|
||
travel in its receipt index (http-delivery §8.2). On a bearer that carries tracks rather than
|
||
files, the group and object receipts ride the receipts track
|
||
([moq-streaming-format](moq-streaming-format.md) §4.1); this revision defines no carriage of
|
||
claims on a track, so claims travel as receipt files on whatever file path the sink has.
|
||
- **The declaration rides with the group.** A publisher that declares a transmission **SHOULD**
|
||
send the transmission claim on the same bearer instance and in the same session as the
|
||
group's receipts, so that a sink receives the declaration that covers what it received.
|
||
- **Key logs.** A key log a bearer carries is the document of jwp-receipts §7.2, at the path
|
||
`/.well-known/wai-keys` relative to the origin it names, and anchors nothing (§8.2).
|
||
- **Reconciliation.** A sink sends its reconciliations, when it has a path, as receipt files;
|
||
where the bearer's own reporting carries files or opaque payloads, it **MAY** carry them
|
||
there. A reconciliation is the same claim whatever carried it.
|
||
|
||
The mappings below are **informative**: each says where these ride in the bearer's own
|
||
structures and what path back it defines.
|
||
|
||
### 9.2 FLUTE (RFC 6726)
|
||
|
||
Each envelope, receipt file and receipt index is one FLUTE file, named in the File Delivery
|
||
Table by its `Content-Location`, with `Content-Type` and, where the sender gives it,
|
||
`Content-MD5` (§3.4.2). The table admits further attributes (§3.4.2), and WAI defines none:
|
||
everything a sink checks is in the files. FLUTE is a unidirectional delivery service with no
|
||
feedback (§7.4), so a transmission over it is declared `unidirectional` unless another standard
|
||
adds a path back, and reconciliation goes over another path or none.
|
||
|
||
### 9.3 DVB multicast ABR (ETSI TS 103 769)
|
||
|
||
The multicast server carries each object as a transport object of FLUTE (Annex F) or ROUTE
|
||
(Annex H), and the gateway serves it to players over HTTP. Envelopes ride as the media
|
||
objects; receipt files, receipt indexes and key logs ride as the "other resources" a
|
||
`ResourceLocator` carousels in the same session (clause 10.2.3.14.3; ancillary objects, clause
|
||
8.3.4.4). Unicast repair (clause 9) and service reporting (clause 11: a JSON report posted to
|
||
`/dvb/mabr/reportingInformationInstance`) are paths back where the deployment has them; a
|
||
report's object-delivery event carries a status, not the object (Table 11.1.2.2-1), so a
|
||
reconciliation, which names objects, travels beside it as a receipt file. A deployment with
|
||
both paths declares `multicast`; one without, `unidirectional`. The integrity and authenticity
|
||
the specification adds (clause 12: content digests and HTTP message signatures) are the
|
||
gateway's checks of transport objects and change no WAI byte.
|
||
|
||
### 9.4 DVB-I (ETSI TS 103 770)
|
||
|
||
A service list signals the instances of a service — among them multicast transport streams
|
||
and DASH (clause 5.5.18) — and an identifier-based instance whose locator is an opaque URI
|
||
(clause 5.5.18.8). It carries no objects of its own and defines no path back. Where the service list gives an
|
||
instance a URI — an identifier-based locator, or a DASH presentation's address — a publisher's
|
||
`bearer_id` for that instance **SHOULD** be that URI, so a sink reading the list finds the
|
||
declaration by it.
|
||
|
||
### 9.5 ATSC 3.0 (A/331)
|
||
|
||
ROUTE carries envelopes as file-mode objects described by the Extended FDT, or in entity mode
|
||
(Annex A.3.3.2, A.3.3.3); receipt files, receipt indexes and key logs ride as locally cached
|
||
non-real-time files in the same ROUTE session, with relative `Content-Location` (clause
|
||
8.1.1.2), or inside a signed package (Annex A.3.3.5). A/331 requires its signalling tables to
|
||
be signed (clause 5.9; A/360:2026-08 clause 5.2.2); that signs the signalling, not the media
|
||
files, and is the broadcaster's statement, not the publisher's. Service usage reporting
|
||
(A/333:2026-04) reports consumption by service and interval, not by file or object, and is
|
||
unsigned; byte-range repair over broadband (A/331 clause 8.3) is a path back where the receiver
|
||
has broadband. A receiver with broadband sends reconciliations as receipt files; the broadcast
|
||
transmission is declared `unidirectional`, and the broadband path, where a group is also served
|
||
over it, is a `bidirectional` instance of its own.
|
||
|
||
### 9.6 3GPP MBMS and 5G multicast-broadcast (TS 26.346, TS 26.517)
|
||
|
||
MBMS download delivery carries each file over FLUTE with its own `Content-Location` (TS 26.346
|
||
clause 7.2); 5G multicast-broadcast user services carry objects by their object distribution
|
||
method, on the same download profile, in single, collection, carousel or streaming mode (TS
|
||
26.517 clause 6.2), and envelopes, receipt files, indexes and key logs ride as objects of the
|
||
session. File repair (TS 26.346 clause 9.3; TS 26.517 clause 6.2.4) is a path back. MBMS
|
||
reception reporting (TS 26.346 clause 9.4) lists each file received by its `fileURI`, with
|
||
`Content-MD5` where the table carried it, and is unsigned: it names files, not objects of a
|
||
group, and a reconciliation travels beside it. TS 26.517 defines no reception report, and
|
||
TS 26.502 leaves one for further study (clause 4.2.5). A session whose receivers report
|
||
declares `multicast`; one without a path back, `unidirectional`.
|
||
|
||
### 9.7 MoQ over multicast (`draft-ramadan-moq-multicast-00`)
|
||
|
||
The draft maps MoQ tracks onto source-specific or any-source multicast endpoints listed in a
|
||
catalog extension, each datagram carrying one MMTP packet, with a MoQ unicast session first and
|
||
unicast failover and repair (§1.1, §4, §6). The receipts track rides as any track; a sink that
|
||
holds the unicast session is reached `multicast` with a path back, and one that only joined the
|
||
multicast group is reached `unidirectional`. The draft's own signed per-group manifest (§7.2)
|
||
is the draft's, beside the receipts; WAI reads nothing from it. No MoQ working-group draft
|
||
(`draft-ietf-moq-transport-22` among them) defines multicast at this revision.
|
||
|
||
### 9.8 HbbTV (ETSI TS 102 796)
|
||
|
||
An HbbTV application reads files of a broadcast DSM-CC object carousel (ETSI TS 102 809
|
||
clause 7.1, ETSI TS 102 796 clause 7.2.2) and fetches over broadband (clause 7.3.2). V1.8.1
|
||
(HbbTV 2.0.5) adds WebAssembly for applications (clause A.3.28, Annex R), so a sink built for
|
||
WebAssembly runs inside an application. Envelopes, receipt files and key logs ride as carousel
|
||
files; the carousel is `unidirectional`, and the application's broadband path is the path back
|
||
for reconciliations. A key log carried in the carousel is not an anchor (§8.2): the anchor is
|
||
provisioned with the application, by its relying party. The carousel's own signing (ETSI TS
|
||
102 809 clause 9) is optional in HbbTV and signs the carousel, not the publisher's objects.
|
||
|
||
## 10. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `transmission_not_transmitted` | §4.2 rule 1 |
|
||
| `transmission_subject_mismatch` | §4.3 step 1 |
|
||
| `transmission_signer_mismatch` | §4.3 step 2 |
|
||
| `reconciliation_not_received` | §5.2 rule 1 |
|
||
| `reconciliation_bitmap_malformed` | §5.2 rule 2 |
|
||
| `reconciliation_subject_mismatch` | §5.3 step 1 |
|
||
| `reconciliation_by_publisher` | §5.3 step 2 |
|
||
| `reconciliation_leaf_count_mismatch` | §5.3 step 3 |
|
||
| `reconciliation_objects_not_held` | §5.3 step 4 |
|
||
| `reconciliation_root_mismatch` | §5.3 step 4 |
|
||
| `bearer_contradicted` | §6.2 steps 4 and 5, §6.5 |
|
||
| `bearer_not_declared` | §6.2 step 5, §6.4, §6.5 |
|
||
| `sink_not_confirmed` | §6.2 step 5 |
|
||
| `reconciliation_duplicate_sink` | §6.2 step 5 |
|
||
| `delivery_not_reconciled` | §6.4 |
|
||
| `delivery_exceeds_reconciled` | §6.4 |
|
||
| `anchor_on_bearer` | §8.2, §8.3 step 3 |
|
||
|
||
A group or object also raises the codes of jwp-receipts §3.3, a claim every code of jwp-receipts
|
||
§3.1 steps 1 to 5 and those of step 6 named in §4.3 and §5.3, and a confirmation the codes of
|
||
jwp-receipts §7.7.
|
||
|
||
## 11. Conformance
|
||
|
||
[`live-conformance/bearers/`](../live-conformance/bearers/) holds the corpus, written from fixed
|
||
seeds by `wai_bearer_vectors gen` as `vectors.json`. It has:
|
||
|
||
- **One receipt on every bearer**: one group and its object receipts, byte for byte, with a
|
||
declaration of each class, verified by a sink holding the bytes; the object verdicts are the
|
||
same in every case, and the group's bytes are checked equal across them.
|
||
- **Verdicts**: groups `published`, `transmitted` and `reconciled`; a group simulcast on two
|
||
instances, each with its own count and bound; partial and whole reconciliations, a labelled
|
||
group among them; a duplicate sink, joined; a relying party counting confirmed keys only.
|
||
- **Energy**: bounds before and after reconciliations arrive, an unmetered transmission with
|
||
no bound, and a hardware-class figure.
|
||
- **Negative controls**, one for each reason of §10 that a verdict or a claim raises: a
|
||
transmitted-only group presented as delivered; a partial reconciliation presented as delivered
|
||
to a sink; a reconciliation over a leaf that is not the group's; one signed by the publisher's
|
||
key; one whose objects are not held; a declaration over another group, one signed by another
|
||
key, and two that contradict each other; a reconciliation over an undeclared instance; a
|
||
sink whose bearer was not declared; and malformed bodies.
|
||
- **Offline**: confirmation under a pinned anchor with no network; a revocation that arrives on
|
||
the bearer; a log forged on the bearer for a pinned origin; a log that arrives on the bearer
|
||
for an origin not pinned (`anchor_on_bearer`); and a hardware-class transmission figure
|
||
confirmed beside its declaration.
|
||
|
||
A conforming verifier gives every case the outcome, the reason and everything else the corpus
|
||
states. `wai_bearer_vectors verify` and the test `tests/bearer_receipts.rs` check that the file
|
||
regenerates byte for byte, that `wai-rs` gives every case its outcome, that every reason of §10
|
||
appears, and that §10 lists exactly the reference's codes. `live-conformance/bearer_verify.py`
|
||
is the second implementation, written from this text: it shares no code with `wai-rs`; it
|
||
verifies groups and objects with the JWP corpus's second implementation
|
||
(`jwp-conformance/jwp_verify.py`), claims through steps 1–5 with the claim corpus's
|
||
(`claim-conformance/claim_verify.py`), and confirmations with the key-discovery corpus's
|
||
(`signer-conformance/signer_verify.py`), and writes §4.3, §5.3, §6, §7 and §8 itself; and it
|
||
mutates copies of the corpus and requires every copy to fail.
|
||
|
||
## 12. Security and privacy considerations
|
||
|
||
- **A declaration is the publisher's word.** Nothing checks that a bearer instance exists or
|
||
that its class is the one declared. A false `bidirectional` gains nothing here: no class of
|
||
bearer makes a group delivered (§6.3).
|
||
- **A reconciliation is the sink's word** (§5.4). Keys are cheap; a party that wants a group to
|
||
look widely received signs many reconciliations. That raises `sinks` and `whole` and lowers
|
||
the bound, and both are reported as counts of keys (§7). A relying party that needs more
|
||
counts confirmed keys only.
|
||
- **Replay.** A reconciliation names the group by its link hash, so it cannot be moved to
|
||
another group; within one group a second one of a key adds nothing (§6.2 step 5).
|
||
- **What a sink discloses.** A sink on a broadcast bearer is unobserved:
|
||
[unidirectional-delivery](unidirectional-delivery.md) §2's property is that the source learns
|
||
nothing. A reconciliation gives that up for the groups it names, to the distributor that
|
||
receives it. Reconciling is part of the delivery service a distributor operates over WAI, and
|
||
this extension sets no consent rule of its own for it. Where a regime asks the distributor
|
||
for a lawful basis, the distributor records it as a `lawful-basis` claim
|
||
([jwp-receipts](jwp-receipts.md) §2.4.6). A sink **MAY** sign each reconciliation with a
|
||
fresh `did:key`, which unlinks its reconciliations and leaves each group's count unchanged.
|
||
- **A bearer that lies about keys.** A log on the bearer anchors nothing (§8.2), so whoever
|
||
controls the bearer can withhold revisions — a revocation among them — but cannot add a key
|
||
to an anchored origin. A sink that has had no path for long holds a stale log, and its
|
||
confirmation reports the head it used.
|