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. 1. Compute Poseidon Hash

    Using poseidon-lite (BN128 field), hash the wallet address, GPS coordinates (×1000), and Unix timestamp.

  2. 2. Generate Witness

    The transpiled Rust witness calculator (rust_witness) computes all intermediate circuit signals from the inputs.

  3. 3. Generate Groth16 Proof

    Arkworks (pure Rust) generates the proof using the witness and the .zkey proving key. This runs natively on ARM64 in <1 second.

  4. 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

  1. Device → generates proof + Poseidon hash
  2. Lambda → accepts proof, passes to blockchain with calldata
  3. StarkNet → stores proof + hash on-chain (on-chain verification planned)
  4. 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 + value where p is 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.

Next Steps