Some checks failed
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 7s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 18s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Failing after 1m2s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 2m23s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 3m34s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 5m9s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 5m7s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 5m20s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 7m46s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 8m20s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (push) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Has been cancelled
wai-app and Create / wai-app · does this change touch it (push) Has been cancelled
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
wai-audit collector and player integration / does this change touch it (push) Has been cancelled
wai-audit collector and player integration / collector · linux (push) Has been cancelled
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Has been cancelled
WAI operation catalogue / does this change touch it (push) Has been cancelled
WAI operation catalogue / the catalogue and every door · linux (push) Has been cancelled
wai-fleet service / does this change touch it (push) Has been cancelled
wai-fleet service / service · linux (push) Has been cancelled
wai-fleet service / service · macos (push) Has been cancelled
wai-ledger service / does this change touch it (push) Has been cancelled
wai-ledger service / service · linux (push) Has been cancelled
wai-ledger service / service · macos (push) Has been cancelled
wai-quantum for JavaScript / does this change touch it (push) Has been cancelled
wai-quantum for JavaScript / javascript · linux (push) Has been cancelled
wai-quantum studio and desktop / does this change touch it (push) Has been cancelled
wai-quantum studio and desktop / studio · browser (push) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (push) Has been cancelled
wai-quantum studio and desktop / desktop app · linux (push) Has been cancelled
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai-sink-android native sink library / does this change touch it (push) Has been cancelled
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Has been cancelled
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Has been cancelled
wai-sink-swift native sink package / does this change touch it (push) Has been cancelled
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Has been cancelled
WAI Studio / does this change touch it (push) Has been cancelled
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Has been cancelled
WAI Studio / Studio in Chromium and Firefox · macos (push) Has been cancelled
WAI Studio / Studio in WebKit · linux (push) Has been cancelled
wai-key-proxy / does this change touch it (push) Has been cancelled
wai-key-proxy / proxy · linux (push) Has been cancelled
A .wai file can now be a C2PA asset without changing an envelope byte. extensions/c2pa-binding.md registers the record `wai.asset.c2pa`: a C2PA Manifest Store served beside the object at <cid>.c2pa (application/c2pa, C2PA 2.4 §11.4 and §15.5.3.1; an object's response may name it with Link rel="c2pa-manifest", §15.5.3.2), never inside it, so the object's content hash, its receipts and its head proof stay as they were. http-delivery §8.9 gives the location. The store is live provenance's profile (JUMBF layout, claim, EdDSA COSE claim signature, X.509 chain) with three assertions: c2pa.hash.data over every byte of the envelope with no exclusions and an empty pad (§18.5); c2pa.actions.v2 with the source type the signer states; and science.transaction.wai.envelope, which names the envelope's content hash, its capabilities (a WAI2's in table order) and the JWP group receipt it was delivered under, by link hash (also the name of its rcpt/ file), with that receipt's publisher key. The receipt must be in the labelled payload, whose link hash covers its parent. The manifest identifier is derived from the envelope, the receipt and the claim signer's certificate. A verifier checks the claim and chain (live-provenance §11.2 steps 1-6), the assertions, the identifier, the data hash, the content hash and capabilities, the receipt's link hash, signature and publisher, and the envelope's object receipt under it: seventeen reasons, each with its C2PA §15 code where C2PA gives one. The reference is the c2pa_envelope module of wai-rs (feature c2pa_envelope; writer c2pa_envelope::sign); the corpus is live-conformance/c2pa-envelope/ (stores bound to a WAI1 and to a WAI2 of one picture at two sizes, 27 negatives covering every reason), written from fixed seeds by wai_c2pa_envelope_vectors; c2pa_live_verify.py, the second implementation, now also verifies it: 29 vectors agree, 26 of 26 mutated copies are refused and 17 of 17 weakened verifiers are caught. Fourteen faults planted in the reference (each check of the verification skipped in turn, a legacy receipt, bytes after the envelope, a padded data hash, the identifier without its signer) each fail its tests. The C2PA Rust library (c2pa 0.91.7) reads both good stores beside their envelopes as Trusted with assertion.dataHash.match, and reports assertion.dataHash.mismatch when one envelope byte changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
720 lines
47 KiB
Markdown
720 lines
47 KiB
Markdown
# WAI Extension: Deriving Edge
|
||
|
||
> Status: Draft. Registers the record `wai.edge.derivation`: an edge that derives new objects
|
||
> from objects a publisher delivered, publishes each one under receipts of its own, and signs
|
||
> a **derivation record** that chains it to its source. An edge may evaluate a `wai.derive.*`
|
||
> operation of [derived-tracks](derived-tracks.md) over decoded samples, rebuild an object at a
|
||
> lower rung for a sink that lacks the capability it was coded in (decode a
|
||
> `wai.video.int_motion` clip and write a frame of it as a `wai.image.png` still), or
|
||
> re-package an object without re-encoding it (explode a `WAI2` component into its `WAI1`
|
||
> envelope). The record names each input by its content hash and the delivery receipt that
|
||
> carries it, and by the record that derived it where another edge did; the derivation and its
|
||
> parameters; the output by its content hash and receipt; and the energy the derivation took,
|
||
> with its acquisition class. Its **check** says how a verifier holds the edge to it:
|
||
> `replicate`, when every step is pinned, so any verifier holding the inputs re-executes the
|
||
> derivation and compares the output byte for byte; or `attest`, when a step is not (a lossy
|
||
> classical encode, a resampling), so the output rests on the edge's signature. A sink verifies
|
||
> its object back to its source, hop by hop, and its policy may refuse attested hops. Each
|
||
> record carries forward, by digest, every provenance item its inputs had — the claims about
|
||
> them, the C2PA Manifest Stores bound to or embedded in them, and what their own records carry —
|
||
> in a system or a personal compartment, and an AI-origin label that no hop may lower; a hop
|
||
> that omits an item or lowers a label is refused. Relays
|
||
> still forward every envelope and receipt unaltered ([jwp-receipts](jwp-receipts.md) §4): an
|
||
> edge alters nothing it received; what it derives is a new object of its own. No new
|
||
> transport. The reference implementation is the `derive_edge` module of `wai-rs` (feature
|
||
> `derive_edge`) and its reference edge, `wai_edge`; the corpus is
|
||
> [`edge-conformance/`](../edge-conformance/), and its second implementation is
|
||
> `edge-conformance/edge_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision: the WAI texts this one cites; RFC 8032 and RFC 8785; California
|
||
> Assembly Bill 853 (Statutes of 2025, Chapter 674), as chaptered, for Business and Professions
|
||
> Code §22757.1 (n), (o) and (p) and §22757.3.1; and the C2PA Technical Specification, version
|
||
> 2.4, §A.3.2 (a manifest store in PNG) and §18.15 (actions). The operations' names are
|
||
> ISO/IEC 23001-16:2021's, as [derived-tracks](derived-tracks.md) registers them; this extension
|
||
> defines its own parameters and arithmetic for them (§4.2), and does not read the parts of that
|
||
> standard derived-tracks did not read.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: what an edge may derive and what it must not do (§3); the derivations this revision
|
||
registers, how each reads its inputs, what it computes and how it writes its output, and which
|
||
check each takes (§4); the derivation record, its signature and its identity (§5); how an edge
|
||
publishes what it derives, carries its inputs' provenance forward and keeps their AI-origin
|
||
label (§6); verification of a derived object back to its source (§7); who may sign a record
|
||
(§8).
|
||
|
||
Out of scope: a transport, and how a record reaches a sink, which is the carrier's, as it is for
|
||
receipts (jwp-receipts §1); how a sink asks an edge for a derivation, and which derivations an
|
||
edge offers; how an edge measures its energy, which [energy-measurement](energy-measurement.md)
|
||
governs; presentation, which stays with the sink ([derived-tracks](derived-tracks.md) §1); and
|
||
any derivation this revision does not register.
|
||
|
||
Relationship to the core spec: `wai.edge.derivation` 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. A derived object is an ordinary envelope of its own capability, dispatched by
|
||
SPEC §4 like any other.
|
||
|
||
Relationship to [jwp-receipts](jwp-receipts.md): this extension changes no receipt. A relay
|
||
forwards what it receives as §4 there requires, byte for byte, and §4.3's rules stand: a relay
|
||
re-signs no receipt and rewrites no envelope. An edge that derives is a relay for what it
|
||
forwards and a **publisher** for what it derives: each output is a new object with its own
|
||
content hash, in a group the edge signs with its own key (§6). The derivation record is the
|
||
separate, parallel statement §4.3 there asks of a relay that attests, and §7.5 there registers
|
||
its authority (§8).
|
||
|
||
Relationship to [derived-tracks](derived-tracks.md): an operation of §4.2 is that extension's
|
||
operation of the same capability string, executed under conditions this profile fixes, so that
|
||
derived-tracks §3 gives the output's tier from its inputs' (§4.5). The record carries the
|
||
content derived-tracks §4 lists for a receipt over a derived sample, in a layout of its own:
|
||
the executed operation, its parameters, the frame each input was read at, the inputs by
|
||
content hash, and, where the check is `replicate`, the output.
|
||
|
||
Relationship to SPEC §7.1: an output of one input carries `derived_from`, naming that input as
|
||
its motif (§4.3). A sink presents it as what it is, a derived object; it does not reconstruct
|
||
its motif.
|
||
|
||
## 2. Terms
|
||
|
||
- **Edge**: a party that derives objects from objects it received and publishes them; named by
|
||
its Ed25519 key (RFC 8032).
|
||
- **Derivation**: one of §4's, evaluated once: a set of inputs, its parameters, one output.
|
||
- **Input**, **output**: the objects a derivation reads and the object it writes, each a whole
|
||
`WAI1` or `WAI2` envelope (SPEC §2, §7.1).
|
||
- **Record**: the derivation record of §5. Its **hash** is its identity (§5.2).
|
||
- **Source object**: an input whose delivery receipt is signed by a key the verifier takes as a
|
||
source: a publisher it subscribed to (§7.1).
|
||
- **Hop**: a record. The **chain** of a derived object is its record, and, for each of that
|
||
record's inputs that an edge derived, the chain of that input, down to source objects.
|
||
- **Samples**: an image as the canonical `RGB8` buffer of
|
||
[reconstruction-binding](reconstruction-binding.md) §2: row-major, three bytes a pixel, no row
|
||
padding. A pixel is addressed (x, y), x to the right and y down from the top-left. The
|
||
**samples digest** is SHA-256 (FIPS 180-4) over the buffer.
|
||
- **Content hash**: jwp-receipts §2.1. A group receipt's **link hash**: jwp-receipts §3 step 3.
|
||
|
||
The record is JSON under the encodings of jwp-receipts §2.4.3: lowercase hexadecimal byte
|
||
strings, unsigned integers of at most 2^53 − 1 in plain decimal, no other number, no boolean,
|
||
terms and date-times as defined there, no member named twice. JCS is the canonical form of
|
||
that section (RFC 8785).
|
||
|
||
## 3. What an edge may derive
|
||
|
||
### 3.1 Three kinds
|
||
|
||
1. **Evaluate an operation.** Read each input as samples (§4.1), evaluate one operation of §4.2
|
||
over them, and write the result in an image capability (§4.3).
|
||
2. **Rebuild at a lower rung.** Read an object coded in a capability a sink lacks, and write it
|
||
in a capability of SPEC §5's mandatory floor or recommended set: a frame of a
|
||
`wai.video.int_motion` clip, as a `wai.image.png` still or a `wai.image.jpeg` picture. A
|
||
rebuild is an evaluation of `wai.derive.identity` over one frame (§4.2): the read and the
|
||
write change the capability, and the operation changes no sample. It serves a sink at which
|
||
the object is inert (SPEC §4 step 6).
|
||
3. **Re-package.** Change the container without decoding: `wai-edge:explode` writes one
|
||
component of a `WAI2` envelope as its exploded `WAI1` envelope (SPEC §7.1, *Exploding a
|
||
component*; §4.4). The output's payload is the component's bytes.
|
||
|
||
### 3.2 What an edge does not do
|
||
|
||
An edge **MUST NOT**:
|
||
|
||
- alter an envelope or a receipt it received, or forward one under its own signature
|
||
(jwp-receipts §4.3);
|
||
- publish an output under any key but its own, or in a group of another key's track (§6);
|
||
- present an output as the object it was derived from: an output has its own content hash and
|
||
its own receipts, and the source's receipts never cover it;
|
||
- sign a record over a derivation this revision does not register, or over an object it did not
|
||
derive;
|
||
- sign `replicate` for a derivation whose check is `attest` (§4.5);
|
||
- leave out of its record a provenance item an input had, or label its output lower than an
|
||
input (§6.1, §6.2).
|
||
|
||
### 3.3 What an edge derives from
|
||
|
||
An edge derives only from objects it has verified: each input's delivery receipts verify
|
||
(jwp-receipts §3 steps 1–5), its envelope hashes to its content hash, and, for an input another
|
||
edge derived, its chain verifies by §7. It names in its record, for each input, the record that
|
||
derived it, so that a sink can follow the chain (§7.3).
|
||
|
||
## 4. The derivations
|
||
|
||
| derivation | inputs | parameters (§4.2) | output capability | check (§4.5) |
|
||
|---|---|---|---|---|
|
||
| `wai.derive.identity` | 1 | none | `wai.image.png`, `wai.image.jpeg` | `replicate` for `wai.image.png` with no encoder; else `attest` |
|
||
| `wai.derive.crop` | 1 | `x`, `y`, `w`, `h` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.roi_selection` | 1 | `x`, `y`, `w`, `h` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.rotation` | 1 | `quarter_turns` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.mirror` | 1 | `flip` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.grid_composition` | `columns` × `rows` | `columns`, `rows` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.overlay_composition` | 1 to 16 | `width`, `height`, `fill`, `at` | `wai.image.png`, `wai.image.jpeg` | as identity |
|
||
| `wai.derive.scaling` | 1 | `width`, `height` | `wai.image.png`, `wai.image.jpeg` | `attest` |
|
||
| `wai-edge:explode` | 1 | `component` | the component's capability | `replicate` |
|
||
|
||
A derivation is named by the string in its first column. The `wai.derive.*` strings are
|
||
derived-tracks' registered operations; `wai-edge:explode` is a term of this extension and not a
|
||
capability string. The other operations derived-tracks registers are not derivations of this
|
||
revision: `wai.derive.srgb_fill` reads no input, so an output of it chains to no source, and
|
||
`wai.derive.dissolve` has a time structure of its own, which an input read at one frame does
|
||
not give it.
|
||
|
||
### 4.1 Reading an input
|
||
|
||
An evaluation reads each input at one frame, the input's `frame` (§5.1), as samples:
|
||
|
||
- `wai.image.png`: frame 0, read by the still reader of [packaging](packaging.md) §5 (*A piece's ladder, and stills*) as 8-bit
|
||
RGB; the envelope **MUST** hold no chunk but `IHDR`, `IDAT`, `IEND` and `caBX`. An ancillary
|
||
chunk can state a colour representation the samples do not carry (`cHRM`, `gAMA`, `iCCP`,
|
||
`sRGB` and others), and this profile's representation is the samples alone. A `caBX` chunk
|
||
holds a C2PA Manifest Store (C2PA 2.4 §A.3.2): it describes the content and changes none of
|
||
its samples, and the record carries it forward (§6.1).
|
||
- `wai.video.int_motion`: the `WIV1` payload with keyframe 0 (`integer-payloads.md` §3),
|
||
decoded in full by its pinned decode; frame `frame` of the clip, which **MUST** be one of its
|
||
frames. A clip with keyframe 1 decodes through a parameter set, and is not read here.
|
||
|
||
An input of either capability holds `decode-equivalence` (determinism-tiers §2): every
|
||
conforming reader reads the same samples. An input that does not read as its record states it
|
||
— another capability, a frame it does not have, a payload its reader refuses, a chunk this
|
||
section excludes — is refused (`input_unreadable`).
|
||
|
||
### 4.2 The operations
|
||
|
||
Each operation computes its output samples from its inputs' samples and its parameters by the
|
||
rule below, and no other arithmetic. *W* × *H* is an input's width and height; out(i, j) is the
|
||
output pixel at (i, j) and in(x, y) an input's, all three bytes of it. An output's width and
|
||
height are each at least 1, and its samples at most 2^26 bytes; a parameter the inputs cannot
|
||
satisfy (a region outside an input, an output past that bound) is refused (`params_invalid`)
|
||
when the inputs are evaluated (§7.2 step 5).
|
||
|
||
- `wai.derive.identity`, no parameter (`{}`): out = in.
|
||
- `wai.derive.crop` and `wai.derive.roi_selection`, `{ "x", "y", "w", "h" }`: `w` and `h` at
|
||
least 1, `x + w ≤ W`, `y + h ≤ H`; the output is `w` × `h` and out(i, j) = in(x + i, y + j).
|
||
This profile emits an ROI at its own size, so the two operations compute the same samples;
|
||
each is named by its own string.
|
||
- `wai.derive.rotation`, `{ "quarter_turns" }`, 1, 2 or 3 quarter turns clockwise. For 1 and 3
|
||
the output is *H* × *W*, for 2 *W* × *H*; out(i, j) is in(j, H − 1 − i) for 1,
|
||
in(W − 1 − i, H − 1 − j) for 2 and in(W − 1 − j, i) for 3.
|
||
- `wai.derive.mirror`, `{ "flip" }`, `left-right` or `top-bottom`: out(i, j) is in(W − 1 − i, j)
|
||
or in(i, H − 1 − j).
|
||
- `wai.derive.grid_composition`, `{ "columns", "rows" }`, each at least 1, over exactly
|
||
`columns` × `rows` inputs of one size *W* × *H*: input *k* fills the cell at column
|
||
*k* mod `columns` and row ⌊*k* / `columns`⌋ of a `columns`·*W* × `rows`·*H* output. Inputs of
|
||
different sizes are refused (`params_invalid`).
|
||
- `wai.derive.overlay_composition`, `{ "width", "height", "fill", "at" }`: a canvas of
|
||
`width` × `height`, each at least 1, every pixel `fill` (six lowercase hexadecimal digits, the
|
||
red, green and blue bytes); then each input *k*, in order, written over it at its own size
|
||
with its top-left at `at[k]` (`{ "x", "y" }`, one entry per input), opaquely: out(x + i, y + j)
|
||
= in_k(i, j) for every pixel of the input that falls on the canvas. Pixels off the canvas are
|
||
dropped.
|
||
- `wai.derive.scaling`, `{ "width", "height" }`, each at least 1: the input resampled to
|
||
`width` × `height`. This revision pins no resampling arithmetic, so the samples are the
|
||
edge's (§4.5). The reference edge takes the nearest sample,
|
||
in(⌊i · W / `width`⌋, ⌊j · H / `height`⌋); a verifier does not hold an edge to it.
|
||
|
||
Under these rules every operation but scaling is **exact** for the instance it executes, in the
|
||
sense of derived-tracks §2: its inputs and output are 8-bit RGB with no stated colour
|
||
information, and the canvas fill is a constant the record names (same-representation); it
|
||
copies samples and computes none (copies-only); each input is one frame, and the parameters
|
||
have one value (time-invariant); a region or a cell lies on the sample grid, a rotation is a
|
||
multiple of 90° over one resolution, a reflection maps the grid onto itself, and a composed
|
||
input is placed at its own size at integer offsets, opaquely. Scaling computes new sample
|
||
values, and derived-tracks registers it untiered.
|
||
|
||
### 4.3 Writing the output
|
||
|
||
The output of an evaluation is a `WAI1` envelope whose manifest is, with no other bytes and no
|
||
whitespace, members in this order:
|
||
|
||
```
|
||
{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{"capability":C},
|
||
"conditioning":{"kind":K},"derived_from":{"motif":M,"motif_capability":MC,"renderer":R,
|
||
"settings":S},"target":{"frames":1,"h":HEIGHT,"w":WIDTH}}
|
||
```
|
||
|
||
(shown here on three lines). `C` is the output capability and `K` its last dot-separated
|
||
segment (`png`, `jpeg`); `HEIGHT` and `WIDTH` are the output's. `derived_from` (SPEC §7.1) is
|
||
written exactly when the derivation has one input: `M` and `MC` are that input's content hash
|
||
and capability, `R` is the derivation followed by `@1` when the record names no encoder and the
|
||
encoder's `id` otherwise, and `S` is `BLAKE3("wai:render-settings\x01" ‖ JCS(settings))` with
|
||
`settings` the object `{"derivation", "encoder", "frame", "params"}` of the record's members of
|
||
those names and the input's `frame`. An output of several inputs carries no `derived_from`: it
|
||
presents several motifs, and its record names them all. Strings are written as JSON strings,
|
||
integers in decimal; nothing in them needs an escape.
|
||
|
||
The payload:
|
||
|
||
- `wai.image.png`, the record naming no encoder: the samples as packaging §5's still writer
|
||
writes them, every byte a pure function of the samples;
|
||
- otherwise the output of the encoder the record names, run with the settings it states: a PNG
|
||
of the samples for `wai.image.png`, a baseline JPEG of them (ITU-T T.81, SPEC §5) for
|
||
`wai.image.jpeg`. This revision pins no JPEG writer, so a `wai.image.jpeg` output always names
|
||
one.
|
||
|
||
### 4.4 Re-packaging
|
||
|
||
`wai-edge:explode`, `{ "component" }`, reads one input, a `WAI2` envelope, and writes the
|
||
exploded envelope of its entry number `component` (from 0), exactly as SPEC §7.1 defines it:
|
||
the entry's `envelope_manifest` where it carries one, and otherwise the manifest that section
|
||
assembles from the `WAI2` manifest's members, over the entry's rendition bytes. Nothing is
|
||
decoded and no sample is read. The output's capability is the entry's, and the input's
|
||
`capability` in the record names it too. An entry that cannot be exploded, or a `component`
|
||
past the last entry, is refused (`input_unreadable`). A component and its exploded envelope are
|
||
the same object for every rule that names an object by content hash (SPEC §7.1), so an exploded
|
||
output is the object a sink would have taken from the `WAI2`, now carried on its own, under the
|
||
edge's receipts.
|
||
|
||
### 4.5 The check
|
||
|
||
A derivation's check is `replicate` exactly when every step of it is pinned: the reading of its
|
||
inputs (§4.1 pins every reading this revision has), its operation, which is exact (§4.2), and
|
||
its writer, which is packaging §5's still writer for a `wai.image.png` output whose record
|
||
names no encoder, or SPEC §7.1's explode. Otherwise its check is `attest`:
|
||
|
||
- **replicate**: a verifier holding every input re-executes the derivation and requires the
|
||
output envelope, byte for byte (§7.2 step 5). Over inputs at `decode-equivalence` the output's
|
||
samples hold `decode-equivalence` too (derived-tracks §3 rule 1), and so does the output: PNG
|
||
is lossless.
|
||
- **attest**: the step that is not pinned rests on the edge's signature. A verifier still checks
|
||
every input, and every step before the first that is not pinned: the inputs' samples, and,
|
||
where the operation is exact, the samples the edge gave its encoder. For any
|
||
`wai.image.png` output, attested or not, it checks that the output's samples are the ones
|
||
the record states (§7.2 step 3). It never checks the attested step itself.
|
||
|
||
A record's `check` **MUST** be its derivation's (`check_mismatch`): one derivation has one
|
||
check, and a sink reads from the record what it is relying on.
|
||
|
||
## 5. The derivation record
|
||
|
||
### 5.1 Members
|
||
|
||
A record is a JSON object with exactly these members, under §2's encodings:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-derivation"`. |
|
||
| `derivation` | term | A derivation of §4. |
|
||
| `params` | object | Its parameters, exactly the members §4.2 or §4.4 gives it, each an integer, a string, or for `at` an array of `{ "x", "y" }`. |
|
||
| `encoder` | object or `null` | `null` where the output is written by a writer this revision pins (§4.3, §4.4); otherwise `{ "id": term, "settings": object }`, the encoder the edge ran and the settings it ran it with, its statement. |
|
||
| `check` | string | `replicate` or `attest` (§4.5). |
|
||
| `inputs` | array | 1 to 16 inputs, in the order the derivation reads them (below). |
|
||
| `output` | object | The output (below). |
|
||
| `energy` | object or `null` | `null` when the derivation was unmetered. Otherwise `{ "joules_micro", "energy_provenance", "energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref" }`, each as jwp-receipts §2.4.5 writes it: the energy the edge spent on this derivation, reading, operating and writing, with its acquisition class. |
|
||
| `provenance` | array | At most 1024 provenance items carried forward (§6.1, below), in ascending order of `input`, then `kind` as a string, then `digest`, none twice. |
|
||
| `synthesis` | string or `null` | The output's AI-origin label (§6.2): `assist`, `generate`, or `null` for none. |
|
||
| `issued_at` | date-time | When the edge signed the record. Self-asserted. |
|
||
| `signer_pubkey` | 32 B hex | The edge's key. |
|
||
| `signer_id` | string | The edge's identity string, as for a group receipt (jwp-receipts §2.4.1, §7.1). |
|
||
| `sig` | 64 B hex | §5.2. |
|
||
|
||
An input:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `content_hash` | 32 B hex | The input's content hash. |
|
||
| `capability` | term | The capability the derivation read it as (§4.1), or, for `wai-edge:explode`, the capability of the entry exploded. |
|
||
| `receipt` | 32 B hex | The link hash of the group receipt that carries it. |
|
||
| `object_id` | integer | Its `object_id` in that group. |
|
||
| `frame` | integer or `null` | The frame it was read at (§4.1); `null` for `wai-edge:explode`. |
|
||
| `samples` | 32 B hex or `null` | The samples digest of what was read; `null` for `wai-edge:explode`. |
|
||
| `derivation` | 32 B hex or `null` | The hash of the record that derived it, or `null` for an object the edge received from its publisher. |
|
||
| `synthesis` | string or `null` | The input's AI-origin label (§6.2), as `synthesis` above. |
|
||
|
||
A provenance item:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `input` | integer | The position, among the inputs, of the input it came with. |
|
||
| `kind` | string | `claim`: a claim about that input (jwp-receipts §2.4.3); `c2pa`: a C2PA Manifest Store bound to it. |
|
||
| `digest` | 32 B hex | A claim's link hash (jwp-receipts §2.4.3); a manifest store's SHA-256 over its bytes. |
|
||
| `compartment` | string | `system` or `personal` (§6.1). |
|
||
|
||
The output:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `content_hash` | 32 B hex | The output's content hash. |
|
||
| `capability` | term | Its capability. |
|
||
| `receipt` | 32 B hex | The link hash of the edge's group receipt that carries it (§6). |
|
||
| `object_id` | integer | Its `object_id` in that group. |
|
||
| `width`, `height` | integer or `null` | Its size; `null` for `wai-edge:explode`. |
|
||
| `samples` | 32 B hex or `null` | The samples digest of the samples written: those the operation produced, which an attested encoder was given; `null` for `wai-edge:explode`. |
|
||
|
||
### 5.2 Signature and identity
|
||
|
||
```
|
||
sig = Ed25519(signer_key, "wai:derivation\x01" || JCS(record without "sig"))
|
||
record hash = BLAKE3("wai:derivation-link\x01" || JCS(record))
|
||
```
|
||
|
||
The signature is verified under jwp-receipts §3.1 step 3's strict rule. A record **SHOULD** be
|
||
sent in canonical form; relays forward it byte for byte.
|
||
|
||
### 5.3 Reading and rules
|
||
|
||
A reader reads a record and refuses at the first of these that holds:
|
||
|
||
1. It is not one JSON text of the shape §5.1 gives, with every member of the right type and
|
||
encoding, no member missing and none added, at any level, `kind` `"wai-derivation"`, and its
|
||
provenance items in their order, none twice, each naming an input the record has
|
||
(`record_malformed`).
|
||
2. Its `derivation` is not one of §4 (`derivation_unknown`).
|
||
3. Its `params` do not hold exactly the derivation's members, each in its range (§4.2, §4.4):
|
||
`w`, `h`, `width`, `height`, `columns` and `rows` at least 1; `quarter_turns` 1, 2 or 3;
|
||
`flip` one of its two values; `fill` six lowercase hexadecimal digits; `at` one entry per
|
||
input, each `{ "x", "y" }`; a size the parameters fix (a region's, a canvas's, a scaling's)
|
||
within 2^26 sample bytes. Or its `encoder` is `null` where §4.3
|
||
pins no writer (a `wai.image.jpeg` output), or not `null` for `wai-edge:explode`. Or its
|
||
output capability is not one the derivation writes (§4), or, for `wai-edge:explode`, not the
|
||
input's (`params_invalid`).
|
||
4. Its inputs are not the derivation's: another number of them (`columns` × `rows` for a grid),
|
||
an input of a capability §4.1 does not read, a `frame`, input `samples`, output `width`,
|
||
`height` or `samples` that is `null` for an evaluation or not `null` for an explode, or a
|
||
`frame` other than 0 for a `wai.image.png` input (`inputs_invalid`).
|
||
5. Its `check` is not its derivation's (§4.5) (`check_mismatch`).
|
||
6. Its `energy` breaks [energy-measurement](energy-measurement.md) §2: a figure of 0 beside a
|
||
class, the legacy `HwShunt`, a class that does not read, a hardware class without its
|
||
evidence or below the resolution floor (§2.3 there) (`energy_invalid`).
|
||
7. Its `synthesis` is lower than an input's (§6.2) (`synthesis_dropped`).
|
||
|
||
## 6. Publishing the output
|
||
|
||
An edge publishes each output on a track of its own, under its own key, as a publisher does
|
||
(jwp-receipts §2): a group receipt it signs and an object receipt for the output, whose
|
||
`content_hash` is the output's. The record's `output.receipt` and `output.object_id` name them.
|
||
|
||
- The group receipt's `signer_pubkey` is the record's (`output_receipt_mismatch`).
|
||
- The object receipt's `joules_micro` is the record's figure, 0 where `energy` is `null`. Where
|
||
the object receipt carries a class label (jwp-receipts §2.4.2), the label is the record's
|
||
class and evidence, or the unmetered marker where `energy` is `null`
|
||
(`energy_disagrees`). An edge **SHOULD** label its object receipts, and sign its groups over
|
||
the labelled payload.
|
||
- The output is a new object: its receipts bind it to the edge's key and nothing else. What
|
||
binds it to its source is the record.
|
||
|
||
An edge serving a derived object **MUST** offer its subscriber the record of each hop of the
|
||
object's chain, the delivery receipts each record names and the provenance items each carries
|
||
(§6.1), by the same carriage as the object's receipts. Without them a sink can verify the
|
||
object's delivery from the edge and nothing about where it came from. It forwards the source's
|
||
receipts unaltered (jwp-receipts §4.1).
|
||
|
||
### 6.1 Provenance carried forward
|
||
|
||
A derivation makes a new object, and nothing the source carried is in it unless the edge puts
|
||
it there: a still the pinned writer writes holds no chunk but its picture, and an explode writes
|
||
a new envelope. So a record carries its inputs' provenance forward, by reference.
|
||
|
||
The **provenance items** of an input are, of what the edge received:
|
||
|
||
- each claim (jwp-receipts §2.4.3) whose `subject_type` is `object` and whose `subject` is the
|
||
input's content hash, named by its link hash;
|
||
- each C2PA Manifest Store bound to the input, carried beside it (as
|
||
[c2pa-binding](c2pa-binding.md) binds one to an envelope) or embedded in it (for
|
||
`wai.image.png`, the data of a `caBX` chunk, C2PA 2.4 §A.3.2), named by its SHA-256;
|
||
- for an input another edge derived, every item that edge's record carries, whatever input of
|
||
it the item came with.
|
||
|
||
A record **MUST** list every provenance item of every input, each with the input it came with
|
||
and its compartment, and an item that a record below it carries keeps that record's
|
||
compartment. Listing an item copies nothing of it: the record names it by digest, the edge
|
||
offers the item with the chain (§6), and the item's own signature, where it has one, is what
|
||
holds it to what it says. An item the record does not list, of any compartment, has been
|
||
stripped (`provenance_stripped`).
|
||
|
||
The **compartment** is the edge's reading of an item, in the split California Business and
|
||
Professions Code §22757.1 makes: **personal** where the item holds personal information, or
|
||
unique device, system or service information reasonably capable of being associated with a
|
||
particular user (§22757.1(n)), and **system** otherwise: information about the type of device,
|
||
system or service that made the content, or about its authenticity (§22757.1(p)). Information
|
||
inside a digital signature is not personal there (§22757.1(n)(2)), so a signature is system
|
||
provenance. A store embedded in an input the edge reads as system provenance unless it knows
|
||
otherwise.
|
||
|
||
This revision carries every item, personal ones included: an edge strips none, and a sink holds
|
||
the chain's items from the edge that served it. Redacting a personal item under a stated rule,
|
||
with a salted commitment that accounts for the removal so that it is never silent, is a later
|
||
revision's; until it is registered, a personal item is carried as a system one is. A C2PA
|
||
manifest an edge emits for its output names each manifest store of its inputs as an ingredient,
|
||
`parentOf` for a derivation of one input (C2PA 2.4 §18.15: `c2pa.transcoded` for a rebuild,
|
||
`c2pa.repackaged` for an explode); the reference edge emits none.
|
||
|
||
### 6.2 The AI-origin label
|
||
|
||
A record states, for each input and for its output, an **AI-origin label**: `null` where no
|
||
label is known, or, in rising order, `assist` (AI altered the content and generated none of it)
|
||
or `generate` (AI generated the content, or part of it).
|
||
A label may rise along a chain and never fall:
|
||
|
||
- an input another edge derived is labelled at least as that edge's record labels its output
|
||
(`synthesis_dropped`); a source object is labelled as its provenance states, read by the edge;
|
||
- the output is labelled at least as every input (§5.3 rule 7, `synthesis_dropped`), and at
|
||
least as the derivation's own step warrants: an edge whose step synthesizes content (a
|
||
learned upscaler behind an attested scaling, a generated frame) labels its output at least
|
||
`assist`, or `generate` where it generated content.
|
||
|
||
Every derivation this revision pins copies samples or containers and synthesizes nothing, so it
|
||
raises no label; an attested step is labelled by its edge, and a verifier checks that no label
|
||
falls, not what an attested step did. The two labels are the two highest classes of an
|
||
origin claim's order of steps, capture, convert, assist and generate, in which every derivation
|
||
this revision pins is a convert step; a later revision may carry such a claim as a provenance
|
||
item, name the lower classes, and map them onto the digital source types an export writes.
|
||
|
||
## 7. Verification
|
||
|
||
### 7.1 What a verifier holds
|
||
|
||
- **records**: record texts, the subject's among them, and the records of its chain that it
|
||
holds;
|
||
- **receipts**: group and object receipts (jwp-receipts §2.4.1, §2.4.2);
|
||
- **objects**: envelopes it holds, each by the content hash it was given under;
|
||
- **sources**: the publisher keys it subscribed to;
|
||
- **heads**: for each origin it holds one for, the head revision of that origin's key log
|
||
(jwp-receipts §7.2);
|
||
- **provenance**: the claims it holds (jwp-receipts §2.4.3), and the C2PA Manifest Stores it
|
||
holds bound to an object, by the object's content hash;
|
||
- **policy**, three members:
|
||
|
||
| member | values | default | meaning |
|
||
|---|---|---|---|
|
||
| `attested` | `accept`, `refuse` | `accept` | `refuse`: a chain with an `attest` hop is refused (`attested_refused`). |
|
||
| `recompute` | `when-held`, `required` | `when-held` | `required`: a `replicate` hop whose inputs are not all held is refused (`inputs_not_held`); `when-held`: it is accepted as `not-recomputed`. |
|
||
| `authority` | `when-given`, `required` | `when-given` | `required`: a hop that is not confirmed under §8 is refused (`edge_not_authorised`). |
|
||
|
||
A record that reads as a JSON text is held under its hash (§5.2), whether or not it then passes
|
||
§5.3, so that a hop that is held and broken is refused for what it breaks.
|
||
|
||
### 7.2 One record
|
||
|
||
The verifier verifies the subject's record by these steps, and an input's record by the same
|
||
steps when step 4 reaches it. A record verified once is not verified again. At the first step
|
||
that fails, the verification is refused with that step's reason, at that record.
|
||
|
||
1. **Reading** (§5.3): `record_malformed`, `derivation_unknown`, `params_invalid`,
|
||
`inputs_invalid`, `check_mismatch`, `energy_invalid`, `synthesis_dropped`.
|
||
2. **Signature** (§5.2): a key that fails RFC 8032 §5.1.3's decoding or is of small order is
|
||
`bad_key`; a signature that fails, `bad_signature`.
|
||
3. **Output.** The group receipt whose link hash is `output.receipt`, and the object receipt of
|
||
that group at `output.object_id`, are held (`output_receipt_not_held`); the group verifies
|
||
(jwp-receipts §3 steps 1–2), the object receipt is included at its position (§3 step 5
|
||
there), and its `content_hash` is the output's (`output_receipt_invalid`); §6's rules hold
|
||
(`output_receipt_mismatch`, `energy_disagrees`). Where the output envelope is held: it hashes
|
||
to `output.content_hash` (`output_hash_mismatch`); its capability is `output.capability`,
|
||
and, for an evaluation, its manifest is the one §4.3 writes from the record
|
||
(`output_manifest_mismatch`); and, for a `wai.image.png` output, the samples it reads as
|
||
(packaging §5's still reader) are of the record's `width` and `height` and have its `samples`
|
||
digest (`samples_mismatch`): PNG is lossless, so this holds whoever wrote it.
|
||
4. **Each input**, in order:
|
||
1. its group receipt (by `receipt`) and its object receipt (at `object_id`) are held
|
||
(`input_receipt_not_held`); the group verifies, the object receipt is included at its
|
||
position, and its `content_hash` is the input's (`input_receipt_invalid`);
|
||
2. where `derivation` is `null`, the group receipt is signed by a source key; otherwise an
|
||
edge's output is passed off as a source object, and a hop is missing (`chain_skips_hop`);
|
||
3. where `derivation` is not `null`: the input's chain is not longer than 8 hops, the
|
||
subject's record being the first (`chain_too_long`); the record of that hash is held
|
||
(`hop_not_held`) and reads (§5.3, refused at that record); its output's `content_hash`,
|
||
`capability`, `receipt` and `object_id` are the input's (`hop_mismatch`); and it is
|
||
verified by these steps, whose refusal, at that record, is this verification's;
|
||
4. where the input envelope is held: it hashes to `content_hash` (`input_hash_mismatch`); it
|
||
reads as §4.1 or §4.4 has it (`input_unreadable`); and the samples it reads at `frame`
|
||
have the digest `samples` (`samples_mismatch`);
|
||
5. its provenance (§6.1): each claim the verifier holds about it, each manifest store the
|
||
verifier holds bound to it, and, where the input envelope is held, each store embedded in
|
||
it, is an item of the record for this input, of that kind and digest; and, where an edge
|
||
derived it, every item of that edge's record is one too, with its compartment
|
||
(`provenance_stripped`). Where an edge derived it, its `synthesis` is no lower than that
|
||
edge's record's (§6.2, `synthesis_dropped`).
|
||
5. **Recomputation**, where every input is held. For a `replicate` record: evaluate the
|
||
derivation (§4.2–§4.4) over the inputs (`params_invalid` for a parameter they cannot
|
||
satisfy); the output's samples digest, size and envelope **MUST** be the record's, byte for
|
||
byte (`replicate_mismatch`). For an `attest` record: where the operation is exact, evaluate
|
||
it, and the output's `samples`, `width` and `height` **MUST** be the record's
|
||
(`samples_mismatch`); where it is not, nothing of it is recomputed. Where an input is not
|
||
held, a `replicate` record is `not-recomputed`, and refused under the policy
|
||
`recompute: required` (`inputs_not_held`).
|
||
6. **Authority** (§8): `key_log_not_held`, `edge_not_authorised`, `edge_revoked`.
|
||
7. **Policy**: an `attest` record under `attested: refuse` is `attested_refused`.
|
||
|
||
A refusal names its place: `["record", r]` for steps 1 and 2, `["record", r, "output"]` for
|
||
step 3, `["record", r, "input", i]` for step 4, `["record", r, "recompute"]` for step 5,
|
||
`["record", r, "authority", origin]` for step 6, an origin of §8 or, where the hop has none,
|
||
`null`, and `["record", r, "policy"]` for step 7, with *r* the record's position among the
|
||
records given and *i* the input's among its inputs.
|
||
|
||
### 7.3 The chain
|
||
|
||
Step 4 follows each derived input to the record that derived it, and that record's inputs to
|
||
theirs, until every input is a source object. Each hop is verified on its own, against its own
|
||
receipts and inputs. A hop that names a record the verifier does not hold is refused, not
|
||
skipped: a sink that cannot follow a chain to its source knows only that the edge delivered the
|
||
object (§6). A chain cannot loop, since a record names its inputs' records by hash and no hash
|
||
can name a record that names it; the bound of 8 hops is a bound on work.
|
||
|
||
What a verified chain establishes: the subject's object was derived, hop by hop, from the
|
||
source objects the chain reaches, by the derivations its records name with the parameters they
|
||
state, each signed by the edge that made it; each `replicate` hop whose inputs were held was
|
||
re-executed and matched byte for byte; each `attest` hop rests, at its attested step, on its
|
||
edge's signature; and each source object is one its publisher signed for delivery. It does not
|
||
establish that the publisher meant the object to be derived from; that is §8's.
|
||
|
||
### 7.4 Outcome
|
||
|
||
A verifier states `refuse` with a reason and its place, or `accept` with:
|
||
|
||
- `subject`: the subject's record hash;
|
||
- `hops`: each record verified, in the order first reached, each with its `record` hash, its
|
||
`signer` key, its `derivation`, its `check`, `checked` — `recomputed` (a `replicate` record
|
||
re-executed), `not-recomputed` (a `replicate` record with an input not held) or `attested` —
|
||
its `energy`, `null` or `{ "joules_micro", "class" }`, its `authority`: `confirmed`,
|
||
`unconfirmed` or `none` (§8), and its `synthesis` (§6.2);
|
||
- `sources`: each source object reached, in the order first reached, with its `object` content
|
||
hash and the `signer` key and `signer_id` of its group receipt;
|
||
- `energy`: for each acquisition class among the hops, in energy-measurement §2's order,
|
||
strongest first, the sum of their figures of that class; and `unmetered`, the number of hops
|
||
whose `energy` is `null`. Figures of different classes are never summed together, and an
|
||
unmetered hop is never counted as zero. The source objects' figures are their own receipts';
|
||
this total covers the derivations alone;
|
||
- `provenance`: the subject's record's items (§6.1), each its `input`, `kind`, `digest` and
|
||
`compartment`: every item of the chain, carried to the object the sink received.
|
||
|
||
## 8. Authority
|
||
|
||
A derivation is made under the authority of the origin whose content it derives from. A hop's
|
||
**origins** are the origins its chain reaches: for each source object, the origin its group
|
||
receipt's `signer_id` names where it is origin-bound (jwp-receipts §7.1), taken in ascending
|
||
order, each once. The authority is `edge/derive` (jwp-receipts §7.5), and a hop's edge needs it
|
||
from each of its hop's origins, the hops below it included: an origin lists, in its key log,
|
||
the edges it trusts with its content, as it lists its relays and stores.
|
||
|
||
For each origin of a hop, where the verifier holds a head for it: the head is a revision the
|
||
verifier holds for that origin (jwp-receipts §7.2 rules 1–3) (`key_log_not_held`); it has an
|
||
entry whose `key` is the record's `signer_pubkey` and whose `may_sign` holds `edge/derive`
|
||
(`edge_not_authorised`); and that entry's `status` is not `revoked` (`edge_revoked`).
|
||
|
||
A hop is `confirmed` when it has origins and the verifier holds a head for each that passes;
|
||
`unconfirmed` when it has origins and lacks a head for one; and `none` when it has no origin,
|
||
its sources naming no origin. Under the policy `authority: required`, a hop that is not
|
||
`confirmed` is refused (`edge_not_authorised`, at the first origin that lacks a head, or at
|
||
`null`).
|
||
|
||
`edge/derive` is an authority of its own: not `delivery`, which an edge needs from its own
|
||
origin to sign its group receipts, and not `live/relay`, since a relay forwards what it
|
||
received and an edge publishes something new. Retiring an entry does not withdraw it here;
|
||
revoking it does. A metered record's figure needs, beside it, the energy authority of its class
|
||
(`energy/<class>`, jwp-receipts §7.5), which that section's confirmation (§7.4 there) checks
|
||
under an anchored origin; this section checks `edge/derive` alone.
|
||
|
||
## 9. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `record_malformed` | §5.3 rule 1, §7.2 step 1 |
|
||
| `derivation_unknown` | §5.3 rule 2, §7.2 step 1 |
|
||
| `params_invalid` | §4.2, §5.3 rule 3, §7.2 steps 1 and 5 |
|
||
| `inputs_invalid` | §5.3 rule 4, §7.2 step 1 |
|
||
| `check_mismatch` | §4.5, §5.3 rule 5, §7.2 step 1 |
|
||
| `energy_invalid` | §5.3 rule 6, §7.2 step 1 |
|
||
| `bad_key` | §7.2 step 2 |
|
||
| `bad_signature` | §7.2 step 2 |
|
||
| `output_receipt_not_held` | §7.2 step 3 |
|
||
| `output_receipt_invalid` | §7.2 step 3 |
|
||
| `output_receipt_mismatch` | §6, §7.2 step 3 |
|
||
| `energy_disagrees` | §6, §7.2 step 3 |
|
||
| `output_hash_mismatch` | §7.2 step 3 |
|
||
| `output_manifest_mismatch` | §4.3, §7.2 step 3 |
|
||
| `input_receipt_not_held` | §7.2 step 4 |
|
||
| `input_receipt_invalid` | §7.2 step 4 |
|
||
| `chain_skips_hop` | §7.2 step 4 |
|
||
| `chain_too_long` | §7.2 step 4 |
|
||
| `hop_not_held` | §7.2 step 4, §7.3 |
|
||
| `hop_mismatch` | §7.2 step 4 |
|
||
| `input_hash_mismatch` | §7.2 step 4 |
|
||
| `input_unreadable` | §4.1, §4.4, §7.2 step 4 |
|
||
| `samples_mismatch` | §4.5, §7.2 steps 3, 4 and 5 |
|
||
| `provenance_stripped` | §6.1, §7.2 step 4 |
|
||
| `synthesis_dropped` | §5.3 rule 7, §6.2, §7.2 steps 1 and 4 |
|
||
| `replicate_mismatch` | §4.5, §7.2 step 5 |
|
||
| `inputs_not_held` | §7.1, §7.2 step 5 |
|
||
| `key_log_not_held` | §8, §7.2 step 6 |
|
||
| `edge_not_authorised` | §7.1, §8, §7.2 step 6 |
|
||
| `edge_revoked` | §8, §7.2 step 6 |
|
||
| `attested_refused` | §7.1, §7.2 step 7 |
|
||
|
||
## 10. Conformance
|
||
|
||
[`edge-conformance/`](../edge-conformance/) holds the corpus, in one file, `vectors.json`,
|
||
written from fixed seeds by `wai_edge_vectors gen`. Each vector is what a verifier holds (§7.1)
|
||
and the outcome §7.4 gives it. It has:
|
||
|
||
- **Sources**: one publisher, whose `signer_id` is origin-bound, signing an integer clip
|
||
(`wai.video.int_motion`), three PNG stills and a `WAI2` envelope of two renditions; and the key
|
||
log of its origin, listing two edges with `edge/derive`, one edge with `delivery` alone, and
|
||
one revoked.
|
||
- **Replicate hops, recomputed**: a frame of the clip rebuilt as a still; each operation of §4.2
|
||
but scaling over a still; a grid and an overlay over several stills; a component exploded from
|
||
the `WAI2`; and a chain of two hops by two edges, a crop of a rebuilt still, each hop
|
||
recomputed from its inputs.
|
||
- **Attested hops**: a frame rebuilt as a JPEG picture, whose samples are checked and whose
|
||
encode is not; a scaling written as a pinned still; and a replicate crop over an attested
|
||
scaling.
|
||
- **Provenance and labels**: a still whose PNG embeds a manifest store, with a claim about it
|
||
and a store beside it in the personal compartment, every item carried through two hops; a
|
||
still whose provenance says it was generated, its label carried through two hops; and a
|
||
scaling its edge labels `assist`. The corpus's manifest stores are byte strings standing for
|
||
one: a verifier names a store by its SHA-256 and parses none.
|
||
- **Policies**: a chain verified without its inputs' bytes; one under `authority: required`
|
||
whose hops are confirmed; metered hops of two classes, totalled per class.
|
||
- **Refusals**: one vector or more for each reason of §9, each a control of one defect: among
|
||
them a JPEG output whose record names no encoder, a grid stated over more inputs than its
|
||
cells, a parameter its inputs cannot satisfy, an attested JPEG whose stated samples are not
|
||
its frame's, and a scaling whose stated samples are not its still's.
|
||
|
||
A conforming verifier gives every vector the outcome, the reason, the place and the facts the
|
||
corpus states. `wai_edge_vectors verify` and the test `tests/derive_edge.rs` check that the
|
||
file regenerates byte for byte, that `wai-rs` gives every vector its outcome, that every reason
|
||
of §9 appears, and that §9 lists exactly the reference's codes; the same test runs the
|
||
reference edge, `wai_edge`, twice over a source to make a chain of two hops, and verifies it.
|
||
`edge-conformance/edge_verify.py` is the second implementation, written from this text: it
|
||
shares no code with `wai-rs`; it reads delivery receipts with the JWP corpus's second
|
||
implementation (`jwp-conformance/jwp_verify.py`), decodes `WIV1` with the integer payload
|
||
corpus's (`int-payload-conformance/int_payload_verify.py`), explodes `WAI2` envelopes with the
|
||
staged-delivery corpus's (`staged-conformance/staged_verify.py`), reads key-log heads with the
|
||
live-rooms corpus's (`live-conformance/live_rooms_verify.py`), verifies every signature with
|
||
WAI's strict rule (`claim-conformance/_ed25519.py`), and writes stills and evaluates every
|
||
operation itself. It requires every mutated copy of the corpus to fail, and, with each of its
|
||
own checks dropped in turn, requires the corpus to catch the weakened verifier.
|
||
|
||
## 11. Security considerations
|
||
|
||
- **What a record proves.** An edge's signature binds it to the derivation it states. For a
|
||
`replicate` hop whose inputs a verifier holds, nothing rests on that signature but who made
|
||
it: the verifier made the output itself. For an `attest` hop, the attested step is the edge's
|
||
word; an edge that encodes something else and signs it is caught only by a party that compares
|
||
the output with what it should hold. A sink that cannot accept that refuses attested hops.
|
||
- **Chains.** A hop that names its input as a source object when an edge derived it, or that
|
||
names a record the sink cannot get, would hide a derivation; both are refused. A chain is
|
||
verified hop by hop, so a chain is no stronger than its weakest hop, and the facts say which
|
||
hops were re-executed, which were not, and which are attested.
|
||
- **Not the source.** A derived object is never the object it was derived from. The source's
|
||
receipts do not cover it, and its own receipts are the edge's. jwp-receipts §3.2's ancestor
|
||
rule is unchanged: a soft-binding recovery attests the ancestor alone. A verified chain is
|
||
the accounted transformation that recovery lacks, and it is the record that accounts for it.
|
||
- **Energy.** Each hop's figure is its edge's, with its class, and covers that derivation
|
||
alone. Totals are per class, an unmetered hop is not zero, and the source's figures stay its
|
||
own (§7.4).
|
||
- **Keys.** An edge's key signs its records and its delivery receipts. An origin that stops
|
||
trusting an edge revokes its entry, and every record under it is refused for that origin's
|
||
content from then on.
|
||
- **Work.** A chain has at most 8 hops and a record at most 16 inputs; an output's samples are
|
||
bounded. A verifier re-executes only what it holds, and only what §4 pins.
|
||
- **Provenance.** A verifier catches an item stripped from a record only where it holds the
|
||
item, or the record below that carried it: an edge that drops an item it received, before any
|
||
party downstream saw it, is caught by a party that holds what the edge received. Once a record
|
||
lists an item, every hop above it must list it too, so the sink of a chain holds every item's
|
||
digest from the subject's record alone. A compartment is the edge's reading, and the first
|
||
edge to read an item fixes it for the rest of the chain.
|
||
- **Labels.** A label can only rise along a chain, so a transcode cannot launder a generated
|
||
object into an unlabelled one. A source object's label, and the label an attested step
|
||
warrants, are the edge's statements; a verifier checks that no label falls.
|
||
- **Privacy.** A record names objects, keys and provenance items by digest, never a viewer or a
|
||
session, and copies nothing of an item. Personal provenance is carried by digest in this
|
||
revision (§6.1); an edge that must not pass a personal item on can only refuse to derive until
|
||
the redaction rule is registered.
|