Privacy Model
Selective disclosure, range proofs, diversified addresses, and client-side encryption.
The previous sections described what Apertrue proves and how the proving engine works. This section explains what Apertrue hides — the privacy model that protects users while maintaining cryptographic proof of authenticity.
Apertrue's privacy model operates at four layers: selective disclosure (users choose what to reveal), metadata stripping (removing everything the user didn't choose to reveal), client-side encryption (protecting stored media), and on-chain unlinkability (preventing upload correlation). Each layer is independent — compromising one doesn't compromise the others.
Selective disclosure
When a user uploads a photo, the C2PA manifest contains rich metadata: exact GPS coordinates, precise capture time, device make and model, serial numbers, lens specifications, edit history, and software versions. Apertrue lets the user choose the precision at which each category is revealed.
Disclosure levels
Each metadata category has graduated disclosure levels, from fully hidden to fully revealed:
| Category | Levels | Examples |
|---|---|---|
| Location | Hidden, Continent, Country, City, Neighbourhood, Exact | Hidden → proves GPS exists but reveals nothing. City → "London, UK" |
| Time | Hidden, Year, Quarter, Month, Day, Exact | Hidden → proves timestamp exists. Month → "January 2026" |
| Device | Hidden, Category, Manufacturer, Full | Hidden → concealed. Category → "Mobile Phone" |
| Software | Hidden, Type, Name, Full | Type → "Camera App". Full → "ProofMode v0.6.2" |
| Edit history | Hidden, Count, Types, Full | Count → "3 edits". Types → "Crop, Color adjustment" |
The default privacy settings hide location, time, and device information. Edit history defaults to visible for transparency. Users adjust these before uploading — the privacy choices become circuit inputs.
How coarsening works
Coarsening converts exact metadata to the precision matching the user's chosen disclosure level. For location, this means converting GPS coordinates into a bounding region. For time, it means converting an ISO timestamp into a range of Unix seconds.
- Location coarsening. The exact GPS coordinates are converted into a privacy circle — a centre point and radius. At "City" level, the circle covers the city boundaries. At "Country" level, it covers the entire country. At "Hidden", the radius is effectively infinite (a world-covering circle). The circuit proves the exact coordinates fall within this circle without revealing them.
- Time coarsening. The exact timestamp is converted into a range:
[time_min, time_max]. At "Month" level, the range spans the first second of the month to the last second. At "Year" level, it spans January 1 to December 31. At "Hidden", the range spans 1970 to 2100. The circuit proves the exact timestamp falls within this range.
Time range proofs
ProofA's time range proof takes the exact capture timestamp as a private input and the coarsened range bounds as public inputs. The circuit asserts:
The verifier sees only time_min and time_max — the public bounds. The exact timestamp stays private. At "Month" level for January 2026, the verifier learns the photo was taken sometime in January 2026, but not whether it was January 3rd or January 28th.
| Disclosure level | time_min | time_max | What verifier learns |
|---|---|---|---|
| Hidden | 0 (1970-01-01) | 4102444800 (2100-01-01) | Photo has a timestamp (nothing useful) |
| Year | 1735689600 (2026-01-01) | 1767225599 (2026-12-31) | Taken in 2026 |
| Month | 1735689600 (2026-01-01) | 1738367999 (2026-01-31) | Taken in January 2026 |
| Day | 1735689600 (2026-01-01) | 1735775999 (2026-01-01) | Taken on January 1, 2026 |
| Exact | 1735700400 | 1735700400 | Taken at 2026-01-01T03:00:00Z |
The circuit also outputs a time commitment: Poseidon2([exact_timestamp, time_salt], 2). This commitment is stored locally. If the user later needs to reveal the exact timestamp (for example, as court evidence), they can disclose the timestamp and salt. The verifier checks that the Poseidon2 hash matches the commitment from the original proof — no re-proving required.
Location privacy circles
The location range proof works geometrically rather than as simple bounds. The user's exact GPS coordinates are the private input. The public input is a privacy circle defined by a centre point and a radius.
GPS coordinates are scaled to integers with 107 precision (1.1 cm accuracy at the equator). The circuit verifies the Euclidean distance constraint:
The verifier sees the centre and radius (the circle), but not where inside the circle the exact coordinates fall. At "City" level for London, the verifier learns the photo was taken somewhere within London's boundaries, but not which neighbourhood or street.
| Disclosure level | Radius | Privacy guarantee |
|---|---|---|
| Hidden | Infinite (world-covering) | Proves GPS exists, reveals nothing |
| Country | ~500 km (varies) | Somewhere in the country |
| City | ~10 km | Somewhere in the metropolitan area |
| Neighbourhood | ~1 km | Within a district or suburb |
| Exact | 0 m | Precise coordinates revealed |
Like the time proof, the circuit outputs a location commitment: Poseidon2([exact_lat, exact_lon, lat_sign, lon_sign, location_salt], 5). The commitment preserves the exact coordinates for optional future disclosure while revealing nothing about them in the proof.
Metadata stripping
Selective disclosure controls what the proof reveals. Metadata stripping controls what the image file reveals. Even if a user sets all disclosure to "exact", the uploaded file itself has all metadata removed — the proof carries the verified claims, not the raw metadata. A two-stage binary stripper removes all EXIF, C2PA, ICC, IPTC, and comment segments before upload. After stripping, only pixel data and structural markers remain. The full stripping pipeline is detailed in Media Processing & Storage.
Client-side encryption
After stripping, the image is encrypted with AES-256-GCM before leaving the browser. A fresh session key is generated per upload, each file gets a random 96-bit IV, and the session key is wrapped with a key derived from the user's passkey PRF output. The backend stores only ciphertext — it cannot decrypt media even if fully compromised. Decryption keys never leave the browser. The full encryption lifecycle is detailed in Media Processing & Storage.
Nullifier privacy
Nullifiers prevent proof replay — the same image from the same device cannot be verified twice. But the nullifier itself must not reveal which device or which image it corresponds to.
The nullifier is derived from two private values: the image's content hash and a hash of the leaf certificate's public key. The Pedersen hash is a one-way commitment — knowing the nullifier reveals nothing about either input.
- Same image, same device — produces the same nullifier. Second submission rejected (replay prevention).
- Same image, different device — different leaf key hash, different nullifier. Both submissions accepted (legitimate re-signing).
- Different image, same device — different content hash, different nullifier. No link between uploads from the same device.
The nullifier is published as a public value — the backend stores it and the Aztec contract records it. But because Pedersen is one-way, an observer who sees two nullifiers cannot determine whether they came from the same device, the same image, or are completely unrelated.
On-chain unlinkability
When a user submits a proof to the Aztec blockchain, they use a diversified address — a child key derived from their master Aztec secret. Each batch upload increments a nonce, producing a fresh address.
This prevents upload correlation: an observer watching the Aztec mempool cannot determine that three separate batch uploads came from the same user, because each batch originates from a different address. The master secret ties them together, but only the user knows the master secret.
A second layer of protection prevents timing correlation. The browser applies a randomised submission delay before initiating the on-chain transaction, so the gap between a backend verification event and the corresponding Aztec transaction is unpredictable. Combined with diversified addresses and fee abstraction (where a sponsored fee payer contract covers gas on behalf of the user), this makes it significantly harder for an observer to link network activity to specific on-chain submissions.
What the backend sees
After all privacy layers are applied, the backend receives a minimal set of data:
| Data | Backend receives | Purpose |
|---|---|---|
| Image pixels | Encrypted ciphertext | Storage only — cannot decrypt |
| ZK proof | Aggregated proof bytes | Cryptographic verification |
| Root commitment | Single field element | Batch identity |
| Content hashes | Per-image SHA-256 of stripped file | Lookup and deduplication |
| Nullifiers | Per-image Pedersen hash | Replay prevention |
| Trust list root | Merkle root hash | Oracle version check |
| Time bounds | time_min, time_max | Coarsened range only |
| Location bounds | Circle centre + radius | Coarsened region only |
What the backend never receives
- The original C2PA manifest (stripped by the split worker)
- The certificate chain (verified inside the circuit)
- EXIF data: GPS coordinates, device make/model, serial numbers, lens, exposure settings
- Edit history and software versions (stripped)
- Author name or credentials (stripped)
- The AES encryption key (stays in the browser)
- The exact capture timestamp (only the range bounds)
- The exact GPS coordinates (only the privacy circle)
- The passkey private key or PRF secrets
Optional future disclosure
The time commitment and location commitment output by ProofA enable a powerful pattern: prove something now, reveal it later — without re-proving.
- At upload time. The circuit computes
Poseidon2([exact_timestamp, salt])and outputs it as a public commitment. The exact timestamp and salt are stored locally in the encrypted media index. - Later. The user exports the exact timestamp and salt. The verifier computes
Poseidon2([timestamp, salt])and checks it matches the commitment from the original proof. If it matches, the timestamp is proven to be the one that was verified at upload time.
The same mechanism works for location: the user exports exact coordinates and the location salt, and the verifier checks against the location commitment. No interaction with the proving engine or blockchain is needed — the commitment is self-verifying.
Privacy invariants
The privacy model maintains these guarantees regardless of backend behaviour:
- No metadata leakage. The backend receives only stripped pixels and ZK proofs. Even a compromised backend cannot extract EXIF, GPS, device identity, or certificate details.
- User-controlled disclosure. The user chooses precision for each metadata category before upload. The circuit enforces these choices — the proof physically cannot reveal more than the user selected.
- Commitment-based reveal. Exact values are stored as Poseidon2 commitments, not plaintext. Revealing requires the user's cooperation (providing the salt).
- Upload unlinkability. Diversified addresses prevent observers from linking batch uploads to the same user. Nullifiers prevent linking different images to the same device.
- Encryption at rest. The backend stores ciphertext. Decryption requires the user's passkey PRF output, which is hardware-bound and never leaves the device.
The next section covers on-chain private verification — how the Aztec blockchain stores proof commitments as encrypted private state, enabling trustless verification without a centralized backend.