Skip to content

Apertrue/c2pa-extractor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Apertrue

c2pa-extractor

C2PA manifest extraction with raw crypto-witness output — built for ZK proof pipelines.

License Runtime Tests


Extract C2PA (Content Credentials) manifests from images and video — including the raw cryptographic material most C2PA tooling hides: COSE signature bytes, X.509 chains with TBS/SPKI byte offsets, claim/assertion hash chains, and RSA limb encodings ready for ZK circuits.

The official C2PA SDKs answer "is this manifest valid?" This library additionally answers "give me the exact bytes": the Sig_structure that was signed, the signature r‖s, the signer's public key and where it sits inside the DER, the claim bytes, and the offsets binding content hash → claim → assertions. That is what you need to re-verify a C2PA manifest somewhere the SDK can't follow — a ZK circuit, an enclave, a smart contract.

Built at Apertrue to feed the Noir circuits at Apertrue/circuits, which verify C2PA signatures inside zero-knowledge proofs. The two repos together are a complete, open pipeline for proving statements about signed media without revealing it.

How it fits together

flowchart LR
    A[media bytes] --> B[containers/<br/>JPEG · PNG · TIFF · MP4<br/>JUMBF box tree]
    B --> C[cose/<br/>Sig_structure · alg detect]
    B --> D[claims/<br/>claim & assertion bytes<br/>hash-chain offsets]
    C --> E[x509/<br/>chain · TBS/SPKI offsets<br/>PSS validation]
    E --> F[zk/<br/>noir-bignum limbs<br/>Barrett params · toZKInput]
    D --> F
    F --> G[ZK circuit witness]
    B --> H[metadata/<br/>GPS · time · device]
Loading

Everything left of the arrowhead is untrusted parsing; the trust decision belongs to whatever verifies the extracted material (in our case, the circuit — it re-checks the signature and every hash binding, so a malicious parser can't forge anything).

Install

npm install @apertrue/c2pa-extractor

Quick start

Browser (WASM-backed SDK view + raw extraction):

import { configureC2pa, extractC2PA, toZKInput } from '@apertrue/c2pa-extractor/browser';

// Serve @contentauth/c2pa-web's WASM yourself (same-origin avoids CDN SRI issues)
configureC2pa({ wasmSrc: '/wasm/c2pa_bg.wasm' });

const result = await extractC2PA(imageBytes);
if (result.success) {
  console.log(result.signer, result.claims);
  const witness = toZKInput(result);   // circuit-ready input, or null
}

Node:

import { extractC2PA } from '@apertrue/c2pa-extractor/node';
const result = await extractC2PA(await fs.readFile('photo.jpg'));

ZK layer only (no SDK dependency — pure byte work):

import { toNoirLimbs, computeRedcParam } from '@apertrue/c2pa-extractor/zk';

API highlights

Function Entry What it does
extractC2PA(bytes, opts?) browser, node Full extraction: manifest view + RawC2PAData (signature, pubkey, cert chain, hash-chain offsets, signing time)
toZKInput(result) browser, node, zk Convert an extraction result into circuit witness data
configureC2pa({ wasmSrc }) browser Point the WASM loader at your served binary (call before first extraction)
hasC2PAManifest(bytes) browser, node Cheap presence check
quickExtractAlgorithm(bytes) cose <100ms COSE algorithm sniff — pick your circuit variant before committing to a full parse
analyzeProvenanceFromImage(bytes) browser Walk the ingredient/edit chain (depth-capped)
computeContentHash(bytes) browser, node SHA-256 of the asset

RawC2PAData in one glance:

{
  signatureType: 'ecdsa' | 'rsa',
  signatureR?, signatureS?, publicKeyX?, publicKeyY?, curve?,   // ECDSA (low-S normalized)
  rsaData?: RSACircuitData,   // modulus + signature as 18×120-bit noir-bignum limbs,
                              // Barrett reduction params, modulus hash
  signingTime?: number,
  // ...plus claim bytes, claimHash, assertion hash-chain data and byte offsets
}

What it handles

Containers JPEG (APP11 multi-segment reassembly, incl. recovery from known encoder offset bugs), PNG (caBX), MP4/MOV (uuid box), TIFF/DNG/NEF/ARW (tag 0xCD41), remote manifests (dcterms:provenance, allowlisted HTTPS hosts only)
Signatures COSE_Sign1 with ECDSA P-256/P-384 (DER→raw, low-S normalized) and RSA-2048/4096 PKCS#1 v1.5 + PSS (with PSS parameter validation)
Signers exercised in production Leica, Sony, Pixel, Samsung, Adobe (incl. Lightroom remote manifests), ProofMode, ChatGPT/DALL-E outputs
Semantics AI-content state, capture-device classification, trust tiering, edit/ingredient history, normalized GPS/time/device metadata

Package layout

containers/   JUMBF box walker + per-format extraction (jpeg, png, tiff, manifest store)
cose/         COSE_Sign1 parsing, Sig_structure reconstruction, fast algorithm sniffing
x509/         DER walking, chain building, TBS/SPKI offsets, RSA-PSS validation, validity checks
claims/       claim/assertion parsing, hash-chain binding, vendor signer patterns
metadata/     GPS, timestamps, device info — normalized across signer formats
zk/           noir-bignum limb encoding, Barrett reduction params, witness builder   → ./zk
extract/      orchestrator producing RawC2PAData

Subpath exports: . (browser barrel), ./browser, ./node, ./zk.

Security notes

This library parses attacker-controlled bytes; treat its output as untrusted input to your verifier — that's the design.

  • The X.509 path ships with a 75-case malformed-DER fuzz corpus (run in CI-able npm test).
  • Box walkers bound their recovery scans, cap recursion depth, and reject >32-bit box lengths.
  • Remote-manifest fetching is HTTPS-only against an allowlist, with timeouts (SSRF-resistant).
  • x509/validity checks certificate validity windows only — it is deliberately not chain-signature verification. Signature and binding verification belong to the consumer (e.g. the circuits, which re-verify everything in-proof).

Development

npm install
npm test                 # vitest: unit + DER fuzz corpus (90 tests)
npm run test:real-images # downloads CAI public example assets, runs end-to-end
npm run build            # tsup: ESM + d.ts — index, browser, node, zk entries
npm run typecheck

License

Apache-2.0. Contributions welcome — especially additional container formats, signer patterns, and fuzz corpora.

About

C2PA manifest extraction with raw crypto-witness output (JUMBF, COSE, X.509) — built for ZK proof pipelines

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages