ZK Drop unlocks regional airdrops and token presales using zero-knowledge location proofs — without revealing your exact coordinates.
ZK Drop allows users to prove that they are physically located within specific regions (like Argentina, Europe, or Asia) without sharing their coordinates.
This enables projects to offer:
- ✅ Regional airdrops (e.g., South America only)
- 🎯 Fair token presales by location
- 🔒 Private on-chain eligibility using ZK proofs
- User generates a ZK proof on their mobile using GNSS data + region hash.
- A public QR code provides the circuit’s public inputs (
region_hash,challenge,nullifier). - The proof is verified on-chain by a smart contract before allowing access to airdrops or presales.
The circuit checks that:
- The user’s private GPS coordinates (
lat,lon) are within a valid bounding box. - The bounding box (
min_lat,max_lat,min_lon,max_lon) matches the claimedregion_hash. - A challenge is included to generate a nullifier for session uniqueness or anti-replay protection.
use std::hash::poseidon;
fn main(
lat: Field,
lon: Field,
min_lat: pub Field,
max_lat: pub Field,
min_lon: pub Field,
max_lon: pub Field,
region_hash: pub Field,
challenge: pub Field,
nullifier: pub Field,
) {
// Cast to integers for comparison
let lat_i32 = lat as i32;
let lon_i32 = lon as i32;
let min_lat_i32 = min_lat as i32;
let max_lat_i32 = max_lat as i32;
let min_lon_i32 = min_lon as i32;
let max_lon_i32 = max_lon as i32;
// Step 1: Verify location is within the region
assert(lat_i32 >= min_lat_i32);
assert(lat_i32 <= max_lat_i32);
assert(lon_i32 >= min_lon_i32);
assert(lon_i32 <= max_lon_i32);
// Step 2: Verify region_hash is valid
let computed_hash = poseidon::bn254::hash_4([min_lat, max_lat, min_lon, max_lon]);
assert(computed_hash == region_hash);
// Step 3: Prevent proof reuse with a nullifier
let computed_nullifier = poseidon::bn254::hash_2([region_hash, challenge]);
assert(computed_nullifier == nullifier);
}Once the user generates the zero-knowledge proof on their mobile device (after scanning the QR code), they send the result back to the frontend. The frontend then submits the proof to a smart contract which verifies the user’s region without revealing coordinates.
- Frontend shows QR with public inputs (
region_hash, bounding box, challenge). - User scans it with a mobile app and generates a ZK proof locally using GNSS data.
- Mobile app sends
{ proof, publicInputs }to the frontend (e.g. via QR, NFC, deep link, or server). - Frontend calls a Solidity smart contract that uses
verifier.solto verify the proof. - If valid, the contract lets the user:
- Claim an airdrop
- Purchase tokens during a regional presale
- Access other regional features
A typical interaction looks like this:
contract ZKDrop {
IVerifier public verifier;
mapping(bytes32 => bool) public usedNullifiers;
constructor(address _verifier) {
verifier = IVerifier(_verifier);
}
function claimAirdrop(bytes calldata proof, bytes32[] calldata publicInputs) external {
require(verifier.verify(proof, publicInputs), "Invalid ZK proof");
bytes32 nullifier = publicInputs[6];
require(!usedNullifiers[nullifier], "Proof already used");
usedNullifiers[nullifier] = true;
}
}To strengthen resistance against Sybil attacks (users creating multiple wallets to farm airdrops), we are exploring an integration with zk-access — another project from the Noir Hackathon.
zk-access allows users to:
- Generate modular ZK credentials based on past proofs or identity claims.
- Selectively disclose facts like "I haven’t claimed this drop yet" or "I’m a unique person" — without revealing wallet addresses or identities.
Built with ❤️ for the Noir Hackathon.
We believe privacy and fairness. ZK Drop is our step toward a more equitable and anonymous future. 🚀🌍