# 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 second registered watermark, > `wai-mark:tent64s`, lays the same mark out in tiles, so its detector finds the grid again after > a crop, a small rotation or a capture of a screen. 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 schemes' 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 schemes, what they survive, the signpost and the 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); resistance to an adversary who sets out to remove or forge a mark, which neither registered scheme claims (§14); 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 **MAY** also carry a post-quantum signature: the hybrid members `pq_suite`, `pq_key` and `pq_sig` beside `sig`, which [pq-signatures](pq-signatures.md) §3.4's shared check reads and verifies, with its own codes (pq-signatures §8), `sig` then being over the composite's representative of `"wai:synthesis\x01" ‖ JCS(record without "sig" and "pq_sig")`; the member tables of this extension apply to the record without them. A verifier that does not implement pq-signatures refuses them as members its tables do not list (`record_malformed`). 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`, `text` or `volumetric` (a splat set, a mesh or a point cloud; [splat-exact](splat-exact.md) §8) | | `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:` and the SHA-256 of its definition. This revision registers `wai-mark:tent64` (§7.5) and `wai-mark:tent64s` (§7.9). The definition of `wai-mark:tent64` 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; §7.9.1 gives `wai-mark:tent64s`'s. A marking or a detection that names a registered term under another digest is `scheme_mismatch`. A marking of a registered 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 a registered scheme, 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, §7.9.3) 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 a registered scheme, its detector (§7.5.5, §7.9.4) 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, §7.9.7), 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.** Each registered scheme is itself openly disclosed and read with no key (§7.5.5, §7.9.4). Run on any content, its detector says whether the content carries a WAI mark, with the false-positive bound of §7.5.6 or §7.9.5, 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/` (RFC 8615), for each scheme `wai-mark:` 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//payload/

`, `

` a payload in lowercase hexadecimal, with a JSON array of the texts of the origin claims it holds whose payload (§7.2) is `

