ZK-powered media authenticity. Prove that an edited image derives from a real, hardware-signed original — without revealing the original image or transform parameters.
Brevis Vera takes a C2PA-signed photo, applies edits (crop, brighten, grayscale), and produces a compact proof package. Any verifier can confirm the edited image is a legitimate derivative of the signed original — without trusting the editor and without seeing the original.
Proven inside the ZK circuit:
- The C2PA ECDSA P-256 signature over the COSE Sig_structure1 is valid under the committed public key
- The signed claim authentically covers the JPEG file bytes (two-step C2PA asset binding hash chain)
- The JPEG decodes to the committed original pixels
- SHA-256 commitments over original and output pixels are correct
- The output pixels were produced by running exactly the declared transforms on the original pixels
- Transform parameters (crop coordinates, brightness delta) stay private
Checked by the verifier (outside ZK):
- The edited image decodes to the exact output pixels the proof committed to (
output_hash) - The
c2pa_pubkeycommitted by the proof chains to a trusted root certificate (leaf pubkey binding, chain signature validity, cert validity period, root CA pinned against C2PA trust list + Brevis Vera root)
Privacy model: The original JPEG, transform parameters, C2PA protected headers, claim CBOR, and signature bytes are never committed to the proof's public values — only
original_hash,output_hash,transforms_applied, andc2pa_pubkeyare public outputs.
bv-core/ Shared types, integer transform functions, CBOR encoder
bv-capture/ C2PA JUMBF/COSE extraction, RGBA decode, commitment hash
bv-edit/ Host-side editing pipeline (decode → transform → encode)
bv-zkvm/guest/ ZK circuit: ECDSA verification + transform replay (Pico RISC-V guest)
bv-zkvm/host/ Proof generation via Pico SDK
bv-cli/ CLI: `bv prove`, `bv edit`, `bv verify`, `bv sign`
bv-web/ Web prover + verifier: Axum server + browser UI
- Rust nightly-2025-08-04 (auto-selected via
rust-toolchain.toml) - Pico CLI for building the guest ELF:
cargo +nightly-2025-08-04 install --git https://github.com/brevis-network/pico pico-cli
# Build the guest ELF (only needed once, or after changing guest code)
cd bv-zkvm/guest && cargo pico build && cd ../..
# Build the workspace
cargo build --release
# Run all unit tests (excludes slow ZK proof tests)
cargo test --workspace --exclude bv-zkvm-host
# Run ZK proof integration tests (slow — generates real STARK proofs on small
# synthetic images plus an emulation test with C2PA ECDSA verification)
cargo test -p bv-zkvm-host
| File | Resolution | Signer | GPU full prove |
|---|---|---|---|
assets/demo_mini.jpg |
64×42 | Brevis test CA | — |
assets/demo_small.jpg |
320×213 | Brevis test CA | 4.8s |
assets/demo_mid.jpg |
640×426 | Brevis test CA | 8.2s |
assets/demo_large.jpg |
1920×1280 | Brevis test CA | 41.4s |
assets/sony_2.jpg |
4320×2880 | Sony ILCE-1 | 183s (~3 min) |
assets/sony_1.jpg |
8640×5760 | Sony ILCE-1 | 827s (~13.8 min) |
GPU times measured on the Brevis 8-GPU server with compressed STARK (full C2PA-in-ZK pipeline). See docs/scaling.md for cycle counts, CPU times, and future optimizations.
Source JPEGs (unsigned) for re-signing are in assets/demo_source/. Demo images are signed with our test CA (assets/certs/); Sony images carry real hardware C2PA provenance.
Before committing to a long proof run, use --emulate to verify hashes and measure cycle counts in seconds:
# Fastest dry-run (~1.7s, 64×42)
cargo run --release -p bv-cli -- prove assets/demo_mini.jpg --emulate
# Reference demo (~4.8s, 640×426)
cargo run --release -p bv-cli -- prove assets/demo_mid.jpg --emulate
Example output (demo_mid.jpg):
[1/7] Reading original image...
49152 bytes
[2/6] Decoding image to RGBA...
640x426 (1090560 bytes RGBA)
[3/6] Applying transforms...
2 transform(s): [Crop, Brighten]
Output: 320x213
[4/6] Saving edited image to assets/demo_mid.edited.png...
C2PA COSE: extracted (12 bytes protected, 847 bytes claim)
[6/6] Emulating guest program (no proof)...
Emulation complete in 4.8s
Total cycles: 55715024
Chunks: 214
original_hash: a3f1e2b4...
output_hash: 7c9d8a12...
transforms: [Crop, Brighten]
No .bvproof written (emulation does not produce a proof).
Generate a ZK proof. The proof bundle contains the STARK proof bytes plus the guest's public outputs — the original JPEG is not included.
# Fast mode → demo_mid.fast.bvproof
cargo run --release -p bv-cli -- prove assets/demo_mid.jpg --fast
# Full prove pipeline → demo_mid.bvproof
cargo run --release -p bv-cli -- prove assets/demo_mid.jpg
# Custom transforms
cargo run --release -p bv-cli -- prove assets/demo_mid.jpg --fast \
--transforms '[{"Crop":{"x":0,"y":0,"width":320,"height":213}},{"Brighten":{"value":15}}]'
Use --emulate for fast iteration — completes in seconds without generating a proof. See the Test images table above for emulation and GPU prove times.
The proof contains:
proof_bytes— STARK proof (cryptographic)original_hash— SHA-256(width || height || RGBA pixels) of the originaloutput_hash— SHA-256(width || height || RGBA pixels) of the edited outputtransforms_applied— list of transform kinds (Crop, Brighten, Grayscale)c2pa_pubkey— uncompressed EC P-256 public key that signed the original (65 bytes)cert_chain_pem— X.509 certificate chain for trust anchor verification
Not in the proof: original JPEG, original pixels, transform parameters, C2PA headers/claim/signature.
This outputs:
demo_mid.edited.png— the edited imagedemo_mid.bvproof— proof bundle (ordemo_mid.fast.bvproofwith--fast)
Apply transforms and save the edited PNG without generating a ZK proof. Useful for previewing edits locally or preparing files before proving on a remote GPU server.
# Default transforms (top-left quarter crop + brighten 10)
cargo run --release -p bv-cli -- edit assets/sony_1.jpg
# Custom transforms
cargo run --release -p bv-cli -- edit assets/sony_1.jpg \
--transforms '[{"Crop":{"x":136,"y":56,"width":289,"height":226}},{"Brighten":{"value":33}}]'
# With downscale and custom output path
cargo run --release -p bv-cli -- edit assets/sony_1.jpg --max-dim 540 --output my_edit.png
cargo run --release -p bv-cli -- verify assets/demo_mid.edited.png assets/demo_mid.bvproof
The verifier checks:
- ZK proof validity — cryptographic STARK proof verification (fast or full mode)
- Output binding — edited image commitment matches
output_hashfrom verified proof - C2PA trust chain — leaf cert pubkey matches
c2pa_pubkeyfrom proof; chain signatures valid; all certs within validity period; root CA pinned against the C2PA trust list (18 root/intermediate CAs) and Brevis Vera root (fails by default if root untrusted; use--allow-untrusted-rootto override)
Prints VERDICT: AUTHENTIC or VERDICT: VERIFICATION FAILED.
Embed a C2PA manifest into a JPEG using your own certificate. The signed JPEG can then be used as input to bv prove.
cargo run --release -p bv-cli -- sign assets/demo_source.jpg assets/demo_signed.jpg \
--cert assets/certs/ec_chain.pem \
--key assets/certs/ec_key.pem \
--claim-generator "BrevisVera/1.0"
The --claim-generator string follows the C2PA convention "ProductName/Version". The part before the first / is shown as the Device field in bv verify output. Defaults to "BrevisVera/1.0" if omitted.
The test certificate in assets/certs/ is a self-signed ES256 (ECDSA P-256) chain. bv verify will fail by default because the root CA is not in the public C2PA trust list. Use --allow-untrusted-root to override for demo/dev verification.
cargo run --release -p bv-web
# → http://localhost:3000
# Verify: http://localhost:3000/
# Prove: http://localhost:3000/prove
| Transform | Parameters (private) | Example |
|---|---|---|
| Crop | x, y, width, height | {"Crop":{"x":0,"y":0,"width":320,"height":213}} |
| Brighten | value (i32, –255..255) | {"Brighten":{"value":20}} |
| Grayscale | none | "Grayscale" |
Built on Pico ZKVM (RISC-V). The guest program performs four tasks inside the ZK circuit:
-
ECDSA P-256 verification — reconstructs
COSE Sig_structure1 = ["Signature1", protected_headers, b"", claim_cbor]using a hand-rolled CBOR encoder, then verifies the camera's ECDSA signature using standardp256 0.13+ecdsa 0.16(pure software, ~12M cycles). SHA-256 uses the Brevis-patchedsha2fork withSHA_EXTEND/SHA_COMPRESSprecompile acceleration. -
C2PA asset binding — verifies the two-step hash chain linking the authenticated claim to the JPEG file bytes:
SHA-256(data_hash_assertion_raw)appears in the ECDSA-signed claim CBOR, andSHA-256(jpeg_pre || jpeg_post)appears in the assertion. This proves the signed claim authentically covers the exact JPEG bytes passed to the guest. -
In-circuit JPEG decode — reconstructs the full JPEG from
pre || post(bytes before and after the JUMBF manifest), decodes to RGBA usingzune-jpeg(integer IDCT, no_std, no floats), ~6M cycles. -
Transform replay + canonical commitments — applies each transform in order using the same
bv-coreinteger-arithmetic functions as the host, then commitsSHA-256([width: u32 BE][height: u32 BE][RGBA bytes])over original and output pixels.
| Document | Description |
|---|---|
| c2pa.md | C2PA-in-ZK architecture, security model, and measured cycle costs |
| scaling.md | Performance data, GPU/CPU benchmarks, future optimizations (P-256 precompile, Merkle commitment) |