ZKP Architecture
How BlockFact uses zero-knowledge proofs to verify content authenticity without exposing private data.
Overview
Every photo registered through BlockFact includes a Groth16 zero-knowledge proof generated natively on the device. This proof cryptographically attests that the content metadata (wallet, GPS, timestamp) is authentic — without revealing the private inputs to anyone.
The Poseidon Circuit
BlockFact uses a custom circom circuit called PoseidonCheck that:
- • Takes 5 private inputs:
wallet,latitude,longitude,timestamp,expectedHash - • Computes a Poseidon hash of the first 4 inputs
- • Asserts the computed hash equals
expectedHash - • Outputs the hash as a public signal
// Simplified circuit (poseidon_hash_check.circom)
template PoseidonCheck() {
signal input wallet;
signal input latitude;
signal input longitude;
signal input timestamp;
signal input expectedHash;
signal output hashOut;
component hasher = Poseidon(4);
hasher.inputs[0] <== wallet;
hasher.inputs[1] <== latitude;
hasher.inputs[2] <== longitude;
hasher.inputs[3] <== timestamp;
hashOut <== hasher.out;
expectedHash === hashOut;
}The circuit is ~300 constraints, producing a 330KB .zkey file and a 2MB .wasm witness generator.
On-Device Proof Generation
- 1. Compute Poseidon Hash
Using
poseidon-lite(BN128 field), hash the wallet address, GPS coordinates (×1000), and Unix timestamp. - 2. Generate Witness
The transpiled Rust witness calculator (
rust_witness) computes all intermediate circuit signals from the inputs. - 3. Generate Groth16 Proof
Arkworks (pure Rust) generates the proof using the witness and the
.zkeyproving key. This runs natively on ARM64 in <1 second. - 4. Format & Send
The proof is converted from Arkworks projective coordinates to snarkjs affine format (
pi_a,pi_b,pi_c) and sent with the content registration request.
Why Native (Not JavaScript)?
❌ JS (snarkjs) — v3.x
- • 5-10s proof generation
- • Fails on low-end devices
- • No WASM in Hermes engine
- • Blocks the JS thread
✅ Native (Arkworks) — v4.x
- • <1s proof generation
- • Works on all devices
- • Pure Rust, no WASM needed
- • Runs on background thread
Verification Flow
- Device → generates proof + Poseidon hash
- Lambda → accepts proof, passes to blockchain with calldata
- StarkNet → stores proof + hash on-chain (on-chain verification planned)
- Verifier → anyone can verify the proof against the on-chain data
Field Encoding
The circuit operates over the BN128 prime field. Special handling is needed for:
- • Negative longitude: Encoded as
p + valuewherepis the BN128 prime. The Poseidon hash library handles this internally, but the circuit inputs must be field-encoded. - • GPS precision: Coordinates are multiplied by 1000 and rounded to integers (e.g., 18.459° → 18459).
- • Wallet address: Truncated to 128 bits to fit the field element.
Public Signals
The circuit produces 2 public signals: [hashOut, expectedHash]. Since the circuit enforces hashOut === expectedHash, both values are identical — the Poseidon hash. This hash is also stored on-chain as the content identifier.