Add audio and video watermark benchmark oracles, cohorts, and research studies

Extend the development-only benchmark family from images to audio and
video, and close four research directions from the watermark-landscape
map with local, revision-pinned evidence.

Benchmark schema: media_type audio and video rows, audioseal and
videoseal adapters with media-checked adapter binding, sample-domain
audio fidelity (SNR with unbounded_identical), frame-domain video
fidelity (mean PSNR, changed-frame fraction), and per-frame temporal
evidence in videoseal detection records. The report accepts all three
media types and carries SNR columns.

Audio: shared pinned AudioSeal oracle, synthetic and system-voice
speech cohorts with matched negatives, codec, noise, removed, and
forged arms, and the provenance experiment proving visual-only video
cleaning leaves the audio bitstream and watermark verdict invariant.

Video: TorchScript-only VideoSeal oracle (the pip package is unusable
on macOS arm64 and its loader resolves paths relative to cwd), video
cohort with crf/scale/fps attacks and four-state arms, temporal
evaluation measuring the crf decay curve, the frame-aggregation matrix
(computed locally after finding the TorchScript build ignores the
aggregation argument), and real provider clips as carriers.

Forgery: double-embedding study showing the last writer wins completely
under both oracles, scoped remover-trace probe, and the forged/
removed cohort arms encoding each scheme's correct expected verdict
(presence rule for audioseal, message rule for videoseal).

C2PA resolution: measured the five vendors' resolver contracts and the
C2PA 2.2 18.10.5 privacy gradient; product decision recorded that this
tool never resolves over the network. A registry guard pins the
published resolution-API set.

Gate: ruff and pyright src/ clean, 2114 passed / 3 skipped; maintain.sh
stops only on the known unfixed lightning 2.6.5 CVEs, pre-existing.
This commit is contained in:
Victor Kuznetsov committed 2026-09-07 18:23:18 -07:00
1 parent b2496134c2
commit 464fd142ea
29 files changed
+4604 -48

No files matched your search

