WAI Extension: Synthesis Provenance
Mirrored from the canonical text at commit b1681667 ().
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 thec2pa.ai-disclosureassertions 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 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 thesynthesismodule ofwai-rs(featuresynthesis); the corpus islive-conformance/synthesis/, and its second implementation islive-conformance/synthesis_verify.py. The scheme’s battery and measurement are thesynthesis_markmodule (featuresynthesis_mark); its corpus islive-conformance/synthesis-mark/, and its second implementationlive-conformance/synthesis_mark_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision, each pinned to the version named: the Code of Practice on Transparency of AI-Generated Content, final text of 10 June 2026, Section 1 Measures 1.1 to 1.3, 2.1, 2.3 and 3.2 to 3.4 and its Glossary, and Section 2 Measures 1.1 and 1.2 and its Annex 1, as the Commission’s document 129555, whose SHA-256 as fetched on 21 October 2026 was
7bd22c5a3c56eaefda27a5bf7a6118198ef2a9c9255241bd97abf7cdedf9bc28; the EU icons the Commission publishes for that Annex, as fetched on 21 October 2026 from the page it last updated on 13 October 2026 (§10.2); Regulation (EU) 2024/1689, Article 50, paragraphs 2, 4 and 5 as that Code reproduces them, and the last sentence of paragraph 2 in a reproduction of the Official Journal text (the Official Journal itself could not be fetched for this revision); the C2PA Technical Specification, version 2.4, §6.4, §9.3, §18.10, §18.15.2, §18.15.4.5, §18.15.4.8, §18.15.5 and §18.28; RFC 8615; and the IPTC Digital Source Type vocabulary, whose most recent concept change is dated 2024-10-23. A section number of one of those texts in this extension is that version’s.
1. Scope
In scope: the records and their encodings (§2); the classes, the steps and the origin claim (§3); the two pinned integer derivations and the evidence that makes an assistive claim checkable (§4); the digital source type (§5) and the C2PA statement (§6) a claim gives; the two marking layers, their records and their agreement, the registered watermark scheme, what it survives, its signpost and its resolver (§7); AI participants of rooms (§8); the review and when a player presents it (§9); when a label is shown, the EU icons pinned by digest, and a player’s log (§10); verification (§11).
Out of scope: a transport; how a generator, a codec or an editor works; a watermark for audio or for text, which this revision does not register (audio was in this revision’s sight and is not reached; text needs a scheme of its own); finding a mark again after a crop or a rotation, which the registered scheme does not do (§7.5.8); how a manifest is assembled, embedded and signed, which is C2PA’s and provenance’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 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 or text |
class | string | captured, reconstructed, assistive or synthesized (§3.2) |
step | string | capture, convert, assist or generate (§3.2) |
operation | term | what ran: a capability or derivation string, or a term naming the type of operation (upscale, inpainting, speech-synthesis) |
record | 32 B hex or null | the identity of a record that derived the subject, where its deriver signs one; null otherwise |
source | string | the subject’s digital source type (§5) |
models | array | the models the step ran, at most 16, each {"digest", "type"}: a prior’s digest (SPEC §3.1) and trained (a model derived from sampled content) or algorithmic (one that is not); no digest twice |
oversight | string or null | for generate: fully_autonomous, prompt_guided or human_validated (C2PA 2.4 §18.28.4); otherwise null |
inputs | array | the objects the step read, at most 16, each {"content_hash", "origin", "class", "source"}: its content hash, the link hash of its own origin claim or null where it has none, and that claim’s class and source, restated; no content hash twice |
evidence | object or null | for assist: §4.3; otherwise null |
issued_at | date-time | when the signer signed it |
signer_pubkey, sig | §2 |
media, class, step, oversight, a model’s type and an input’s class hold only the
values the table names, and source and an input’s source only the codes of §5. Otherwise
record_malformed.
Who signs: the party that performed the step: the capturing device or its operator, the encoder, the editor, the generator’s provider. A claim carries its signer’s authority and no more; which signers a verifier trusts is its policy.
A verifier is given at most one origin claim for one subject; a second for a subject an earlier
claim names is claim_conflict.
3.2 Steps and the class lattice
The classes are ordered: captured < reconstructed < assistive < synthesized. Each step
gives a class, and constrains the claim:
| step | class it gives | inputs | models | oversight | evidence |
|---|---|---|---|---|---|
capture | captured | none | none | null | null |
convert | reconstructed | at least one | none | null | null |
assist | assistive | exactly one | any | null | an object |
generate | synthesized | any | at least one | not null | null |
A capture states as its source one of digitalCapture, computationalCapture,
screenCapture or digitalCreation: a recording of the world, or a person’s work made without
a generative model. A claim that breaks the table is record_malformed.
- capture records the world, or a person’s authorship: a camera, a microphone, a screen recording, a person typing or drawing.
- convert rebuilds or rearranges its inputs without generating or enhancing anything: an encode, a decode, a transcode, a re-package, a decoder’s reconstruction (neural or not), and a derivation that copies samples or composes them (crop, rotation, grid or overlay composition, derived-tracks). 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
originnames a claim the verifier holds: that claim’s subject is the object of the input’scontent_hash, and itsclassandsourceare the input’s. Otherwiseinput_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
GenerativeDeterministicorBehavioral(SPEC §7), or whose manifest’sintentiscreate, is generated by every sink that presents it, and its claim’sstepisgenerate. A claim of another step over such an envelope isclass_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:
replicateevidence that §4.3 does not admit:evidence_invalid.- With
replicateevidence and the input’s decoded samples held: they arewidth·height· 3 bytes and their digest isinput_samples(input_samples_mismatch); the derivation is run over them, and the digest of its output isoutput_samples(assistive_recompute_mismatch). The claim is then proved. An exact-upscale derivation runs under the prior the verifier holds byparams.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;attestevidence is asserted. - With the subject’s decoded samples held (an object subject), or with a
samplessubject: their digest, or the subject’s, isoutput_samples(samples_mismatch). - 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}],Uthe IPTC term of the claim’ssource; - a
convertof 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
assistof one input:[{"action": "c2pa.opened"}, {"action": "c2pa.edited", "digitalSourceType": U}],Uthe term ofalgorithmicallyEnhanced; - a
generateof one input: the same,Uthe term of the generated source (§5).
- a claim with no input or with several:
D, onec2pa.ai-disclosureassertion (C2PA 2.4 §18.28) for each model whosetypeistrained, in the claim’s order, labelledc2pa.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 agenerate,"contentProfile": {"humanOversightLevel": O}indata,Othe claim’soversight. A model of typealgorithmichas 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):
- With a marking: the detection’s
schemeandscheme_digestare the marking’s (detection_mismatch). - Its scheme and the frames it read (§7.4, §7.6):
scheme_mismatch,mark_evidence_invalid,samples_mismatch,detection_irreproducible,layer_not_proved. - 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. - 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 stepsconvertandassist, 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. Otherwiselayers_disagree. A content that carries the mark of a synthesized claim while its metadata says captured, or the mark of another claim, is refused here. With no claim held, the detection is not checked (not-checked).
7.4 Schemes
A scheme is named by a term wai-mark:<name> and the SHA-256 of its definition. This revision
registers wai-mark:tent64 (§7.5). Its definition is the JCS text (RFC 8785) of this object,
whose bytes are the corpus’s scheme.json (§13):
{"amplitude":12,"cells_per_bit":16,"class_max":3,"code":[255,71,29],"field":"11d",
"generator":"140a722a1a468d36d87a25364e685922a1e56fd1a478c1d","grid":64,"length":248,
"luma":[77,150,29,128,8],"margin":[3,2],"max_frame_bytes":268435456,"media":["image","video"],
"min_height":128,"min_width":128,"orientations":8,"pattern":"tent","sample":"RGB8",
"scheme":"wai-mark:tent64","specification":"extensions/synthesis.md section 7.5",
"stream":"wai-mark:tent64 stream"}
(broken here into lines after commas between members; the definition itself has no line break,
and no space but those inside its strings), and its digest is
4e1bd9f2cac9420fc276434751a3fb9b2b0916128f200bf147009a86168be7d6. The definition pins the
scheme’s parameters, and §7.5 is its algorithm. A marking or a detection that names
wai-mark:tent64 under another digest is scheme_mismatch. A marking of the scheme states its
embedding, and a detection its read, of frames the scheme admits (§7.5.1): otherwise
mark_evidence_invalid.
wai-mark:conformance stays as a scheme with no algorithm: its definition is the text
wai-mark:conformance names no watermark algorithm (synthesis.md section 7.4), and a record of
it states no frames (mark_evidence_invalid). It exercises the records and their agreement apart
from any algorithm, and stands for a scheme a verifier does not implement: a record of it, or of
any term this revision does not register, is a signed statement by its signer and nothing more,
checked against the claim (§7.2, §7.3) but neither recomputed nor run again (§7.6).
7.5 The scheme wai-mark:tent64
An imperceptible mark in the luma of RGB8 frames, embedded by a pinned integer derivation and read back with no key. Integer throughout: the marked frames are a pure function of the unmarked frames and the payload, the same bytes on every machine, and what the detector reads is a pure function of the frames it reads.
7.5.1 Frames
The scheme marks frames: RGB8 buffers (§2), each W × H pixels with W ≥ 128, H ≥ 128
and W·H·3 ≤ 2^28, and F ≥ 1 of them of one size, with F·W·H·3 ≤ 2^53 − 1. A still
picture is one frame; a video’s frames are its decoded frames in presentation order. The luma
of a pixel is Y = (77·R + 150·G + 29·B + 128) >> 8. Frames of another size or count are
not admitted: the detector reads no mark from them, and a record that states them is
mark_evidence_invalid.
7.5.2 The grid and the pattern
A frame is cut into 64 × 64 cells by the column edges xᵢ = ⌊i·W / 64⌋ and the row edges yⱼ = ⌊j·H / 64⌋, i and j from 0 to 64. Cell k = 64·j + i holds the pixels with xᵢ ≤ x < xᵢ₊₁ and yⱼ ≤ y < yⱼ₊₁; its sides are Lₓ = xᵢ₊₁ − xᵢ and Lᵧ = yⱼ₊₁ − yⱼ, each at least 2, and its area is ν = Lₓ·Lᵧ. The grid is a fraction of the frame, so it follows the picture through a rescale or a change of aspect.
The tent over a span of L samples, at position u from 0 to L − 1, is f(u, L) = sgn(c)·(L − |2·|c| − L|) with c = 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:
- 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;
- the signs ε₀ … ε₄₀₉₅: +1 where the next word is even, −1 where it is odd;
- 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 ofwai.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 asint-motion-q8; and the result brought back to full size bywai-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, thenint-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:
- its scheme (§7.4):
scheme_mismatch,mark_evidence_invalid; - for
wai-mark:tent64, with the unmarked frames of its object held: they areframes·width·height·3 bytes and their SHA-256 is the embedding’sunmarked(mark_input_mismatch), and the scheme’s embedding of them (§7.5.4) under the marking’s payload has the SHA-256marked(mark_recompute_mismatch). The marking is then proved; otherwise it is stated; - with an embedding and the object’s decoded frames held: their SHA-256 is the embedding’s
marked(samples_mismatch); - under a policy that requires the second layer proved (§11.1), a marking that is not proved:
layer_not_proved.
A proved marking says that the object’s frames are the scheme’s embedding of those unmarked frames under that payload, and nothing else. What it says of the unmarked frames is what the verifier knows of them: for an object a sink generates at an exact tier (§7.8), the verifier generates them itself and the proof covers the whole output; for unmarked frames a provider hands over, it covers the marking step.
For each detection, at §7.3 step 2, in this order: its scheme (§7.4); with a read and the
object’s decoded frames held, they are frames·width·height·3 bytes and their SHA-256 is
samples (samples_mismatch); and for wai-mark:tent64, the detector (§7.5.5) is run on them,
and the payload it reads, or its finding no mark, is the detection’s payload
(detection_irreproducible). The detection is then reproduced (reproduced), and otherwise
not run again (not-rerun). Under a policy that requires the second layer proved, a detection
that is not reproduced is layer_not_proved.
So a detection that reports a mark in content that carries none, or in content altered beyond what the scheme survives (§7.5.8), is refused wherever the verifier holds the content’s frames: a detection agrees with a claim only for a payload the content carries.
7.7 The signpost and the resolver
The Code’s Measure 3.4(c) has its signatories implement an interoperability solution for their detection mechanisms by 2 February 2027; one of the solutions it names (point ii) is a publicly readable signpost in the content that tells the public which detection solution to use without running every provider’s. Its Glossary has a signpost solution be an openly disclosed marking technique within the content, and its detection mechanism, that indicates which watermark detection mechanism to employ where metadata is not available, and says it may be an imperceptible machine-readable mark. This extension gives three:
- In the content.
wai-mark:tent64is itself openly disclosed and read with no key (§7.5.5). Run on any content, its detector says whether the content carries a WAI mark, with the false-positive bound of §7.5.6, and the payload it reads gives the class and the claim. The claim’s markings name every scheme marked into its object, each by term and digest, so the mark is the signpost to the object’s other marks as well. - In the metadata. Each marking gives a C2PA statement (C2PA 2.4 §18.15.5, §18.10),
{"actions": A, "assertions": D}written in JCS:Ais[{"action": "c2pa.watermarked.bound"}], andDholds one soft-binding assertion,{"label": "c2pa.soft-binding", "data": {"alg": S, "alg-params": G, "blocks": [{"scope": {}, "value": P}]}},Sthe scheme’s term,Gits digest andPthe 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 recommendsalgto name and does not require. - A resolver. A party that serves WAI content MAY answer
GET /.well-known/wai-mark/<name>(RFC 8615), for each schemewai-mark:<name>it uses, with the scheme’s definition, the bytes whose SHA-256 is the scheme’s digest, asapplication/json; andGET /.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 whichwai-rs(synthesis::mark) andlive-conformance/_wai_mark.pyare two implementations (§13). A client checks the definition’s digest against the term’s before relying on it, and each claim it receives against the payload it asked for and against the claim’s signature. This revision does not register the well-known name.
7.8 A sink that generates
Where an object’s envelope generates (§3.3) and a marking of wai-mark:tent64 names it, the
reconstruction a sink is held to is the scheme’s embedding, under the marking’s payload, of the
frames its generation produces: the sink generates, then marks, as one integer derivation appended
to the generative one, so the exact tier the generation holds (generation-equivalence,
determinism-tiers §2) covers the mark, and every viewer at that tier
receives the same marked frames. Its log’s present entry states the marked frames’ digest, the
embedding’s marked; a review of the object names that digest as its reconstruction (§9.1); and a
player that presents the unmarked frames states an output the object does not decode to
(present_output_irreproducible, §9.2). A verifier that holds the envelope generates the unmarked
frames itself, and so proves the marking (§7.6) without taking anyone’s unmarked frames on trust.
8. Live rooms
8.1 AI participants and their events
live-rooms §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:
- presented the review’s object, which the verifier holds the review of
(
claim_unknown,review_subject_mismatch); - under a review whose verdict is not
rejected(review_rejected); - on a path of the review’s tier (
review_path_not_exact): a path of tiernoneorentropy-consistencynever presents a review made atdecode-equivalence; - with the review’s
reconstructionas 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
cueevent, 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 fromaton: its content hash; the link hash of the origin claim it applied, ornull; that claim’s class as it read it, ornull; themediaof §3.1; the tier its path holds (decode-equivalence,generation-equivalence,entropy-consistencyornone, §9.2); its decoder (a term) and variant (a term ornull), as areconstruct-sessionclaim names them (jwp-receipts §2.4.12); and its output’s digest, ornullwhere it states none.{"type": "shown", "label", "asset", "trigger"}: the player showed a label:visualoraudio; the SHA-256 of the asset shown; and why, as the player states it:first-exposure,resumeorinterval.{"type": "resume", "after"}: presentation resumed after an interruption,ad-breakorother.{"type": "review", "subject", "review"}: the player presented the review whose link hash isreviewfor the objectsubject(§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:
-
Every
shownentry’s asset is the policy’svisualfor a visual label and itsaudiofor an audible one (label_asset_mismatch). -
Each
presententry whoseclassis one the policy labels is a labelled presentation at itsat, p, of the label kind itsmediaasks 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
shownentry of that kind is in the log withatat most p +grace_us; - a later one, when a
resumeentry lies between the labelled presentation of its kind before it and it, is a resume: ashownentry of that kind hasatfrom the latest suchresume’satto p +grace_us; - any other, under a policy with an interval, is an interval: a
shownentry of that kind hasatfrom p −interval_usto p +grace_us.
One with no such
shownentry islabel_not_shown, at itspresententry. 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. - the first of its kind is a first exposure: a
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.
- 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. - 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). - 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, underdual_layerrequired, the origin claims in order (§7.1,layer_missing). - Reviews, in order (§9.1:
claim_unknown,review_subject_mismatch). - 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). - The log. Its chain (§10.3,
player_chain_broken); then each entry in order: apresententry (§10.4,present_mismatch; §9.2,present_output_irreproducible) and areviewentry (§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-recomputedorasserted(§4.4); - for each marking, in order: its object, its scheme, whether it is
provedorstated(§7.6), and its C2PA statement (§7.7) as written; - for each detection, in order: its object, its result,
agree,agree-inherited,unmarkedornot-checked(§7.3), and whether it wasreproducedornot-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
reviewentry 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,resumeorinterval) and the index of the firstshownentry that met it; - the file name of the EU icon the label policy’s
visualis, where it is one (§10.2); - the room’s own outcome,
acceptorgap, 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:sr2of 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 awai.generate.spectralenvelope, 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’swai-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.
14. Security and privacy considerations
- A claim carries its signer’s authority. A class is a statement; what this extension adds is that a class can only rise along a chain of claims (§3.2), that an assistive claim over a pinned derivation is checked by recomputation (§4), that a generator’s own envelope cannot be claimed as anything but generated (§3.3), and that the second layer is checked against the first (§7). A synthesized object whose producer signs no claim at all is caught only by the second layer, and only where its producer marked it (§7.5).
- A watermark payload’s seven bytes of link hash locate a claim; they are not a commitment, and a mark can be copied into other content. A detection that agrees with a claim says the content carries that claim’s mark, not that the content is the claim’s subject; the claim’s subject is bound by content hash.
- The registered scheme is read with no key (§7.5.6), so anyone can find the mark and try to
remove it: in the battery, a crop of 2% or a rotation of one degree leaves no mark the detector
reads in any picture measured (§7.5.8). Anyone can also embed a payload of their choosing in any
content, a real photograph among them. The scheme claims no resistance to removal or forgery,
which the Code’s Measure 3.3 asks to be assessed: what it survives is the table’s processing,
and nothing adversarial is claimed. A forged mark agrees only with the claim it names; that
claim’s subject is bound by content hash, so the mark does not make the content its subject;
and content with a claim of its own that reads another claim’s mark, not inherited through its
conversions, is refused (
layers_disagree, §7.3). - A mark carries its 8-byte payload and nothing else, and a verifier that holds the unmarked frames proves it (§7.5.6, §7.6): no identifier of a viewer, a session, a device or an account. The detector runs on the device that holds the content, and needs no upload. A resolver’s payload lookup (§7.7) tells the resolver which claim a client looks for; a client that does not want that holds the claims itself, or asks a party it trusts.
- Evidence states digests of samples, never samples. A verifier that recomputes holds the input’s decoded samples, which it decoded itself.
- A player’s log is signed under a key of the session and names objects, claims, labels and instants of the session’s clock: no viewer, no device and no account. It shows what that key reports, and is not evidence that a person watched.
- A review names its reviewer’s key and, where given, a role the reviewer states; a role is the reviewer’s statement until an authority attests it.
- A relay can withhold a claim but cannot alter one. A sink that receives an object without the claim its chain should hold sees the gap in the chain of inputs (§3.4).