Privacy & Blockchain

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:

CategoryLevelsExamples
LocationHidden, Continent, Country, City, Neighbourhood, ExactHidden → proves GPS exists but reveals nothing. City → "London, UK"
TimeHidden, Year, Quarter, Month, Day, ExactHidden → proves timestamp exists. Month → "January 2026"
DeviceHidden, Category, Manufacturer, FullHidden → concealed. Category → "Mobile Phone"
SoftwareHidden, Type, Name, FullType → "Camera App". Full → "ProofMode v0.6.2"
Edit historyHidden, Count, Types, FullCount → "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:

Time range constraint
assert(time_min <= exact_timestamp);
assert(exact_timestamp <= time_max);

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 leveltime_mintime_maxWhat verifier learns
Hidden0 (1970-01-01)4102444800 (2100-01-01)Photo has a timestamp (nothing useful)
Year1735689600 (2026-01-01)1767225599 (2026-12-31)Taken in 2026
Month1735689600 (2026-01-01)1738367999 (2026-01-31)Taken in January 2026
Day1735689600 (2026-01-01)1735775999 (2026-01-01)Taken on January 1, 2026
Exact17357004001735700400Taken 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:

Location privacy circle constraint
let dx = exact_lat_scaled - center_lat_scaled;
let dy = exact_lon_scaled - center_lon_scaled;
assert(dx * dx + dy * dy <= radius_squared_scaled);

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 levelRadiusPrivacy guarantee
HiddenInfinite (world-covering)Proves GPS exists, reveals nothing
Country~500 km (varies)Somewhere in the country
City~10 kmSomewhere in the metropolitan area
Neighbourhood~1 kmWithin a district or suburb
Exact0 mPrecise 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.

Note
Latitude and longitude are stored as absolute values with separate sign flags because Noir's native field arithmetic is over positive integers. The signs are encoded as 0 (positive) or 1 (negative) and included in the commitment to prevent sign-flip attacks — otherwise an attacker could claim a photo taken at 51.5°N was taken at 51.5°S.

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.

Nullifier derivation
let leaf_key_hash = pedersen_hash(leaf_key_material);
let nullifier = pedersen_hash([content_hash, leaf_key_hash]);

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.

Diversified address derivation: Master Aztec Secret fans out to Address #1 (nonce 0), Address #2 (nonce 1), Address #3 (nonce 2). Each address points to a separate batch upload. Dashed lines between batch uploads labelled 'unlinkable to outside observers'. Each batch uses a fresh diversified address — only the user can link them.

What the backend sees

After all privacy layers are applied, the backend receives a minimal set of data:

DataBackend receivesPurpose
Image pixelsEncrypted ciphertextStorage only — cannot decrypt
ZK proofAggregated proof bytesCryptographic verification
Root commitmentSingle field elementBatch identity
Content hashesPer-image SHA-256 of stripped fileLookup and deduplication
NullifiersPer-image Pedersen hashReplay prevention
Trust list rootMerkle root hashOracle version check
Time boundstime_min, time_maxCoarsened range only
Location boundsCircle centre + radiusCoarsened 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.

  1. 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.
  2. 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.

Use case: court evidence
A journalist uploads a photo with location set to "Country" level. Months later, a court requests the exact location as evidence. The journalist exports the coordinates and salt from their local media index. The court verifies the Poseidon2 hash matches the on-chain commitment — proving the exact location was captured at the original proof time, not fabricated after the fact.

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.