open-standards/wai/extensions/synthesis.md
Transaction Science 00abf9b8e5 Merge PQ1 #435 (hybrid Ed25519 + ML-DSA-65 signatures) into train/wai-c
Merged head 7eaf392745.

By meaning:
- jwp-receipts §3.1 step 3 gains PQ1's hybrid sentence; step 4 keeps the
  base's class range (§2.4.5 to §2.4.15, the bearer classes included).
- claim_receipt.rs: the bearer classes' reasons and labels and PQ1's
  Pq(..) reason, each kept.
- Cargo.lock: the train's lock with PQ1's dependency resolved into it
  (cargo metadata, resolve only): its packages are exactly the union of
  both sides' (11 added beside TEL's keccak 0.1.6, aes-gcm, hkdf; none
  removed or re-versioned, every checksum as either side has it).
- Cargo.toml: TEL's and PQ1's optional dependencies, both tests.
- wai-conformance.yml: paths unioned; PQ1's step and its corpus in the
  regeneration loop merged without conflict.
- Shared lists kept both sides: CHANGELOG (PQ1's entry on top), SPEC §5
  (PQ1's paragraph after SPLAT's), REGISTRY rows in order, registry
  constants and wire identifiers. capabilities.json taken from the PR and
  regenerated next.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 10:35:14 -04:00

105 KiB
Raw Permalink Blame History

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 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/, 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/, 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's; key discovery for the signers of these records, which a verifier takes from jwp-receipts §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 §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

Relationship to timed-text and live-events: 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 §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: 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 §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 §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 §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 §2 (row-major, three bytes a pixel, no row padding); for another medium, the canonical buffer its capability's *-equivalence names (determinism-tiers §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 §6.1)
media string audio, video, image, text or volumetric (a splat set, a mesh or a point cloud; splat-exact §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). 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 2w × 2h 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(2x, 2y) = a;
  • out(2x + 1, 2y) = (a + b + 1) >> 1;
  • out(2x, 2y + 1) = (a + c + 1) >> 1;
  • out(2x + 1, 2y + 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 §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 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 §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 over decoded pixels, is a supplement of that kind here and not the second layer.

Under a policy that requires both layers (§11.1), an object claim of class synthesized with no marking record naming it (§7.2) is layer_missing.

7.2 The payload and the marking record

A watermark carrying a claim holds an 8-byte payload: the code of the claim's class (0 captured, 1 reconstructed, 2 assistive, 3 synthesized), then the first seven bytes of the claim's link hash. Read from the content alone, with no metadata, it says the class and points at the claim. Seven bytes find a claim; they do not prove one, and the claim's signature does that. The payload is the whole of what a mark carries: it names no viewer, no session, no device and no account (§7.5.6).

The party that embeds the mark signs a marking record, kind "wai-marking":

member type meaning
kind string "wai-marking"
subject 32 B hex the content hash of the marked object
origin 32 B hex the link hash of its origin claim
scheme term the watermark scheme (§7.4)
scheme_digest 32 B hex the SHA-256 of the scheme's definition
payload 8 B hex the payload embedded
embedding object or null the frames the mark was embedded into (§7.6)
issued_at date-time when it signed it
signer_pubkey, sig §2

An embedding is an object of exactly these members: frames, width and height, integers each at least 1, the count and size of the frames; unmarked and marked, each 32 B hex, the SHA-256 of the unmarked and of the marked frames, each frame its canonical buffer (§2), frame after frame. Otherwise record_malformed.

A verifier holds the claim the marking names (claim_unknown); that claim's subject is the marking's object, and the marking's payload is the claim's (§7.2) (marking_mismatch). Then §7.6.

7.3 Detection and agreement

A detector that reads a watermark signs a detection record, kind "wai-detection": the members of a marking but origin and embedding, with payload the 8 bytes read, or null where it found no mark, and with read, the frames it read: an object of exactly the members frames, width and height (integers, each at least 1) and samples (32 B hex, the SHA-256 of those frames' canonical buffers, frame after frame), or null; otherwise record_malformed. It is the signed detection result of the Code's Sub-measure 2.1.2: the content by its hash, the detection solution by the scheme's term and digest, and a time stamp; and its kind says the result rests on a watermark (Measure 2.3), the claim being the metadata. For each detection, in order, with the origin claim whose subject is the detection's object (if the verifier holds one) and the first marking whose subject it is (if it holds one):

  1. With a marking: the detection's scheme and scheme_digest are the marking's (detection_mismatch).
  2. Its scheme and the frames it read (§7.4, §7.6): scheme_mismatch, mark_evidence_invalid, samples_mismatch, detection_irreproducible, layer_not_proved.
  3. With no payload read: where a marking says the object was marked, watermark_absent, the second layer having been removed; otherwise the object is unmarked.
  4. With a payload read and a claim held: the payload is the claim's (§7.2), and the detection agrees (agree); or it is the payload of a claim the verifier holds that the claim reaches through inputs along steps convert and assist, each input's claim held, and the detection agrees by inheritance (agree-inherited): a conversion that does not mark its output again carries its input's mark, and its claim names that input. Otherwise layers_disagree. A content that carries the mark of a synthesized claim while its metadata says captured, or the mark of another claim, is refused here. With no claim held, the detection is not checked (not-checked).

7.4 Schemes

A scheme is named by a term wai-mark:<name> and the SHA-256 of its definition. This revision registers wai-mark:tent64 (§7.5) 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 = 2u + 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) 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 §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 = 2x + 1 − W and v = 2y + 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 (2s + 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 < ⌊3W/4⌋, ⌊H/4⌋ ≤ y < ⌊3H/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/<name> (RFC 8615), for each scheme wai-mark:<name> it uses, with the scheme's definition, the bytes whose SHA-256 is the scheme's digest, as application/json; and GET /.well-known/wai-mark/<name>/payload/<p>, <p> a payload in lowercase hexadecimal, with a JSON array of the texts of the origin claims it holds whose payload (§7.2) is <p>, empty where it holds none. The definition names the section that specifies the detector, a public specification any third party implements (the Code's Sub-measure 2.1.1 (i)), of which wai-rs (synthesis::mark and 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 §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 π(4j) … π(4j + 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 ⌈7W_q/20⌉ to ⌊3W_q/4⌋, dy from −⌊3dx/20⌋ to ⌊3dx/20⌋ and e from ⌈7H_q/20⌉ to ⌊3H_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 − 2c + 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ₓ = 32mₓ by nᵧ = 32mᵧ. 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 = π(4j + 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 §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 §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 §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 §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 §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/ 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/ holds the registered scheme's corpus, written from fixed seeds by wai_synthesis_mark_vectors gen: scheme.json, the bytes of the scheme's definition (§7.4); the frames its vectors name, each a raw RGB8 file frames/<SHA-256>.rgb; vectors.json; and battery.json. vectors.json holds the stream's first values and the mask; the codewords of six payloads and the decoding of each with 0, 1, 29, 30 and 40 bits flipped; the embeddings of a generated still, a dead-leaves stand-in for a photograph and a clip of three frames, each with its marked frames' digest and each bit's amplitude; what the detector reads from stored frames; a battery sample, every picture attack of §7.5.7 applied to the corpus's still, marked and unmarked, with the digest of each result and what the detector reads from it; and cases of §11.1 whose frames are files. battery.json is the measurement of §7.5.8 over the measurement sets: each picture's PSNR, floor and SSIM, each attack's tallies, the unmarked detections, the words of §7.5.6 and the attacks every set survived.

The cases accept a generated still whose marking is proved and whose detection is reproduced, presented with a review at generation-equivalence (§7.8); a clip proved likewise; a re-encoding whose detection agrees by inheritance; a crop beyond what the scheme survives, read as unmarked; and a marking and a detection that are stated. They refuse a marking whose frames carry another claim's payload and one whose frames have one bit more changed (mark_recompute_mismatch); unmarked frames of another picture (mark_input_mismatch); decoded frames that are not the marked ones (samples_mismatch); a detection of a mark in a photograph that carries none and one in the crop (detection_irreproducible); a capture claim over the marked still (layers_disagree); a marking not proved under a policy that requires it (layer_not_proved); the registered term under another digest (scheme_mismatch); frames below the least size (mark_evidence_invalid); and a player that presents the unmarked frame (present_output_irreproducible). The codes mark_input_mismatch, mark_recompute_mismatch and detection_irreproducible need frames the scheme admits, and only this corpus gives them.

wai_synthesis_mark_vectors verify and the test tests/synthesis_mark.rs check that every file regenerates byte for byte and no other is there, the battery's among them; that wai-rs gives every vector and case what it states; that §7.5.3's generator polynomial and §7.5.4's definition are the reference's; and that §7.5.8's claims are attacks the measurement shows survived and §7.5.6's figures are the measurement's. wai_synthesis_mark_vectors measure <dir> <out> runs the battery over the PNG files of a directory; §7.5.8's photographs' column is its result over the files the corpus's README names by digest, which this repository does not hold. live-conformance/synthesis_mark_verify.py is the second implementation, written from this text and sharing no code with wai-rs: it reads the scheme's definition and checks its digest; builds the stream, the shuffle, the signs, the mask and the generator polynomial from §7.5.3; embeds and detects, with a decoder of its own; applies every picture attack of §7.5.7 and requires the battery sample's results; verifies every case with synthesis_verify.py; checks §7.5.8's claims against battery.json's tallies; and mutates copies of the corpus and requires every copy to be refused.

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).