+133
View File
@@ -0,0 +1,133 @@
# Audio provenance experiment (local AudioSeal)
Development-only study, run on 2026-09-06 from `scripts/audioseal_experiment.py`.
It answers the first open item of the original watermark-landscape research:
does a video remain provenance-positive through its untouched audio track
after a visual-only cleaning pass? AudioSeal (Meta, MIT license) is used as a
local matched oracle: the same package embeds the watermark and reads it back,
so no provider API is contacted and no external upload happens.
This page records the design, the pinned inputs, and the measured outcome. It
is not a detector-quality claim: carriers are synthetic non-speech signals,
and AudioSeal is not any provider's production audio watermark.
## Design
The experiment separates three questions and never merges them:
1. `bitstream_identity`: are the cleaned video's audio packets byte-identical
to the source video's audio packets?
2. `cleaning_invariance`: does the detector verdict on the decoded audio
change across the visual cleaning pass?
3. `audio_attack`: how does the watermark respond to audio-path processing
only, with no video involved?
Stages:
- Three deterministic 8-second 16 kHz mono synthetic carriers (AM tone stack,
box-smoothed "pinkish" noise, white noise), a fixed 16-bit message, clean
and marked WAVs. Watermark SNR 17.9-34.6 dB.
- Three system-voice speech carriers rendered by the local macOS `say` tool
(Samantha, Daniel, Milena; no network, byte-deterministic per voice, pinned
by voice, text digest, and macOS version), each with clean and marked rows
and per-second interval readings on both the marked audio and a variant
whose seconds two through four were overwritten with 10 dB-SNR noise while
every other sample stays bit-identical.
- Audio attacks on the marked tone-stack WAV: AAC-LC 128k at 16 kHz and 48
kHz, MP3 128k and 64k, 44.1 kHz resample, 8 kHz lowpass, additive noise at
10 dB SNR, 0.5 gain.
- Three video arms muxing the same marked WAV under a stamped Sora-like mark
(24 frames, 12 fps, 840x480): PCM in MOV (bitstream-preserving), AAC 128k at
16 kHz, AAC 128k at 48 kHz. Each arm runs the production
`remove_video_visible` path, which transcodes video and stream-copies
audio. Audio packets are extracted container-independently and hashed before
and after cleaning.
Weights are pinned to Hugging Face revision
`3c19eba53390776cf2cc9ed5f6c9ac67ce72ecba` and verified against pinned SHA-256
digests before loading; the loader mirrors `AudioSeal.parse_model` with the
checkpoint bytes pinned and now lives in the shared `scripts/audioseal_oracle.py`.
Dependency versions at run time: audioseal 0.2.0, torch 2.13.0, Python
3.12.13, ffmpeg 9.0.1, macOS 26.6.2 for the system voices. The executed code is
bound by hash: experiment script
`sha256:cc1f8360403ca1e4f04d72717b4b18ff84ca0718fa01bc1a8316dc189359b5b9`,
oracle
`sha256:d765e31b4c439fda82536b72bb602b55dac666d13e954809b42f48acfad1d2e8`,
case rows
`sha256:99b56a68fdb7789f4adc9f321eadf86277354a383270fe6d689b2c87eba83883`.
Within one fixed environment two independent runs produced byte-identical
case files (verified twice, before and after the oracle refactor). A
full-extras `uv sync` between two recorded runs changed the cleaned-video
H.264 packet bytes - every detector reading, every audio packet digest, and
all source artifacts stayed identical, only the re-encoded video stream
moved - so cross-environment comparisons must bind on the audio evidence and
readings, not on cleaned-container hashes.
## Measured outcome, 2026-09-06
| Stage | Reading |
| --- | --- |
| Clean carriers (matched negatives) | mean detect 0.003-0.008, frac above threshold 0 |
| Marked carriers | mean detect >= 0.9997, bit accuracy 0.94-1.0 |
| All video arms, source audio | mean detect >= 0.9999, bit accuracy 1.0 |
| All video arms, after visual cleaning | identical readings; audio packets byte-identical |
| MP3 128k/64k, AAC 128k (16 and 48 kHz), 44.1 kHz resample, 8 kHz lowpass, 0.5 gain | mean detect >= 0.9999, bit accuracy 1.0 |
| Raw ADTS AAC decode | mean detect 0.990, bit accuracy 0.69 |
| Additive noise, 10 dB SNR | mean detect 0.30, bit accuracy 0.5 |
The thesis holds in this model: `remove_video_visible` leaves the audio
bitstream untouched in every arm, so the audio watermark verdict is invariant
across visual cleaning, and a cleaned video stays provenance-positive through
its audio track. The result is scoped to AudioSeal as the mark; it proves the
copy semantics of the cleaning path, not that any specific provider embeds a
comparable audio mark.
The speech carriers close the out-of-domain gap the synthetic trio exposed:
all three system voices decoded the full 16-bit message (the pinkish synthetic
carrier had flipped one bit), with watermark SNR of 27.9-32.2 dB and clean
speech far below the decision rule. Per-second interval readings localize the
signal in time: overwriting seconds two through four with 10 dB noise killed
exactly those two windows (0.19 falling to 0.0009 mean probability) while the
untouched windows kept their original readings to the sixth decimal. The
final window of a clip is a short remainder (0.1 s here) and its readings are
accordingly less stable - visible in the tables, never dropped.
Three findings qualify the naive readings:
- **Codecs are not the attack.** At these rates every codec and resample round
trip preserved both detection and the full message, on synthetic and speech
carriers alike. Full-clip additive noise at 10 dB SNR killed every speech
detection while the white and pinkish synthetic carriers survived it: speech
has quiet moments where the noise dominates, so the effective removal lever
on audio is loud additive noise, and its power depends on the carrier. A
removal pipeline that wants an audio path clean must not rely on
transcoding alone.
- **The decode path is part of the measurement.** Decoding raw ADTS keeps the
encoder's 1024-sample priming head in the stream, which alone moved the
verdict from 0.9999/full message to 0.990/partial message; a gapless-aware
container (MP4/M4A edit list, or MP3 gapless info) decodes sample-identical
audio and restores the reading. The experiment carries the raw-ADTS arm
(`aac_128k_adts_raw`) explicitly so the artifact stays a named measurement
instead of contaminating codec rows.
- **Voice names are not carrier identity.** macOS 26.6.2 renders `Alex` and
`Samantha` byte-identically, so a naive three-voice set was one carrier
counted twice. The shipped script verifies distinct content hashes across
configured voices and refuses an aliased set; diversity claims rest on the
hash, never the name.
Detector cost on this host (Apple silicon, CPU): cold first call 176-465 ms
across runs, warm median 158-160 ms for 8-second clips. These are diagnostic
host timings, not a certified profile.
An earlier iteration derived per-carrier seeds from Python's `hash()`, which is
salted per process; it produced different carrier bytes on every run. The
shipped script derives seeds from `zlib.crc32`, and the two recorded final runs
are byte-identical.
## Follow-ups
- The speech and interval layers are complete; the remaining audio-side
upgrade is folding per-second windows into the benchmark schema itself, if a
case-level question ever needs them as rows. The forgery/remover-forensics
and VideoSeal temporal items remain separate open directions from the
original research map.
+226 -12
View File
@@ -1,11 +1,15 @@
# Watermark benchmark kernel
`scripts/watermark_benchmark.py` is a development-only runner for reproducible
image-watermark experiments. It calls the existing local DWT-DCT and TrustMark
detectors through thin adapters. It does not add a runtime command, download a
corpus, or contact a provenance oracle or provider API. The optional TrustMark
package can fetch its official Adobe model weights when its local package cache
is incomplete; prepare that dependency before an offline run.
image-, audio-, and video-watermark experiments. It calls the existing local
DWT-DCT and TrustMark detectors plus the revision-pinned AudioSeal and
VideoSeal oracles through thin adapters. It does not add a runtime command,
download a corpus, or contact a provenance oracle or provider API. The
optional TrustMark package can fetch its official Adobe model weights when its
local package cache is incomplete, the audioseal adapter fetches its pinned
checkpoints the same way, and the videoseal adapter downloads one pinned
TorchScript file into a user cache; prepare those dependencies before an
offline run.
The kernel answers three different questions and never merges their answers:
@@ -36,10 +40,10 @@ The fixed fields are:
| `schema_version` | Integer `1`. |
| `case_id` | Unique case identity. |
| `pair_id` | Groups related clean, marked, attacked, and removed cases. |
| `media_type` | `image` in schema v1. |
| `adapter` | `dwt-dct` or `trustmark`. |
| `media_type` | `image`, `audio`, or `video` in schema v1. |
| `adapter` | `dwt-dct` or `trustmark` for `image`; `audioseal` for `audio`; `videoseal` for `video`. The loader rejects a known adapter named against the wrong media type. |
| `arm` | `positive`, `matched_negative`, `wrong_key`, or `hard_negative`. |
| `state` | `clean`, `marked`, `attacked`, or `removed`. |
| `state` | `clean`, `marked`, `attacked`, `removed`, or `forged`. `forged` names an artifact carrying a watermark with a message different from the adapter oracle's fixed one; the matched verifier's correct answer there is `not_detected` with the forensic detail carried by the study layer, not the verdict. |
| `path`, `sha256` | Artifact path and pinned content digest. |
| `reference_path`, `reference_sha256` | Both strings or both `null`; the fidelity reference. |
| `source_revision` | Corpus, generator, or acquisition revision. |
@@ -85,10 +89,110 @@ pixels produce `psnr_db: null` with
`psnr_status: unbounded_identical`, never a misleading numeric zero. Missing or
incomparable references remain explicit states rather than fabricated metrics.
Schema v1 deliberately has no aggregate score, confidence interval, video
adapter, automatic attack generator, removal runner, or provider oracle. Add
those only after the case-level evidence is large enough to define and test the
corresponding statistical or media contract.
Schema v1 deliberately has no aggregate score, confidence interval, automatic
attack generator, removal runner, or provider oracle. Add those only after the
case-level evidence is large enough to define and test the corresponding
statistical or media contract.
## Audio rows
`media_type: audio` rows decode every artifact and reference to mono 16 kHz
float32 through the system `ffmpeg` before any adapter runs, mirroring the
image path where decoding also stays outside the detector interval. Audio rows
therefore require `ffmpeg` on `PATH`; a missing or failing decode is the same
explicit `error` status as an undecodable image.
The `audioseal` adapter reads the shared revision-pinned oracle in
`scripts/audioseal_oracle.py` (Meta AudioSeal, MIT license, Hugging Face
revision and checkpoint SHA-256 pinned and verified at load). Its `detected`
status is the detector's own documented rule: the fraction of per-sample
probabilities above 0.5 reaching 0.5. The `label` is the decoded 16-bit message
as a binary string, so a case whose payload differs from the embedded one is
still `detected` with a different label, never `not_detected`. A `wrong_key`
arm for this adapter is therefore a different-message row, not a clean row.
Audio fidelity compares the decoded sample arrays against the explicit
reference: identical decoded samples produce `snr_db: null` with
`snr_status: unbounded_identical`, never a misleading numeric zero; a silent
reference reports `zero_reference_power`; a length mismatch stays an explicit
`incomparable` state. SNR here is reference signal power over error power, the
sample-domain analogue of the image PSNR contract.
## Audio cohort v1
`scripts/watermark_benchmark_audio_cohort.py` builds the audio analogue of the
synthetic image cohort: three deterministic synthetic carriers (AM tone stack,
smoothed noise, white noise), the pinned AudioSeal message, matched clean
negatives, MP3 128k, gapless M4A AAC 128k, and deterministic 10 dB additive
noise attacks, plus one never-embedded white-noise hard negative. Since v2
the audio cohort also carries the four-state arms: a `removed` row where the
noise transform measurably destroys the mark (the tone stack only - the
synthetic noise carriers survive 10 dB noise, so their noise rows stay open
`attacked` measurements), and `forged` rows embedding a foreign message with
`expected: detected`, because the audioseal rule is watermark presence and
the decoded label is what exposes the foreign payload. 20 cases in total,
everything hashed into a strict manifest validated through the
benchmark loader.
```bash
uv run --extra dev python scripts/watermark_benchmark_audio_cohort.py \
--output-dir .local-eval/watermark-benchmark-audio-cohort-v1
uv run --extra dev python scripts/watermark_benchmark.py \
.local-eval/watermark-benchmark-audio-cohort-v1/manifest.jsonl \
--output .local-eval/audio-cohort-v1-results.jsonl
```
### Development baseline, 2026-09-06
The first local run used kernel
`sha256:2216169ab7c33f7439720f0934e8ac6ba8379e510bfaeabe008f820c7658701d`,
oracle `sha256:bd67e2b1be5c486384ab1f64709f661241451ab1ea7592cc56f0eefb91c415ab`,
and recipe `sha256:f361163b4fbf23b885c026751c544e97be0bf054906c1aad455276992a2768d2`,
producing manifest `sha256:b6c48684f0c85e882159f6d5c4fbeea2f43c3b24d3a2c3a034209c6f531ba73d`
and case rows `sha256:66ac23bd926925dc5baf7216c03f8a7246eeb473315a17701358a3803ab7b0d1`.
Dependencies were audioseal 0.2.0, torch 2.13.0, and ffmpeg 9.0.1.
| State and transform | Cases | Detected | Not detected |
| --- | ---: | ---: | ---: |
| clean matched negatives + hard negative | 4 | 0 | 4 |
| marked, audioseal embed | 3 | 3 | 0 |
| attacked, MP3 128k | 3 | 3 | 0 |
| attacked, AAC 128k (gapless M4A) | 3 | 3 | 0 |
| attacked, 10 dB additive noise | 3 | 2 | 1 |
Every clean and never-embedded negative stayed below the decision rule. All
codecs retained detection with the full message on the tone-stack and
white-noise carriers; the pinkish-noise carrier decoded one flipped payload bit
while remaining detected, matching the experiment's per-carrier bit-accuracy
observation. The 10 dB noise arm destroyed the tone-stack detection but not
white or pinkish noise: carrier-dependent robustness is exactly what the
matched arms exist to expose. Nearest-rank adapter timing: cold first call
405.1 ms, warm median 124.2 ms, warm max 157.4 ms for 8-second clips on this
host. As with the image baselines, these are diagnostic host rows, not a
certified profile.
### Speech extension
`--with-speech` adds fifteen more rows: three system-voice speech carriers
(Samantha, Daniel, Milena) rendered by the local macOS `say` tool - no
network, byte-deterministic per voice on a fixed macOS version - each with a
matched clean negative, a marked positive, and the same three attacks. Every
speech row's transform parameters carry the full TTS provenance (tool, macOS
version, voice, text digest, duration). The builder verifies the configured
voices render distinct bytes and refuses an aliased set, because macOS 26.6.2
renders `Alex` and `Samantha` byte-identically and a name is not a carrier
identity.
The first `--with-speech` run (2026-09-06, 31 cases total, recipe
`sha256:3f291f49ab9ef2499085708fb614e3723ebee6d35f0b509fc36f017ec5a86c3b`,
manifest
`sha256:b9baa79066deb002402cc20166835887d67a5158ff9ce7bbc1963e5d603db3a5`,
case rows
`sha256:abc68590f59fbddab1c086371d2854622b41ea756ba61a7a87b76cb6df64c386`)
kept every synthetic verdict and added: speech clean 0/3 detected, speech
marked 3/3 with the full message, codecs 3/3, and full-clip 10 dB noise 0/3 -
unlike the synthetic white and pinkish carriers, speech has quiet moments
where the noise dominates. Speech marked fidelity sat at 27.9-32.2 dB SNR.
## Synthetic cohort v1
@@ -275,6 +379,116 @@ uncommitted. The exact inputs and implementations are therefore bound by hash:
The local timing rows were host-contended and are not used for a new latency
claim. The controlled resource profile below remains the performance evidence.
## Video rows
`media_type: video` rows decode every artifact and reference to float RGB
frames in [0, 1] as (T, H, W, 3) through the system `ffmpeg`, staying outside
the detector interval like the image and audio decodes. Video fidelity
compares decoded frames against the explicit reference clip: mean PSNR over
frames, a changed-frame fraction, `unbounded_identical` for bit-identical
pixel sequences, and an explicit `shape_mismatch` state when frame counts or
geometry differ - which is why geometry-changing attack arms carry no
reference instead of a fabricated comparison.
The `videoseal` adapter loads Meta's standalone TorchScript release of
VideoSeal (MIT license) from `scripts/videoseal_oracle.py` - one pinned file,
no `videoseal` package, whose wheel pulls a research dependency chain without
macOS arm64 wheels and whose loader resolves cards relative to the working
directory. The upstream video evaluation ignores the model's detection head
(it prepends a constant) and scores the aggregated 256-bit message decode, so
the adapter's `detected` is its own explicit rule: bit accuracy of the
averaged decode against the oracle's fixed message at or above 0.9. The
`label` is the decoded message as 64 hex characters. Because the rule is
message accuracy, a different-message row reads `not_detected` - the matched
verifier rejects the foreign payload - unlike the audioseal presence rule
above. Every videoseal detection record
carries a `temporal` block - frame count, min/mean/max and the full per-frame
bit-accuracy series - and the aggregation is computed in the oracle from raw
per-frame logits, because the TorchScript build ignores the aggregation
argument of `detect_video_and_aggregate`. The aggregation matrix itself
(avg versus norm-weighted) is evaluated in
[VideoSeal temporal evaluation](videoseal-temporal-evaluation.md), not in the
kernel, which keeps plain avg as its canonical rule.
## Video cohort v1
`scripts/watermark_benchmark_video_cohort.py` builds the video analogue of
the other cohorts: two deterministic synthetic 64-frame 256x256 clips (moving
gradient, moving texture), the pinned 256-bit message, matched clean
negatives, one never-embedded hard negative, and three attacks - a realistic
H.264 re-encode at crf 23, a 75 percent downscale, and a frame-rate halving
that drops half the keyframes. Since v2 the video cohort also carries the
four-state arms: `removed` rows reusing the crf 23 re-encode that measurably
takes both synthetic carriers below the rule, and `forged` rows embedding a
foreign message with `expected: not_detected`, because the videoseal rule is
message accuracy and the matched verifier rejects a foreign payload. 15
cases in total.
```bash
uv run --extra dev python scripts/watermark_benchmark_video_cohort.py \
--output-dir .local-eval/watermark-benchmark-video-cohort-v1
uv run --extra dev python scripts/watermark_benchmark.py \
.local-eval/watermark-benchmark-video-cohort-v1/manifest.jsonl \
--output .local-eval/video-cohort-v1-results.jsonl
```
### Development baseline, 2026-09-07
The first local run used kernel
`sha256:7a3906cebc3c7cacbac7e204c014d1d6281615e7acbbc5b3a8fcecc939ca68c7`,
oracle `sha256:6c889bf399e8e3bada439eee4a1e9c0b04177be78dfee756d5daa79e73db71b3`,
recipe `sha256:dd06ae1c40ef7d7f6fd2b258b20444a3250254a5ef258542a4eaf43cc501342b`,
manifest
`sha256:bb6a7dda6b3897794f2fbfe47291329bb7b9ff7ee57c0634ebb386e58acd5e7a`,
and case rows
`sha256:5793b8ba33c55e135e5bef43f33fd2699bb57bae6ca8ead38b1dab0123d1dd12`.
The checkpoint is the TorchScript build `y_256b_img.jit`
`sha256:5c7a4581c36fc6090aafdcfb3999123bae5172a4847f22e2da4e7fd1a39d1e1b`
pinned to upstream commit `870ca7f`.
| State and transform | Cases | Detected | Not detected |
| --- | ---: | ---: | ---: |
| clean matched negatives + hard negative | 3 | 0 | 3 |
| marked, videoseal embed (crf 8 artifact) | 2 | 2 | 0 |
| attacked, H.264 crf 23 | 2 | 0 | 2 |
| attacked, 75 percent downscale + crf 18 | 2 | 0 | 2 |
| attacked, frame rate halved | 2 | 2 | 0 |
Direct oracle readings fill in what the threshold hides: the marked clips
decode at 0.992-0.996 bit accuracy (48.5 and 41.4 dB mean PSNR against their
clean carriers), the crf 23 re-encode collapses to 0.56-0.57, the downscale
to 0.69, and the halved frame rate keeps 0.996. Two cautions travel with
these numbers: the watermark survives H.264 only at generous quality - a
realistic crf 23 re-encode erases it on these smooth synthetic carriers,
while a high-bitrate uniform-noise carrier survived crf 18 in the isolated
probe, so retention is carrier- and quality-dependent - and dropping half
the frames does not touch the message because the secret repeats across
keyframes. These are first-baseline observations on synthetic carriers, not
robustness curves; a sweep across crf values and real-content carriers is
the natural follow-up.
### Four-state baseline, 2026-09-07 (v2 cohorts)
The v2 runs used kernel
`sha256:22f2c1dd5c3b7aeb96090c4c578c6d1841ee62d90958dd1c5c7ad7db71835eec`
with audio recipe
`sha256:31c021144212a43565fb3746fadc835bfc37ea8ac98998fad9bba9e72410e82c`
(manifest
`sha256:43c7f77f1d4537c00d4b0dd5128e9becc6c860cdd196a7ba2f7541f4276784ec`,
case rows
`sha256:c73d7dddfdb90262e96be4d67ec1567d9046812661928cc816cb9f97f4a01a7e`)
and video recipe
`sha256:8f25929304ef4f94721667968ee4bb89ebf584a2108de7e02db36c855dc2ddc8`
(manifest
`sha256:b5659848c0d05e205f80ab36480e70fdf7e5f9df4d8ada6ffadec42c97b88998`,
case rows
`sha256:c9ad56848bb7efe200aaf1189a8eb03f7571567e6702edd195a62f812fa05815`).
Every v1 verdict was unchanged; the new arms read: audio removed 0/1
detected, audio forged 3/3 detected under the presence rule, video removed
0/2, video forged 0/2 under the message rule - zero expected mismatches.
The mechanism underneath these rows, including double embedding, is measured
in [Watermark forgery study](watermark-forgery-study.md).
## Aggregate repeated runs
`scripts/watermark_benchmark_report.py` turns one or more result JSONL files
+83
View File
@@ -0,0 +1,83 @@
# C2PA soft-binding resolution research
Development-only investigation, 2026-09-07, completing the privacy-tiered C2PA
resolution item from the original research map. The outcome is a product
decision rather than a feature: this tool resolves nothing over the network,
and this page records what the resolution ecosystem would learn if it did.
## The normative shape
C2PA 2.2 specification section 18.10.5 defines the soft binding resolution
API: a standard way of retrieving manifest stores from a resolution endpoint
given **a soft binding value, a manifest identifier, or an asset** - three
input modes that are exactly three privacy exposure levels. The algorithm
registry (`c2pa-org/softbinding-algorithm-list`) carries the endpoint URIs in
`softBindingResolutionApis`. As of the packaged revision `c0218cd628c9`,
8 of 53 entries publish them.
## Measured contracts
| Algorithm | Kind | Endpoint | Input | Content leaves | Content digest leaves |
| --- | --- | --- | --- | --- | --- |
| `com.aiwatermark.audioseal.1` / `videoseal` / `pixelseal` | watermark | `aiwatermark.com/api/v1/resolve` | `GET ?payload={extracted payload}&alg=` | no | no |
| `com.joinmonolith.sha256` | fingerprint | `api.joinmonolith.com/api/c2pa/matches/byBinding` | `GET ?alg&value=base64(SHA-256 of asset)` | no | yes |
| `me.deepmark.audio.vigil.128` | watermark | `resolution-api.deepmark.me` | payload-based per vendor site | no | no |
| `ai.trufo.pawprint.watermark` / `.fingerprint` | both | `c2pa.trufo.ai/v1` | not published publicly | ? | ? |
| `io.blockfact.audio.watermark.32` | watermark | `api.blockfact.io/api/soft-binding/v1` | not published publicly | ? | ? |
Sources, verified 2026-09-07 against the vendors' own pages: the Monolith
soft-binding documentation (lookup endpoints public by design, "a verifier is
usually a stranger"; only manifest minting is authenticated), the AIWatermark
algorithm page ("after extracting the watermark payload from an audio file,
validators can resolve the payload"; model source Meta FAIR AudioSeal), the
Deepmark product description ("the signal is detected and its payload
resolved"), the Trufo developer documentation and `trufo-py` repository
(signing-side only), and the BlockFact API reference (its public verify
endpoint takes a Starknet transaction hash, not a soft binding).
The privacy gradient, concretely:
- **Payload-only** resolution (AIWatermark, Deepmark) sends a locally
extracted watermark payload - opaque bits that identify the mark, not the
file. This is the mode our pinned local oracles could serve without ever
touching the network with content or a digest.
- **Digest** resolution (Monolith) sends base64(SHA-256) of the whole asset.
No bytes leave, but the digest is linkable to the exact content: the
registry learns you are inspecting that precise file.
- **Undocumented** (Trufo, BlockFact): no public contract; any integration
would be guesswork and is not attempted.
## The product decision
This tool does not resolve anything over the network, at any tier, and no
`local_only` / `binding_only` / `full_content` modes ship. The reasoning:
- The tool's user processes their own generated or edited content and wants
to know which provenance signals it carries and to remove them. Local
payload extraction - already available through the pinned development
oracles - answers the identification question without a network.
- Querying a vendor's resolver tells that vendor someone is inspecting the
specific content (directly for digest-based lookups, via the payload for
payload-based ones). For a removal tool that is a leak in the opposite
direction of its purpose, and it contradicts the offline runtime boundary
the project already guarantees.
- No user scenario of this tool requires manifest recovery from a registry:
that is a verifier-of-third-party-content function, which is outside the
intended use.
The runtime boundary stays: identification reports locally decoded evidence
only, and `docs/known-limitations.md` states the resolution boundary in user
terms. A guard test pins the packaged registry's published-API set, so an
upstream ecosystem change surfaces as a conscious decision recorded against
this page rather than a silent regeneration.
## Investigation note
The first reconnaissance pass concluded "upstream publishes zero resolution
APIs" because the probe searched the registry JSON for guessed key names
instead of the `softBindingResolutionApis` key the sync actually reads. The
drift-guard test written from that wrong premise failed immediately against
the packaged snapshot and exposed the error - eight live endpoints, including
resolution hosting for the exact AudioSeal and VideoSeal schemes this project
benchmarks. The corrected guard now pins the real set. Field names are
verified against the consumer's schema, never guessed.
+4
View File
@@ -50,4 +50,8 @@ The current behavior is defined by the code, tests, README, and user guides.
- [OpenAI SynthID oracle ladders](synthid-oracle-ladders.md) (routing hub)
- [SynthID detector and removal plan](synthid-detector-removal-plan.md) (chronological mixed archive)
- [Text protection research](text-protection-research.md)
- [Audio provenance experiment](audio-provenance-experiment.md) (local AudioSeal matched-oracle study of video audio tracks across visual cleaning)
- [VideoSeal temporal evaluation](videoseal-temporal-evaluation.md) (per-frame scores, aggregation matrix, and crf decay on synthetic and real clips)
- [Watermark forgery study](watermark-forgery-study.md) (four-state model and double-embedding: the last writer wins under both oracles)
- [C2PA resolution research](c2pa-resolution-research.md) (measured resolver contracts, the privacy gradient, and the decision to never resolve over the network)
- [Chroma1 engine research](chroma1-engine-research.md)
+21
View File
@@ -207,6 +207,15 @@ The public video results make that boundary explicit without decoding audio:
the nested audio status reports `copied_if_present` and `unverified`. CLI output
uses the same terms. These fields add no media pass or model inference.
A local matched-oracle study has now measured the boundary concretely:
[Audio provenance experiment](audio-provenance-experiment.md) embeds AudioSeal
into synthetic carriers, runs the visible video-cleaning path, and shows the
audio packets byte-identical and the watermark verdict invariant across
cleaning, while additive noise on the audio path is the effective lever. The
boundary above still stands for provider marks: this tool still does not
decode them, and a `copied_if_present` audio track remains `unverified` in
runtime output.
The shipped engine streams sampled frames in bounded batches, computes its
fidelity metrics incrementally, and pipes regenerated pixels directly to
ffmpeg. Its frame and latent memory is therefore bounded by `--batch-size`
@@ -346,6 +355,18 @@ Memory needs depend on the profile, input size, dtype, and card.
## Metadata and formats
### Identification never resolves anything over the network
C2PA soft bindings can be resolved to manifests through vendor endpoints
(the registry carries eight of them), and querying one always tells the
vendor that someone is inspecting the specific content - directly through
the asset digest for fingerprint lookups, through the watermark payload for
payload-based ones. This tool deliberately has no resolution mode, tier, or
opt-in: identification reports only locally decoded evidence, and the
watermark decoders in this repository run offline against pinned weights.
The contracts, the privacy gradient, and the reasoning are recorded in
[the resolution research](c2pa-resolution-research.md).
### Missing metadata does not mean clean
Screenshots, social platforms, and re-encoding can remove metadata while a
+26
View File
@@ -432,6 +432,32 @@ resource tier; its CPU throughput makes a six-provider online matrix
counterproductive. Store generated outputs and the detailed CSV only under
`.local-eval/`.
The audio side of that contract now has a local matched-oracle study:
[Audio provenance experiment](audio-provenance-experiment.md) embeds AudioSeal
into synthetic carriers, verifies the copied-audio bitstream identity and a
watermark verdict invariant across `remove_video_visible`, and measures the
audio-path attacks separately. Its carriers are synthetic non-speech audio
and its oracle is AudioSeal, so it validates the copy semantics, not provider
audio marks. The audio and video arms are now first-class benchmark rows:
[benchmark kernel](benchmark-kernel.md) accepts `media_type: audio` and
`media_type: video` with the revision-pinned `audioseal` and `videoseal`
adapters, and both cohorts carry matched negatives, codec, and degradation
arms as strict manifest cases. The temporal layer is closed too: videoseal
detection records carry per-frame bit accuracy, and
[VideoSeal temporal evaluation](videoseal-temporal-evaluation.md) measures
the aggregation matrix and the crf decay curve on synthetic and real clips.
What remains open here is a wider real-content sweep and the learned temporal
inpainting comparison.
The privacy-tiered C2PA resolution item is closed as a product decision:
[the resolution research](c2pa-resolution-research.md) measured the five
vendors' resolver contracts (payload-only, asset digest, or unpublished),
grounded them in C2PA 2.2 section 18.10.5, and concluded that this tool
resolves nothing over the network at any tier - querying a resolver tells
the vendor someone is inspecting the specific content, which is a leak in
the opposite direction of a removal tool's purpose. A registry guard test
pins the published-API set so ecosystem changes surface consciously.
The same full-clip gate runs the public metadata-only path against its real MP4
and verifies unchanged file size, decoded frames, stream properties, and AAC
packets. A separate synthetic large-`mdat` test rejects full-source
+84
View File
@@ -0,0 +1,84 @@
# VideoSeal temporal evaluation
Development-only study, run on 2026-09-07 from
`scripts/videoseal_temporal_study.py`, answering the three temporal questions
the case-level benchmark verdict aggregates away: how decoded bit accuracy
decays across H.264 quality, whether the frame-aggregation choice changes the
outcome, and how per-frame accuracy moves in time on real provider clips
versus synthetic carriers.
Carriers: the two synthetic cohort clips (moving gradient, moving texture)
plus the committed real provider videos for Veo and Sora, standardized to a
64-frame 256x256 prefix (first 64 frames, center-crop, INTER_AREA resize).
Attacks: crf 18/23/28/32 re-encodes, 75 percent downscale, and frame-rate
halving. Everything is local; the real clips are already-committed project
fixtures with public test clearance.
## The TorchScript aggregation trap
The first matrix run produced identical numbers under all four aggregations,
and that was a measurement artifact, not a finding: the standalone TorchScript
build ignores the `aggregation` argument of `detect_video_and_aggregate` and
returns the same thresholded bits for every choice (verified by checksum on a
single artifact). The oracle now computes the aggregations itself from the raw
per-frame logits using the exact upstream `extract_message` formulas - avg,
squared_avg, and l1/l2-norm-weighted averages - and the recorded matrix comes
from those. The formula port is pinned by a hand-computed unit test.
## Matrix (bit accuracy of the aggregated 256-bit decode)
| Carrier | crf18 | crf23 | crf28 | crf32 | scale .75 | fps .5 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| moving_gradient (clean 0.52) | 0.887 | 0.570 | 0.559 | 0.500 | 0.691 | 0.996 |
| moving_texture (clean 0.48) | 0.602 | 0.559 | 0.531 | 0.586 | 0.879 | 0.980 |
| real_veo (clean 0.52) | 0.801 | 0.723 | 0.621 | 0.574 | 1.000 | 0.855 |
| real_sora (clean 0.51) | 0.996 | 1.000 | 0.992 | 0.902 | 1.000 | 0.996 |
Readings, not verdicts: the benchmark adapter's rule is 0.9 under plain avg.
- **Carrier content dominates.** The real Sora clip keeps the message through
every attack including crf 32; the real Veo clip decays smoothly with
quality; the smooth synthetic gradient dies at crf 23. Any "survives H.264"
claim must name the carrier and the crf.
- **Downscale is not an attack on real content.** 0.75 downscale plus re-encode
scored a perfect 1.000 on both real clips - resize was a training
augmentation, and the antialias resample appears to help the detector. On
the synthetic gradient it still costs 0.31.
- **Frame-rate halving is harmless.** Every carrier kept the message after
dropping half the frames: the 256-bit secret repeats across keyframes, so
temporal redundancy, not frame count, carries it.
- **Aggregation choice matters at the margin.** Norm-weighted averaging
recovered 2-6 points on damaged arms (moving_texture scale: 0.879 avg
versus 0.941 l1/l2, crossing the 0.9 rule) but never rescued a collapsed
case and never lifted a clean negative above chance. The kernel keeps plain
avg as its canonical rule; the matrix is the evidence for when that choice
is conservative.
Per-frame accuracy on the marked clips: real_sora sits at 0.988-1.000 on
every frame, real_veo spans 0.547-0.910, moving_texture 0.457-0.961 - the
temporal spread is itself carrier-dependent, and the kernel's
`detection.temporal` block now records it for every videoseal case.
## Pins
Study script
`sha256:682ccf14b21d6ac17bb6500a7efb364247eebae2d9e0e8895ffac33e6d294d4f`,
oracle
`sha256:5aa67ceaca1b0050727aa0a75440b1bf4c583435b3bf9e8ef2e97fadd46a035c`,
case rows
`sha256:5494c9bbe8a6085ecbd27fd9732dadf8d004dab7be159e905051e854dabe64aa`.
Checkpoint: TorchScript `y_256b_img.jit`
`sha256:5c7a4581c36fc6090aafdcfb3999123bae5172a4847f22e2da4e7fd1a39d1e1b`
at upstream commit `870ca7f`. The kernel re-run of the video cohort with
temporal records used kernel
`sha256:657b0d7e35af75eb97b9bd9b81c15fcae754399f27999e08a5c28c494e69cf05`
and produced case rows
`sha256:435215e9a55589f6d83e34d62f34469c079db078a0a174ead64ffaa74af664c5`
with unchanged verdicts.
## Follow-ups
- A crf sweep on more real carriers (the other committed provider clips) to
widen the carrier-dominated picture beyond two samples.
- Temporal inpainting comparison remains open from the original research map;
the per-frame rows recorded by the kernel are the substrate for it.
+76
View File
@@ -0,0 +1,76 @@
# Watermark forgery study
Development-only study, run on 2026-09-07 from
`scripts/watermark_forgery_study.py`, opening the forgery/remover-forensics
direction from the original research map. It measures the four-state model
(clean / marked / removed / forged) and the double-embedding question for both
pinned oracles: does embedding message B over an A-marked artifact leave A,
B, or mutual interference?
Carriers: the tone-stack audio carrier, the synthetic moving-gradient video
clip, and the real committed Sora clip. Message B is the oracle's message
generator at seed 8. Everything local; artifacts under `.local-eval/`.
## The double-embedding answer: the last writer wins, completely
| Cell | AudioSeal acc vs A | vs B | VideoSeal (gradient) vs A | vs B | VideoSeal (real Sora) vs A | vs B |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| marked A | 1.000 | 0.44 | 1.000 | 0.47 | 1.000 | 0.47 |
| removed | 0.50 (presence 0.28) | 0.44 | 0.57 | 0.48 | 1.000 | 0.47 |
| forged B on clean | 0.44 | 1.000 | 0.47 | 1.000 | 0.47 | 1.000 |
| **forged B on marked A** | **0.44** | **1.000** | **0.47** | **1.000** | **0.48** | **0.99** |
| forged B on removed | 0.56 | 0.875 | 0.47 | 1.000 | 0.47 | 1.00 |
Embedding B over marked A reads as pure B: the original message decays to
coin-flip accuracy under both schemes, on synthetic and real content alike.
There is no mutual interference and no residue of A. With an unkeyed, openly
published embedder this is the expected outcome - and now it is a measurement,
not an assumption. The forensic consequence: payload attribution without key
control is meaningless against an attacker who can run the same open embedder;
only presence detection survives, and even that carries the attacker's
payload. Provider schemes that rely on secret keys are a different question
this study cannot answer.
Two boundary observations travel with the table:
- The removal transform is carrier-dependent, as the cohorts already showed:
10 dB noise removes the tone stack (presence 0.28) and crf 23 removes the
gradient clip (0.57), but the real Sora clip keeps a perfect 1.000 decode
through the same crf 23 - the same number the temporal study recorded.
- A forged row reads `detected` under the AudioSeal presence rule (the label
carries the foreign payload) and `not_detected` under the VideoSeal
message-accuracy rule. Both cohort fixtures encode their scheme's correct
expected value, and the benchmark docs state the two rules side by side.
## Remover-trace probe (scoped first evidence)
High-to-low frequency band energy ratio on clean versus noise-removed audio:
the tone stack jumps from 8.6e-05 to 2.3e-02 (a 269x trace), the pinkish
carrier 0.016 to 0.039, and the white-noise carrier does not separate at all
(0.330 to 0.332) - a noise carrier absorbs a noise remover. This detects the
trace of THIS study's gaussian-noise removal only; it is not a general
remover detector, and the probe JSON says so in its scope field.
## Cohort arms
The audio cohort (v2, 20 cases) and video cohort (v2, 15 cases) now carry the
four states as strict manifest rows: `removed` rows where the transform
measurably destroys the mark (audio: noise on tone stack; video: crf 23 on
both synthetic carriers), and `forged` rows embedding message B with the
scheme-correct expected verdict. Zero expected-result mismatches in both
recorded runs.
## Pins
Study script
`sha256:3985522c862ac44a0c7ef8aa02946a11701439a94d67f5867f00819f638c032b`,
case rows
`sha256:03d446c08a5daed623a4be2980e097bb8bbc0cd7a6d3ab3f931d25aa58570a16`.
Oracles and checkpoints are pinned as in the kernel document.
## Follow-ups
- A keyed-scheme perspective needs a scheme with actual key control; WmForger
and WMCopier diffusion-attack baselines remain the map's heavyweight item.
- A general remover-trace detector would need features beyond one band ratio
and validation against removals this project did not produce.