# WAI Extension: Delivery Settlement
> Status: Draft. Registers the record `wai.delivery.settlement`: delivery paid on what the
> party that received it signed, not on what the party that sent it logged. The party
> downstream of a delivery, a next hop or a sink, signs an **acknowledgement**, `WDA1`, of what
> it received in a window: per object class, the objects by content hash under
> [jwp-receipts](jwp-receipts.md) §2.2's tree, the byte ranges of each, and their byte count. A
> relay is the downstream party of the acknowledgement it signs upstream and the upstream party
> of those its downstream sign for it, and both name the objects by the same content hashes. A
> hop whose downstream will not or cannot sign states that delivery in a **declaration**, the
> same record signed by the hop itself, which is counted in a class of its own,
> `unacknowledged`: never as zero and never as acknowledged. A **byte-range proof**, `WBR1`,
> shows that given bytes are a given range of an object without the rest of it, over BLAKE3's
> chunk tree. A retrieval that costs money (a fetch below the live edge, a recording fetch, an
> origin fill past a cache) is billed only to a grant whose holder's proof of possession the
> request carried ([live-rooms](live-rooms.md) §10.5); an ungranted retrieval is free. Per
> party and per period, a **settlement statement**, `WSS1`, states the acknowledged bytes by
> object class, the declared bytes apart, the party's energy by acquisition class from its
> per-hop records ([live-metering](live-metering.md) §3), never summed across classes, and the
> amount under a declared tariff in integer micro-units; a verifier recomputes every figure
> from the acknowledgements, and refuses a statement that does not recompute. A statement's
> **attribution lines** pay the owners of the works that conditioned an output from its
> acknowledged deliveries, under a basis [influence](influence.md) verifies. No new
> transport, no payment rail and no money. The reference implementation is the `settlement`
> module of `wai-rs` (feature `settlement`); the corpus is
> [`live-conformance/settlement/`](../live-conformance/settlement/), and its second
> implementation is `live-conformance/settlement_verify.py`.
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
>
> Texts read for this revision: the WAI texts this one cites; RFC 7937 (§3.2 to §3.4.1, the
> logging file, its directives and the `cdni_http_request_v1` record); RFC 9246 (§4.5, the
> logging fields of URI Signing, and §6.2.1, the record type `cdni_http_request_v2`); the
> BLAKE3 specification (its chunk chaining values, parent nodes and ROOT flag); RFC 6962
> §2.1; RFC 8032; FIPS 180-4. Of SVTA2050, SVTA2007 and SVTA2006 only their published
> summaries were read; §9.3 says what follows from that.
## 1. Scope
In scope: the acknowledgement, its entries, its object classes, its items, its signature, its
rules and its chains (§3); the declaration of a delivery nobody acknowledged (§4); the
byte-range proof and the audit it answers (§5); who may trigger a retrieval that costs money,
and how an acknowledgement records it (§6); the settlement statement, its lines, its tariff,
its energy, its attribution lines and its recomputation (§7); verification (§8); the mapping
onto CDNI and open caching logs (§9); and a viewer that relays objects it cannot read (§10).
Out of scope: a transport; money, a payment rail, an invoice and any contact with a party
outside the records; how a party chooses its tariff; how a hop measures its energy, which
[energy-measurement](energy-measurement.md) governs; the delivery receipts themselves, which
[jwp-receipts](jwp-receipts.md) defines and which a downstream party checks before it
acknowledges anything; and the attribution an attribution line is paid from (§7.8), which
[influence](influence.md) defines and verifies.
Relationship to the core spec: `wai.delivery.settlement` is a **record** ([SPEC](../SPEC.md)
§5): a signed record a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
## 2. Terms
- **Party**: an Ed25519 key (RFC 8032) that delivers or receives objects: a **hop** (a relay, a
cache, an origin, a compositor, an ingest gateway, a deriving edge) or a **sink** (a viewer,
a player, an agent).
- **Upstream** and **downstream**: of one delivery, the party that sent the bytes and the party
that received them.
- **Object**: one WAI envelope as it travels, sealed bytes for a sealed object
([confidentiality](confidentiality.md) §8.1). Its **content hash** is jwp-receipts §2.1's,
`BLAKE3("moq-jwp:object\x01" ‖ envelope)`, and its **length** is the envelope's length in
bytes.
- **Time**: microseconds since 1970-01-01T00:00:00Z, the unit of a room's `t0`
(live-rooms §3.1). A **window** and a **period** are spans of time, each from its `start`
(included) to its `end` (excluded).
- **Tree root** of a list of content hashes: jwp-receipts §2.2's root in its first (unlabelled)
form: each leaf `BLAKE3("moq-jwp:merkle-leaf\x01" ‖ content_hash)`, each node
`BLAKE3("moq-jwp:merkle-node\x01" ‖ left ‖ right)`, the last node of an odd level paired with
itself, a single leaf its own root, and 32 zero bytes for no leaf.
- *u64*, *u32*, *u16*, *u8*: little-endian unsigned integers of that many bits. A **digest** is
SHA-256 (FIPS 180-4), 32 bytes. **Zero** of a key or digest field is 32 zero bytes.
## 3. The acknowledgement
### 3.1 Layout
| field | size | meaning |
|---|---|---|
| `magic` | 4 | `WDA1` (ASCII) |
| `version` | 1 | 1 in this revision |
| `mode` | 1 | 1 an **acknowledgement**, signed by `downstream`; 2 a **declaration** (§4), signed by `upstream` |
| `role` | 1 | the downstream party's role: 1 a hop, 2 a sink |
| `upstream` | 32 | the key of the party that delivered |
| `downstream` | 32 | the key of the party that received; in a declaration, zero for a downstream party with no key the upstream knows |
| `start`, `end` | 8 each | u64: the window the record covers |
| `prev` | 32 | the digest of the record before it in its chain (§3.7); zero for the first |
| `entries` | 2 + 141 × *n* | a u16 count *n*, then each entry (§3.2) |
| `sig` | 64 | §3.5 |
### 3.2 Entries
An entry states what the downstream party received in the window of one object class, under
one grant and one proof:
| field | size | meaning |
|---|---|---|
| `class` | 1 | the object class (§3.3) |
| `grant` | 32 | zero, or the digest of a room grant (live-rooms §4.2): for a delivery class the grant the downstream party received under, for a retrieval class the grant the retrieval is billed to (§6) |
| `proof` | 32 | zero, or the SHA-256 of the proof record (`WRP1`, live-rooms §10.5.1) the retrieval's request carried (§6) |
| `objects` | 4 | u32: the items of the entry (§3.4) |
| `bytes` | 8 | u64: the sum of its items' byte counts |
| `root` | 32 | the tree root of its items' content hashes, in the items' order |
| `items` | 32 | SHA-256 over the 17 bytes `wai:settle-items` (ASCII) and 0x01, followed by its item list (§3.4) |
### 3.3 Object classes
| class | name | what it counts |
|---|---|---|
| 1 | `live` | media objects delivered to a subscription at or after the live edge, as they were published |
| 2 | `fetch` | objects served from a store in answer to a request that seeks back below the live edge (live-rooms §10.6) or that fetches a recording ([live-recording](live-recording.md) §8) |
| 3 | `fill` | objects a cache or a relay retrieved from its upstream to answer a request it could not answer from what it held |
| 4 | `ancillary` | events, catalogs, receipts tracks, timed text and every other object that is not media |
Classes 1 and 4 are **delivery classes**; classes 2 and 3 are **retrieval classes**, which cost
the serving party a store or an upstream fetch and are billed only to a grant (§6). An entry of
class 3 is signed by the cache or relay that filled, for its upstream, and is billed to the
grant of the request it was filling for.
### 3.4 Items
An entry's **item list** is the concatenation of its items, each 48 bytes:
| field | size | meaning |
|---|---|---|
| `content_hash` | 32 | the object's content hash |
| `start`, `end` | 8 each | u64: the byte range of the object's envelope the downstream party received; a whole object is `start` 0 and `end` its length |
An item's **byte count** is `end` − `start`. An object received in parts (an HTTP range
request, a fill that stopped) is acknowledged by the ranges received. A downstream party lists
the items of a whole group in the group's order, so that its entry's root is the root the
group's publisher signed (jwp-receipts §2.3) when the entry holds exactly that group, whole and
unlabelled. The item lists travel beside the record, unsigned; the record signs each list's
digest and the root over its content hashes, so the list can only be the one its signer signed,
and one object's place in an entry can be shown by its jwp-receipts §2.4.2 inclusion proof
against `root`, without the other items.
### 3.5 Digest and signature
The **digest** of a record is SHA-256 over its bytes before `sig`. Its **signature** is Ed25519
over the 15 bytes `wai:settle-ack` (ASCII) and 0x01, followed by the bytes the digest covers,
by `downstream` in an acknowledgement and by `upstream` in a declaration, verified strictly as
[SPEC](../SPEC.md) §7 *Signatures* requires: a key that fails step 1 or 2 is `bad_key`, and a
signature that fails a later step is `bad_signature`.
### 3.6 Reading and rules
A reader reads exactly the bytes it is given as one record, in the order of §3.1, and refuses
at the first of these that holds: fewer than 4 bytes, `truncated`; a magic other than `WDA1`,
`malformed`; no fifth byte, `truncated`; a version other than 1, `unknown_version`; the bytes
end before a field the layout requires, `truncated`; bytes after `sig`, `malformed`. It then
refuses as `ack_invalid` a record that breaks any of these:
- `mode` is 1 or 2, `role` is 1 or 2, and `start` is below `end`;
- `upstream` is not zero, and in an acknowledgement `downstream` is not zero;
- there is at least one entry, and the entries are in strictly ascending order of (`class`,
`grant`, `proof`), each `class` 1 to 4;
- each entry's `objects` is at least 1 and its `bytes` at least its `objects`;
- an entry of a delivery class has a zero `proof`; an entry of a retrieval class has `grant`
and `proof` both zero or both not zero.
An acknowledgement whose `downstream` is its `upstream` is refused `self_acknowledged`: a party's
statement that it received what it delivered is not an acknowledgement, and a hop states its
own delivery only in a declaration (§4). Then the signature (§3.5): `bad_key`, `bad_signature`.
An item list is read as items of 48 bytes, its length a multiple of 48, each item's `start`
below its `end`, and no two items sharing a byte of one object (the same content hash, with
ranges that overlap): otherwise `items_invalid`. It
**recomputes** against its entry when it holds `objects` items, the sum of their byte counts is
`bytes`, the tree root of their content hashes in order is `root`, and its digest is `items`:
otherwise `items_mismatch`. The sum is taken in integers wide enough not to overflow.
### 3.7 Chains
A **chain** is the records of one `mode`, one `upstream` and one `downstream`. Its records
cover windows that do not overlap, and each names in `prev` the digest of the record of its
chain whose window comes before its own; the first record of a chain names zero. So a party's
acknowledgements to one upstream are a sequence, and an acknowledgement signed for one window
cannot stand for another: the window is signed, and so is its place in the chain.
**Through a relay.** A relay is the downstream party of the acknowledgement it signs for its
upstream, and the upstream party of the acknowledgements its own downstream parties sign for
it. A relay forwards envelopes unaltered (jwp-receipts §4.1), so the two name the objects by the
same content hashes: what a relay is paid for can be matched, object by object, against what it
acknowledged receiving. A deriving edge ([deriving-edge](deriving-edge.md)) is paid for objects
of its own, which its derivation records name with the inputs it acknowledged. A hop's
**billable bytes** are exactly the bytes its downstream parties signed for: the sum of the
`bytes` of the entries of the acknowledgements naming it as `upstream` (§7.6).
## 4. Unacknowledged delivery
### 4.1 Declarations
A downstream party may not sign: a player that holds no key, a viewer admitted by Privacy Pass
(live-rooms §10.4), a sink that will not, or a link with no return path. The hop that delivered
to it states that delivery in a **declaration**: a record of §3's layout with `mode` 2, signed
by the hop, naming the downstream party's key where it has one and zero otherwise. A
declaration's entries and items keep §3's rules. Deliveries to downstream parties with no key
form one chain per hop, so in one window they are stated together, in one declaration.
### 4.2 What a declaration is
A declaration is the delivering party's own statement. Its bytes are counted in a class of
their own, **unacknowledged**:
- **never zero**: an entry states at least one byte, so a declaration says what was delivered,
and a statement states the declared bytes beside the acknowledged ones (§7.1);
- **never billable as acknowledged**: no line of a statement is computed from a declaration
(§7.6), and a deployment that pays for declared delivery does so outside the statement;
- **never both**: no byte of an object is both acknowledged and declared for one downstream
party in windows that overlap. A declaration naming a downstream key whose items share a byte
of an object with the items of an acknowledgement by that key, for the same upstream, in a
window that overlaps the declaration's, is refused `declared_and_acknowledged` (§8.2 step 4).
### 4.3 Bearers with no return path
A unidirectional bearer settles under unidirectional-delivery §4: what a transmitter sent over
it is declared, never acknowledged, and a sink's later reconciliation is reported apart from
the chain as that section requires. This extension adds nothing for it.
## 5. Byte-range proofs
### 5.1 The tree
An object's content hash is the BLAKE3 root, in hash mode, over 1024-byte chunks of
`X = "moq-jwp:object\x01" ‖ envelope`, the chunk at byte offset 1024 × *i* of `X` having chunk
counter *i*. With *L* = 15 + the object's length, `left(n) = 1024 · 2^⌊log₂((n − 1)/1024)⌋`
for *n* above 1024 splits a subtree of *n* bytes into its left and right children
([http-delivery](http-delivery.md) §6.3, whose head proof is the case of a range that starts
at 0). A **range proof** shows that the bytes it carries are the bytes at given offsets of the
object with a given content hash: it carries the chunks that hold the range, and the chaining
value of every subtree of `X` that lies wholly outside them.
### 5.2 Layout
| field | size | meaning |
|---|---|---|
| `magic` | 4 | `WBR1` (ASCII) |
| `version` | 1 | 1 in this revision |
| `content_hash` | 32 | the object's content hash |
| `envelope_len` | 8 | u64: the object's length |
| `start`, `end` | 8 each | u64: the byte range of the envelope it proves |
| `n_cv` | 1 | u8: the chaining values it carries |
| `cvs` | 32 × `n_cv` | the chaining values, in the order §5.4 consumes them |
| `bytes` | the rest | the covered bytes (below) |
With *c*0 = 1024 · ⌊(15 + `start`) / 1024⌋ and *c*1 = min(*L*, 1024 ·
⌈(15 + `end`) / 1024⌉), the **covered chunks** are the bytes of `X` from *c*0 to
*c*1, and `bytes` is the part of them that is envelope: the envelope's bytes from
max(*c*0, 15) − 15 to *c*1 − 15. A verifier knows the 15 bytes of the
domain and never reads them from the record.
### 5.3 Reading
A reader refuses at the first of these that holds: fewer than 4 bytes, `truncated`; a magic
other than `WBR1`, `malformed`; no fifth byte, `truncated`; a version other than 1,
`unknown_version`; fewer than 62 bytes, `truncated`; `start` not below `end`, `range_invalid`;
`end` above `envelope_len`, or *L* past what a u64 holds, `range_outside_object`; fewer bytes
than 62 + 32 × `n_cv` + the covered length (`bytes` above), `truncated`; more, `malformed`.
### 5.4 Verification
A range proof is checked against the content hash *H* of the object it is offered for. With
the covered chunks of `X` held, and in order:
1. its `content_hash` is *H*: otherwise `range_proof_mismatch`;
2. where it answers an audit (§5.6), its range is within the audited item's: otherwise
`range_not_acknowledged`;
3. the root computes to *H*, consuming its chaining values exactly: otherwise
`range_proof_mismatch`. When *c*0 is 0 and *c*1 is *L*, the covered
chunks are the whole of `X`: `n_cv` is 0 and the root is BLAKE3 of `X`. Otherwise
```
cv(s, n):
if s + n ≤ c0 or s ≥ c1: take the next chaining value from cvs
elif c0 ≤ s and s + n ≤ c1: the non-root chaining value of the n bytes of X at s,
a subtree whose first chunk has counter s / 1024
else: parent_cv(cv(s, left(n)), cv(s + left(n), n − left(n)))
root = parent_root(cv(0, left(L)), cv(left(L), L − left(L)))
```
`parent_cv` and `parent_root` are BLAKE3's parent node in hash mode, without and with the ROOT
flag. *c*0 is a multiple of 1024, and so is *c*1 unless it is *L*, so a
subtree the procedure meets lies wholly inside the covered chunks or wholly outside them once it
is split far enough; a single chunk is never split. A chaining value left over, or one missing,
is `range_proof_mismatch`.
### 5.5 What a proof establishes
A proof that passes establishes that the bytes from `start` to `end` it carries are those bytes
of the envelope whose content hash is *H*. The root does not encode the object's length, so
`envelope_len` is established only when *c*1 is *L*, when the proof's last covered
chunk is the object's last: a **tail proof**. A proof of a range that ends before the last chunk
establishes the tree's shape and not the length (as http-delivery §6.3 check 3 notes of a head
proof), and a verifier reports whether the length was established. A proof is a pure function
of the object and the range: anyone holding the object can make one, and a proof over bytes
that are not the object's at those offsets fails step 3.
### 5.6 Audits
An auditor (the party paid, the party paying, or a party settling between them) may name an
item of an acknowledgement and a range within it, and ask the downstream party that signed it
for a range proof. The audit passes when the proof verifies against the item's content hash
(§5.4) and its range is within the item's; a downstream party that cannot answer has not shown
that it holds the bytes it signed for. A proof carries the covered chunks, so an audit costs the
range rounded out to whole chunks and two chaining values per level of the tree, not the
object: a few chunks drawn at random audit a large object. Over a sealed object the proof is
over the sealed bytes, which a party that cannot open them can prove as well as one that can
(§10).
## 6. Paid retrieval
### 6.1 The rule
A hop **MUST NOT** perform a retrieval of a retrieval class (§3.3) that costs money (a fetch
from its store below the live edge or of a recording, a fill from its upstream past what it
holds) on a request that carries no grant and no proof of possession of it (live-rooms §10.5).
An ungranted request is served free from what the hop holds, or refused, as the hop's policy
says: a relay that seeks back refuses it (live-rooms §10.6, `seek_not_granted`), and an HTTP
store may serve a preview free. A retrieval performed on an ungranted request, a fill among
them, is the serving hop's own cost and is never billed to a grant holder.
### 6.2 Recording the grant
The acknowledgement of a retrieval names, in its entry, the grant the retrieval is billed to and
the proof the request carried: `grant` the grant's digest, `proof` the SHA-256 of the `WRP1`
record. Over MOQT the proof is the session's `GRANT_PROOF` (live-rooms §10.5.2), one for every
retrieval of the session; over HTTP it is the request's `WAI-Room-Proof` (live-rooms §10.5.3).
A sink that fetched names its own grant and proof. A cache that filled for a request names the
grant and the proof that request carried, and hands the grant and the proof records to its
upstream beside its acknowledgement. An entry of a retrieval class with a zero `grant` records
a retrieval no grant was presented for: it is acknowledged, and free (§7.6).
### 6.3 Checking
For each entry naming a grant, a verifier checks, in order:
1. the grant is among the grants of a room it is given (§8.1): otherwise `record_missing`;
2. the grant is good throughout the record's window, `nbf` × 10^6 ≤ `start` and `end` ≤ `exp` ×
10^6: otherwise `outside_grant_window`;
3. for a delivery class, the grant's `principal` is the record's `downstream`: otherwise
`grant_wrong_principal`;
4. for a retrieval class, the proof whose SHA-256 is `proof` is given (`record_missing`), and
it passes live-rooms §10.5.4 steps 2 to 5 for the grant: it reads (`pop_malformed`), names
the grant (`pop_wrong_grant`), its `key` is the grant's `principal` (`pop_wrong_key`), and
its signature verifies (`pop_bad_signature`);
5. for class 2, the grant holds the seek power (live-rooms §3.3, bit 4): otherwise
`seek_not_granted`.
Steps 6 and 7 of live-rooms §10.5.4, the challenge and the context, were the serving hop's when
the request arrived: a verifier of a statement holds neither the challenge nor the request.
What it holds instead is the rule that one presentation pays for what one session or one
request retrieved: a proof named by entries of two records is refused `pop_replayed` (§8.2
step 4). Revocation through the room authority's key log (live-rooms §5) was the serving hop's
to check at the request, and is not checked again.
## 7. The settlement statement
### 7.1 Layout
Per party and per period, the party signs one statement of what it is owed:
| field | size | meaning |
|---|---|---|
| `magic` | 4 | `WSS1` (ASCII) |
| `version` | 1 | 1 in this revision |
| `party` | 32 | the key the statement settles, which signs it |
| `start`, `end` | 8 each | u64: the period |
| `records` | 2 + 32 × *n* | a u16 count, then the digests of the acknowledgements and declarations naming `party` as `upstream` that it settles, in strictly ascending order |
| `hops` | 2 + 32 × *k* | a u16 count, then the digests of the party's per-hop records (live-metering §3), in strictly ascending order |
| `tariff` | 3 + 32 | `currency` (three capitals `A`–`Z`, ISO 4217; three zero bytes for no tariff), then four u64 prices, one per object class in class order, each in millionths of a minor unit of `currency` per 10^9 bytes |
| `classes` | 64 | for each object class in order, two u64: the acknowledged `objects` and `bytes` |
| `unacknowledged` | 20 | `declarations` (u32), then `objects` and `bytes` (u64 each): the declared delivery (§4) |
| `unmetered` | 2 | u16: the hop records whose figure is the unmetered marker |
| `totals` | 1 + 13 × *m* | a u8 count, then for each acquisition class among the metered hop records: `tag` (u8), `figures` (u32) and `joules` (u64, µJ), in strictly ascending order of `tag` |
| `lines` | 2 + 90 × *p* | a u16 count, then each line (§7.2) |
| `amount` | 8 | u64: the sum of the delivery lines' `amount`, in millionths of a minor unit: what the party is owed |
| `sig` | 64 | §7.4 |
### 7.2 Lines
A line is 90 bytes:
| field | size | meaning |
|---|---|---|
| `kind` | 1 | 1 a delivery line; 2 an attribution line (§7.8) |
| `payee` | 32 | the key paid |
| `class` | 1 | the object class |
| `payer` | 32 | who the line is billed to (below) |
| `objects` | 8 | u64: the acknowledged objects |
| `bytes` | 8 | u64: their acknowledged bytes |
| `amount` | 8 | u64: the line's amount, in millionths of a minor unit |
A **delivery line** (kind 1) has `payee` the statement's `party`, and states, for one object
class and one payer, the objects and bytes of the entries of the statement's acknowledgements
of that class billed to that payer. The **payer** of an entry of a delivery class is its
record's `downstream`, the party that received; of an entry of a retrieval class, its `grant`,
the grant the retrieval is billed to, or zero for a retrieval no grant was presented for. A line
of a retrieval class whose payer is zero is **free**.
An **attribution line** (kind 2) pays a party that is not a hop, the owner of a work that
conditioned an output, from the output's acknowledged deliveries: its `payee` is the key paid,
its `payer` the **basis** it is paid from, an attribution [influence](influence.md) §6.1
defines, named by its link hash, and its `objects`, `bytes` and `amount` are §7.8's. It is what
the statement's party owes, not what it is owed, so it is never in `classes` or `amount`. It sits
beside the delivery lines with no change to the layout.
### 7.3 Tariff and amounts
The tariff gives each object class a price in millionths of a minor unit of `currency` per 10^9
bytes. A line's **amount** is ⌊`bytes` × *p* / 10^9⌋ with *p* its class's price, computed in
integers wide enough not to overflow, and 0 for a free line. The statement's `amount` is the
sum of its lines' amounts. With no tariff every price is 0 and so is every amount. The smallest
price, 1, is one thousandth of a minor unit per 10^12 bytes. An amount is rounded down once per
line, so a statement never states more than its bytes at its prices.
### 7.4 Digest and signature
The **digest** of a statement is SHA-256 over its bytes before `sig`; its **signature** is
Ed25519 by `party` over the 21 bytes `wai:settle-statement` (ASCII) and 0x01, followed by the
bytes the digest covers, verified strictly (SPEC §7 *Signatures*).
### 7.5 Reading and rules
A reader reads exactly the bytes it is given as one statement, as §3.6 reads a record with
`WSS1` for `WDA1` (`truncated`, `malformed`, `unknown_version`), and refuses as
`classes_summed` a statement whose `totals` names tag 0: a total over figures of no one class
(live-metering §4.2). It refuses as `statement_invalid` a statement that breaks any of these:
- `start` is below `end`; `records` holds at least one digest; `records` and `hops` are in
strictly ascending order;
- with no tariff every price is 0; otherwise `currency` is three capitals `A`–`Z`;
- each total's `tag` is 2 to 5, its `figures` above 0, and the totals in strictly ascending
order of `tag`;
- each line's `kind` is 1 or 2, its `class` 1 to 4 and its `objects` above 0; a delivery line's
`payee` is the `party`, and an attribution line's `payee` and `payer` are not zero; the lines
are in strictly ascending order of (`kind`, `payee`, `class`, `payer`).
Then the signature (§7.4): `bad_key`, `bad_signature`.
### 7.6 Recomputation
Every figure of a statement is recomputed from the records it names:
- the **delivery lines**: for each object class and payer among the entries of its
acknowledgements (mode 1), one line, with the sums of those entries' `objects` and `bytes` and
the amount of §7.3. No line is computed from a declaration;
- the **attribution lines**: from each basis its attribution lines name, the lines of §7.8;
- `classes`: for each object class, the sums of its delivery lines' `objects` and `bytes`;
- `unacknowledged`: the number of its declarations (mode 2), and the sums of their entries'
`objects` and `bytes`;
- `unmetered` and `totals`: from its hop records, as live-metering §4.5 computes a receipt's
from its hop entries (§7.7);
- `amount`: the sum of the delivery lines' amounts.
A verifier compares in this order and refuses at the first difference:
1. a free line whose `amount` is not 0: `paid_without_grant`;
2. a delivery line whose class and payer no entry gives, or that states more objects or more
bytes than its entries give, and an attribution line whose basis's output has no delivery in
its class or fewer deliveries or bytes than it states: `bytes_not_acknowledged`, the bytes a
statement bills that no acknowledgement covers;
3. a total that live-metering §4.5 calls summed across classes: `classes_summed`;
4. any other difference, a line missing, fewer bytes than acknowledged, an amount, a class, the
declared figures, `unmetered`, a total: `statement_mismatch`.
A figure that cannot be held in its field is a difference.
### 7.7 Energy
A statement states the party's energy over the period from its per-hop records (live-metering
§3) as live-metering §4.5 states a session's: `unmetered` counts the records whose figure is
the unmetered marker, which are in no total and are never presented as zero joules; each total
is one acquisition class's number of figures and their sum; and nothing sums the figures of two
classes. A record counts when its `hop` is the `party` and its span, placed in time by its
room's `t0`, lies within the period: `t0` + its `start` ≥ the period's `start` and `t0` + its
`end` ≤ the period's `end`. The energy is **whole** when `unmetered` is 0 and at least one
record is named, **partial** when a named record is unmetered, and **none** when no record is
named. Joules are not divided among lines or bytes in this revision: an energy figure says what
the party spent, and a line says what it is owed.
### 7.8 Attribution lines
A **basis** ([influence](influence.md) §6.1) gives: an output's content hash *H*; its **whole**
*T*, in units; and its payable buckets, each a key, its units *u* (at most *T*), a per-use price
*f* (an amount, 0 to 2^53 − 1) and a share *s* in parts per million (0 to 1 000 000), *f* in
millionths of a minor unit of the statement's currency. For the basis and an object class *c*,
over the statement's acknowledgements (mode 1):
- the **deliveries** *D* are the acknowledgements whose class-*c* entries hold an item of *H*;
- the **bytes** *Y* are the sum of the byte counts of those items, and the **paid bytes** *Y*ₚ
the sum over those of them in entries that are not free (a retrieval class with a zero
`grant`, §7.2);
- the **revenue** is *R* = ⌊*Y*ₚ × *p* / 10^9⌋, *p* the class's price (§7.3).
For each key with a payable bucket in the basis and each class with *D* above 0, there is one
attribution line: `payee` the key, `class` *c*, `payer` the basis, `objects` *D*, `bytes` *Y*,
and `amount` the sum over the key's buckets of
```
f × D + ⌊R × s × u / (1 000 000 × T)⌋
```
computed in integers wide enough not to overflow. Each bucket's revenue share is rounded down
once, so no payee is paid more than its share at its price; what the rounding leaves stays with
the party, and is never added to another payee's line. No attribution line is computed from a
declaration, and a key with no payable bucket in the basis has none.
## 8. Verification
### 8.1 Inputs
A verifier is given the statement; the records it names, each with the item list of each of its
entries; the proof records (`WRP1`) its entries name; the per-hop records it names; the
**rooms** the grants and hop records come from, each a room descriptor (live-rooms §3) and the
room's grants in order (live-rooms §4.3); and the **bases** its attribution lines name, each
verified as [influence](influence.md) §6.2 verifies it.
### 8.2 Steps
It runs these steps and stops at the first refusal, which it reports with the reason and where:
1. **The statement** (§7.5), at `statement`: `truncated`, `malformed`, `unknown_version`,
`classes_summed`, `statement_invalid`, `bad_key`, `bad_signature`.
2. **The rooms**, at `room` and the room id: each descriptor reads and verifies (live-rooms §3.4,
with its reasons), and its grants check (live-rooms §4.3, with its reasons, at `grant` and
the grant's index).
3. **Each record**, in the statement's order, at `record` and its index:
1. a record whose digest it names is given: otherwise `record_missing`;
2. it reads and verifies (§3.6): `truncated`, `malformed`, `unknown_version`, `ack_invalid`,
`self_acknowledged`, `bad_key`, `bad_signature`;
3. its `upstream` is the statement's `party`: otherwise `ack_wrong_party`;
4. its window is within the period, `start` ≤ its `start` and its `end` ≤ `end`: otherwise
`ack_outside_period`, an acknowledgement replayed from another period among them;
5. each entry, in order, at `entry` and its index: its item list is given
(`record_missing`), reads (`items_invalid`) and recomputes (`items_mismatch`); and, where
it names a grant, §6.3 steps 1 to 5.
4. **The chains**, at `record` and an index. Order each chain's records (§3.7) in the statement
by (`start`, index). Then, for each record in the statement's order that has a record *p*
before it in that order, *p* the last such: its window overlaps *p*'s (*p*'s `end` is above
its `start`), `ack_overlap`; or its `prev` is not *p*'s digest, `ack_chain_broken`. Then, in
the statement's order, each declaration that breaks §4.2's last rule is
`declared_and_acknowledged`. Then, in the statement's order of records and each record's
order of entries, an entry naming a proof that an entry of an earlier record names is
`pop_replayed`, at `entry` and its index.
5. **Each hop record**, in the statement's order, at `hop` and its index: a record whose digest
it names is given (`record_missing`); it reads as live-metering §3.6 reads a record of the
room its `room` field names, with that section's reasons; that room is given
(`record_missing`); its `hop` is the `party` (`hop_not_party`); and its span is within the
period (§7.7, `span_outside_period`).
6. **The bases** (§7.8), at `basis` and an index *k*: for each payer of an attribution line, in
the order the lines first name it, the *k*-th, the basis is given (`record_missing`). A
verifier of this extension alone holds no basis; influence §6.2 gives each, verified.
7. **The figures** (§7.6), at `statement`: `paid_without_grant`, `bytes_not_acknowledged`,
`classes_summed`, `statement_mismatch`.
A verifier finds a record, a hop record or a proof among the bytes it is given by digest: the
digest of given bytes, of a record, a hop record and a descriptor alike, is SHA-256 over all but
their last 64 bytes, and of a proof record SHA-256 over all of it. A room that does not read is
named by the digest of its descriptor's bytes.
### 8.3 Outcome
- **refuse**: the reason and where.
- **accept**: the statement's digest; its currency; the acknowledged objects and bytes by object
class; each delivery line with its amount and whether it is free; where it has any, each
attribution line with its payee, class, basis, objects, bytes and amount; the declared figures;
the energy's coverage, `unmetered` and the totals by class label; and the amount. Every figure
is what the acknowledgements, the declarations, the hop records and the bases give.
A range proof's verifier (§5.4) reports **refuse** with its reason, or **accept** with the
range, `envelope_len`, the covered chunks, and whether the length was established (§5.5).
## 9. Logs
### 9.1 CDNI logging records
A CDN that settles under this extension and exchanges logs under RFC 7937 maps each item of an
acknowledgement of an HTTP delivery onto one `cdni_http_request_v1` record (RFC 7937 §3.4.1),
or `cdni_http_request_v2` where it uses URI Signing (RFC 9246 §6.2.1):
| RFC 7937 field | what it carries here |
|---|---|
| `date`, `time`, `time-taken` | when the surrogate finished the request; the record's window holds that instant |
| `cs-method`, `protocol`, `sc-status` | the request; `sc-status` 200 for an item of a whole object, 206 for a range, whose `Content-Range` gives the item's `start` and `end` |
| `cs-uri`, `u-uri` | the object's URI; under http-delivery §5.2 it is `.wai`, and the cid is the item's `content_hash` |
| `sc-entity-bytes` | the item's byte count |
| `sc-total-bytes` | the response's bytes with its status line and headers, which an acknowledgement never counts |
| `s-cached` | 1 for an item of class 1, 2 or 4 served from what the surrogate held; 0 when it filled, whose fill is a class 3 entry of the surrogate's own acknowledgement upstream |
| `c-groupid`, `s-ip`, `s-hostname`, `s-port` | no counterpart: an acknowledgement names the downstream party by its key, never by an address |
| `s-sid`, `s-ccid` | no counterpart |
| `cs(WAI-Room-Proof)` | where request headers are logged, the proof of a retrieval (§6.2); the record's `proof` is its SHA-256 |
| `s-uri-signing` (RFC 9246 §4.5) | 200 when the request's URI Signing JWT, mapped from a grant (live-rooms §10.3), verified |
The acknowledgement's digest travels in a `remark` directive (RFC 7937 §3.3), `#remark:
wai-settle-ack `, before the records of its items. RFC 7937's tools
ignore a remark, and a settlement verifier reads it to find the record a log line is covered by.
### 9.2 What a log cannot carry
Every record of an RFC 7937 file is the serving side's statement. The file's `SHA256-hash`
directive is a hash its writer computes over the file, which detects truncation and carries no
key; `claimed-origin` is the writer's claim of who it is, and `established-origin` the
receiver's note of whom its transport authenticated (RFC 7937 §3.3). No field carries a
signature by the party that received the bytes, and no field binds the bytes served to a
content hash. A log is therefore what a declaration is (§4): the delivering party's own word. A
converter maps a log onto declarations, never onto acknowledgements; an acknowledgement's
counter-signature, the downstream party's signature over the content hashes and byte counts, is
what no log field expresses, and it travels beside the log as the record.
### 9.3 Open caching logging and request routing
SVTA2050 extends RFC 7937's pull mode with batch push and streaming transfer, and defines record
types, one of them minimal for billing; it extends the footprint and capabilities interface to
advertise logging. Its body was not read for this revision, only its published summary, so this
revision maps its records only through the RFC 7937 fields they carry (§9.1); a field it adds is
not mapped, and §9.2 holds for it as for any log a serving side writes. SVTA2007 and SVTA2006
define how an upstream CDN delegates requests to an open cache and provisions that delegation. A
request router that delegates or redirects a request moves no bytes and is not a party of a
settlement. A redirected request reaches a new target, so it carries a proof over the new
target's challenge (live-rooms §10.5.3), and a cache that fills for it records that proof (§6.2).
## 10. Viewers as relays over ciphertext
A viewer that forwards objects to other viewers is a hop for them: it is the upstream party of
the acknowledgements they sign, and it is paid for the bytes they acknowledge, on the terms of
any relay. In an end-to-end encrypted room ([confidentiality](confidentiality.md)) the objects
it forwards are sealed, and their content hash is over the sealed bytes (confidentiality §8.1):
- an acknowledgement names the sealed objects by those content hashes, so it binds the
ciphertext, and the downstream party signs for exactly the bytes that travelled;
- a forwarder needs no key of the room's group: what it is paid for, the bytes, and what it
must prove, the range proof of §5, are both over the sealed bytes, so a forwarder that is not
a member, or a member that forwards a layer it does not open, earns acknowledged bytes without
opening anything;
- the publisher's receipts bind the same sealed bytes (confidentiality §8.1), so a downstream
party checks what it received against them before it acknowledges, and an object a forwarder
altered fails its receipt and is never acknowledged.
The corpus holds a viewer's statement for sealed objects it forwarded, and a range proof over a
sealed object.
## 11. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
| `ack_invalid` | §3.6, §8.2 step 3 |
| `self_acknowledged` | §3.6, §8.2 step 3 |
| `items_invalid` | §3.6, §8.2 step 3 |
| `items_mismatch` | §3.6, §8.2 step 3 |
| `ack_wrong_party` | §8.2 step 3 |
| `ack_outside_period` | §8.2 step 3 |
| `ack_overlap` | §3.7, §8.2 step 4 |
| `ack_chain_broken` | §3.7, §8.2 step 4 |
| `declared_and_acknowledged` | §4.2, §8.2 step 4 |
| `range_invalid` | §5.3 |
| `range_outside_object` | §5.3 |
| `range_proof_mismatch` | §5.4 |
| `range_not_acknowledged` | §5.4, §5.6 |
| `statement_invalid` | §7.5, §8.2 step 1 |
| `paid_without_grant` | §6.1, §7.6 |
| `bytes_not_acknowledged` | §7.6 |
| `statement_mismatch` | §7.6 |
| `hop_not_party` | §7.7, §8.2 step 5 |
| `span_outside_period` | §7.7, §8.2 step 5 |
A verifier of this extension also raises `truncated`, `malformed` and `unknown_version` for a
record, a range proof or a statement that does not read; `bad_key` and `bad_signature` for their
signatures; `record_missing` (live-metering §9) for an input not given; `classes_summed`
(live-metering §9) for a total over classes; live-rooms §11's `outside_grant_window`,
`grant_wrong_principal`, `seek_not_granted`, `pop_malformed`, `pop_wrong_grant`,
`pop_wrong_key`, `pop_bad_signature` and `pop_replayed` for a grant and its proof; every code a
descriptor and its grants raise (live-rooms §3.4, §4.3), at `room`; and every code
live-metering §3.6 raises for a per-hop record, at `hop`.
## 12. Conformance
[`live-conformance/settlement/`](../live-conformance/settlement/) holds the corpus, in one file,
`vectors.json`, written from fixed seeds by `wai_settlement_vectors gen`. It holds objects,
rooms, acknowledgements and declarations with their item lists, proof records, per-hop records,
and:
- **statements**, each with the outcome §8.3 gives it. **Accepted**: a relay paid by a cache
downstream of it and by a viewer, with a declaration for a viewer that did not sign and one for
viewers with no key, and its energy in two classes, each in its own total; a cache paid for a
viewer's fetch below the live edge under its grant and proof, a range of an object that viewer
received in part, and a fetch served free; an origin paid for a cache's fills billed to the
grant whose request they filled for, beside a fill no grant was presented for; a viewer paid
for sealed objects it forwarded, under no tariff; and a relay with an unmetered hop record,
whose energy is partial. **Refused**: each reason of §11 at least once, each vector a control
of one defect, among them a statement billing bytes no acknowledgement covers, an
acknowledgement signed by the hop itself, a paid fetch with no grant, a total summing joules
across classes, a statement that does not recompute, and an acknowledgement replayed from
another period; an attribution line whose basis is not given; and codes of the extensions
this one reads. The attribution lines of a statement whose bases are given are
[influence](influence.md)'s corpus.
- **ranges**: range proofs, each checked against a content hash, some as the answer to an audit
of an acknowledged item, with the outcome of §8.3. **Accepted**: a range inside an object
across chunk boundaries, its head, its tail (which establishes the length), a range within one
chunk, a whole object of one chunk and of several, a range of a sealed object, an audit
answered, and an interior range whose stated length is wrong and leaves the tree's shape
unchanged, accepted with its length not established (§5.5). **Refused**: a range past the
object's end, an empty range, bytes of another object at the same offsets, a range past the
true end under a stated length that is longer, a chaining value dropped, added or changed, a
proof for another content hash, an audit answered with a range the item does not cover, and
records that do not read.
A conforming verifier gives every statement and every range proof the outcome, the reason, the
place and the figures the corpus states. `wai_settlement_vectors verify` and the test
`tests/settlement.rs` check that the file regenerates byte for byte, that `wai-rs` gives every
vector its outcome, that every reason of §11 refuses a vector, and that §11 lists exactly the
reference's codes. `live-conformance/settlement_verify.py` is the second implementation, written
from this text: it shares no code with `wai-rs`, verifies signatures with WAI's strict rule
written from RFC 8032 (`claim-conformance/_ed25519.py`), computes BLAKE3 and its chaining values
with `claim-conformance/_blake3.py` (the module the HTTP head proof's verifier reads its tree
from), reads rooms and grants with `live_rooms_verify.py` and per-hop records with
`live_metering_verify.py`, recomputes every figure itself, gives every vector the same outcome,
requires every mutated copy of the corpus to fail, and requires the corpus to fail a copy of
itself with each of its checks dropped.
## 13. Security and privacy considerations
- **Who pays on whose word.** A hop's bill rests on its downstream parties' signatures; a
declaration rests on its own, and is stated apart. A hop that inflates what it delivered can
inflate only its declarations, which bill nothing.
- **Collusion and sybils.** A hop and a downstream party that collude can acknowledge bytes that
never moved, and no record can tell; a range proof shows that a party holds bytes, not that
they crossed a link. A payer therefore honours acknowledgements only from keys it can place: a
hop's key that the hop's origin lists with the authority of its role (jwp-receipts §7.5,
`live/relay`, `live/compositor`, `live/ingest`), and a sink whose entry names a grant, whose
`principal` the acknowledgement must be (§6.3 step 3), which only the room authority can issue.
A statement names every downstream key in its lines, so a payer sees whom it is paying for.
- **Replay.** Windows are signed and chained (§3.7); a record counts only in the period that
holds its window, and a proof pays for one record. A statement for one period cannot reuse a
record of another, and two statements of one party for periods that do not overlap cannot
name one record.
- **Paid retrieval.** A retrieval is billed only to a grant whose holder signed a proof, so a
party that holds no grant cannot run up a grant holder's bill, and a cache cannot bill a fill
to a grant without a proof its holder made. The proof binds a session or a request, not an
object, so a cache holding a proof can name it for any fill of that session's requests: the
rule that one proof pays for one record bounds this to one window.
- **Range proofs.** A proof binds bytes to offsets of a content hash. It establishes an object's
length only from its last chunk (§5.5), and a verifier reports whether it did.
- **What an acknowledgement reveals, and to whom.** An acknowledgement and its settlement are the
mechanics of the delivery service itself, as its receipts are: a sink that takes delivery from a
service signs them as part of that delivery, and this extension sets no gate of its own before
it does. What each party learns:
- the **upstream party** learns, from an acknowledgement signed for it, the downstream party's
key, its window, and the content hashes, byte ranges and counts it received: what that key
received from it, which it delivered and so already knew, now counter-signed;
- a **party verifying a statement** (the payer, or a party settling between them) learns the
same of every record the statement names, the grants and proofs of its retrievals, and the
party's per-hop records; a sink's key and grant name the principal the room authority
granted (live-rooms §13);
- a **party shown only a statement** learns its lines (each downstream key or grant digest, its
object class, objects, bytes and amount), its declared totals and its energy, and no object:
the statement names records by digest and never names an object;
- **nobody else**: the records and item lists travel between the parties that settle, and
nothing here publishes them.
A key per room keeps a viewer's acknowledgements in one room from linking to its others
(live-events §11); a viewer admitted with no key is declared, not acknowledged, and is named by
no key at all. Where a regime asks the operator to state the lawful basis on which it processes
what acknowledgements reveal, the operator states it as a `lawful-basis` declaration
(jwp-receipts §2.4.6), one purpose for settlement with its own basis; this extension does not
restate or check it.
- **What a statement is not.** It is not an invoice and not a payment: it is the recomputable
figures a payment would rest on. Its amount, what the party is owed, is an integer of
millionths of a minor unit, rounded down once per line; its attribution lines are what it owes
the owners of the works that conditioned its outputs, each rounded down once per bucket.