`, 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 `synthesis::mark::sync`) 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 a registered scheme 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. ### 7.9 The scheme `wai-mark:tent64s` The mark of `wai-mark:tent64` laid out in tiles. Its embedding is §7.5.4's, under a layout in which every quarter of the frame holds the same pattern, so a detector that does not find the grid in place finds it again from the pattern's own period: after a crop, a small rotation, a rescale or a capture of a screen. Nothing is added for this: the marked frames are as far from the unmarked ones as `tent64`'s (§7.9.7), and integer throughout, a pure function of the unmarked frames and the payload. The detector is integer throughout too, so what it reads is a pure function of the frames, the same in every implementation. It is a second term and not a revision of `wai-mark:tent64`: the same unmarked frames and payload give other marked frames, a marking of `tent64` commits to `tent64`'s by digest (§7.2), and §7.6 recomputes a marking under the definition its term and digest name. `tent64` stays registered unchanged. #### 7.9.1 The definition The definition is the JCS text of this object, whose bytes are the corpus's `scheme-tent64s.json` (§13), broken into lines as §7.4's is: ``` {"amplitude":12,"cells_per_bit":16,"cells_per_tile_bit":4,"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", "resync":{"bins":[4,8],"candidates":8,"decodes":4,"frames":8,"robust":[25,5,100], "slope":[3,20],"span":[[7,20],[3,4]],"subsample":64,"working_side":1024},"sample":"RGB8", "scheme":"wai-mark:tent64s","specification":"extensions/synthesis.md section 7.9", "stream":"wai-mark:tent64s stream","tiles":[2,2]} ``` and its digest is `36ab9338f3ecade8adbf73190b1f3510f9bc6851339e3cfad18b0863923c8e8c`. What the scheme does not state below is §7.5's: the frames and the luma (§7.5.1); the grid, the tent, a cell's statistic and mass (§7.5.2); the code, the codeword and the decoding (§7.5.3, §7.5.5); the amplitude, the margin and the embedding of each bit (§7.5.4). #### 7.9.2 The tiled layout The grid's 64 × 64 cells form 2 × 2 **tiles** of 32 × 32 cells. Cell (*i*, *j*) is at the **position** *p* = 32·(*j* mod 32) + (*i* mod 32) of its tile, which has row ⌊*p* / 32⌋ and column *p* mod 32. The **stream** is §7.5.3's under the label `wai-mark:tent64s stream`, and its words are taken in this order: 1. the **shuffle** π of the positions: π starts as 0, 1, …, 1023; for *i* from 1023 down to 1, with *r* the next word, the entries at *i* and at *r* mod (*i* + 1) are swapped; 2. the **signs** ε₀ … ε₁₀₂₃ of the positions: +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 is carried by the positions π(4*j*) … π(4*j* + 3), so by those 4 positions in each of the 4 tiles, 16 cells; the positions π(992) … π(1023) carry nothing. A cell's sign is its position's. π begins 410, 485, 761, 10, and the corpus states more of it. Every tile holds the same signs and the same bits, so the mark repeats with a period of half the frame's width and half its height: exactly where they are even, to within one sample otherwise. #### 7.9.3 Embedding §7.5.4, with this layout: each bit's amplitude is the least from 0 to 12 that meets the margin over its 16 cells, and every frame of a video carries the whole payload. #### 7.9.4 Detection The detector is given frames, *F* of *W* × *H*; frames §7.5.1 does not admit carry no mark. Below, rnd(*n* / *d*) is *n* / *d* rounded to the nearest integer, halves away from zero (for *d* < 0, as −*n* / −*d*), ⌊ ⌋ rounds down, *a* mod *b* is in 0 … *b* − 1, and isqrt is the integer square root. **Stage 1: the grid in place.** §7.5.5 with this layout: in each orientation from 0 to 7, the bits' statistics summed over the frames are read, unmasked and decoded, and the first orientation that gives a payload gives the result. Where none does, stage 2. **Stage 2: the grid found again**, on the first min(*F*, 8) frames: 1. **The working plane** of a frame: its luma, or with *k* = ⌈max(*W*, *H*) / 1024⌉ above 1, the ⌊*W*/*k*⌋ × ⌊*H*/*k*⌋ samples each ⌊(Σ + ⌊*k*²/2⌋) / *k*²⌋ of its *k* × *k* block of luma, Σ the block's sum. *W*′ × *H*′ is its size. 2. **Robust values**: *r*(*x*, *y*) = 25·*Y*(*x*, *y*) less the sum of *Y* over the 5 × 5 samples centred there, each coordinate clamped to the plane, then clamped to −100 … 100. 3. **The sign plane**: with *L*ₓ = max(2, ⌊*W*′/64⌋) and *L*ᵧ = max(2, ⌊*H*′/64⌋), *q*(*x*, *y*) = sgn Σ *f*(*u*, *L*ₓ)·*f*(*v*, *L*ᵧ)·*r*(*x* + *u*, *y* + *v*) over 0 ≤ *u* < *L*ₓ, 0 ≤ *v* < *L*ᵧ, for 0 ≤ *x* ≤ *W*′ − *L*ₓ and 0 ≤ *y* ≤ *H*′ − *L*ᵧ: the tent pattern's response at the size of a cell, *W*_q × *H*_q of them. 4. **The autocorrelation**: *A*(*dx*, *dy*) = Σ *q*(*x*, *y*)·*q*(*x* + *dx*, *y* + *dy*) over the frames and over the samples where both are in the plane, and the **overlap** *o*(*dx*, *dy*) = (*W*_q − |*dx*|)·(*H*_q − |*dy*|); both 0 where |*dx*| ≥ *W*_q or |*dy*| ≥ *H*_q. They are exact integers, however they are computed. 5. **The candidates.** For *dx* from ⌈7*W*_q/20⌉ to ⌊3*W*_q/4⌋, *dy* from −⌊3*dx*/20⌋ to ⌊3*dx*/20⌋ and *e* from ⌈7*H*_q/20⌉ to ⌊3*H*_q/4⌋, in that order (*e* fastest): the lattice vectors **b**₁ = (*dx*, *dy*) and **b**₂ = (*g*, *e*), *g* = rnd(−*dy*·*e* / *dx*), at right angles; its **score** is ⌊65536·Σ*A* / isqrt(Σ*o*)⌋ over the four points **b**₁, **b**₂, **b**₁ + **b**₂ and **b**₁ − **b**₂ (isqrt(0) read as 1). In score order, highest first and ties in the listing order, the first 8 candidates each more than 2 from every earlier one in *dx*, in *dy* or in *e*. 6. **Refined**, each candidate's vectors in units of 1/64 of a sample: *X*₁ = 64·*dx* + *v*(*A*(*dx* − 1, *dy*), *A*(*dx*, *dy*), *A*(*dx* + 1, *dy*)) and *Y*₁ = 64·*dy* + *v*(*A*(*dx*, *dy* − 1), *A*(*dx*, *dy*), *A*(*dx*, *dy* + 1)), and *X*₂, *Y*₂ likewise about (*g*, *e*), where *v*(*l*, *c*, *r*) is 0 where *D* = *l* − 2*c* + *r* ≥ 0 and otherwise rnd(32·(*r* − *l*) / −*D*) clamped to −32 … 32, the vertex of the parabola through the three. Δ = *X*₁·*Y*₂ − *X*₂·*Y*₁; a candidate with Δ ≤ 0 is dropped. 7. **The fold.** A cell of the folded tile has *m*ₓ = ⌊*X*₁/2048⌋ and *m*ᵧ = ⌊*Y*₂/2048⌋ bins along its sides, each clamped to 4 … 8; the tile is *n*ₓ = 32*m*ₓ by *n*ᵧ = 32*m*ᵧ. Each sample (*x*, *y*) of each working plane has *u* = ⌊64·*n*ₓ·(*Y*₂·*x* − *X*₂·*y*) / Δ⌋ and *v* = ⌊64·*n*ᵧ·(*X*₁·*y* − *Y*₁·*x*) / Δ⌋, its tile's parity *c* = (⌊*u*/*n*ₓ⌋ mod 2) + 2·(⌊*v*/*n*ᵧ⌋ mod 2), and adds *r*(*x*, *y*) to the bin (*u* mod *n*ₓ, *v* mod *n*ᵧ) of the fold *F*_c. 8. **The phase.** For *p*ᵧ from 0 to *m*ᵧ − 1 and *p*ₓ from 0 to *m*ₓ − 1: each fold's cell statistics *S*_c[*b*][*a*] = Σ *f*(*s*, *m*ₓ)·*f*(*t*, *m*ᵧ)·*F*_c[(*b*·*m*ᵧ + *p*ᵧ + *t*) mod *n*ᵧ][(*a*·*m*ₓ + *p*ₓ + *s*) mod *n*ₓ] over 0 ≤ *s* < *m*ₓ, 0 ≤ *t* < *m*ᵧ (row *b*, column *a*), their sum *S*, and how well the tiles agree, Σ (*S*² − Σ_c *S*_c²) over the 1024 cells. The candidate's **tile** is *S* at the first phase with the greatest agreement. 9. **The hypotheses**: for each candidate in order, each orientation *o* from 0 to 7 and each offset (*x*₀, *y*₀), *y*₀ then *x*₀ from 0 to 31: the tile **seen in orientation** *o*, *S*_o[*y*][*x*] = ±*S*[*y*₂][*x*₂] with (*x*₂, *y*₂) as §7.5.5 maps a 32 × 32 plane and the sign − where exactly one of bits 0 and 1 of *o* is set (the tent is odd, so a mirror negates it); bit *j*'s four values *e*ₖ = ε_p·*S*_o[(⌊*p*/32⌋ + *y*₀) mod 32][(*p* mod 32 + *x*₀) mod 32], *p* = π(4*j* + *k*); and its **agreement**, the number of pairs *k* < *l* over every bit with *e*ₖ > 0 and *e*ₗ > 0 both true or both false. The four cells of a bit carry the same bit, so at the true alignment they agree, and elsewhere they agree by chance. 10. **Decoding.** In agreement order, highest first and ties in the listing order, the first 4 hypotheses each read bit *j* as 1 where Σₖ *e*ₖ > 0, unmask it with μⱼ and decode it within 29 (§7.5.5); the first that gives a payload gives the result, with its orientation and the bits corrected. Where none does, the frames carry **no mark**. The search reaches a lattice vector from 7/20 to 3/4 of the plane's side and up to 3/20 of its length off its axis: a crop that leaves less than about 4/3 of a tile in either direction, a rotation beyond about 8.5°, and a frame shown inside a larger one are out of its reach. #### 7.9.5 What it guarantees - **No other channel**, as §7.5.6: the marked frames are a function of the unmarked frames and the payload, which a verifier recomputes (§7.6). - **Imperceptibility.** §7.5.6's floor under this layout, which at every size measured is `tent64`'s: 36.66 dB at 512 × 512, 38.73 dB at 256 × 256 and 35.76 dB at 768 × 512. - **False positives.** The detector decodes at most 12 words, 8 in stage 1 and 4 in stage 2. Under §7.5.6's model each gives a payload with probability at most 2^58·*V*(248, 29) / 2^248, so a clip or a picture gives one with probability at most 12 times that: 2^−60.80, about 5.0·10^−19. The model is checked against the words the detector decodes from the 32 unmarked pictures of the measurement sets, 8 in stage 1 and 4 in stage 2 from each: 384 words have a mean of 124.14 ones with a standard deviation of 7.78, and the 71,424 pairs of those words from different pictures differ in a mean of 123.90 bits with a standard deviation of 7.93 and in no fewer than 74; the model gives 124 and 7.87 for both. The least is further below the mean than the model makes likely over so many pairs: stage 2's words of pictures with large even regions lean together, towards the mask, which §7.5.3 keeps more than 29 bits from every codeword. No unmarked picture of any set, under any attack of the battery, gives a payload. - **Key-free**, as §7.5.6. Anyone can also make content repeat with the period of the tiles, and stage 2 then folds along that lattice; it reads a payload only where a codeword is there to read (the corpus's forged lattice, §13). #### 7.9.6 The battery's additions The battery of this scheme is §7.5.7's, with these picture attacks after its own, the attack at index *i* of the whole list having the seed *i*: - `crop-25%`, as §7.5.7's crops; - `crop-left-p%`, `crop-right-p%`, `crop-top-p%` and `crop-bottom-p%` for *p* of 2, 5, 10 and 25: ⌊*W*·*p*/100⌋ columns removed from the left or the right, or ⌊*H*·*p*/100⌋ rows from the top or the bottom; - `rotate-5`: §7.5.7's rotation with the cosine and sine at 2^16 of (65287, 5712); - `screen-a` and `screen-b`, a simulated capture of a screen: area resampling to ⌊*W*·*n*/*d*⌋ × ⌊*H*·*n*/*d*⌋, *n*/*d* 5/4 and 4/5; the rotation of `rotate-1` and, for `screen-b`, of `rotate-2` clockwise, its sine negated; each sample *v* then rnd(*v*·(*v* + 255) / 510), a display's curve; then `noise-4` at the attack's seed. The corpus also derives frames by a crop from one side of any percent below 100, and by **periodic**: each frame tiled by its own top-left ⌊*W*/2⌋ × ⌊*H*/2⌋, so that its content repeats with the period of the tiles. #### 7.9.7 What it survives On the measurement sets of §7.5.8 and the same photographs, each cell as there: | attack | `spectral` | `leaves` | photographs | |---|---|---|---| | `none` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `int-motion-q4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (3) | | `int-motion-q8` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `int-motion-q16` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `int-motion-q32` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (4) | | `int-motion-q48` | 16 / 16 (2) | 16 / 16 (5) | 24 / 24 (6) | | `staged-base-q8` | 16 / 16 (0) | 16 / 16 (10) | 24 / 24 (16) | | `denoise3` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (5) | | `denoise3-x4` | 16 / 16 (0) | 16 / 16 (10) | 24 / 24 (18) | | `scale-3/4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (3) | | `scale-1/2` | 16 / 16 (0) | 16 / 16 (3) | 24 / 24 (4) | | `scale-1/3` | 16 / 16 (12) | 16 / 16 (11) | 19 / 24 (29) | | `scale-1/4` | 16 / 16 (0) | 16 / 16 (2) | 24 / 24 (16) | | `scale-3/2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (3) | | `upscale2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (4) | | `aspect-3/4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (3) | | `half-then-int-motion-q16` | 16 / 16 (0) | 16 / 16 (6) | 24 / 24 (8) | | `crop-1%` | 16 / 16 (0) | 16 / 16 (29) | 24 / 24 (27) | | `crop-2%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (23) | | `crop-5%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (24) | | `crop-10%` | 16 / 16 (0) | 16 / 16 (1) | 23 / 24 (16) | | `mirror` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `rotate-90` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `rotate-0.5` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (19) | | `rotate-1` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (14) | | `rotate-2` | 16 / 16 (0) | 16 / 16 (2) | 23 / 24 (10) | | `noise-4` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `noise-16` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (3) | | `brightness+32` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (8) | | `brightness-32` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (6) | | `contrast-1/2` | 16 / 16 (0) | 16 / 16 (0) | 24 / 24 (2) | | `contrast-3/2` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (14) | | `local-blur` | 16 / 16 (0) | 16 / 16 (4) | 24 / 24 (28) | | `crop-25%` | 16 / 16 (0) | 16 / 16 (5) | 23 / 24 (27) | | `crop-left-2%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (15) | | `crop-right-2%` | 16 / 16 (0) | 16 / 16 (1) | 23 / 24 (9) | | `crop-top-2%` | 16 / 16 (0) | 16 / 16 (1) | 23 / 24 (13) | | `crop-bottom-2%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (13) | | `crop-left-5%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (19) | | `crop-right-5%` | 16 / 16 (0) | 16 / 16 (2) | 23 / 24 (10) | | `crop-top-5%` | 16 / 16 (0) | 16 / 16 (1) | 23 / 24 (14) | | `crop-bottom-5%` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (18) | | `crop-left-10%` | 16 / 16 (0) | 16 / 16 (2) | 24 / 24 (22) | | `crop-right-10%` | 16 / 16 (0) | 16 / 16 (2) | 23 / 24 (11) | | `crop-top-10%` | 16 / 16 (0) | 16 / 16 (2) | 23 / 24 (11) | | `crop-bottom-10%` | 16 / 16 (0) | 16 / 16 (2) | 23 / 24 (18) | | `crop-left-25%` | 16 / 16 (0) | 16 / 16 (3) | 23 / 24 (21) | | `crop-right-25%` | 16 / 16 (0) | 16 / 16 (4) | 23 / 24 (15) | | `crop-top-25%` | 16 / 16 (0) | 16 / 16 (5) | 23 / 24 (22) | | `crop-bottom-25%` | 16 / 16 (0) | 16 / 16 (5) | 23 / 24 (20) | | `rotate-5` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (24) | | `screen-a` | 16 / 16 (0) | 16 / 16 (1) | 24 / 24 (14) | | `screen-b` | 16 / 16 (0) | 16 / 16 (3) | 23 / 24 (15) | | 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 (1) | | `scale-1/2` | 4 / 4 (0) | | `noise-16` | 4 / 4 (0) | No picture gave another payload, and no unmarked picture, under any attack, gave any. 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: `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-1/4`, `scale-3/2`, `upscale2`, `aspect-3/4`, `half-then-int-motion-q16`, `crop-1%`, `crop-2%`, `crop-5%`, `mirror`, `rotate-90`, `rotate-0.5`, `rotate-1`, `noise-4`, `noise-16`, `brightness+32`, `brightness-32`, `contrast-1/2`, `contrast-3/2`, `local-blur`, `crop-left-2%`, `crop-bottom-2%`, `crop-left-5%`, `crop-bottom-5%`, `crop-left-10%`, `rotate-5`, `screen-a`. 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 `scale-1/3`, `crop-10%`, `rotate-2`, `crop-25%`, `crop-right-2%`, `crop-top-2%`, `crop-right-5%`, `crop-top-5%`, `crop-right-10%`, `crop-top-10%`, `crop-bottom-10%`, `crop-left-25%`, `crop-right-25%`, `crop-top-25%`, `crop-bottom-25%` and `screen-b`, nor any parameter between or beyond those measured. Every set generated survived every attack of the table. One photograph, a dense natural texture (`bc34a3ce58dea09dce1704c997171602de90cb34d0c8503a988b77f473d39b08` in the corpus's README), did not give its payload in 14 of the crops and rotations, nor another (`10349e963c5c813d327852f82c1795fa4148d69fedffc4c589bee458e3ac3d53`) in `screen-b`: there the autocorrelation of the picture's own texture outweighs the mark's at the lattice, and stage 2's eight candidates miss it. Resampling to 1/3, which `tent64` survives on `spectral` alone, is read on both sets, on `leaves` by stage 2, but not on five photographs. Imperceptibility, as §7.5.8 states it, beside `tent64`'s: | set | PSNR, least … mean (dB) | floor (dB) | SSIM, least … mean | `tent64`'s PSNR | `tent64`'s SSIM | |---|---|---|---|---|---| | `spectral` | 44.71 … 44.99 | 36.66 | 0.9682 … 0.9730 | 44.86 … 45.02 | 0.9688 … 0.9732 | | `leaves` | 43.31 … 43.67 | 36.66 | 0.9677 … 0.9697 | 43.05 … 43.69 | 0.9661 … 0.9696 | | `clips` | 41.84 … 42.05 | 38.73 | 0.9439 … 0.9482 | 41.90 … 42.08 | 0.9439 … 0.9495 | | photographs | 41.78 … 43.63 | 35.76 | 0.9772 … 0.9844 | 41.39 … 43.58 | 0.9761 … 0.9842 | The floor is unchanged and every figure is within 0.4 dB or 0.002 of `tent64`'s, either way: the tiled layout carries each bit in as many cells, with the same amplitudes and margin, so finding the grid again spends no imperceptibility. ## 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/.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

` 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. The same directory holds `wai-mark:tent64s`'s files, sharing its frames: `scheme-tent64s.json`, the bytes of §7.9.1's definition; `vectors-tent64s.json`, with the tile's shuffle, signs and mask, the embeddings of the corpus's still and clip, what the detector reads from stored frames and from frames derived from them, with stage 2's lattice, bins, phase, offset, candidate, rank and agreement where it reads by stage 2, a battery sample of every picture attack of §7.9.6 on the still, marked and unmarked, and cases; and `battery-tent64s.json`, the measurement of §7.9.7 over the measurement sets. Frames a crop, a rotation, a capture of a screen or `periodic` makes are not stored: a vector names the stored frames, their size and count, the attack and its seed, and each implementation derives them. The cases accept the still's marking proved and its detection reproduced in stage 1; a crop of 10% from the left and a rotation of 2° of the marked still, each a conversion whose detection agrees by inheritance; a simulated capture of a screen; and the marked clip cropped, its three frames folded together; each of the last four read by stage 2 and reproduced. They refuse a detection of the still's mark in a crop of 40% from the left, beyond stage 2's reach, and one in unmarked content forged to repeat with the tiles' period (`detection_irreproducible`); the term under `tent64`'s digest (`scheme_mismatch`); and a marking of this scheme over the frames `tent64` marked (`mark_recompute_mismatch`). `wai_synthesis_mark_vectors verify`, `tests/synthesis_mark.rs` and `synthesis_mark_verify.py` check these files as they check `tent64`'s, the second implementation running both stages of the detector with an autocorrelation and a fold of its own, and `wai_synthesis_mark_vectors measure tent64s` gives the photographs' column of §7.9.7. ## 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 schemes are 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 `tent64` mark the detector reads in any picture measured (§7.5.8), and `tent64s`, which finds the grid again after them (§7.9.7), is undone by a crop beyond its search or by content made to outweigh it. Anyone can also embed a payload of their choosing in any content, a real photograph among them, or make content repeat with the tiles' period (§7.9.5). Neither scheme claims resistance to removal or forgery, which the Code's Measure 3.3 asks to be assessed: what each survives is its 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).