c2pa-binding §4.8 to §4.10 and §6.4: after its three assertions a store of wai.asset.c2pa may carry wai-mark:tent64 as a C2PA soft-binding assertion naming its robustness claim, the envelope's ISCC identifier, and the Creator Assertions Working Group's identity assertions (1.3, cawg.x509.cose); a verifier may hold its anchors as trust lists in the C2PA Trust List's form, pinned by the SHA-256 of their bytes. The algorithm-list entries for tent64 and band64 are written in extensions/c2pa-soft-binding-entries.json. extensions/iscc.md: ISO 24138's codec, ISCC-SEQ, Instance-, Image- and Audio-Codes and the ISCC-CODE from the IEP texts, replaying the public test vectors; WAI's integer pre-processing. extensions/detection.md: wai.media.detection, a detector's signed verdict at an operating point named by digest, its evidence a robustness record of binding type detector; refused as provenance, never a change to an origin claim's class. Corpora c2pa-identity/, iscc/ and detection/, each with a second implementation in Python. Co-Authored-By: Claude Opus 5.5 <[email protected]>
132 KiB
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 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 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 third,wai-mark:band64, marks sound: the same payload in the band energies of speech and music, embedded by a pinned integer derivation, read after a trim, a change of rate or of level and the lossy codecs speech meets at the bitrates measured, and measured over generated and recorded speech. 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 schemes' battery and measurement are thesynthesis_markmodule (featuresynthesis_mark); its corpus islive-conformance/synthesis-mark/, and its second implementationlive-conformance/synthesis_mark_verify.py. The audio scheme's are theaudio_markmodule (featuresaudio_markandrobustness), the corpuslive-conformance/audio-mark/, andlive-conformance/audio_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), with the claim the reference generator signs and how a claim names a model no prior pins (§3.5); 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 text, which this revision does not register (text needs a scheme of its own); resistance to an adversary who sets out to remove or forge a mark, which no 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), or for a model no prior pins the digest §3.5 names it by, 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.
3.5 A generation's claim, and a model no prior pins
What the reference generator signs. wai generate, the reference generator of
wai.generate.spectral (SPEC §5), signs for each picture it makes an origin claim with these
values:
| member | value |
|---|---|
subject |
{"type": "samples", "samples"}: the picture by the digest of its samples (§2); it writes RGB8, which is that buffer |
media, class, step |
image, synthesized, generate |
operation |
wai.generate.spectral |
record |
the receipt hash of the asset-provenance receipt the same key signs for the generation: the model file and the instruction object it read, the picture it wrote, and the digest of the project file it ran under (schema/README.md, Operation receipts) |
source |
algorithmicMedia (§5) |
models |
one: the model file by its SHA-256, type algorithmic. The spectral model's tables are written by hand, not derived from sampled content |
oversight |
prompt_guided: a person's prompt guides it |
inputs, evidence |
[] and null |
The claim is kept beside that receipt, its name the receipt's with .wai-origin.json for
.wai-receipt.json. A verifier that holds both checks the claim as §11 checks a case that holds it
alone, then that its record is the receipt beside it, that one key signs both, and that its
subject is the samples the receipt names as written; wai verify does, and so does
live-conformance/synthesis_verify.py claim, which a test runs on what the binary writes.
A model no prior pins. A step may run a model whose bytes the signer does not hold and no prior
pins: one reached at an endpoint, as a person's own model is reached from Create (wai-app). Its
entry in models names it by the SHA-256 of the UTF-8 of the endpoint's URL, one line feed
(U+000A), and the name the endpoint takes for the model; its type is trained, unless the signer
knows the model is not. That digest identifies the model the step called and pins no bytes: a
verifier reads it as a name, never as a pin it can hold a model to, and §6 carries it as the model's
identifier as it carries any other. The URL itself is not in the claim, since a person's own
endpoint can be an address on their own machine; a signer that names it does so elsewhere.
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. An embedding of audio has rate, channels and length (§7.10.1) in place of
frames, width and height, its digests of the canonical S16LE buffers. 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 of audio rate, channels, length
and samples (§7.10.1), 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).
A detector that judges content without reading a mark — a classifier of synthetic media — signs a verdict of detection instead: a claim of its own, checked for its arithmetic and its evidence, stated beside the origin claims and never one of them, and never a change to a claim's class (detection §6).
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), which mark pictures, and
wai-mark:band64 (§7.10), which marks sound. 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 and §7.10.1
wai-mark:band64'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 or samples the scheme admits (§7.5.1, §7.10.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 a registered scheme, with the unmarked frames of its object held: they are
frames·width·height·3 bytes (for audio,length·channels·2) and their SHA-256 is the embedding'sunmarked(mark_input_mismatch), and the scheme's embedding of them (§7.5.4, §7.9.3, §7.10.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 the bytes the read gives and their SHA-256 is
samples (samples_mismatch); and for a registered scheme, its detector (§7.5.5, §7.9.4,
§7.10.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, §7.9.7, §7.10.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:
- In the content. Each registered scheme is itself openly disclosed and read with no key (§7.5.5, §7.9.4, §7.10.5). Run on any content, its detector says whether the content carries a WAI mark, with the false-positive bound of §7.5.6, §7.9.5 or §7.10.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,synthesis::mark::syncandsynthesis::mark::audio) andlive-conformance/_wai_mark.pywith_wai_audio_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 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:
- 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;
- the signs ε₀ … ε₁₀₂₃ of the positions: +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 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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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%andcrop-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-aandscreen-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 ofrotate-1and, forscreen-b, ofrotate-2clockwise, its sine negated; each sample v then rnd(v·(v + 255) / 510), a display's curve; thennoise-4at 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.
7.10 The scheme wai-mark:band64
A mark in the band energies of sound, the peer of wai-mark:tent64 for audio: the same payload
(§7.2), the same code (§7.5.3), the same marking and detection records (§7.2, §7.3) and the same
checks (§7.6). The signal's mono mix is cut into frames of 40 ms at a hop of 20 ms and transformed
exactly at bins of 25 Hz; eight bands from 250 Hz to 4 kHz are grouped into cells of two
adjacent bands over two tiles of two frames; each cell's log-energy checkerboard is moved to a
point of a lattice that its bit selects, by scaling the cell's bands up or down; and the
detector reads each bit from the cells that carry it, after finding the alignment again. Integer
throughout: the marked samples are a pure function of the unmarked samples and the payload, the
same bytes on every machine, and what the detector reads is a pure function of the samples it
reads. A frame lasts the same time and a bin is the same frequency at every rate the scheme
admits, so a change of rate keeps the grid; the cell's statistic is a difference of differences
of log energies, so a gain, an equalisation that is constant in time and an envelope the two bands
share leave it unchanged.
7.10.1 The definition and the samples
The definition is the JCS text of this object, whose bytes are the audio corpus's
scheme-band64.json (§13), broken into lines as §7.4's is:
{"bands":[10,16,24,34,48,66,89,120,160],"cell":[2,2,2],"class_max":3,"code":[255,71,29],
"cosine":[16384,"a7696da7f66ce84cf20395c21f431defa0a0934ec77ba673c3af36f6dfa7fe21"],
"field":"11d","frames_per_second":50,"gain_max":320,"gate":3400,
"generator":"140a722a1a468d36d87a25364e685922a1e56fd1a478c1d","length":248,"log":[256,8],
"max_channels":8,"max_length":268435456,"media":["audio"],"passes":3,"phases":4,
"rates":[8000,12000,16000,22050,24000,32000,44100,48000],"sample":"S16LE",
"scheme":"wai-mark:band64","slots":62,"specification":"extensions/synthesis.md section 7.10",
"step":848,"stream":"wai-mark:band64 stream","window":"sine"}
and its digest is
0e4eb9c7173ac96fcea614335c3def949b229e21587c1f2175b8245369c4e512.
The scheme marks samples: C channels of signed 16-bit samples at a rate R, L samples a
channel, whose canonical buffer is S16LE, each sample two bytes little-endian, the channels
interleaved; with R one of the eight rates of the definition, 1 ≤ C ≤ 8 and
R ≤ L ≤ 2^28. Samples of another rate, count or length are not admitted: the detector reads
no mark from them, and a record that states them is mark_evidence_invalid. The mono mix at
an instant is ⌊Σ / C⌋, Σ the sum of its C samples.
A marking of this scheme states its embedding and a detection its read with the members
rate, channels and length (integers, each at least 1) in place of frames, width and
height; the digests are of the canonical buffers, L·C·2 bytes; otherwise
record_malformed. A record of this scheme that states frames, or of a picture scheme that
states audio, is mark_evidence_invalid.
Below, rnd(n / d) is n / d rounded to the nearest integer, halves away from zero, ⌊ ⌋ rounds down, and a mod b is in 0 … b − 1.
7.10.2 Frames, bands and cells
The hop is h = R / 50 samples and a frame is N = 2h: frame f at the phase p holds the mono mix's samples x[p + f·h + t], 0 ≤ t < N, for every f with p + f·h + N ≤ L.
The cosine table of a frame of N is c[i] = rnd(16384·cos(2πi / 4N)) for i from 0
to 4N − 1: the nearest integer, which is never a half. Its values are pinned by the digest the
definition's cosine member holds, the SHA-256 of every admitted rate's table in the order of
rates, each value two bytes big-endian two's complement; a table computed in floating point
whose digest is not that one is not this scheme's. The window is w[t] =
c[(N − 2t − 1) mod 4N], the sine window, and at bin k the cosine is
Cₖ[t] = c[4kt mod 4N] and the sine Sₖ[t] = c[(4kt − N) mod 4N]. Bin k is
the frequency 25·k Hz at every rate.
A frame's spectrum at bin k is the exact pair Reₖ = Σ x·w[t]·Cₖ[t] and
Imₖ = Σ x·w[t]·Sₖ[t] over its samples, and its energy in band b, 0 ≤ b < 8, is
E = Σ (Reₖ² + Imₖ²) over the bins k from bands[b] to bands[b + 1] − 1. A tile of
band b at frame f is E of frame f plus E of frame f + 1. The log of an energy
e ≥ 1 is lg(e) = 256·k + φ, k the position of e's highest set bit: m is e shifted
so that it has 63 bits (m = e >> (k − 62), or e << (62 − k)), and eight times in turn
m becomes (m·m) >> 62 and then, where m ≥ 2^63, the next bit of φ is 1 and m is
halved, else that bit is 0; lg(0) = 0. One unit is 1/256 of an octave of energy, about
0.0118 dB.
A cell at frame f over the pair i, 0 ≤ i < 4, spans the bands 2i and 2i + 1 and the frames f to f + 3: its tile A at frame f and its tile B at frame f + 2. Its statistic is
D = (lg A₂ᵢ − lg B₂ᵢ) − (lg A₂ᵢ₊₁ − lg B₂ᵢ₊₁),
and a cell any of whose four tiles has no energy, or a log below the gate, has no statistic: it carries nothing. The gate of a signal's frames at a phase is the median of the logs of every tile there, every band at every frame but the last (the element at index ⌊n/2⌋ of the n logs in ascending order), less 3400: a tile 40 dB or more below the median tile is too quiet to carry a bit.
7.10.3 The layout
The stream is §7.5.3's under the label wai-mark:band64 stream, and its words are taken in
this order:
- the shuffle π of the 248 cells of a period: π starts as 0, 1, …, 247; for i from 247 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.
A period is 248 frames, 4.96 s: 62 slots of four frames, each with a cell in each of the four pairs. The cell of pair i in slot s is cell q = 62i + s, and it carries bit j of the code where π(j) = q: every cell of a period carries one bit and every bit one cell. The payload repeats every period. π begins 186, 29, 23, 26, and the corpus states more of it (§13).
The code is §7.5.3's, its codeword c of the payload and its mask: bit j is transmitted as tⱼ = cⱼ ⊕ μⱼ. The lattices of a cell's signed statistic ε_q·D are the integers 212 + 848·n (bit 0) and 636 + 848·n (bit 1), n any integer: a quarter-step either side of 0, so a statistic of 0 is as far from both. A cell's soft value of a statistic D is v = min(φ, 848 − φ) − |φ − 424|, φ = (ε_q·D − 212) mod 848: its distance to bit 0's lattice less its distance to bit 1's, from −424 to 424, above 0 nearer bit 1's. The step, 848 units, is about 10 dB.
7.10.4 Embedding
Given unmarked samples and a payload, at phase 0 and under its gate, for every slot of every period the samples hold, the cell at frame f = 4u (u = 0, 1, …, while f + 3 is a frame) over each pair i, in slot s = u mod 62: where it has a statistic D, with q = 62i + s and τ = ε_q·D, its target is the point of the lattice of tⱼ nearest τ, z = o + 848·⌊(τ − o + 424) / 848⌋ with o = 212 + 424·tⱼ, and its want starts as z − τ.
A tile gain a, from −320 to 320, scales a tile's bins by (1024 + a) / 1024; a cell at gain a scales tile A of band 2i and tile B of band 2i + 1 by (1024 + a) / 1024, and the other two by (1024 − a) / 1024, which raises its statistic by about S(a) = 2·(lg((1024 + a)²) − lg((1024 − a)²)) for a ≥ 0. A want y is met by the gain sgn(ε_q·y)·a, a the least from 0 to 320 with S(a) ≥ |y|, or 320 where none is.
Three syntheses, each from the unmarked samples:
- each cell's gain is its want's;
- the samples are synthesised under those gains (below);
- after the first and the second synthesis only: the mono mix of the result is cut into frames at phase 0 again, each cell's statistic D′ is taken there (0 where it has none), its error is z − ε_q·D′, the gate not applied, and its want becomes the want plus rnd(4·error / 5), clamped to −848 … 848.
The marked samples are the third synthesis.
Synthesis. Each frame f and band b has a gain g, the gain its cell gives it (0 for frames and bands no cell with a statistic covers). For each channel, and each frame f with any gain not 0: Reₖ and Imₖ of the channel's own samples in frame f (its samples, not the mix), for the bins of each band b whose gain is not 0; then for each t from 0 to N − 1, V[t] = Σ g·(Reₖ·Cₖ[t] + Imₖ·Sₖ[t]) over those bins, and the frame's change at sample f·h + t is rnd(w[t]·V[t] / (N·2^49)), in units of 2^−16 of a sample. A sample's changes are summed over the frames that hold it, and the marked sample is the unmarked one plus rnd(sum / 2^16), clamped to −32768 … 32767. Every product fits in 128 bits.
The change of a frame is its bands' content windowed and scaled: the sine window squared sums to 1 across overlapping frames, so a gain held over a tile scales the tile's bands by it, and the change is in the bands from 250 Hz to 4 kHz alone. The mark is a function of the content it scales: silence carries none, a loud band carries one in proportion, and each want is at most a half-step from where the content already was. The second and third syntheses correct what overlapping frames and neighbouring bands take from the first.
7.10.5 Detection
The detector is given samples; samples §7.10.1 does not admit carry no mark. It tries 248 frame offsets o and, within each, the 4 phases pₙ = ⌊n·h / 4⌋, n = 0 … 3, in that order. At offset o and phase pₙ, with r = (4 − o mod 4) mod 4: the cells at frames r + 4u of phase pₙ (u = 0, 1, … while the cell's last frame is one) over each pair i, in slot s = ((r + o) / 4 + u) mod 62, each with a statistic under the phase's gate, add their soft value to the sum of the bit their cell q = 62i + s carries. The bit read, rⱼ, is 1 where its sum is above 0 and 0 otherwise (a bit no cell reached reads 0), and the word is rⱼ ⊕ μⱼ, decoded within 29 as §7.5.5 decodes; the first alignment whose word gives a payload gives the result, with its offset, its phase and the bits corrected. Where none does, the samples carry no mark.
A trim of the start moves the cells against the frames by a whole number of frames, which an offset undoes, and by a part of a hop, which the nearest phase leaves within an eighth of a hop. Every period's cells add to the same bits, so a longer signal reads more surely; a signal shorter than a period carries no bit in some of its cells' slots and reads no payload.
7.10.6 What it guarantees
- No other channel, as §7.5.6: the marked samples are a function of the unmarked samples and the payload alone, which a verifier recomputes (§7.6).
- Imperceptibility. The mark changes only the bands from 250 Hz to 4 kHz, each by a factor of at most 1344/1024 (2.36 dB) up or 704/1024 (3.25 dB) down, and only as far as a want asks, and silent content not at all. Measured in §7.10.7.
- False positives. The model of §7.5.6, each read bit 0 or 1 with probability ½ independently in each alignment: the shuffle, the signs and the mask set each bit apart from the content's own structure, and the lattices' place a quarter-step either side of 0 leaves a bit of unmarked content to the sign of its cells' own statistics. Each alignment gives a payload with probability at most 2^58·V(248, 29) / 2^248, and the detector decodes at most 992 words, so samples give one with probability at most 992 times that: 2^−54.43, about 4.1·10^−17. Silence reads every bit 0, which unmasks to μ, which is more than 29 bits from every codeword. The model is checked against what the detector reads and against unmarked speech and music (§7.10.7).
- Key-free, as §7.5.6.
7.10.7 The battery and what it survives
The battery applies each attack below to marked samples and to the same samples unmarked,
and runs the detector on the result. The pinned attacks are robustness-evidence §4.1's audio
transforms, integer functions of the samples, so a run of them gives the same bytes on every
machine; the attack at index i has the seed i: none; gain-1/4, gain-1/2, gain-3/2;
trim-start-500, trim-start-1310, trim-both-1000 (milliseconds removed from the start, or
from each end); resample-8000, resample-48000, resample-44100 (area resampling to that rate)
and resample-trip-8000 (to 8 kHz and back); noise-64 and noise-512 (uniform noise of that
amplitude); mix-noise-30dB, mix-noise-20dB and mix-noise-10dB (noise of 1/1000, 1/100 and
1/10 of the signal's power); mulaw (G.711 μ-law); requantize-8 (8 bits kept); echo-50ms and
echo-150ms (an echo at half and a quarter of the level); and mono (the mono mix). The
measured attacks are robustness-evidence §4.2's: lossy codecs at the bitrates speech uses,
run by an encoder whose output is not pinned, so their results are a measurement of that encoder
and are stated: opus-6k to opus-64k (RFC 6716 at 6, 8, 12, 16, 24, 32 and 64 kbit/s, tuned
for speech), mp3-32k, mp3-64k, aac-32k, aac-64k, g722, g726-32k and g723-1.
The measurement sets are generated by the reference from fixed seeds (§13): voice, 16 voices
of 20 s at 16 kHz in one channel, a source-filter model whose pitch falls across each phrase, with
fricatives and pauses; and music, 8 pieces of 15 s at 24 kHz in two channels, chords, a bass, a
melody and a hi-hat. Each item is marked under its own payload, class 3 then the first seven bytes
of SHA-256(wai-mark:battery payload ‖ 0x00 ‖ its name). Recorded speech is measured too:
speech, 24 recordings of read English speech from a public corpus, each read as 16 kHz mono and
cut to its first 20 s, and speech-10s, 24 cut to 10 s, named by the SHA-256 of their samples in
the audio corpus's README and not held in this repository. Each cell gives the items that gave
their payload, of those marked, and in parentheses the most bits the code corrected among them.
No item gave another payload, and no unmarked item, under any attack, gave any.
The pinned attacks on the generated sets:
| attack | voice |
music |
|---|---|---|
none |
16 / 16 (0) | 8 / 8 (0) |
gain-1/4 |
16 / 16 (2) | 8 / 8 (0) |
gain-1/2 |
16 / 16 (1) | 8 / 8 (0) |
gain-3/2 |
16 / 16 (0) | 8 / 8 (0) |
trim-start-500 |
16 / 16 (4) | 8 / 8 (0) |
trim-start-1310 |
16 / 16 (29) | 8 / 8 (12) |
trim-both-1000 |
16 / 16 (4) | 8 / 8 (0) |
resample-8000 |
16 / 16 (4) | 8 / 8 (1) |
resample-48000 |
16 / 16 (0) | 8 / 8 (0) |
resample-44100 |
16 / 16 (2) | 8 / 8 (0) |
resample-trip-8000 |
16 / 16 (2) | 8 / 8 (1) |
noise-64 |
16 / 16 (28) | 8 / 8 (0) |
noise-512 |
0 / 16 | 8 / 8 (1) |
mix-noise-30dB |
15 / 16 (26) | 8 / 8 (0) |
mix-noise-20dB |
1 / 16 (29) | 8 / 8 (0) |
mix-noise-10dB |
0 / 16 | 8 / 8 (4) |
mulaw |
16 / 16 (26) | 8 / 8 (0) |
requantize-8 |
2 / 16 (27) | 8 / 8 (0) |
echo-50ms |
1 / 16 (27) | 8 / 8 (24) |
echo-150ms |
15 / 16 (29) | 8 / 8 (4) |
mono |
16 / 16 (0) | 8 / 8 (0) |
The whole battery, the measured attacks with it, on recorded speech and on the generated voices:
| attack | speech |
speech-10s |
voice |
|---|---|---|---|
none |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (0) |
gain-1/4 |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (2) |
gain-1/2 |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (1) |
gain-3/2 |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (0) |
trim-start-500 |
24 / 24 (0) | 24 / 24 (5) | 16 / 16 (4) |
trim-start-1310 |
24 / 24 (18) | 24 / 24 (29) | 16 / 16 (29) |
trim-both-1000 |
24 / 24 (0) | 24 / 24 (6) | 16 / 16 (4) |
resample-8000 |
24 / 24 (6) | 24 / 24 (27) | 16 / 16 (4) |
resample-48000 |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (0) |
resample-44100 |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (2) |
resample-trip-8000 |
24 / 24 (6) | 24 / 24 (25) | 16 / 16 (2) |
noise-64 |
24 / 24 (12) | 20 / 24 (26) | 16 / 16 (28) |
noise-512 |
0 / 24 | 0 / 24 | 0 / 16 |
mix-noise-30dB |
24 / 24 (21) | 17 / 24 (29) | 15 / 16 (26) |
mix-noise-20dB |
8 / 24 (29) | 0 / 24 | 1 / 16 (29) |
mix-noise-10dB |
0 / 24 | 0 / 24 | 0 / 16 |
mulaw |
24 / 24 (1) | 24 / 24 (7) | 16 / 16 (26) |
requantize-8 |
24 / 24 (23) | 12 / 24 (29) | 2 / 16 (27) |
echo-50ms |
0 / 24 | 0 / 24 | 1 / 16 (27) |
echo-150ms |
16 / 24 (29) | 3 / 24 (26) | 15 / 16 (29) |
mono |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (0) |
opus-6k |
0 / 24 | 0 / 24 | 0 / 16 |
opus-8k |
0 / 24 | 0 / 24 | 0 / 16 |
opus-12k |
3 / 24 (29) | 0 / 24 | 2 / 16 (28) |
opus-16k |
24 / 24 (20) | 16 / 24 (29) | 16 / 16 (25) |
opus-24k |
24 / 24 (4) | 24 / 24 (9) | 16 / 16 (9) |
opus-32k |
24 / 24 (1) | 24 / 24 (4) | 16 / 16 (2) |
opus-64k |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (0) |
mp3-32k |
24 / 24 (8) | 24 / 24 (16) | 16 / 16 (6) |
mp3-64k |
24 / 24 (0) | 24 / 24 (3) | 16 / 16 (3) |
aac-32k |
24 / 24 (18) | 22 / 24 (29) | 16 / 16 (14) |
aac-64k |
24 / 24 (3) | 24 / 24 (5) | 16 / 16 (3) |
g722 |
24 / 24 (2) | 24 / 24 (6) | 16 / 16 (25) |
g726-32k |
24 / 24 (17) | 22 / 24 (29) | 15 / 16 (28) |
g723-1 |
0 / 24 | 0 / 24 | 0 / 16 |
The scheme claims to survive an attack only where every item of every set, marked, gave its
payload and none, unmarked, gave any. It claims to survive gain-1/4, gain-1/2, gain-3/2, trim-start-500, trim-start-1310, trim-both-1000, resample-8000, resample-48000, resample-44100, resample-trip-8000, mulaw, mono, opus-24k, opus-32k, opus-64k, mp3-32k, mp3-64k, aac-64k and g722.
It does not claim to survive noise-64, noise-512, mix-noise-30dB, mix-noise-20dB,
mix-noise-10dB, requantize-8, echo-50ms, echo-150ms, opus-6k, opus-8k, opus-12k,
opus-16k, aac-32k, g726-32k or g723-1, nor any parameter between or beyond those measured,
nor content unlike the sets. Three of these depend on the length read: 20 s of recorded speech
survived opus-16k, aac-32k and g726-32k in every item, and 10 s did not. The codec at 12 kbit/s
and below and G.723.1 code the spectral envelope coarser than a cell, white noise fills the quiet
tiles, and an echo of a tile's length blurs a cell's two tiles into each other; none of them is
claimed. The measured attacks ran on one encoder build, named in the corpus's README; another
encoder at the same bitrate is another measurement.
Imperceptibility, as the SNR of every sample against the unmarked samples and the segmental SNR of 20 ms frames, each clamped to −10 … 35 dB:
| set | SNR, least … mean (dB) | segmental SNR, least … mean (dB) |
|---|---|---|
voice |
23.05 … 24.40 | 25.83 … 26.91 |
music |
21.41 … 21.86 | 22.09 … 22.44 |
speech |
20.25 … 21.64 | 22.86 … 26.09 |
The mark is a gain of the bands from 250 Hz to 4 kHz that follows their content, not added noise: its SNR counts the whole change of those bands, and it leaves the content above 4 kHz and the silences untouched.
False positives, against §7.10.6's model. Words the detector decodes from unmarked items, at the offsets 0, 62, 124 and 186 of phase 0: over the generated sets, 96 words with a mean of 123.42 ones and a standard deviation of 7.29, and the 4,416 pairs from different items differing in a mean of 123.37 bits with a standard deviation of 8.41 and in no fewer than 88; over the recorded speech, 96 words with a mean of 124.71 ones and a standard deviation of 8.40, the pairs differing in a mean of 124.04 bits, 7.95, and no fewer than 94. The model gives 124 and 7.87 for both. The detector read no mark in any of the 2,620 recordings of the public corpus's test set, unmarked and whole, 19,452 s of speech, nor in 60 pieces of generated music of 60 s, 3,600 s, each with all 992 alignments decoded, nor in any unmarked item under any attack of the battery.
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.
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
live-conformance/audio-mark/ holds wai-mark:band64's
corpus, written from fixed seeds by wai_audio_mark_vectors gen: scheme-band64.json, the bytes of
§7.10.1's definition; the unmarked audio its vectors name, each a raw S16LE file
audio/<SHA-256>.pcm, a generated voice and generated music in two channels; vectors.json, with
the layout's first values and the mask, values of the logarithm and of the gain's shift, the
embeddings of the voice and the music with each cell's gain, what the detector reads from stored
audio and from audio derived from it by a transform of robustness-evidence §4.1 (its mono mix, a
quarter of its level, a change of rate, trims, noise), a battery sample of every pinned attack of
§7.10.7 on the voice, marked and unmarked, and cases; battery-band64.json, the measurement of
§7.10.7's pinned attacks over the generated sets; and under robustness/ the scheme's records
(robustness-evidence §10), the measured ones among them. The cases accept generated speech whose
marking is proved and whose detection is reproduced, presented with a review at
generation-equivalence (§7.8); generated music in two channels proved likewise; the speech through
G.711 μ-law, trimmed by 1.31 s and resampled to 16 kHz, each a conversion whose detection agrees by
inheritance; and a recording read as unmarked. They refuse a marking whose samples carry another
claim's payload and one whose samples have one bit more changed (mark_recompute_mismatch);
unmarked samples one bit off (mark_input_mismatch); decoded samples that are not the marked
ones (samples_mismatch); a detection of a mark in unmarked music and one in three seconds of the
speech, less than a period (detection_irreproducible); a capture claim over the marked speech
(layers_disagree); a marking not proved under a policy that requires it (layer_not_proved);
the term under tent64's digest (scheme_mismatch); samples at a rate not admitted and frames
of pictures stated for audio (mark_evidence_invalid); and a player that presents the unmarked
speech (present_output_irreproducible). wai_audio_mark_vectors verify and the test
tests/audio_mark.rs check that every generated file regenerates byte for byte and that no other
file but the measured records is there; that wai-rs gives every vector, case and record what it
states; that §7.10.1's definition, §7.10.3's shuffle and §7.10.6's bound are the reference's; and
that §7.10.7's tables are the measurements' and it claims only attacks every measurement shows
survived. live-conformance/audio_mark_verify.py is the second implementation, written from this
text and sharing no code with wai-rs: with _wai_audio_mark.py it computes the cosine tables and
checks their digest, builds the layout, embeds with the three syntheses, detects over every offset
and phase with the code of _wai_mark.py, applies the pinned transforms of sound, verifies every
case with synthesis_verify.py and every record with robustness_verify.py, the conformance
record's harness rerun end to end, checks §7.10.7 against the measurements, and mutates copies of
the corpus and weakens its own verifier in turn, and requires every copy to fail.
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;
band64's mark is undone by the processing §7.10.7 shows lost, and every scheme's by content made to outweigh it: in the battery, a crop of 2% or a rotation of one degree leaves notent64mark the detector reads in any picture measured (§7.5.8), andtent64s, 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).