zkbounty
The protocol

How a real-world moment becomes a cryptographic fact

zkbounty replaces human judgment with a deterministic pipeline. Here is exactly what happens between a hunter pressing record and an on-chain payout.

The pipeline

From a real-world moment to an on-chain payout

Five stages turn a physical action into a cryptographic fact. The hunter never exposes who they are - only that the criteria were met.

01

Capture

The hunter records the action through the SDK. Raw camera sensor data, IMU, and barometric altitude are read directly from the device - never a gallery upload.

sdk.capture()
02

Attest

The secure enclave signs the raw metadata with a hardware-bound key. GPS is locked across multiple constellations and the current Solana slot hash is folded in.

ed25519_enclave
03

Strip

Every personal identifier - device IDs, faces, exact identity - is removed before anything leaves the device. Only the facts the bounty needs survive.

redact(pii)
04

Prove

A succinct zero-knowledge proof is generated: it asserts the attestations matched the bounty parameters - coordinate, slot window, device class - revealing nothing else.

physical_action_v2
05

Settle

The on-chain verifier program checks the proof against the bounty in a single transaction. Match → escrow releases. No match → funds stay locked. No human in the loop.

verify_and_pay()
Attestation layers

Four signatures, four classes of fraud eliminated

Each attestation closes a specific attack. A proof must carry every layer the bounty requires - partial proofs never settle.

Secure Enclave

A hardware-bound key inside the device's secure enclave signs the raw capture metadata. The private key is generated in, and never leaves, the chip.

Defeats

Emulators, rooted devices, and replayed captures - none can produce a valid enclave signature.

GPS Lock

A multi-constellation GNSS fix is taken at capture time and reduced to a coordinate, locked against the bounty's geofence with a haversine distance check.

Defeats

Mock-location apps and after-the-fact tagging - the lock is part of the signed payload.

Sensor Fusion

Camera and IMU signatures (rolling-shutter timing, lens noise, motion) are folded into the proof as a provenance fingerprint of genuine capture.

Defeats

Screen recordings, re-encodes, stock footage, and AI-generated media that lack a real sensor fingerprint.

Slot Binding

The most recent Solana slot hash is mixed into the attestation, binding the capture to a point in time that did not exist before it happened.

Defeats

Backdating and pre-recording - a proof cannot reference a slot hash from the future or reuse an old one outside the window.

One verifier program. Every proof. No exceptions.

A bounty's criteria compile to an on-chain Solana program. When a proof arrives, the program checks it against the bounty in a single transaction - geofence, slot window, device class, attestation set - and releases escrow only on a complete match.

  • Immutable once deployed - issuers can't move the goalposts
  • Deterministic - the same proof always yields the same result
  • Public - anyone can audit the exact settlement logic
verify_proof()release_escrow()Groth16 · BN254
verifier.rs
pub fn verify_proof(ctx: Context<Verify>, p: Proof) -> Result<()> {
    let b = &ctx.accounts.bounty;

    // 1. zk proof attests the public inputs honestly
    require!(groth16_verify(&b.vk, &p.proof, &p.inputs), Err::BadProof);

    // 2. coordinate inside the geofence
    require!(haversine(p.inputs.coord, b.center) <= b.radius_m, Err::Geo);

    // 3. capture slot inside the window
    require!(b.window.contains(p.inputs.slot), Err::SlotWindow);

    // 4. all required attestations present
    require!(p.inputs.attest_mask & b.required == b.required, Err::Attest);

    // match -> release escrow to the hunter
    release_escrow(ctx, b.reward)?;
    emit!(ProofVerified { slot: p.inputs.slot, hunter: p.inputs.payee });
    Ok(())
}

The chain learns the fact - never the person

Personal identifiers are stripped on the device, before any proof is generated. The zero-knowledge proof asserts only that the bounty criteria were satisfied. Faces, device IDs, exact identity, and raw media never touch the network.

What gets published on-chain is a boolean and a payout address - nothing that could deanonymize the hunter or expose bystanders.

strip.ts
// runs on-device, pre-proof
const redacted = sdk.redact(capture, {
  faces: "blur+drop",        // bystander privacy
  deviceId: "drop",          // no device fingerprint
  exif: "drop",              // no embedded identity
  keep: ["coord", "slot", "sensorSig"], // only what the bounty needs
});

const proof = await sdk.prove(redacted, bounty.criteria);
// proof.publicInputs -> { coordInGeofence: true, slotInWindow: true, ... }
// nothing else leaves the device