Resolved by meaning: synthesis::Case holds both the priors (exact-upscale) and the unmarked frames (§7.6); synthesis §11.1 lists both inputs and the three policy members; the synthesis-mark corpus's cases hold no prior; exact-upscale passes synthesis's second_layer policy through; the live-conformance table gains the upscale row in its generator column; both CI steps and both CHANGELOG entries are kept. Generated files are main's here and are regenerated in the next commit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
1283 lines
84 KiB
Markdown
1283 lines
84 KiB
Markdown
# WAI Extension: Synthesis Provenance
|
||
|
||
> Status: Draft. Registers the record `wai.media.synthesis`: what a piece of media is, for every
|
||
> kind of media WAI carries. An **origin claim** says of one object, one group or one track that
|
||
> it is **captured**, **reconstructed** (a codec, neural or not, rebuilding what was captured),
|
||
> **assistive** (an edit that does not substantially alter its input, such as an upscale or a
|
||
> denoise) or **synthesized** (generated). The claim names each input by the link hash of the
|
||
> input's own claim, and its class is the greater of the class its own step gives and its
|
||
> inputs' classes, so the class rises along every chain of derivations and never falls. The
|
||
> claim restates the content's IPTC digital source type, which is a function of the claim, and
|
||
> so is its C2PA statement: the actions and the `c2pa.ai-disclosure` assertions a manifest
|
||
> carries. An assistive claim carries **evidence**: where its step is one of the two integer
|
||
> derivations this extension pins, or a super-resolution derivation of
|
||
> [exact-upscale](exact-upscale.md) under a prior pinned by digest, a verifier holding the input's
|
||
> decoded samples (and that prior) recomputes the output and compares it, so the claim that the
|
||
> edit is assistive is checked, not trusted. A picture a sink presents and does not publish is
|
||
> named in a claim by the digest of its samples. The
|
||
> signed claim is the first of two **marking layers**; the second is a watermark whose payload is
|
||
> a function of the claim, and a verifier refuses a detection that disagrees with it. The
|
||
> registered watermark, `wai-mark:tent64`, is a mark in the luma of pictures and video embedded
|
||
> by a pinned integer derivation, so a verifier that holds the unmarked frames recomputes the
|
||
> marked ones and knows the mark carries its payload and nothing else; anyone reads it with no
|
||
> key, through a code whose false-positive bound is derived and checked; and an attack battery
|
||
> measures what it survives, which is all it claims. A **signpost** in the content and in the
|
||
> metadata names the detection solution, and a well-known path resolves it. An **AI participant**
|
||
> of a room is marked in its grant, and its messages, captions and tracks carry its machine
|
||
> origin. A **review** is a signed verdict over one exact reconstruction, which a player presents
|
||
> only for a presentation whose decode path holds the review's tier, so one review holds for
|
||
> every viewer of that output. A **label policy** says when a label must be shown, and a player's
|
||
> signed **log** shows whether it was. No new transport. The reference implementation is the
|
||
> `synthesis` module of `wai-rs` (feature `synthesis`); the corpus is
|
||
> [`live-conformance/synthesis/`](../live-conformance/synthesis/), and its second
|
||
> implementation is `live-conformance/synthesis_verify.py`. The scheme's battery and measurement
|
||
> are the `synthesis_mark` module (feature `synthesis_mark`); its corpus is
|
||
> [`live-conformance/synthesis-mark/`](../live-conformance/synthesis-mark/), and its second
|
||
> implementation `live-conformance/synthesis_mark_verify.py`.
|
||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||
>
|
||
> Texts read for this revision, each pinned to the version named: the Code of Practice on
|
||
> Transparency of AI-Generated Content, final text of 10 June 2026, Section 1 Measures 1.1 to 1.3,
|
||
> 2.1, 2.3 and 3.2 to 3.4 and its Glossary, and Section 2 Measures 1.1 and 1.2 and its Annex 1, as
|
||
> the Commission's document 129555, whose SHA-256 as fetched on 21 October 2026 was
|
||
> `7bd22c5a3c56eaefda27a5bf7a6118198ef2a9c9255241bd97abf7cdedf9bc28`; the EU icons the Commission publishes for
|
||
> that Annex, as fetched on 21 October 2026 from the page it last updated on 13 October 2026
|
||
> (§10.2); Regulation (EU) 2024/1689, Article 50, paragraphs 2, 4 and 5 as that Code reproduces
|
||
> them, and the last sentence of paragraph 2 in a reproduction of the Official Journal text (the
|
||
> Official Journal itself could not be fetched for this revision); the C2PA Technical
|
||
> Specification, version 2.4, §6.4, §9.3, §18.10, §18.15.2, §18.15.4.5, §18.15.4.8, §18.15.5 and
|
||
> §18.28; RFC 8615; and the IPTC Digital Source Type vocabulary, whose most recent concept change
|
||
> is dated 2024-10-23. A section number of one
|
||
> of those texts in this extension is that version's.
|
||
|
||
## 1. Scope
|
||
|
||
In scope: the records and their encodings (§2); the classes, the steps and the origin claim
|
||
(§3); the two pinned integer derivations and the evidence that makes an assistive claim
|
||
checkable (§4); the digital source type (§5) and the C2PA statement (§6) a claim gives; the
|
||
two marking layers, their records and their agreement, the registered watermark scheme, what it
|
||
survives, its signpost and its resolver (§7); AI participants of rooms (§8); the
|
||
review and when a player presents it (§9); when a label is shown, the EU icons pinned by digest,
|
||
and a player's log (§10); verification (§11).
|
||
|
||
Out of scope: a transport; how a generator, a codec or an editor works; a watermark for audio or
|
||
for text, which this revision does not register (audio was in this revision's sight and is not
|
||
reached; text needs a scheme of its own); finding a mark again after a crop or a rotation, which
|
||
the registered scheme does not do (§7.5.8); how a manifest is assembled, embedded and signed,
|
||
which is C2PA's and
|
||
[provenance](provenance.md)'s; key discovery for the signers of these records, which a verifier
|
||
takes from [jwp-receipts](jwp-receipts.md) §7 as it does for any other key; and the rendering of a
|
||
label, beyond the asset it shows and the instants it shows it at.
|
||
|
||
Relationship to the core spec: `wai.media.synthesis` 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.
|
||
|
||
Relationship to [timed-text](timed-text.md) and [live-events](live-events.md): a caption's
|
||
`origin` (live-events §4.10), and a chat's or a reaction's (§4.4, §4.5 there), is this
|
||
extension's class for a text event, with no record of its own: origin 1 is a person's text,
|
||
class `captured` with source `digitalCreation`; origin 2 a machine's, class `synthesized`
|
||
generated with oversight `fully_autonomous`; origin 3 a machine's reviewed by a person, class
|
||
`synthesized` with oversight `human_validated`. Timed-text §10.3's export is then §5's and §6's
|
||
for such a step.
|
||
|
||
Relationship to [energy-binding](energy-binding.md) §6.1: a decode, and any codec, reconstructs;
|
||
it does not create, and a neural codec does not make its output AI-generated. This extension
|
||
keeps that: a codec is a `convert` step (§3.2), which names no model, asserts no source of its
|
||
own and keeps its input's class, and its C2PA statement is `c2pa.opened` and `c2pa.converted`
|
||
with no digital source type (§6).
|
||
|
||
Relationship to [determinism-tiers](determinism-tiers.md): a pinned derivation of §4 is integer
|
||
throughout, so its output is a pure function of its input's samples, and a claim over it is
|
||
checked by recomputation, at the tier `replicate` promises. A review (§9) is presented only where
|
||
the presentation holds the review's tier, under the rule of determinism-tiers §2 (*What a tier
|
||
covers*): a tier covers what the sink emits.
|
||
|
||
Duties this extension supports: the two machine-readable marking layers of the Code of Practice
|
||
(Section 1, Measure 1.1: Sub-measure 1.1.1, signed and time-stamped metadata; Sub-measure 1.1.2,
|
||
an imperceptible watermark) and its richer provenance information (Measure 1.3: the model, the
|
||
timestamp, the type of operations); a detection mechanism given as a public specification with
|
||
two implementations (Sub-measure 2.1.1), its signed results (Sub-measure 2.1.2), the reliability
|
||
and robustness measurements of Measures 3.2 and 3.3, and a signpost for the interoperability of
|
||
detection (Measure 3.4(c)(ii)); the disclosure of AI systems that interact with people
|
||
(Article 50(1)); and the placement of a label at first exposure, after interruptions and at
|
||
regular intervals (Section 2, Sub-measures 1.2.1 to 1.2.3). WAI states what its records carry and
|
||
what a verifier checks. It makes no statement about any regime, and a deployment confirms what a
|
||
regime asks of it against that regime's text.
|
||
|
||
## 2. Records
|
||
|
||
Every record of this extension is a JSON object (RFC 8259) under the encodings of
|
||
[jwp-receipts](jwp-receipts.md) §2.4.3: byte strings in lowercase hexadecimal; integers unsigned,
|
||
at most 2^53 − 1, in plain decimal; no other number; no boolean; a **term** and a **date-time**
|
||
as defined there. A record is read as jwp-receipts §3.1 step 1 reads a claim: RFC 8259's grammar,
|
||
no member named twice in one object, at most 32 levels of nesting. Each record has exactly the
|
||
members its section lists, no member missing and none added, at any level, and a `kind` naming
|
||
it; one that does not is `record_malformed`.
|
||
|
||
Every record carries `signer_pubkey` (32 bytes, the Ed25519 key of RFC 8032 that signs it) and
|
||
`sig` (64 bytes):
|
||
|
||
```
|
||
sig = Ed25519(signer_key, "wai:synthesis\x01" || JCS(record without "sig"))
|
||
link_hash = BLAKE3("wai:synthesis-link\x01" || JCS(record))
|
||
```
|
||
|
||
JCS is RFC 8785, which on these values reduces to what jwp-receipts §2.4.3 states. The signature
|
||
is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key that fails step 1 or 2 is
|
||
`bad_key`, and a signature that fails a later step `bad_signature`. A record is named by its
|
||
link hash wherever another names it. A record whose members carry `issued_at` holds a date-time
|
||
there, the instant its signer signed it: the time stamp the Code of Practice's Sub-measure 1.1.1
|
||
asks for, self-asserted.
|
||
|
||
A digest of samples is SHA-256 (FIPS 180-4) over a canonical buffer: for an image, the `RGB8`
|
||
buffer of [reconstruction-binding](reconstruction-binding.md) §2 (row-major, three bytes a
|
||
pixel, no row padding); for another medium, the canonical buffer its capability's
|
||
`*-equivalence` names ([determinism-tiers](determinism-tiers.md) §2). An object's **content
|
||
hash** is jwp-receipts §2.1's.
|
||
|
||
## 3. Classes and the origin claim
|
||
|
||
### 3.1 The origin claim
|
||
|
||
An origin claim, `kind` `"wai-origin"`, has these members:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-origin"` |
|
||
| `subject` | object | what it is about: `{"type": "object", "content_hash"}`, an object by its content hash; `{"type": "group", "receipt"}`, a group by its group receipt's link hash (jwp-receipts §3 step 3); `{"type": "track", "track", "from_group"}`, a track by its digest (live-rooms §6.2: SHA-256 over the rendering of its namespace, one zero byte and its name) from the group `from_group` on; or `{"type": "samples", "samples"}`, a picture a party presents or holds and does not publish as an object, by the digest of its samples (§2): a sink's upscale or generated frame ([exact-upscale](exact-upscale.md) §6.1) |
|
||
| `media` | string | `audio`, `video`, `image` or `text` |
|
||
| `class` | string | `captured`, `reconstructed`, `assistive` or `synthesized` (§3.2) |
|
||
| `step` | string | `capture`, `convert`, `assist` or `generate` (§3.2) |
|
||
| `operation` | term | what ran: a capability or derivation string, or a term naming the type of operation (`upscale`, `inpainting`, `speech-synthesis`) |
|
||
| `record` | 32 B hex or `null` | the identity of a record that derived the subject, where its deriver signs one; `null` otherwise |
|
||
| `source` | string | the subject's digital source type (§5) |
|
||
| `models` | array | the models the step ran, at most 16, each `{"digest", "type"}`: a prior's digest (SPEC §3.1) and `trained` (a model derived from sampled content) or `algorithmic` (one that is not); no digest twice |
|
||
| `oversight` | string or `null` | for `generate`: `fully_autonomous`, `prompt_guided` or `human_validated` (C2PA 2.4 §18.28.4); otherwise `null` |
|
||
| `inputs` | array | the objects the step read, at most 16, each `{"content_hash", "origin", "class", "source"}`: its content hash, the link hash of its own origin claim or `null` where it has none, and that claim's class and source, restated; no content hash twice |
|
||
| `evidence` | object or `null` | for `assist`: §4.3; otherwise `null` |
|
||
| `issued_at` | date-time | when the signer signed it |
|
||
| `signer_pubkey`, `sig` | | §2 |
|
||
|
||
`media`, `class`, `step`, `oversight`, a model's `type` and an input's `class` hold only the
|
||
values the table names, and `source` and an input's `source` only the codes of §5. Otherwise
|
||
`record_malformed`.
|
||
|
||
Who signs: the party that performed the step: the capturing device or its operator, the
|
||
encoder, the editor, the generator's provider. A claim carries its signer's authority and no
|
||
more; which signers a verifier trusts is its policy.
|
||
|
||
A verifier is given at most one origin claim for one subject; a second for a subject an earlier
|
||
claim names is `claim_conflict`.
|
||
|
||
### 3.2 Steps and the class lattice
|
||
|
||
The classes are ordered: `captured` < `reconstructed` < `assistive` < `synthesized`. Each step
|
||
gives a class, and constrains the claim:
|
||
|
||
| step | class it gives | inputs | models | `oversight` | `evidence` |
|
||
|---|---|---|---|---|---|
|
||
| `capture` | `captured` | none | none | `null` | `null` |
|
||
| `convert` | `reconstructed` | at least one | none | `null` | `null` |
|
||
| `assist` | `assistive` | exactly one | any | `null` | an object |
|
||
| `generate` | `synthesized` | any | at least one | not `null` | `null` |
|
||
|
||
A `capture` states as its `source` one of `digitalCapture`, `computationalCapture`,
|
||
`screenCapture` or `digitalCreation`: a recording of the world, or a person's work made without
|
||
a generative model. A claim that breaks the table is `record_malformed`.
|
||
|
||
- **capture** records the world, or a person's authorship: a camera, a microphone, a screen
|
||
recording, a person typing or drawing.
|
||
- **convert** rebuilds or rearranges its inputs without generating or enhancing anything: an
|
||
encode, a decode, a transcode, a re-package, a decoder's reconstruction (neural or not), and a
|
||
derivation that copies samples or composes them (crop, rotation, grid or overlay composition,
|
||
[derived-tracks](derived-tracks.md)). A codec names no model: its parameter set is pinned in
|
||
its envelope, and it reconstructs what its input holds.
|
||
- **assist** edits one input without substantially altering it: an upscale, a denoise, a colour
|
||
correction. It is the step whose claim needs evidence (§4).
|
||
- **generate** produces content by a model: a generated picture, voice or video, an inpainting,
|
||
a voice clone, a talking head, a frame generated between two others, an avatar a model drives.
|
||
|
||
The **class of a claim** is the greatest of its step's class and its inputs' stated classes. A
|
||
claim whose `class` is another is `class_mismatch`. So a conversion of a synthesized input is
|
||
synthesized, and a transcode, a clip or a re-package can never lower what its input was.
|
||
|
||
### 3.3 Checks against what a verifier holds
|
||
|
||
- **Inputs.** For each input whose `origin` names a claim the verifier holds: that claim's
|
||
subject is the object of the input's `content_hash`, and its `class` and `source` are the
|
||
input's. Otherwise `input_mismatch`. An input whose claim is not held is taken as stated.
|
||
- **The subject's own envelope.** Where the subject is an object and the verifier holds its
|
||
envelope (SPEC §2): an envelope whose capability's registry class is `GenerativeDeterministic`
|
||
or `Behavioral` (SPEC §7), or whose manifest's `intent` is `create`, is generated by every sink
|
||
that presents it, and its claim's `step` is `generate`. A claim of another step over such an
|
||
envelope is `class_understated`.
|
||
|
||
### 3.4 Through every derivation
|
||
|
||
A party that derives an object from others and publishes it signs an origin claim for it whose
|
||
inputs name the inputs' claims; where it signs a record of the derivation too, the claim's
|
||
`record` names that record. A relay forwards claims as jwp-receipts §4.1 has it forward any
|
||
receipt, byte for byte, and alters none. So each object a sink receives has a claim, and the
|
||
chain of claims behind it reaches the capture or generation it came from:
|
||
|
||
| derivation | the output's claim |
|
||
|---|---|
|
||
| a relay forwards an object | none: the object is the same, and its claim holds for it |
|
||
| an object re-packaged, or a `WAI2` component exploded (SPEC §7.1) | the same object for every rule that names objects by content hash; its claim holds |
|
||
| an encode, a transcode, a rung of a ladder, a decoder's reconstruction | `convert` of the input |
|
||
| a crop, a rotation, a composition of several inputs | `convert` of the inputs |
|
||
| an upscale, a denoise, a sink-side enhancement | `assist` of the input, with evidence (§4); a picture the sink presents and does not publish is the subject by its samples |
|
||
| a frame generated between two others, a talking head from a picture and a voice, an inpainting | `generate` over the inputs |
|
||
| a clip of a recording, a segment of a track | the claim of each group or object it holds, unchanged |
|
||
|
||
A track or group claim covers each object of the track or group; an object claim, where one is
|
||
given, is the object's.
|
||
|
||
## 4. Assistive evidence
|
||
|
||
### 4.1 `wai-synthesis:upscale2`
|
||
|
||
The derivation `wai-synthesis:upscale2`, with no parameters (`{}`), maps a *w* × *h* `RGB8` buffer
|
||
`in` to a 2*w* × 2*h* buffer `out`. For each pixel (*x*, *y*) of `in` and each of its three bytes,
|
||
with *x*₁ = min(*x* + 1, *w* − 1), *y*₁ = min(*y* + 1, *h* − 1), *a* = in(*x*, *y*),
|
||
*b* = in(*x*₁, *y*), *c* = in(*x*, *y*₁) and *d* = in(*x*₁, *y*₁):
|
||
|
||
- out(2*x*, 2*y*) = *a*;
|
||
- out(2*x* + 1, 2*y*) = (*a* + *b* + 1) >> 1;
|
||
- out(2*x*, 2*y* + 1) = (*a* + *c* + 1) >> 1;
|
||
- out(2*x* + 1, 2*y* + 1) = (*a* + *b* + *c* + *d* + 2) >> 2.
|
||
|
||
This is the half-sample interpolation `wai.video.int_motion` predicts with (SPEC §5), in the same
|
||
integer arithmetic.
|
||
|
||
### 4.2 `wai-synthesis:denoise3`
|
||
|
||
The derivation `wai-synthesis:denoise3`, with no parameters (`{}`), maps a *w* × *h* `RGB8`
|
||
buffer `in` to a *w* × *h* buffer `out`: for each pixel (*x*, *y*) and each of its three bytes,
|
||
|
||
out(*x*, *y*) = (8 + Σ *k*(*i*, *j*) · in(clamp(*x* + *i*), clamp(*y* + *j*))) >> 4,
|
||
|
||
the sum over *i*, *j* ∈ {−1, 0, 1}, with *k* = 4 at (0, 0), 2 where exactly one of *i*, *j* is 0,
|
||
and 1 at the four corners; clamp keeps a coordinate within 0 to *w* − 1 (or *h* − 1).
|
||
|
||
### 4.3 The evidence
|
||
|
||
An assist claim's `evidence` is an object of exactly these members:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `tier` | string | `replicate`: the step is a pinned derivation, checkable by recomputation; `attest`: it is not, and the claim asserts that the edit is assistive |
|
||
| `derivation` | term | the derivation run |
|
||
| `params` | object | its parameters |
|
||
| `width`, `height` | integer | the input's size, each at least 1 |
|
||
| `input_samples` | 32 B hex | the digest of the input's samples (§2) |
|
||
| `output_samples` | 32 B hex | the digest of the output's samples |
|
||
|
||
`tier` and the sizes outside these values are `record_malformed`. `replicate` evidence names
|
||
`wai-synthesis:upscale2` or `wai-synthesis:denoise3`, with `params` `{}`, or
|
||
`wai-upscale:sr2` or `wai-upscale:sr4` ([exact-upscale](exact-upscale.md) §3), with `params`
|
||
`{"prior"}`, the SHA-256 of the prior as 32 bytes of hex (exact-upscale §3.5); and an output of
|
||
at most 2^26 bytes (4 · `width` · `height` · 3 for the upscale and for `wai-upscale:sr2`, 16 ·
|
||
`width` · `height` · 3 for `wai-upscale:sr4`, `width` · `height` · 3 for the denoise); otherwise
|
||
`evidence_invalid`. A derivation evaluated in float, or by any operation this revision does not
|
||
pin, states `attest`: the edit may well be assistive, but its claim can only assert it.
|
||
|
||
A model-driven derivation joins the pinned set when a text registers it in integer arithmetic at
|
||
`decode-equivalence`, with its parameter set pinned (SPEC §3.1); its evidence then names it and
|
||
its pin, and is checked the same way. [exact-upscale](exact-upscale.md) registers two, whose
|
||
evidence names the prior by digest.
|
||
|
||
### 4.4 Checking the evidence
|
||
|
||
A verifier is given the decoded samples of the objects it holds (§11.1). For an assist claim, in
|
||
this order:
|
||
|
||
1. `replicate` evidence that §4.3 does not admit: `evidence_invalid`.
|
||
2. With `replicate` evidence and the input's decoded samples held: they are `width` · `height` ·
|
||
3 bytes and their digest is `input_samples` (`input_samples_mismatch`); the derivation is run
|
||
over them, and the digest of its output is `output_samples`
|
||
(`assistive_recompute_mismatch`). The claim is then **proved**. An exact-upscale derivation runs
|
||
under the prior the verifier holds by `params.prior`; one that does not read, or an input past
|
||
the derivation's limits under it (exact-upscale §3.1, §3.3), has no output
|
||
(`evidence_invalid`). Without the input's samples, or without the prior, it is **not
|
||
recomputed**; `attest` evidence is **asserted**.
|
||
3. With the subject's decoded samples held (an object subject), or with a `samples` subject: their
|
||
digest, or the subject's, is `output_samples` (`samples_mismatch`).
|
||
4. Under a policy that requires proof (§11.1), a claim that is not proved:
|
||
`assistive_not_proved`.
|
||
|
||
A proved claim says that this output is that pinned derivation of that input, and nothing else:
|
||
a fact any verifier holding the input's samples checks, at the tier `replicate` promises.
|
||
|
||
## 5. The digital source type
|
||
|
||
A claim's `source` is a code of the IPTC Digital Source Type vocabulary, whose terms are
|
||
`http://cv.iptc.org/newscodes/digitalsourcetype/` followed by the code. It is a function of the
|
||
claim:
|
||
|
||
| step | inputs | `source` |
|
||
|---|---|---|
|
||
| `capture` | none | as stated: `digitalCapture`, `computationalCapture`, `screenCapture` or `digitalCreation` |
|
||
| `convert` | one | the input's `source` |
|
||
| `convert` | several | `compositeSynthetic` when an input's class is `synthesized`; else `compositeCapture` when every input's source is `digitalCapture` or `computationalCapture`; else `composite` |
|
||
| `assist` | one | the input's `source` when its class is `synthesized`; else `algorithmicallyEnhanced` |
|
||
| `generate` | any | the **generated source**: `algorithmicMedia` when no model is `trained`; else `compositeWithTrainedAlgorithmicMedia` when `oversight` is `human_validated` or an input's class is not `synthesized`; else `trainedAlgorithmicMedia` |
|
||
|
||
A claim whose `source` is another is `source_mismatch`. The codes are the vocabulary's own
|
||
definitions: `algorithmicallyEnhanced` is an algorithmic modification that leaves the main
|
||
content unchanged; `trainedAlgorithmicMedia` media created by a model trained on captured
|
||
content; `compositeWithTrainedAlgorithmicMedia` an edit made with such a model, and, by C2PA 2.4
|
||
§18.28.3's pairing, generated content a person reviewed and approved; `algorithmicMedia` media
|
||
created purely by an algorithm with no sampled training data. A `reconstructed` claim keeps its
|
||
input's code: a codec makes no claim about what it reconstructed.
|
||
|
||
## 6. The C2PA statement
|
||
|
||
A claim gives one statement, the JSON object `{"actions": A, "assertions": D}` written in JCS
|
||
(RFC 8785):
|
||
|
||
- `A`, the actions of the manifest's first actions assertion (C2PA 2.4 §18.15.2):
|
||
- a claim with no input or with several: `[{"action": "c2pa.created", "digitalSourceType": U}]`,
|
||
`U` the IPTC term of the claim's `source`;
|
||
- a `convert` of one input: `[{"action": "c2pa.opened"}, {"action": "c2pa.converted"}]`, with
|
||
no digital source type, as [energy-binding](energy-binding.md) §6.1 has it for a decode: the
|
||
input is the parent ingredient, and its own manifest states its source;
|
||
- an `assist` of one input: `[{"action": "c2pa.opened"}, {"action": "c2pa.edited",
|
||
"digitalSourceType": U}]`, `U` the term of `algorithmicallyEnhanced`;
|
||
- a `generate` of one input: the same, `U` the term of the generated source (§5).
|
||
- `D`, one `c2pa.ai-disclosure` assertion (C2PA 2.4 §18.28) for each model whose `type` is
|
||
`trained`, in the claim's order, labelled `c2pa.ai-disclosure`, `c2pa.ai-disclosure__1`, …
|
||
(C2PA 2.4 §6.4): `{"label": L, "data": {"modelType": "c2pa.types.model", "modelIdentifier":
|
||
"tag:transaction.science,2026:wai/model/" ‖ the digest in lowercase hexadecimal}}`, and, for a
|
||
`generate`, `"contentProfile": {"humanOversightLevel": O}` in `data`, `O` the claim's
|
||
`oversight`. A model of type `algorithmic` has none: C2PA 2.4 §18.28.3 attaches the assertion
|
||
only where a trained model was invoked. The model is named by digest, as timed-text §10.3 names
|
||
a caption's.
|
||
|
||
Embedding the statement in a manifest, and signing that, is C2PA's and provenance's. A rendition
|
||
of a playlist whose objects include one of class `synthesized` SHOULD carry the
|
||
`public.machine-generated` characteristic (`draft-pantos-hls-rfc8216bis-22` §4.4.6.1), as
|
||
timed-text §10.2 has it for captions.
|
||
|
||
## 7. Two marking layers
|
||
|
||
### 7.1 The layers
|
||
|
||
The **first layer** is the origin claim: signed, time-stamped metadata (the Code of Practice's
|
||
Sub-measure 1.1.1). The **second** is an imperceptible watermark embedded in the content
|
||
(Sub-measure 1.1.2), whose payload is a function of the claim (§7.2). The Code asks for both on
|
||
the outputs of generative systems and treats fingerprinting or logging as a supplement that is
|
||
never sufficient alone (Sub-measure 1.1.3); a content-bound commitment, such as a
|
||
[reconstruction binding](reconstruction-binding.md) over decoded pixels, is a supplement of that
|
||
kind here and not the second layer.
|
||
|
||
Under a policy that requires both layers (§11.1), an object claim of class `synthesized` with no
|
||
marking record naming it (§7.2) is `layer_missing`.
|
||
|
||
### 7.2 The payload and the marking record
|
||
|
||
A watermark carrying a claim holds an 8-byte **payload**: the code of the claim's class (0
|
||
`captured`, 1 `reconstructed`, 2 `assistive`, 3 `synthesized`), then the first seven bytes of
|
||
the claim's link hash. Read from the content alone, with no metadata, it says the class and points
|
||
at the claim. Seven bytes find a claim; they do not prove one, and the claim's signature does
|
||
that. The payload is the whole of what a mark carries: it names no viewer, no session, no device
|
||
and no account (§7.5.6).
|
||
|
||
The party that embeds the mark signs a **marking** record, `kind` `"wai-marking"`:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-marking"` |
|
||
| `subject` | 32 B hex | the content hash of the marked object |
|
||
| `origin` | 32 B hex | the link hash of its origin claim |
|
||
| `scheme` | term | the watermark scheme (§7.4) |
|
||
| `scheme_digest` | 32 B hex | the SHA-256 of the scheme's definition |
|
||
| `payload` | 8 B hex | the payload embedded |
|
||
| `embedding` | object or `null` | the frames the mark was embedded into (§7.6) |
|
||
| `issued_at` | date-time | when it signed it |
|
||
| `signer_pubkey`, `sig` | | §2 |
|
||
|
||
An `embedding` is an object of exactly these members: `frames`, `width` and `height`, integers
|
||
each at least 1, the count and size of the frames; `unmarked` and `marked`, each 32 B hex, the
|
||
SHA-256 of the unmarked and of the marked frames, each frame its canonical buffer (§2), frame
|
||
after frame. Otherwise `record_malformed`.
|
||
|
||
A verifier holds the claim the marking names (`claim_unknown`); that claim's subject is the
|
||
marking's object, and the marking's payload is the claim's (§7.2) (`marking_mismatch`). Then
|
||
§7.6.
|
||
|
||
### 7.3 Detection and agreement
|
||
|
||
A detector that reads a watermark signs a **detection** record, `kind` `"wai-detection"`: the
|
||
members of a marking but `origin` and `embedding`, with `payload` the 8 bytes read, or `null`
|
||
where it found no mark, and with `read`, the frames it read: an object of exactly the members
|
||
`frames`, `width` and `height` (integers, each at least 1) and `samples` (32 B hex, the SHA-256
|
||
of those frames' canonical buffers, frame after frame), or `null`; otherwise `record_malformed`.
|
||
It is the signed detection result of the Code's Sub-measure 2.1.2: the content by its hash, the
|
||
detection solution by the scheme's term and digest, and a time stamp; and its kind says the result
|
||
rests on a watermark (Measure 2.3), the claim being the metadata. For each detection, in order,
|
||
with the origin claim whose subject is the detection's object (if the verifier holds one) and the
|
||
first marking whose subject it is (if it holds one):
|
||
|
||
1. With a marking: the detection's `scheme` and `scheme_digest` are the marking's
|
||
(`detection_mismatch`).
|
||
2. Its scheme and the frames it read (§7.4, §7.6): `scheme_mismatch`, `mark_evidence_invalid`,
|
||
`samples_mismatch`, `detection_irreproducible`, `layer_not_proved`.
|
||
3. With no payload read: where a marking says the object was marked, `watermark_absent`, the
|
||
second layer having been removed; otherwise the object is **unmarked**.
|
||
4. With a payload read and a claim held: the payload is the claim's (§7.2), and the detection
|
||
**agrees** (`agree`); or it is the payload of a claim the verifier holds that the claim reaches
|
||
through inputs along steps `convert` and `assist`, each input's claim held, and the detection
|
||
**agrees by inheritance** (`agree-inherited`): a conversion that does not mark its output again
|
||
carries its input's mark, and its claim names that input. Otherwise `layers_disagree`. A
|
||
content that carries the mark of a synthesized claim while its metadata says captured, or the
|
||
mark of another claim, is refused here. With no claim held, the detection is **not checked**
|
||
(`not-checked`).
|
||
|
||
### 7.4 Schemes
|
||
|
||
A scheme is named by a term `wai-mark:<name>` and the SHA-256 of its definition. This revision
|
||
registers `wai-mark:tent64` (§7.5). Its definition is the JCS text (RFC 8785) of this object,
|
||
whose bytes are the corpus's `scheme.json` (§13):
|
||
|
||
```
|
||
{"amplitude":12,"cells_per_bit":16,"class_max":3,"code":[255,71,29],"field":"11d",
|
||
"generator":"140a722a1a468d36d87a25364e685922a1e56fd1a478c1d","grid":64,"length":248,
|
||
"luma":[77,150,29,128,8],"margin":[3,2],"max_frame_bytes":268435456,"media":["image","video"],
|
||
"min_height":128,"min_width":128,"orientations":8,"pattern":"tent","sample":"RGB8",
|
||
"scheme":"wai-mark:tent64","specification":"extensions/synthesis.md section 7.5",
|
||
"stream":"wai-mark:tent64 stream"}
|
||
```
|
||
|
||
(broken here into lines after commas between members; the definition itself has no line break,
|
||
and no space but those inside its strings), and its digest is
|
||
`4e1bd9f2cac9420fc276434751a3fb9b2b0916128f200bf147009a86168be7d6`. The definition pins the
|
||
scheme's parameters, and §7.5 is its algorithm. A marking or a detection that names
|
||
`wai-mark:tent64` under another digest is `scheme_mismatch`. A marking of the scheme states its
|
||
`embedding`, and a detection its `read`, of frames the scheme admits (§7.5.1): otherwise
|
||
`mark_evidence_invalid`.
|
||
|
||
`wai-mark:conformance` stays as a scheme with no algorithm: its definition is the text
|
||
`wai-mark:conformance names no watermark algorithm (synthesis.md section 7.4)`, and a record of
|
||
it states no frames (`mark_evidence_invalid`). It exercises the records and their agreement apart
|
||
from any algorithm, and stands for a scheme a verifier does not implement: a record of it, or of
|
||
any term this revision does not register, is a signed statement by its signer and nothing more,
|
||
checked against the claim (§7.2, §7.3) but neither recomputed nor run again (§7.6).
|
||
|
||
### 7.5 The scheme `wai-mark:tent64`
|
||
|
||
An imperceptible mark in the luma of RGB8 frames, embedded by a pinned integer derivation and read
|
||
back with no key. Integer throughout: the marked frames are a pure function of the unmarked frames
|
||
and the payload, the same bytes on every machine, and what the detector reads is a pure function
|
||
of the frames it reads.
|
||
|
||
#### 7.5.1 Frames
|
||
|
||
The scheme marks **frames**: RGB8 buffers (§2), each *W* × *H* pixels with *W* ≥ 128, *H* ≥ 128
|
||
and *W*·*H*·3 ≤ 2^28, and *F* ≥ 1 of them of one size, with *F*·*W*·*H*·3 ≤ 2^53 − 1. A still
|
||
picture is one frame; a video's frames are its decoded frames in presentation order. The **luma**
|
||
of a pixel is *Y* = (77·*R* + 150·*G* + 29·*B* + 128) >> 8. Frames of another size or count are
|
||
not admitted: the detector reads no mark from them, and a record that states them is
|
||
`mark_evidence_invalid`.
|
||
|
||
#### 7.5.2 The grid and the pattern
|
||
|
||
A frame is cut into 64 × 64 **cells** by the column edges *x*ᵢ = ⌊*i*·*W* / 64⌋ and the row edges
|
||
*y*ⱼ = ⌊*j*·*H* / 64⌋, *i* and *j* from 0 to 64. Cell *k* = 64·*j* + *i* holds the pixels with
|
||
*x*ᵢ ≤ *x* < *x*ᵢ₊₁ and *y*ⱼ ≤ *y* < *y*ⱼ₊₁; its sides are *L*ₓ = *x*ᵢ₊₁ − *x*ᵢ and
|
||
*L*ᵧ = *y*ⱼ₊₁ − *y*ⱼ, each at least 2, and its **area** is ν = *L*ₓ·*L*ᵧ. The grid is a fraction
|
||
of the frame, so it follows the picture through a rescale or a change of aspect.
|
||
|
||
The **tent** over a span of *L* samples, at position *u* from 0 to *L* − 1, is
|
||
*f*(*u*, *L*) = sgn(*c*)·(*L* − |2·|*c*| − *L*|) with *c* = 2*u* + 1 − *L* and sgn(0) = 0:
|
||
negative over the span's first half and positive over its second, odd about its centre, and
|
||
summing to 0. A pixel's **weight** is ω(*x*, *y*) = *f*(*x* − *x*ᵢ, *L*ₓ)·*f*(*y* − *y*ⱼ, *L*ᵧ) in
|
||
its cell; |ω| ≤ ν. A cell's **statistic** over a luma plane is *s*ₖ = Σ ω(*x*, *y*)·*Y*(*x*, *y*)
|
||
and its **mass** *q*ₖ = Σ |ω(*x*, *y*)|, each over the cell's pixels. A constant added to every
|
||
luma sample leaves a statistic unchanged; a gain scales it.
|
||
|
||
#### 7.5.3 The layout and the code
|
||
|
||
The **stream** is the sequence of 32-bit words, read big-endian, eight from each digest, of
|
||
SHA-256(`wai-mark:tent64 stream` ‖ *n*) for *n* = 0, 1, 2, …, the label in ASCII and *n* a 4-byte
|
||
big-endian counter. Its words are taken in this order:
|
||
|
||
1. the **shuffle** π: π starts as 0, 1, …, 4095; for *i* from 4095 down to 1, with *r* the next
|
||
word, the entries at *i* and at *r* mod (*i* + 1) are swapped;
|
||
2. the **signs** ε₀ … ε₄₀₉₅: +1 where the next word is even, −1 where it is odd;
|
||
3. the **mask** μ₀ … μ₂₄₇: the lowest bit of the next word.
|
||
|
||
Bit *j* of the code, *j* from 0 to 247, is **carried** by the 16 cells π(16·*j*) … π(16·*j* + 15);
|
||
the cells π(3968) … π(4095) carry nothing. π begins 1787, 1650, 1946, 384, and the corpus states
|
||
more of it (§13).
|
||
|
||
The **code** is the binary BCH code of length 255 and designed distance 59 over GF(2^8) defined
|
||
by *x*^8 + *x*^4 + *x*^3 + *x*^2 + 1, α a root: its generator *g*(*x*) is the least common
|
||
multiple of the minimal polynomials of α^1 … α^58, of degree 184, so the code has 71 information
|
||
bits and corrects 29 errors. Its coefficients from *x*^184 down, in hexadecimal, are
|
||
`140a722a1a468d36d87a25364e685922a1e56fd1a478c1d`. It is shortened to 248 bits: a payload's 64
|
||
bits *b*₀ … *b*₆₃, *b*₀ the most significant bit of its first byte, are
|
||
*m*(*x*) = Σ *b*ᵢ·*x*^(63 − *i*), its **codeword** is
|
||
*c*(*x*) = *m*(*x*)·*x*^184 + (*m*(*x*)·*x*^184 mod *g*(*x*)), and codeword bit *j* is the
|
||
coefficient of *x*^(247 − *j*): bits 0 to 63 are the payload's, the rest its parity. This code is
|
||
chosen because a mark's errors arrive as independent bits after the shuffle has scattered each
|
||
bit's cells over the frame, which a binary code corrects bit by bit; because its decoding is
|
||
algebraic, so every correct decoder gives the same result; and because a decoder that corrects
|
||
only within its radius has a false-positive probability that is an exact count (§7.5.6), which a
|
||
code that decodes by likelihood does not give.
|
||
|
||
Bit *j* is **transmitted** as *t*ⱼ = *c*ⱼ ⊕ μⱼ, with the sign σⱼ = +1 for 1 and −1 for 0. The mask
|
||
keeps a frame with no structure from reading as a codeword: a flat frame reads every bit as 0,
|
||
which unmasks to μ, and μ and its complement are each more than 29 bits from every codeword. A
|
||
bit's **statistic** over a luma plane is *T*ⱼ = Σ εₖ·*s*ₖ and its **mass** *Q*ⱼ = Σ *q*ₖ, each over
|
||
its 16 cells.
|
||
|
||
#### 7.5.4 Embedding
|
||
|
||
Given unmarked frames and a payload, each frame is marked on its own, under the same payload. For
|
||
bit *j* and an integer **amplitude** *a* from 0 to 12, the frame **at amplitude** *a* for bit *j*
|
||
has, at each pixel of each cell *k* that carries *j*, the delta
|
||
|
||
δ = sgn(*z*)·⌊(2·*a*·|*z*| + ν) / (2·ν)⌋, with *z* = σⱼ·εₖ·ω(*x*, *y*)
|
||
|
||
(*a*·*z*/ν rounded to the nearest integer, halves away from zero) added to each of *R*, *G* and
|
||
*B*, each sum clamped to 0 … 255. Bit *j*'s amplitude *a*ⱼ is the least *a* at which the luma of
|
||
the frame at amplitude *a* meets the **margin** 2·σⱼ·*T*ⱼ ≥ 3·*Q*ⱼ, and 12 where no *a* does. The
|
||
**marked frame** is the unmarked frame with every cell that carries a bit *j* changed at *a*ⱼ; the
|
||
cells that carry nothing are unchanged. The cells of different bits are disjoint, so each bit is
|
||
embedded on its own, and a bit's statistic in the marked frame is its statistic at *a*ⱼ.
|
||
|
||
The amplitude is informed by the frame: a bit whose statistic already meets the margin is left as
|
||
it is (*a*ⱼ = 0), and the frame is changed only as far as the margin asks, up to the cap, which
|
||
binds where the picture's own texture opposes the bit most. A bit that does not meet the margin
|
||
at 12 is left to the code.
|
||
|
||
**Video.** Every frame carries the whole payload, embedded on its own, and the detector sums the
|
||
bits' statistics over the frames it is given (§7.5.5). The payload is repeated in each frame,
|
||
rather than the codeword spread over a group of frames, so that any one frame, a clip of any
|
||
length, a clip with frames dropped or re-timed and a still taken from the video each carry the
|
||
whole payload, with no position in time to recover; and the frames read together give a stronger
|
||
reading than one.
|
||
|
||
#### 7.5.5 Detection
|
||
|
||
The detector is given frames, *F* of *W* × *H*; frames §7.5.1 does not admit carry no mark. It
|
||
tries the 8 **orientations** *o* = 0, 1, …, 7 in order. A frame **seen in orientation** *o* is the
|
||
*W*′ × *H*′ luma plane (*W*′ = *H* and *H*′ = *W* where bit 2 of *o* is set, otherwise *W* and *H*)
|
||
whose sample at (*x*, *y*) is the frame's luma at (*x*₂, *y*₂), where *x*₁ = *W*′ − 1 − *x* where
|
||
bit 0 of *o* is set and *x* otherwise, *y*₁ = *H*′ − 1 − *y* where bit 1 is set and *y* otherwise,
|
||
and (*x*₂, *y*₂) = (*y*₁, *x*₁) where bit 2 is set and (*x*₁, *y*₁) otherwise. The eight are the
|
||
quarter turns and mirrors, and one of them undoes any of them.
|
||
|
||
In orientation *o*, each bit's statistic is summed over the frames seen in *o*; the bit **read**,
|
||
*r*ⱼ, is 1 where that sum is above 0 and 0 otherwise; and the **word** is *r*ⱼ ⊕ μⱼ. The word is
|
||
decoded within a distance of 29: to the codeword at most 29 bits from it, which is unique where it
|
||
exists, or to none. A codeword whose first payload byte is at most 3, a class code, gives the
|
||
**payload**, with the orientation and the number of bits corrected, and the detector stops;
|
||
otherwise it tries the next orientation. Where none gives a payload, the frames carry **no mark**.
|
||
Any decoder that finds the codeword within 29 bits whenever there is one, and none otherwise, gives
|
||
this result: the reference finds the error locator, the shortest linear recurrence the
|
||
syndromes satisfy, searches the 248 positions for its roots, and checks that what it corrected has
|
||
no syndrome.
|
||
|
||
#### 7.5.6 What it guarantees
|
||
|
||
- **No other channel.** The marked frames are a function of the unmarked frames and the 8-byte
|
||
payload alone: no key, no seed and no choice is left to the embedder. A verifier that holds the
|
||
unmarked frames recomputes the marked ones and compares their digest (§7.6), and where they are
|
||
equal the content carries exactly the declared payload, which is a function of the claim, and
|
||
nothing else: no identifier of a viewer, a session, a device or an account can ride on such a
|
||
mark. A forensic watermark ([forensic-watermark](forensic-watermark.md)) serves the other
|
||
purpose: its variants differ from viewer to viewer by design, so that a leaked copy names the
|
||
session it came from. The two share no record, and a deployment that does both marks
|
||
AI-generated content with this scheme and sessions with that one.
|
||
- **Imperceptibility.** A sample moves by at most 12, and only in the cells that carry a bit. A
|
||
frame's **floor** is the PSNR its embedding cannot fall below:
|
||
10·log10(255²·3·*W*·*H* / *S*), *S* the sum over the carrying cells' samples of the square of the
|
||
delta at amplitude 12 with no clamp (a clamp only shortens a delta). It depends on the frame's
|
||
size alone: 36.66 dB at 512 × 512, 38.73 dB at 256 × 256, 35.76 dB at 768 × 512. No admitted
|
||
size's floor is below 20·log10(255/12) = 26.55 dB, which frames whose cells are all 2 × 2
|
||
pixels come within 0.14 dB of. Each picture's measured PSNR and SSIM are in §7.5.8.
|
||
- **False positives.** Model frames with no mark as giving, in each orientation, a word of 248
|
||
bits each 0 or 1 with probability ½ independently: the shuffle, the signs and the mask set each
|
||
read bit apart from the picture's own structure. In one orientation the word gives a payload
|
||
only when it lies within 29 bits of one of the 2^58 codewords whose first byte is a class code;
|
||
those balls are disjoint, so the probability is 2^58·*V*(248, 29) / 2^248, with
|
||
*V*(*n*, *t*) = Σ C(*n*, *i*) over *i* from 0 to *t*, and over the 8 orientations at most 8 times
|
||
that: 2^−61.39, about 3.3·10^−19. A clip is read once, so the bound is the same for it. The model
|
||
is checked against what the detector reads: the words read from the 32 unmarked pictures of the
|
||
measurement sets (§7.5.8) in their 8 orientations have a mean of 124.33 ones with a standard
|
||
deviation of 7.85, and the 31,744 pairs of those words from different pictures differ in a mean
|
||
of 123.91 bits with a standard deviation of 7.95 and in no fewer than 91; the model gives 124
|
||
and 7.87 for both. No unmarked picture of any set, under any attack of the battery, gives a
|
||
payload.
|
||
- **Key-free.** The detector needs no key and no secret: anyone with this text reads the mark,
|
||
which is what lets any party check the second layer against the first. It also lets anyone find
|
||
the mark and try to remove or overwrite it, or copy a payload into other content (§14).
|
||
|
||
#### 7.5.7 The battery
|
||
|
||
What the scheme survives is measured, not asserted. The **battery** applies each attack below to
|
||
marked frames, and to the same frames unmarked, and runs the detector (§7.5.5) on the result. The
|
||
attacks are integer functions of the frames, and the noise comes from a fixed stream, so a run of
|
||
the battery gives the same bytes on every machine. The attack at index *i* of a list, from 0, has
|
||
the seed *i*. Below, rnd(*n* / *d*) is *n* / *d* rounded to the nearest integer, halves away from
|
||
zero, and every result is clamped to 0 … 255.
|
||
|
||
Attacks on pictures, in the list's order:
|
||
|
||
- `none`.
|
||
- `int-motion-q4`, `-q8`, `-q16`, `-q32`, `-q48`: each frame encoded as a keyframe of
|
||
`wai.video.int_motion` ([integer-payloads](integer-payloads.md) §3) at step *q* and decoded:
|
||
each sample *v* becomes 128 + rnd((*v* − 128) / *q*)·*q*.
|
||
- `staged-base-q8`: each 2 × 2 block averaged, (*a* + *b* + *c* + *d* + 2) >> 2 per channel, to a
|
||
frame of half each side; that frame as `int-motion-q8`; and the result brought back to full size
|
||
by `wai-synthesis:upscale2` (§4.1): the base layer of a staged picture whose refinement is not
|
||
delivered.
|
||
- `denoise3`, `denoise3-x4`: `wai-synthesis:denoise3` (§4.2), once and four times.
|
||
- `scale-3/4`, `scale-1/2`, `scale-1/3`, `scale-1/4`, `scale-3/2`: **area resampling** to
|
||
⌊*W*·*n*/*d*⌋ × ⌊*H*·*n*/*d*⌋. Along an axis of *N* samples resampled to *M*, destination sample
|
||
*X* covers *X*·*N* to (*X* + 1)·*N*, and source sample *s* covers *s*·*M* to (*s* + 1)·*M*, in
|
||
units of 1/*M* of a source sample; the length of their overlap is *s*'s weight in *X*, and a
|
||
destination's weights sum to *N*. A destination value is ⌊(2·Σ + *N*ₓ·*N*ᵧ) / (2·*N*ₓ·*N*ᵧ)⌋,
|
||
Σ the sum over the source of the product of its two weights and its value.
|
||
- `upscale2`: `wai-synthesis:upscale2`.
|
||
- `aspect-3/4`: area resampling of the width alone to ⌊*W*·3/4⌋.
|
||
- `half-then-int-motion-q16`: `scale-1/2`, then `int-motion-q16`.
|
||
- `crop-1%`, `crop-2%`, `crop-5%`, `crop-10%`: ⌊*W*·*p*/200⌋ columns removed from each side and
|
||
⌊*H*·*p*/200⌋ rows from the top and from the bottom.
|
||
- `mirror`: the columns in reverse order.
|
||
- `rotate-90`: a quarter turn clockwise, to *H* × *W*, whose pixel (*x*′, *y*′) is the frame's
|
||
(*y*′, *H* − 1 − *x*′).
|
||
- `rotate-0.5`, `rotate-1`, `rotate-2`: a rotation about the centre by that many degrees, whose
|
||
cosine and sine at 2^16 are (65534, 572), (65526, 1144) and (65496, 2287). Destination pixel
|
||
(*x*, *y*) reads the frame at *X* = (cos·*u* + sin·*v* + (*W* − 1)·2^16) >> 1 and
|
||
*Y* = (−sin·*u* + cos·*v* + (*H* − 1)·2^16) >> 1, in units of 2^−16 of a pixel, with
|
||
*u* = 2*x* + 1 − *W* and *v* = 2*y* + 1 − *H*; with ξ = *X* >> 16, φ = *X* mod 2^16, and η, ψ
|
||
likewise from *Y*, its value is (*p*(ξ, η)·(2^16 − φ)·(2^16 − ψ) + *p*(ξ + 1, η)·φ·(2^16 − ψ) +
|
||
*p*(ξ, η + 1)·(2^16 − φ)·ψ + *p*(ξ + 1, η + 1)·φ·ψ + 2^31) >> 32, where *p* is the frame's
|
||
sample and 0 outside it.
|
||
- `noise-4`, `noise-16`: each sample *v*, in order (frame, row, column, then *R*, *G*, *B*), becomes
|
||
*v* + (*w* mod (2*s* + 1)) − *s*, *w* the next word of the stream of
|
||
SHA-256(`wai-mark:battery noise` ‖ seed ‖ *n*), the seed and *n* each 4 bytes big-endian, read as
|
||
§7.5.3 reads its stream.
|
||
- `brightness+32`, `brightness-32`: *v* + 32, *v* − 32.
|
||
- `contrast-1/2`, `contrast-3/2`: 128 + rnd((*v* − 128)·*n* / *d*).
|
||
- `local-blur`: the rectangle ⌊*W*/4⌋ ≤ *x* < ⌊3*W*/4⌋, ⌊*H*/4⌋ ≤ *y* < ⌊3*H*/4⌋, a quarter of the
|
||
picture, cut into blocks of 16 × 16 from its corner (the last ones shorter), each channel of each
|
||
block replaced by its mean, rounded half up: a large region blurred, as a face is blurred for
|
||
privacy (the Code's Measure 3.3).
|
||
|
||
Attacks on clips: `none`; `clip-int-motion-q8` and `clip-int-motion-q24`, the clip encoded as
|
||
`wai.video.int_motion` with motion (blocks of 16, a search radius of 4, half-pel vectors, step
|
||
*q*) by the reference encoder and decoded; `frame-drop`, the frames 0, 2, 4, …; `one-frame`, the
|
||
first frame alone; and `scale-1/2` and `noise-16`, as above, on every frame.
|
||
|
||
#### 7.5.8 What it survives
|
||
|
||
The **measurement sets** are generated by the reference from fixed seeds (§13): `spectral`, 16
|
||
pictures of 512 × 512 from `wai.generate.spectral`'s generator and its committed model; `leaves`,
|
||
16 dead-leaves pictures of 512 × 512, discs of random colour, size and shading painted over each
|
||
other, then noise of up to 2 a sample; and `clips`, 4 clips of 8 frames of 256 × 256, a window
|
||
panning over two pictures of each kind. Each picture is marked under its own payload. The
|
||
**photographs** are 24 lossless photographs of 768 × 512 from a test suite of image coding, not in
|
||
this repository, measured with the reference (§13) and named by the SHA-256 of their files in the
|
||
corpus's README; their column is informative.
|
||
|
||
Each cell gives the pictures that gave their payload, of those marked, and in parentheses the most
|
||
bits the code corrected among them. No picture gave another payload, and no unmarked picture, under
|
||
any attack, gave any.
|
||
|
||
| attack | `spectral` | `leaves` | photographs |
|
||
|---|---|---|---|
|
||
| `none` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `int-motion-q4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `int-motion-q8` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `int-motion-q16` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `int-motion-q32` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (4) |
|
||
| `int-motion-q48` | 16 / 16 (1) | 16 / 16 (4) | 24 / 24 (2) |
|
||
| `staged-base-q8` | 16 / 16 (0) | 16 / 16 (11) | 24 / 24 (20) |
|
||
| `denoise3` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (5) |
|
||
| `denoise3-x4` | 16 / 16 (0) | 16 / 16 (7) | 24 / 24 (24) |
|
||
| `scale-3/4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) |
|
||
| `scale-1/2` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (6) |
|
||
| `scale-1/3` | 16 / 16 (28) | 0 / 16 | 4 / 24 (29) |
|
||
| `scale-1/4` | 16 / 16 (0) | 16 / 16 (1) | 23 / 24 (19) |
|
||
| `scale-3/2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `upscale2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `aspect-3/4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) |
|
||
| `half-then-int-motion-q16` | 16 / 16 (0) | 16 / 16 (4) | 24 / 24 (7) |
|
||
| `crop-1%` | 16 / 16 (0) | 2 / 16 (29) | 4 / 24 (26) |
|
||
| `crop-2%` | 0 / 16 | 0 / 16 | 0 / 24 |
|
||
| `crop-5%` | 0 / 16 | 0 / 16 | 0 / 24 |
|
||
| `crop-10%` | 0 / 16 | 0 / 16 | 0 / 24 |
|
||
| `mirror` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `rotate-90` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `rotate-0.5` | 16 / 16 (1) | 0 / 16 | 0 / 24 |
|
||
| `rotate-1` | 0 / 16 | 0 / 16 | 0 / 24 |
|
||
| `rotate-2` | 0 / 16 | 0 / 16 | 0 / 24 |
|
||
| `noise-4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `noise-16` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `brightness+32` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (4) |
|
||
| `brightness-32` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (5) |
|
||
| `contrast-1/2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (1) |
|
||
| `contrast-3/2` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (10) |
|
||
| `local-blur` | 16 / 16 (0) | 16 / 16 (3) | 24 / 24 (21) |
|
||
|
||
| attack on clips | `clips` |
|
||
|---|---|
|
||
| `none` | 4 / 4 (0) |
|
||
| `clip-int-motion-q8` | 4 / 4 (0) |
|
||
| `clip-int-motion-q24` | 4 / 4 (0) |
|
||
| `frame-drop` | 4 / 4 (0) |
|
||
| `one-frame` | 4 / 4 (2) |
|
||
| `scale-1/2` | 4 / 4 (0) |
|
||
| `noise-16` | 4 / 4 (0) |
|
||
|
||
The scheme **claims to survive** an attack only where every picture of every column, marked, gave
|
||
its payload and none, unmarked, gave any. For pictures, these attacks, each with the parameter
|
||
measured: `int-motion-q4`, `int-motion-q8`, `int-motion-q16`, `int-motion-q32`, `int-motion-q48`,
|
||
`staged-base-q8`, `denoise3`, `denoise3-x4`, `scale-3/4`, `scale-1/2`, `scale-3/2`, `upscale2`,
|
||
`aspect-3/4`, `half-then-int-motion-q16`, `mirror`, `rotate-90`, `noise-4`, `noise-16`,
|
||
`brightness+32`, `brightness-32`, `contrast-1/2`, `contrast-3/2`, `local-blur`. For clips:
|
||
`clip-int-motion-q8`, `clip-int-motion-q24`, `frame-drop`, `one-frame`, `scale-1/2`, `noise-16`.
|
||
|
||
It does **not** claim to survive a crop of any size, a rotation by a small angle, or area
|
||
resampling to 1/3 or 1/4, nor any parameter between or beyond those measured. A crop or a small
|
||
rotation moves the cells against the grid: half a cell turns a bit's statistic to its opposite,
|
||
and the detector searches no shift and no angle. Resampling to 1/4 survives on the measurement
|
||
sets, where a 512 × 512 cell of 8 × 8 becomes exactly 2 × 2, and lost one photograph; resampling to
|
||
1/3 lands cells on fractions of a pixel and is not survived.
|
||
|
||
Imperceptibility, as PSNR of the RGB8 buffer and SSIM of the luma (8 × 8 windows at a stride of 4,
|
||
*C*₁ = (0.01·255)², *C*₂ = (0.03·255)², the least over a clip's frames):
|
||
|
||
| set | PSNR, least … mean (dB) | floor (dB) | SSIM, least … mean |
|
||
|---|---|---|---|
|
||
| `spectral` | 44.86 … 45.02 | 36.66 | 0.9688 … 0.9732 |
|
||
| `leaves` | 43.05 … 43.69 | 36.66 | 0.9661 … 0.9696 |
|
||
| `clips` | 41.90 … 42.08 | 38.73 | 0.9439 … 0.9495 |
|
||
| photographs | 41.39 … 43.58 | 35.76 | 0.9761 … 0.9842 |
|
||
|
||
### 7.6 Proving a marking and reproducing a detection
|
||
|
||
A verifier is given, beside the records, the decoded frames of the objects it holds and the
|
||
**unmarked frames** of marked objects (§11.1). For each marking, after §7.2's checks, in this
|
||
order:
|
||
|
||
1. its scheme (§7.4): `scheme_mismatch`, `mark_evidence_invalid`;
|
||
2. for `wai-mark:tent64`, with the unmarked frames of its object held: they are
|
||
`frames`·`width`·`height`·3 bytes and their SHA-256 is the embedding's `unmarked`
|
||
(`mark_input_mismatch`), and the scheme's embedding of them (§7.5.4) under the marking's
|
||
payload has the SHA-256 `marked` (`mark_recompute_mismatch`). The marking is then **proved**;
|
||
otherwise it is **stated**;
|
||
3. with an embedding and the object's decoded frames held: their SHA-256 is the embedding's
|
||
`marked` (`samples_mismatch`);
|
||
4. under a policy that requires the second layer proved (§11.1), a marking that is not proved:
|
||
`layer_not_proved`.
|
||
|
||
A proved marking says that the object's frames are the scheme's embedding of those unmarked frames
|
||
under that payload, and nothing else. What it says of the unmarked frames is what the verifier
|
||
knows of them: for an object a sink generates at an exact tier (§7.8), the verifier generates them
|
||
itself and the proof covers the whole output; for unmarked frames a provider hands over, it covers
|
||
the marking step.
|
||
|
||
For each detection, at §7.3 step 2, in this order: its scheme (§7.4); with a `read` and the
|
||
object's decoded frames held, they are `frames`·`width`·`height`·3 bytes and their SHA-256 is
|
||
`samples` (`samples_mismatch`); and for `wai-mark:tent64`, the detector (§7.5.5) is run on them,
|
||
and the payload it reads, or its finding no mark, is the detection's `payload`
|
||
(`detection_irreproducible`). The detection is then **reproduced** (`reproduced`), and otherwise
|
||
**not run again** (`not-rerun`). Under a policy that requires the second layer proved, a detection
|
||
that is not reproduced is `layer_not_proved`.
|
||
|
||
So a detection that reports a mark in content that carries none, or in content altered beyond
|
||
what the scheme survives (§7.5.8), is refused wherever the verifier holds the content's frames: a
|
||
detection agrees with a claim only for a payload the content carries.
|
||
|
||
### 7.7 The signpost and the resolver
|
||
|
||
The Code's Measure 3.4(c) has its signatories implement an interoperability solution for their
|
||
detection mechanisms by 2 February 2027; one of the solutions it names (point ii) is a publicly
|
||
readable **signpost** in the content that tells the public which detection solution to use without
|
||
running every provider's. Its Glossary has a signpost solution be an openly disclosed marking
|
||
technique within the content, and its detection mechanism, that indicates which watermark
|
||
detection mechanism to employ where metadata is not available, and says it may be an imperceptible
|
||
machine-readable mark. This extension gives three:
|
||
|
||
1. **In the content.** `wai-mark:tent64` is itself openly disclosed and read with no key (§7.5.5).
|
||
Run on any content, its detector says whether the content carries a WAI mark, with the
|
||
false-positive bound of §7.5.6, and the payload it reads gives the class and the claim. The
|
||
claim's markings name every scheme marked into its object, each by term and digest, so the mark
|
||
is the signpost to the object's other marks as well.
|
||
2. **In the metadata.** Each marking gives a C2PA statement (C2PA 2.4 §18.15.5, §18.10),
|
||
`{"actions": A, "assertions": D}` written in JCS: `A` is `[{"action": "c2pa.watermarked.bound"}]`,
|
||
and `D` holds one soft-binding assertion, `{"label": "c2pa.soft-binding", "data": {"alg": S,
|
||
"alg-params": G, "blocks": [{"scope": {}, "value": P}]}}`, `S` the scheme's term, `G` its digest
|
||
and `P` the payload, each byte string written here in lowercase hexadecimal and carried as a
|
||
CBOR byte string in the manifest. The empty scope covers the whole content. The manifest's
|
||
assembler adds the action's hashed reference to the assertion (`relatedAssertions`, C2PA 2.4
|
||
§18.15.4.8). The scheme's term is not an entry of C2PA's soft-binding algorithm list, which C2PA
|
||
2.4 §9.3.2 recommends `alg` to name and does not require.
|
||
3. **A resolver.** A party that serves WAI content **MAY** answer
|
||
`GET /.well-known/wai-mark/<name>` (RFC 8615), for each scheme `wai-mark:<name>` it uses, with
|
||
the scheme's definition, the bytes whose SHA-256 is the scheme's digest, as `application/json`;
|
||
and `GET /.well-known/wai-mark/<name>/payload/<p>`, `<p>` a payload in lowercase hexadecimal,
|
||
with a JSON array of the texts of the origin claims it holds whose payload (§7.2) is `<p>`, empty
|
||
where it holds none. The definition names the section that specifies the detector, a public
|
||
specification any third party implements (the Code's Sub-measure 2.1.1 (i)), of which
|
||
`wai-rs` (`synthesis::mark`) and `live-conformance/_wai_mark.py` are two implementations
|
||
(§13). A client checks the
|
||
definition's digest against the term's before relying on it, and each claim it receives against
|
||
the payload it asked for and against the claim's signature. This revision does not register the
|
||
well-known name.
|
||
|
||
### 7.8 A sink that generates
|
||
|
||
Where an object's envelope generates (§3.3) and a marking of `wai-mark:tent64` names it, the
|
||
reconstruction a sink is held to is the scheme's embedding, under the marking's payload, of the
|
||
frames its generation produces: the sink generates, then marks, as one integer derivation appended
|
||
to the generative one, so the exact tier the generation holds (generation-equivalence,
|
||
[determinism-tiers](determinism-tiers.md) §2) covers the mark, and every viewer at that tier
|
||
receives the same marked frames. Its log's `present` entry states the marked frames' digest, the
|
||
embedding's `marked`; a review of the object names that digest as its reconstruction (§9.1); and a
|
||
player that presents the unmarked frames states an output the object does not decode to
|
||
(`present_output_irreproducible`, §9.2). A verifier that holds the envelope generates the unmarked
|
||
frames itself, and so proves the marking (§7.6) without taking anyone's unmarked frames on trust.
|
||
|
||
## 8. Live rooms
|
||
|
||
### 8.1 AI participants and their events
|
||
|
||
[live-rooms](live-rooms.md) §4.4 marks an **AI participant**: its grant's `ai` attribute names the
|
||
model that acts for it. Its `chat`, `reaction` and `caption` events state origin 2 or 3 with that
|
||
model ([live-events](live-events.md) §4.4, §4.5, §4.10); a room verifier refuses one that does
|
||
not, `ai_unmarked` (live-rooms §9.2 step 6), and a relay does not forward it.
|
||
|
||
### 8.2 Its tracks
|
||
|
||
Every track an AI participant publishes is synthesized media. Its publisher **MUST** sign, for
|
||
each layer track it publishes, a track claim (§3.1, `subject` of type `track`) of class
|
||
`synthesized`, step `generate`, whose `models` include the model the grant's `ai` names; the
|
||
claim is signed by the participant's key or a room key. A relay **MUST NOT** forward the objects
|
||
of such a track while it holds no such claim.
|
||
|
||
A verifier given a room (§11.1) replays its lifecycle events in the merged order (live-rooms
|
||
§9.2): each `join` and `grant_exchange` sets its subject's grant, each `leave` clears it, and for
|
||
each `publish` and `takeover` whose subject's grant names a model in `ai`, each layer track
|
||
*k* from 0 to `layers` − 1, owned by the subject (its key in lowercase hexadecimal) or by the slot
|
||
(`_` and its name), of the published media kind, is named by its digest (live-rooms §6.2). A track
|
||
with no claim as above is `ai_track_unlabelled`, at that lifecycle event.
|
||
|
||
## 9. Certified synthesis
|
||
|
||
### 9.1 The review
|
||
|
||
A person or an organisation that reviews a synthesized output signs a **review**, `kind`
|
||
`"wai-review"`:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-review"` |
|
||
| `subject` | 32 B hex | the content hash of the object reviewed |
|
||
| `origin` | 32 B hex | the link hash of its origin claim, of class `synthesized` |
|
||
| `tier` | string | the tier at which the reconstruction reviewed is portable: `decode-equivalence` or `generation-equivalence` ([determinism-tiers](determinism-tiers.md) §2; SPEC §7 `GenerativeDeterministic`) |
|
||
| `reconstruction` | 32 B hex | the digest of the canonical output the reviewer saw (§2) |
|
||
| `decoder` | term | the decoder the reviewer's output came from |
|
||
| `variant` | term or `null` | its decode path, where it has more than one |
|
||
| `scope` | string | what was reviewed: `accuracy` (words, a script), `rendering` (pixels, a voice, signing), `translation` |
|
||
| `verdict` | string | `approved`, `approved-with-notes` or `rejected` |
|
||
| `notes` | 32 B hex or `null` | the digest of the notes, `null` exactly for `approved` |
|
||
| `role` | term or `null` | a role the reviewer holds for this scope, as the reviewer states it |
|
||
| `issued_at` | date-time | when it signed it |
|
||
| `signer_pubkey`, `sig` | | §2 |
|
||
|
||
A verifier holds the claim a review names (`claim_unknown`), whose subject is the review's object
|
||
and whose class is `synthesized` (`review_subject_mismatch`).
|
||
|
||
### 9.2 Presenting a review
|
||
|
||
A player **proves its tier** by stating its decode path and the digest of what it presented, in a
|
||
`present` entry of its log (§10.3): the decoder, its variant, the tier the path holds, and the
|
||
output's digest. The tier a path holds is the weakest along it: the decoder's tier at its pin
|
||
(SPEC §3.1), and `none` where any float stage, or any processing after the decode that alters the
|
||
medium, lies on the path ([determinism-tiers](determinism-tiers.md) §2 *What a tier covers*: a
|
||
tier covers what the sink emits). A pinned integer derivation of §4 run over the decode's output,
|
||
under a prior the player holds by digest where it takes one, alters the medium by a pure
|
||
function of it, and the path keeps its tier; the output's digest is then the derivation's. A verifier that holds the object's decoded samples (the output
|
||
of a decode at the stated tier) checks the stated digest against them:
|
||
`present_output_irreproducible` where it differs.
|
||
|
||
A player **MAY** present a review, in a `review` entry of its log, only for an object it has
|
||
presented, and only where its latest presentation of the object before that entry:
|
||
|
||
1. presented the review's object, which the verifier holds the review of
|
||
(`claim_unknown`, `review_subject_mismatch`);
|
||
2. under a review whose verdict is not `rejected` (`review_rejected`);
|
||
3. on a path of the review's tier (`review_path_not_exact`): a path of tier `none` or
|
||
`entropy-consistency` never presents a review made at `decode-equivalence`;
|
||
4. with the review's `reconstruction` as its output's digest (`review_reconstruction_mismatch`).
|
||
|
||
Where these hold, the viewer received the output the reviewer saw, byte for byte: one review
|
||
holds for every viewer whose presentation is exact at that tier, and for no other.
|
||
|
||
## 10. Label display
|
||
|
||
### 10.1 When a label is shown
|
||
|
||
A **label policy**, `kind` `"wai-label-policy"`, signed by the party that presents content to
|
||
viewers, says which content is labelled and how:
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-label-policy"` |
|
||
| `classes` | array | the classes labelled, 1 to 4 of §3.2's, in its order, none twice |
|
||
| `visual` | 32 B hex | the SHA-256 of the visual label shown with visual media |
|
||
| `audio` | 32 B hex or `null` | the SHA-256 of the audible label played with audio-only media; `null` for none |
|
||
| `interval_us` | integer or `null` | the longest a labelled presentation goes without a label, in microseconds of the log's clock; at least 1 when not `null` |
|
||
| `grace_us` | integer | how long after the instant it is due a label may come |
|
||
| `issued_at` | date-time | when it signed it |
|
||
| `signer_pubkey`, `sig` | | §2 |
|
||
|
||
A label is due, for each kind (visual for `video`, `image` and `text`, audible for `audio`):
|
||
|
||
- at **first exposure**: the first presentation of labelled content of that kind in the session
|
||
(the Code of Practice's Sub-measure 1.2.1(d));
|
||
- after each **interruption**: the first presentation of labelled content of that kind after the
|
||
presentation resumed from an ad break or another interruption (Sub-measure 1.2.2(b); live-ads'
|
||
breaks end on a `cue` event, live-events §4.3);
|
||
- at **intervals**: where the policy has one, every presentation of labelled content of that kind
|
||
that is neither, no longer than the interval after the last label (Sub-measures 1.2.2(b) and
|
||
1.2.3).
|
||
|
||
### 10.2 The EU icons, pinned
|
||
|
||
The Code of Practice's Annex 1 publishes three EU icons, for content fully generated by AI
|
||
(`AI GENERATED`), for content partially modified by AI (`AI MODIFIED`), and a basic one (`AI`),
|
||
each in four variations. The Commission publishes them as two archives: SVG
|
||
(`https://ec.europa.eu/newsroom/dae/redirection/document/129546`, SHA-256
|
||
`f76707b69b546677b173975c018528e5e549e12afb05550f32c2f46500ffbefd` as fetched) and PNG
|
||
(`https://ec.europa.eu/newsroom/dae/redirection/document/129547`, SHA-256
|
||
`aedc51656497a2e8f3e42798e5749a929952f5b262bad368ee3057f58c0f52c7` as fetched). Each file is
|
||
pinned here by the SHA-256 of its bytes:
|
||
|
||
| file | SHA-256 |
|
||
|---|---|
|
||
| `LABEL_AI GENERATED_black transparent.svg` | `63d28ab55916b4548edff21d5fbcac065a13e5fb936e9b304ff0bada45f2fac1` |
|
||
| `LABEL_AI GENERATED_black.svg` | `503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f` |
|
||
| `LABEL_AI GENERATED_white transparent.svg` | `d19736f6da9d38af3cffce8ba08f3f7658b573e1794c173fcd8b55a5c200ed44` |
|
||
| `LABEL_AI GENERATED_white.svg` | `10125cdef3fc60a351df72fe1266bb00ea046086921bd1bb20135f0f07b5ed36` |
|
||
| `LABEL_AI MODIFIED_black transparent.svg` | `9ab09f54c1ccef01799af43794cd93a8af6607ba9ac6b9526fb90a02be8ddf3d` |
|
||
| `LABEL_AI MODIFIED_black.svg` | `2e7349e5eca4ee78eeef160dfef4545c31850932245373a07c5401e73e6599c0` |
|
||
| `LABEL_AI MODIFIED_white transparent.svg` | `7ccbce41f9f821a574007b32806ddb6d635ee8bbc5d753ccd05f66f5a3165845` |
|
||
| `LABEL_AI MODIFIED_white.svg` | `0c72a7d569a1447e2982a843e156bd5f9cb2ab6f201943c6c68f988dc2f1bb6e` |
|
||
| `LABEL_AI_black transparent.svg` | `f8e20f1dde8c7d95940da08960b1063620812acc9b4ea96eb5ad051f80f67c21` |
|
||
| `LABEL_AI_black.svg` | `58ffe859a4d74829d397f534a988081bcefb716849c7d3b84fa7a026dd1d257f` |
|
||
| `LABEL_AI_white transparent.svg` | `52d87cb3f6a191dba24796f745e1eeb80d342883acd1a2e6d856342ad853766c` |
|
||
| `LABEL_AI_white.svg` | `5b3b94ae67fea55c4f2d013d0b8ed5b876c1533555ed0ed60e688f2f3ccf1a50` |
|
||
| `LABEL_AI GENERATED_black transparent.png` | `74c8c4ee720c4a3286c3c1b9f216ac9b918ebf449e8c421dde5a40cd216b27e6` |
|
||
| `LABEL_AI GENERATED_black.png` | `8d0af57bc93ba3797042a4b56db757d85f924d9b0200b75b8842da7be5e402a2` |
|
||
| `LABEL_AI GENERATED_white transparent.png` | `06c4c7c00738e0e44c35f5927e72842ff7a9dd757e0374335b9cc1ba6bb52199` |
|
||
| `LABEL_AI GENERATED_white.png` | `f139d8de4b5173f47a777431e7e03d9258d8ea7ee1ee1a062febc7e9fff067fc` |
|
||
| `LABEL_AI MODIFIED_black transparent.png` | `f48f6c5137834fff87c701eb9a44934ac51c6b5d826b9b5cfead1b88024f8387` |
|
||
| `LABEL_AI MODIFIED_black.png` | `0f32e4afbe1459baaf425c78373ea541421ecdb61803438892e2fc07df67da8f` |
|
||
| `LABEL_AI MODIFIED_white transparent.png` | `1f3b435b0bf775a168309c4ede49b23c7fa94b759a72623932e6ccb7bdef6203` |
|
||
| `LABEL_AI MODIFIED_white.png` | `64241cf4f87eb0837bd234cb63716c7d78f4e9dbbb8866f40464ca4e6bfbfead` |
|
||
| `LABEL_AI_black transparent.png` | `24da93847c04a5c86c714c6cd018c45331b47df32936b617b3c8476933d97fa9` |
|
||
| `LABEL_AI_black.png` | `0814d5f04533c118f81aed0d353ea75d1df4171c40b962dba1dfa044a3d84779` |
|
||
| `LABEL_AI_white transparent.png` | `5b8c743b679a62453a4bc25070b82bb4db33ad9c68f4ef1b6ef7bb562dd3a4e3` |
|
||
| `LABEL_AI_white.png` | `b9041e416fed455722a3aaa06a5ea0eb376d8a8e11385fdc8e3e8ee6998cdf26` |
|
||
|
||
A policy whose `visual` is one of these digests shows that EU icon; any other digest names an
|
||
equivalent label, which the Code allows (Section 2, Commitment 1). A player that shows a pinned
|
||
icon shows exactly those bytes. No audible EU label is published: the Annex says an audio-only
|
||
icon will be developed, and the Code's audio work begins with a task force. A policy's `audio` is
|
||
then the digest of the disclaimer or earcon its signer chooses (Sub-measure 1.2.3), until a
|
||
revision of this table pins a published one.
|
||
|
||
### 10.3 The player's log
|
||
|
||
A player signs a **log** of a viewing session, one record per entry, `kind` `"wai-player"`, under a
|
||
key of that session (no identity of the viewer or the device):
|
||
|
||
| member | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | `"wai-player"` |
|
||
| `session` | 32 B hex | fresh random bytes of the session, the same in every entry |
|
||
| `seq` | integer | the entry's position, from 0 |
|
||
| `prev` | 32 B hex or `null` | the link hash of the entry before it; `null` for entry 0 |
|
||
| `at` | integer | the session clock at the entry, in microseconds; never below the entry before it's |
|
||
| `entry` | object | one of the four below |
|
||
| `signer_pubkey`, `sig` | | §2, the same key in every entry |
|
||
|
||
The entries:
|
||
|
||
- `{"type": "present", "subject", "origin", "class", "media", "tier", "decoder", "variant",
|
||
"output"}`: the player presented an object from `at` on: its content hash; the link hash of the
|
||
origin claim it applied, or `null`; that claim's class as it read it, or `null`; the `media` of
|
||
§3.1; the tier its path holds (`decode-equivalence`, `generation-equivalence`,
|
||
`entropy-consistency` or `none`, §9.2); its decoder (a term) and variant (a term or `null`), as a
|
||
`reconstruct-session` claim names them (jwp-receipts §2.4.12); and its output's digest, or `null`
|
||
where it states none.
|
||
- `{"type": "shown", "label", "asset", "trigger"}`: the player showed a label: `visual` or `audio`;
|
||
the SHA-256 of the asset shown; and why, as the player states it: `first-exposure`, `resume` or
|
||
`interval`.
|
||
- `{"type": "resume", "after"}`: presentation resumed after an interruption, `ad-break` or
|
||
`other`.
|
||
- `{"type": "review", "subject", "review"}`: the player presented the review whose link hash is
|
||
`review` for the object `subject` (§9.2).
|
||
|
||
A log whose `seq` is not each entry's position, whose `prev` is not the link hash of the entry
|
||
before, whose `at` goes back, or whose `session` or key changes, is `player_chain_broken`, at the
|
||
first entry that breaks it.
|
||
|
||
### 10.4 The obligations
|
||
|
||
Each `present` entry whose `origin` names a claim the verifier holds states that claim's class,
|
||
for that claim's object (`present_mismatch`). Under a label policy, in this order:
|
||
|
||
1. Every `shown` entry's asset is the policy's `visual` for a visual label and its `audio` for an
|
||
audible one (`label_asset_mismatch`).
|
||
2. Each `present` entry whose `class` is one the policy labels is a **labelled presentation** at
|
||
its `at`, *p*, of the label kind its `media` asks for (§10.1). Taking them in the log's order,
|
||
each kind on its own:
|
||
- the first of its kind is a **first exposure**: a `shown` entry of that kind is in the log
|
||
with `at` at most *p* + `grace_us`;
|
||
- a later one, when a `resume` entry lies between the labelled presentation of its kind
|
||
before it and it, is a **resume**: a `shown` entry of that kind has `at` from the latest such
|
||
`resume`'s `at` to *p* + `grace_us`;
|
||
- any other, under a policy with an interval, is an **interval**: a `shown` entry of that kind
|
||
has `at` from *p* − `interval_us` to *p* + `grace_us`.
|
||
|
||
One with no such `shown` entry is `label_not_shown`, at its `present` entry. A label not shown
|
||
at first exposure is caught by the player's own signed log: the log states the presentation and
|
||
when the label came.
|
||
|
||
## 11. Verification
|
||
|
||
### 11.1 Inputs
|
||
|
||
A verifier is given, each in a list: origin claims, marking records, detection records and
|
||
reviews; a label policy or none; a player's log or none; the envelopes of the objects it holds
|
||
(each named by its content hash); the decoded samples it holds, each with the content hash of
|
||
the object decoded; the priors it holds ([exact-upscale](exact-upscale.md) §3.1), each named by
|
||
its SHA-256; the unmarked frames it holds (§7.6), each with the content hash of the marked object
|
||
they are the unmarked frames of; a room or none, as live-rooms §9.1 gives a room verifier its
|
||
inputs; and a **policy** of three members:
|
||
|
||
| member | values | default | meaning |
|
||
|---|---|---|---|
|
||
| `dual_layer` | `when-given`, `required` | `when-given` | `required`: §7.1's rule applies (`layer_missing`) |
|
||
| `assistive` | `accept-asserted`, `require-proof` | `accept-asserted` | `require-proof`: §4.4 step 4 applies (`assistive_not_proved`) |
|
||
| `second_layer` | `accept-stated`, `require-proof` | `accept-stated` | `require-proof`: every marking is proved and every detection reproduced (§7.6, `layer_not_proved`) |
|
||
|
||
### 11.2 Steps
|
||
|
||
The verifier runs these steps and stops at the first refusal, which it reports with the reason
|
||
and where: a record by its list and index, the label policy, or the room as live-rooms §9.1 states
|
||
a place in it.
|
||
|
||
1. **Reading.** Each record, list by list (origin claims, markings, detections, reviews, the label
|
||
policy, the log's entries), each in order, reads (§2, §3.1, §3.2, §4.3, §7.2, §7.3, §9.1, §10.1,
|
||
§10.3) and its signature verifies: `record_malformed`, `bad_key`, `bad_signature`.
|
||
2. **Origin claims**, in order, each by these checks in order: a subject named before
|
||
(`claim_conflict`); its class (§3.2, `class_mismatch`); its source (§5, `source_mismatch`); its
|
||
inputs and its envelope (§3.3: `input_mismatch`, `class_understated`); its evidence (§4.4:
|
||
`evidence_invalid`, `input_samples_mismatch`, `assistive_recompute_mismatch`,
|
||
`samples_mismatch`, `assistive_not_proved`).
|
||
3. **Marking.** The markings in order (§7.2: `claim_unknown`, `marking_mismatch`; §7.6:
|
||
`scheme_mismatch`, `mark_evidence_invalid`, `mark_input_mismatch`, `mark_recompute_mismatch`,
|
||
`samples_mismatch`, `layer_not_proved`); the detections in order (§7.3 step 1,
|
||
`detection_mismatch`; step 2, by §7.6: `scheme_mismatch`, `mark_evidence_invalid`,
|
||
`samples_mismatch`, `detection_irreproducible`, `layer_not_proved`; steps 3 and 4:
|
||
`watermark_absent`, `layers_disagree`); then, under `dual_layer` `required`, the origin claims
|
||
in order (§7.1, `layer_missing`).
|
||
4. **Reviews**, in order (§9.1: `claim_unknown`, `review_subject_mismatch`).
|
||
5. **The room.** Live-rooms §9: a refusal there is this verification's, at the room. A gap is not
|
||
a refusal. Then its AI participants' tracks (§8.2, `ai_track_unlabelled`).
|
||
6. **The log.** Its chain (§10.3, `player_chain_broken`); then each entry in order: a `present`
|
||
entry (§10.4, `present_mismatch`; §9.2, `present_output_irreproducible`) and a `review` entry
|
||
(§9.2: `claim_unknown`, `review_subject_mismatch`, `review_rejected`, `review_path_not_exact`,
|
||
`review_reconstruction_mismatch`); then, under a label policy, §10.4's steps 1 and 2
|
||
(`label_asset_mismatch`, `label_not_shown`).
|
||
|
||
### 11.3 What an accepted case states
|
||
|
||
- for each origin claim, in order: its link hash, class and source, its C2PA statement (§6) as
|
||
written, and for an assist claim whether it is `proved`, `not-recomputed` or `asserted` (§4.4);
|
||
- for each marking, in order: its object, its scheme, whether it is `proved` or `stated` (§7.6),
|
||
and its C2PA statement (§7.7) as written;
|
||
- for each detection, in order: its object, its result, `agree`, `agree-inherited`, `unmarked` or
|
||
`not-checked` (§7.3), and whether it was `reproduced` or `not-rerun` (§7.6);
|
||
- for each AI participant's track, in the order §8.2 reaches them: its digest and the claim that
|
||
labels it;
|
||
- for each `review` entry of the log: its index and the review's link hash;
|
||
- for each labelled presentation that had a label due: its index, the trigger (`first-exposure`,
|
||
`resume` or `interval`) and the index of the first `shown` entry that met it;
|
||
- the file name of the EU icon the label policy's `visual` is, where it is one (§10.2);
|
||
- the room's own outcome, `accept` or `gap`, where a room was given.
|
||
|
||
## 12. Reasons
|
||
|
||
The codes this extension adds, each with where it is raised:
|
||
|
||
| reason | raised at |
|
||
|---|---|
|
||
| `record_malformed` | §2, §3.1, §3.2, §4.3, §11.2 step 1 |
|
||
| `bad_key` | §2 |
|
||
| `bad_signature` | §2 |
|
||
| `claim_conflict` | §3.1 |
|
||
| `class_mismatch` | §3.2 |
|
||
| `source_mismatch` | §5 |
|
||
| `input_mismatch` | §3.3 |
|
||
| `class_understated` | §3.3 |
|
||
| `evidence_invalid` | §4.3, §4.4 steps 1 and 2 |
|
||
| `input_samples_mismatch` | §4.4 step 2 |
|
||
| `assistive_recompute_mismatch` | §4.4 step 2 |
|
||
| `samples_mismatch` | §4.4 step 3, §7.6 |
|
||
| `assistive_not_proved` | §4.4 step 4 |
|
||
| `claim_unknown` | §7.2, §9.1, §9.2 |
|
||
| `marking_mismatch` | §7.2 |
|
||
| `scheme_mismatch` | §7.4, §7.6 |
|
||
| `mark_evidence_invalid` | §7.4, §7.5.1, §7.6 |
|
||
| `mark_input_mismatch` | §7.6 |
|
||
| `mark_recompute_mismatch` | §7.6 |
|
||
| `detection_mismatch` | §7.3 |
|
||
| `detection_irreproducible` | §7.6 |
|
||
| `watermark_absent` | §7.3 |
|
||
| `layers_disagree` | §7.3 |
|
||
| `layer_missing` | §7.1 |
|
||
| `layer_not_proved` | §7.6, §11.1 |
|
||
| `ai_track_unlabelled` | §8.2 |
|
||
| `player_chain_broken` | §10.3 |
|
||
| `present_mismatch` | §10.4 |
|
||
| `present_output_irreproducible` | §9.2 |
|
||
| `review_subject_mismatch` | §9.1, §9.2 |
|
||
| `review_rejected` | §9.2 |
|
||
| `review_path_not_exact` | §9.2 |
|
||
| `review_reconstruction_mismatch` | §9.2 |
|
||
| `label_asset_mismatch` | §10.4 |
|
||
| `label_not_shown` | §10.4 |
|
||
|
||
A verifier given a room also raises every code a room verifier raises (live-rooms §11, and the
|
||
live-events codes it lists), at the room: `ai_unmarked` among them, for an AI participant's
|
||
event that does not state its machine origin (live-rooms §9.2 step 6).
|
||
|
||
## 13. Conformance
|
||
|
||
[`live-conformance/synthesis/`](../live-conformance/synthesis/) holds the corpus, in one file,
|
||
`vectors.json`, written from fixed seeds by `wai_synthesis_vectors gen`. It lists the EU icons of
|
||
§10.2 and the **vectors**: each a case of §11.1, its outcome, and, accepted, everything §11.3
|
||
states; refused, its reason and its place. It has:
|
||
|
||
- **Accepted**: a capture, a neural codec's reconstruction of it, and its two pinned derivations,
|
||
each proved by recomputation from the reconstruction's decoded samples; a sink's
|
||
`wai-upscale:sr2` of that reconstruction under a seeded prior, claimed about the samples it
|
||
presents and proved from the reconstruction's samples and the prior; a float
|
||
super-resolution step whose attest evidence is asserted; a generated video, an algorithmic
|
||
audio generation from a `wai.generate.spectral` envelope, an inpainting a person reviewed, a
|
||
composite of a capture and the generated video, and a transcode of the video, each synthesized
|
||
object marked in both layers, a detection that agrees and one that finds the capture unmarked;
|
||
a room whose AI host states its model in its chat and caption and labels its speech track; and a
|
||
review of the generated video presented on an exact path, with labels shown at first exposure,
|
||
after an ad break and within an interval.
|
||
- **Refused**: each reason of §12 at least once but the three only the scheme's corpus gives
|
||
(below), each a control of one defect: among them a
|
||
synthesized object labelled captured (`class_understated`), an assistive claim whose
|
||
recomputation differs (`assistive_recompute_mismatch`), once over a pinned derivation of §4.1
|
||
and once over a sink's `wai-upscale:sr2`, a prior that does not read (`evidence_invalid`), the
|
||
two marking layers disagreeing
|
||
(`layers_disagree`), a review presented on a non-exact path (`review_path_not_exact`), an AI
|
||
participant's unmarked chat (`ai_unmarked`, raised by the room) and a track it publishes with no
|
||
claim (`ai_track_unlabelled`), and a label not shown at first exposure, after a break, and within
|
||
the interval (`label_not_shown`).
|
||
|
||
A conforming verifier gives every vector the outcome, the reason, the place and the statements
|
||
the corpus states. `wai_synthesis_vectors verify` and the test `tests/synthesis.rs` check that the
|
||
file regenerates byte for byte, that `wai-rs` gives every vector its outcome, that §12 lists
|
||
exactly the reference's codes, and that §10.2's table pins exactly the reference's icons.
|
||
`live-conformance/synthesis_verify.py` is the second implementation, written from this text: it
|
||
shares no code with `wai-rs`; it runs the exact-upscale derivations with
|
||
`live-conformance/_int_sr.py`, written from that extension's §3; it reads §10.2's pins and §12's
|
||
codes from this text,
|
||
verifies each room with the live-rooms corpus's second implementation (`live_rooms_verify.py`),
|
||
every signature with WAI's strict rule (`claim-conformance/_ed25519.py`) and every link hash with
|
||
BLAKE3 written from its specification (`claim-conformance/_blake3.py`); and it mutates copies of
|
||
the corpus and requires every copy to be refused.
|
||
|
||
[`live-conformance/synthesis-mark/`](../live-conformance/synthesis-mark/) holds the registered
|
||
scheme's corpus, written from fixed seeds by `wai_synthesis_mark_vectors gen`: `scheme.json`, the
|
||
bytes of the scheme's definition (§7.4); the frames its vectors name, each a raw RGB8 file
|
||
`frames/<SHA-256>.rgb`; `vectors.json`; and `battery.json`. `vectors.json` holds the stream's
|
||
first values and the mask; the codewords of six payloads and the decoding of each with 0, 1, 29,
|
||
30 and 40 bits flipped; the embeddings of a generated still, a dead-leaves stand-in for a
|
||
photograph and a clip of three frames, each with its marked frames' digest and each bit's
|
||
amplitude; what the detector reads from stored frames; a **battery sample**, every picture attack
|
||
of §7.5.7 applied to the corpus's still, marked and unmarked, with the digest of each result and
|
||
what the detector reads from it; and **cases** of §11.1 whose frames are files. `battery.json` is
|
||
the measurement of §7.5.8 over the measurement sets: each picture's PSNR, floor and SSIM, each
|
||
attack's tallies, the unmarked detections, the words of §7.5.6 and the attacks every set
|
||
survived.
|
||
|
||
The cases accept a generated still whose marking is proved and whose detection is reproduced,
|
||
presented with a review at generation-equivalence (§7.8); a clip proved likewise; a re-encoding
|
||
whose detection agrees by inheritance; a crop beyond what the scheme survives, read as unmarked;
|
||
and a marking and a detection that are stated. They refuse a marking whose frames carry another
|
||
claim's payload and one whose frames have one bit more changed (`mark_recompute_mismatch`);
|
||
unmarked frames of another picture (`mark_input_mismatch`); decoded frames that are not the marked
|
||
ones (`samples_mismatch`); a detection of a mark in a photograph that carries none and one in the
|
||
crop (`detection_irreproducible`); a capture claim over the marked still (`layers_disagree`); a
|
||
marking not proved under a policy that requires it (`layer_not_proved`); the registered term under
|
||
another digest (`scheme_mismatch`); frames below the least size (`mark_evidence_invalid`); and a
|
||
player that presents the unmarked frame (`present_output_irreproducible`). The codes
|
||
`mark_input_mismatch`, `mark_recompute_mismatch` and `detection_irreproducible` need frames the
|
||
scheme admits, and only this corpus gives them.
|
||
|
||
`wai_synthesis_mark_vectors verify` and the test `tests/synthesis_mark.rs` check that every file
|
||
regenerates byte for byte and no other is there, the battery's among them; that `wai-rs` gives
|
||
every vector and case what it states; that §7.5.3's generator polynomial and §7.5.4's definition are
|
||
the reference's; and that §7.5.8's claims are attacks the measurement shows survived and §7.5.6's
|
||
figures are the measurement's. `wai_synthesis_mark_vectors measure <dir> <out>` runs the battery
|
||
over the PNG files of a directory; §7.5.8's photographs' column is its result over the files the
|
||
corpus's README names by digest, which this repository does not hold.
|
||
`live-conformance/synthesis_mark_verify.py` is the second implementation, written from this text
|
||
and sharing no code with `wai-rs`: it reads the scheme's definition and checks its digest; builds
|
||
the stream, the shuffle, the signs, the mask and the generator polynomial from §7.5.3; embeds and
|
||
detects, with a decoder of its own; applies every picture attack of §7.5.7 and requires the battery
|
||
sample's results; verifies every case with `synthesis_verify.py`; checks §7.5.8's claims against
|
||
`battery.json`'s tallies; and mutates copies of the corpus and requires every copy to be refused.
|
||
|
||
## 14. Security and privacy considerations
|
||
|
||
- A claim carries its signer's authority. A class is a statement; what this extension adds is
|
||
that a class can only rise along a chain of claims (§3.2), that an assistive claim over a pinned
|
||
derivation is checked by recomputation (§4), that a generator's own envelope cannot be claimed
|
||
as anything but generated (§3.3), and that the second layer is checked against the first (§7).
|
||
A synthesized object whose producer signs no claim at all is caught only by the second layer,
|
||
and only where its producer marked it (§7.5).
|
||
- A watermark payload's seven bytes of link hash locate a claim; they are not a commitment, and a
|
||
mark can be copied into other content. A detection that agrees with a claim says the content
|
||
carries that claim's mark, not that the content is the claim's subject; the claim's subject is
|
||
bound by content hash.
|
||
- The registered scheme is read with no key (§7.5.6), so anyone can find the mark and try to
|
||
remove it: in the battery, a crop of 2% or a rotation of one degree leaves no mark the detector
|
||
reads in any picture measured (§7.5.8). Anyone can also embed a payload of their choosing in any
|
||
content, a real photograph among them. The scheme claims no resistance to removal or forgery,
|
||
which the Code's Measure 3.3 asks to be assessed: what it survives is the table's processing,
|
||
and nothing adversarial is claimed. A forged mark agrees only with the claim it names; that
|
||
claim's subject is bound by content hash, so the mark does not make the content its subject;
|
||
and content with a claim of its own that reads another claim's mark, not inherited through its
|
||
conversions, is refused (`layers_disagree`, §7.3).
|
||
- A mark carries its 8-byte payload and nothing else, and a verifier that holds the unmarked
|
||
frames proves it (§7.5.6, §7.6): no identifier of a viewer, a session, a device or an account.
|
||
The detector runs on the device that holds the content, and needs no upload. A resolver's
|
||
payload lookup (§7.7) tells the resolver which claim a client looks for; a client that does not
|
||
want that holds the claims itself, or asks a party it trusts.
|
||
- Evidence states digests of samples, never samples. A verifier that recomputes holds the input's
|
||
decoded samples, which it decoded itself.
|
||
- A player's log is signed under a key of the session and names objects, claims, labels and
|
||
instants of the session's clock: no viewer, no device and no account. It shows what that key
|
||
reports, and is not evidence that a person watched.
|
||
- A review names its reviewer's key and, where given, a role the reviewer states; a role is the
|
||
reviewer's statement until an authority attests it.
|
||
- A relay can withhold a claim but cannot alter one. A sink that receives an object without the
|
||
claim its chain should hold sees the gap in the chain of inputs (§3.4).
|