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]>
15 KiB
WAI Extension: Detection Verdicts
Status: Draft. Registers the record
wai.media.detection: a synthetic-media detector's signed verdict over one object, a claim of the detector's operator and of nobody else. A verdict names the detector at its operating point — its term, the SHA-256 of its model or program, and its threshold — by the digest of that definition; states the detector's score for the object and its threshold as exact fractions, and the verdict the score gives at the threshold; and names the robustness record (robustness-evidence §5, its binding typedetector) that measures the detector at that operating point. A verifier checks a verdict's arithmetic, its signature and its evidence. A verdict is never provenance: it is refused where it is presented as an origin claim (synthesis §3.1), as the record that derived a subject, as an input's origin, or as an assertion of a C2PA manifest (c2pa-binding §6.2 step 4); and a verifier states it beside the origin claims of the case, whose classes it never raises or lowers. No new transport. The reference implementation is thedetectionmodule ofwai-rs(featuredetection); the corpus islive-conformance/detection/, and its second implementation islive-conformance/detection_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision, each pinned to the version named: the C2PA Technical Specification, version 2.5 (October 2026), §2.3.9 and §2.3.10 (provenance and provenance data) and §16.3.7 (soft bindings, which a detector is not); and the Code of Practice on Transparency of AI-Generated Content as synthesis pins it, Section 1 Measures 2.1 and 2.3, which distinguish a detection resting on a watermark from one that does not.
1. Scope
In scope: the record's encodings (§2); the verdict (§3); the evidence it names (§4); verification (§5); why a verdict is never provenance (§6); the reasons (§7); and the corpus (§8).
Out of scope: any detector: this extension names a detector by digest and says nothing of how it
works or how good it is, which its robustness records state; a watermark's detection, which
synthesis §7.3 records as wai-detection and checks against the claim the mark's payload names;
what a party does with a verdict; and a verdict over a group, a track or a span.
2. Records
A verdict is a JSON object under the encodings and the reading of synthesis §2 — byte strings in
lowercase hexadecimal, unsigned integers of at most 2^53 − 1, no other number, no boolean, strict
reading of RFC 8259, exactly the members §3 lists, and a kind naming it; otherwise
record_malformed — with one exception: a rate member is read after every other member, and
one that holds anything but an exact fraction is rate_not_exact. A rate is [n, d], two
integers with 1 ≤ d and n ≤ d, in lowest terms (robustness-evidence §7). It is signed:
sig = Ed25519(signer_key, "wai:detection\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:detection-link\x01" || JCS(record))
JCS is RFC 8785. The signature is verified by WAI's strict rule (SPEC §7
Signatures): a key that fails step 1 or 2 is bad_key, a signature that fails a later step
bad_signature. A verdict is named by its link hash. Its issued_at is a date-time of
jwp-receipts §2.4.3, self-asserted.
3. The verdict
kind "wai-detection-verdict", signed by the party that ran the detector:
| member | type | meaning |
|---|---|---|
kind |
string | "wai-detection-verdict" |
subject |
32 B hex | the content hash of the object examined (jwp-receipts §2.1) |
media |
string | image, video, audio or text |
detector |
term | the detector's name |
model |
32 B hex | the SHA-256 of the detector's model or program |
threshold |
rate | the score at and above which the detector gives synthetic |
detector_digest |
32 B hex | the digest of the detector's definition at this operating point, below |
score |
rate | the detector's score for the object |
verdict |
string | synthetic where score ≥ threshold, not-detected otherwise |
robustness |
32 B hex or null |
the link hash of the robustness record that measures the detector at this operating point (§4), or null where none is named |
issued_at |
date-time | when it signed |
signer_pubkey, sig |
§2 |
The definition of a detector at an operating point is the JSON object {"detector": D, "media": M, "model": H, "threshold": T} of the verdict's members of those names, and its
digest is the SHA-256 of its JCS. A verdict whose detector_digest is another is
detector_digest_mismatch: the digest names the detector and its threshold together, so a
verdict cannot move its threshold and keep its evidence. A score is a fraction the detector's
operator states; a verifier compares it with the threshold exactly, and a verdict that is not
the one they give is verdict_inconsistent.
4. The evidence
A detector's evidence is a robustness record (robustness-evidence §5) with binding_type
detector, whose algorithm is the detector's term, whose algorithm_digest is the verdict's
detector_digest, and whose media is the verdict's: it measures the detector at that operating
point. Robustness-evidence §5 reads it as it reads any record: its corpus's items are content the
detector is meant to flag, each value the one byte 01; an item survives an attack where the
detector, run on it attacked, gives synthetic; its negatives are content the detector is meant
not to flag, and a false positive is one it flags, attacked. A verifier that does not run the
detector reads the record as stated (robustness-evidence §5 check 8), its table its signer's
statement, checked for its arithmetic and its signature. A verdict whose robustness names a
record the verifier does not hold, verified, is robustness_not_held; one whose record is of
another detector, digest, binding type or media is robustness_mismatch.
5. Verification
5.1 Inputs
A verifier is given a case: the origin claims presented as the provenance of the case's
objects (synthesis §3.1), the robustness records and claims it was given (robustness-evidence
§9.1), and the verdicts, each a list of texts in order; and its policy, evidence: required,
every verdict names a record, or when-given.
5.2 Steps
The verifier runs these steps and stops at the first refusal, which it reports with the reason, the list and the index in it.
- The provenance. Each text of the provenance list, in order, is an origin claim: one whose
kindis a verdict's isverdict_as_provenance; one that does not read as synthesis §2 and §3.1 read an origin claim, or whose signature does not verify, isprovenance_invalid. - The evidence. The robustness list verifies as robustness-evidence §9.2 has it, under the
policy
stated: otherwiserobustness_invalid, at the record that failed. - The verdicts, in order: each reads and its signature verifies (§2, §3:
record_malformed,rate_not_exact,bad_key,bad_signature); itsdetector_digestis its definition's (detector_digest_mismatch); itsverdictis its score's at its threshold (verdict_inconsistent); and its evidence (§4):nullunderevidencerequired, or a record not held, isrobustness_not_held, and a record of another operating pointrobustness_mismatch. - No verdict as provenance. No origin claim names a verdict's link hash as its
record, the record that derived its subject, or as an input'sorigin, the claim of an input: otherwiseverdict_as_provenance, at that claim.
A case every step accepts is accepted. This verifier reads the origin claims for their subjects and classes; verifying them as provenance — their classes against their inputs and steps, their evidence, their marks — is synthesis §11's.
5.3 What an accepted case states
- for each origin claim of an object, in order: its link hash, its object, and its class, as the claim signs it;
- for each verdict, in order: its link hash, its object, its detector, its verdict, the robustness
record it names or
null, and where it stands beside the claim of the same object:agreeswhere the verdict issyntheticand the claim's classsynthesized, or the verdictnot-detectedand the class another;disagreesotherwise;no-claimwhere the case holds no claim of the object.
The classes are the claims' own, whatever the verdicts: a case states the same classes with its verdicts as without them.
6. Never provenance
Provenance is what the parties that made a piece of content state about how they made it, each signing for its own step (synthesis §3.1; C2PA 2.5 §2.3.9). A detector made nothing: its verdict is an inference by a third party from the content alone, and a classifier's inference is a different kind of statement from a signed account of a step. So a verdict:
- is never an origin claim, nor the record that derived a subject, nor an input's claim (§5.2 steps 1 and 4);
- is never an assertion of a C2PA manifest: a store holding one, labelled
science.transaction.wai.detection, is refused (c2pa-binding §6.2 step 4,detection_as_provenance), since the manifest's assertions are its claim signer's statements; - never raises or lowers an origin claim's class (§5.3). A
syntheticverdict over a claim of captured content disagrees with it, and the claim stays captured; anot-detectedverdict over a claim of synthesized content disagrees with it, and the claim stays synthesized. Synthesis §3.2 raises a class only along a chain of signed steps, and a verdict is not a step; - is not a soft binding: a soft binding is a value computed from the content to find its provenance again (C2PA 2.5 §16.3.7), and a verdict is a judgement about the content.
A party that relies on a verdict relies on the detector's operator and on the detector's measured robustness, by its policy; a verifier states the verdict beside the claims so that neither is mistaken for the other.
7. Reasons
| reason | raised at |
|---|---|
record_malformed |
§2, §3, §5.2 step 3 |
rate_not_exact |
§2, §5.2 step 3 |
bad_key |
§2, §5.2 step 3 |
bad_signature |
§2, §5.2 step 3 |
provenance_invalid |
§5.2 step 1 |
verdict_as_provenance |
§5.2 steps 1 and 4, §6 |
robustness_invalid |
§5.2 step 2 |
detector_digest_mismatch |
§3, §5.2 step 3 |
verdict_inconsistent |
§3, §5.2 step 3 |
robustness_not_held |
§4, §5.2 step 3 |
robustness_mismatch |
§4, §5.2 step 3 |
8. Conformance
live-conformance/detection/ holds the corpus, written from
fixed seeds by wai_detection_vectors gen: vectors.json, and each origin claim, robustness
record and verdict a file of its own. Every key is derived from the seeds, and none is a key
anyone holds. The detector is the corpus's stand-in, wai-detector:conformance: it scores a
picture by the share of its pixels whose grey, (77 R + 150 G + 29 B + 128) >> 8, is at least 128,
its model the SHA-256 of the text of that definition the corpus states, its threshold [1, 2].
It detects nothing about how a picture was made; it stands for a detector a verifier does not
implement. Its robustness records are measured: the generator runs it, at the threshold [1, 2]
and at [3, 4], over two bright pictures as items and two dark ones as negatives under four
attacks of robustness-evidence §4.
- Accepted cases: four objects whose claims say captured, synthesized, captured and synthesized, and a verdict over each — two that agree with their claims and two that disagree, the classes stated as signed; the same claims with no verdict, stating the same classes; and a verdict over an object no claim names.
- Refused cases, each a control of one defect: a verdict whose signature is altered, one under a key that does not decode, one with a member §3 does not list, one whose score is not in lowest terms, one whose threshold is not its definition's, one whose verdict is not its score's, one that names a record the verifier does not hold, one that names none under the policy that requires one, one that names the record of another threshold, a robustness record that does not verify, an origin claim that does not read and one whose signature is not its signer's, a verdict in the place of an origin claim, an origin claim that names a verdict as the record that derived its object, and one that names a verdict as its input's claim. Every reason of §7 refuses one.
A conforming verifier gives every case the outcome, the reason, the list and the index, and for
an accepted case what §5.3 states, as the corpus states them. wai_detection_vectors verify and
the test tests/detection.rs check that every file regenerates byte for byte, that no other file
is there, that wai-rs gives every case its outcome, that every reason of §7 refuses a case, that
the case with verdicts states the classes the case without them states, and that §7 lists exactly
the reference's codes. live-conformance/detection_verify.py is the second implementation,
written from this text: it shares no code with wai-rs; it verifies signatures with WAI's strict
rule (claim-conformance/_ed25519.py), hashes with BLAKE3 from claim-conformance/_blake3.py,
verifies the robustness records with robustness-evidence's second implementation, gives every case
the same outcome, mutates copies of the accepted cases and requires every copy to be refused, and
drops each of its own checks in turn and requires the corpus to catch it.
9. Security and privacy considerations
- A verdict is an opinion. It says what a detector, at a threshold its operator chose, made of the content; detectors miss content and flag content, and their rates move with the content and the attack. The robustness record says how this detector did on the corpus it names, under the attacks it names, and no more (robustness-evidence §14).
- Disagreement is information. A verdict that disagrees with a signed claim is reported, not resolved: the claim is its signer's statement, attributable to that signer, and the verdict the detector operator's. A relying party that weighs them does so by its own policy.
- Thresholds. A verdict's threshold is in its signed definition, so an operator cannot report a score against a threshold other than the one its evidence measured.
- Privacy. A verdict names an object by its content hash and its detector by digest; it names no viewer. A verdict about a person's content is a statement about that content made by a third party, and its publication is that party's act.