The extension text (extensions/influence.md), the influence module of wai-rs behind the feature influence, its corpus generator and committed corpus; settlement gains attribution lines (kind 2) recomputed from a basis, and contribution notes the sibling record. Co-Authored-By: Claude Opus 5.5 <[email protected]>
776 lines
48 KiB
Markdown
776 lines
48 KiB
Markdown
# 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*<sub>0</sub> = 1024 · ⌊(15 + `start`) / 1024⌋ and *c*<sub>1</sub> = min(*L*, 1024 ·
|
||
⌈(15 + `end`) / 1024⌉), the **covered chunks** are the bytes of `X` from *c*<sub>0</sub> to
|
||
*c*<sub>1</sub>, and `bytes` is the part of them that is envelope: the envelope's bytes from
|
||
max(*c*<sub>0</sub>, 15) − 15 to *c*<sub>1</sub> − 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*<sub>0</sub> is 0 and *c*<sub>1</sub> 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*<sub>0</sub> is a multiple of 1024, and so is *c*<sub>1</sub> 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*<sub>1</sub> 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 `<cid>.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 <digest in hexadecimal>`, 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.
|