Skip to content

Isaaco3349/zkpass

Repository files navigation

ZKPass

Private KYC compliance on Stellar — prove you qualify without revealing who you are.

Built for Stellar Hacks: Real-World ZK · June 2026


The Problem

Right now, if you want to send money across borders using a stablecoin on Stellar, one of two things happens:

  1. No compliance check — the transfer goes through, but regulated institutions won't touch the rail.
  2. Full KYC — you hand over your passport, address, and financial history to a centralized database that can be breached, sold, or used against you.

Neither is acceptable. And for users in markets like Nigeria — where entire countries get blanket-blocked on global payment rails despite millions of individually compliant citizens — the status quo is both unfair and technically unnecessary.

There is a third option. Zero-knowledge proofs let you prove you meet the requirements without revealing anything about yourself. ZKPass builds that for Stellar.


What ZKPass Does

ZKPass lets a user prove — cryptographically, on Stellar — that:

  • ✅ They are at least 18 years old
  • ✅ Their jurisdiction is not on a sanctions list
  • ✅ Their KYC score meets a minimum threshold (set by the dApp)

The proof is generated entirely off-chain on the user's device. The Stellar smart contract only ever sees: valid or not valid. No name. No age. No country. No score.

A downstream stablecoin contract can gate any transfer behind ZKPass.is_verified(user) — getting real compliance without real surveillance.


Live Deployment

Network Stellar Testnet
Contract ID CBWVJJ2CZSGRG43TSISDQUSD3LCDXUDPDF4TM6SCZOJIXGPYHU5SUAIO
Initialize tx stellar.expert/explorer/testnet/tx/2e8973ac...
verify_kyc tx stellar.expert/explorer/testnet/tx/33efd191...
Event emitted kyc/verified with user address + Poseidon commitment
Return value true

The contract is live, initialized, and has successfully processed a verify_kyc call end-to-end on testnet.


How It Works

User's Device                          Stellar Testnet
─────────────────────────────          ──────────────────────────

 [KYC Oracle]
  Returns: age, country,
  kycScore (private)
       │
       ▼
 [Circom Circuit]
  kyc_proof.circom
  Proves: age≥18, country OK,
  score≥70, commitment valid
       │
       │  Groth16 Proof
       │  + Public signals
       ▼
 [Prover Script] ────────────────────► [ZKPass Verifier Contract]
                       submit proof        verify_kyc(user, proof, signals)
                                               │
                                               ▼
                                       emit kyc/verified event
                                               │
                                               ▼
                                       [Downstream dApp]
                                       check is_verified(user)
                                       → allow/block transfer

ZK is load-bearing here

The circuit (circuits/kyc_proof.circom) enforces three real constraints:

  • A GreaterEqThan comparator for age vs minAge
  • Inequality checks against a sanctions country code list
  • A GreaterEqThan comparator for kycScore vs minKycScore
  • A Poseidon commitment that binds the proof to the exact private inputs — preventing a user from proving with fabricated data

The Stellar contract accepts the Groth16 proof and public signals, validates the circuit output, records the verification on-chain, and emits a kyc/verified event that downstream contracts can listen for.


Tech Stack

Layer Technology
ZK Circuit Circom 2.0 + circomlib
Proof system Groth16 (snarkjs)
On-chain contract Rust + soroban-sdk 22
Deployment Stellar Testnet (stellar-cli 27.0.0)
Proof generation Node.js + snarkjs

Project Structure

zkpass/
├── circuits/
│   ├── kyc_proof.circom        # ZK circuit (age, jurisdiction, score checks)
│   ├── kyc_proof.r1cs          # Compiled constraint system
│   ├── kyc_proof_final.zkey    # Proving key (generated by setup_ceremony.sh)
│   ├── verification_key.json   # Verification key
│   └── kyc_proof_js/           # WASM witness generator (compiled by circom)
│
├── contracts/
│   └── zkpass-verifier/
│       ├── src/lib.rs          # Soroban Groth16 verifier contract
│       └── Cargo.toml          # soroban-sdk 22, no external crypto deps
│
├── prover/
│   ├── mock_kyc_oracle.js      # Simulates KYC provider response
│   ├── generate_proof.js       # Generates Groth16 proof off-chain
│   └── package.json
│
├── scripts/
│   ├── setup_ceremony.sh       # Downloads ptau, compiles circuit, trusted setup
│   └── deploy_contract.sh      # Builds + deploys Soroban contract to testnet
│
└── README.md

Getting Started

Prerequisites

  • Node.js 18+
  • Rust + Cargo (rustup from https://rustup.rs)
  • rustup target add wasm32-unknown-unknown
  • stellar-cli 27.0.0 (see install below)

Install stellar-cli

curl -L https://github.com/stellar/stellar-cli/releases/download/v27.0.0/stellar-cli-27.0.0-x86_64-unknown-linux-gnu.tar.gz -o stellar.tar.gz
tar -xzf stellar.tar.gz
sudo mv stellar /usr/local/bin/
stellar --version

1. Clone and install

git clone https://github.com/Isaaco3349/zkpass.git
cd zkpass

2. Run the trusted setup

This downloads the Powers of Tau file, compiles the Circom circuit, and generates proving/verification keys.

bash scripts/setup_ceremony.sh

3. Generate a proof

cd prover
npm install
node generate_proof.js

This runs the mock KYC oracle, feeds inputs into the circuit, generates a Groth16 proof, and verifies it locally.

4. Create a testnet identity and fund it

stellar keys generate --global deployer --network testnet --fund
stellar keys address deployer

5. Build and deploy the contract

cd contracts/zkpass-verifier
cargo build --target wasm32-unknown-unknown --release
stellar contract optimize --wasm target/wasm32-unknown-unknown/release/zkpass_verifier.wasm
cd ../..
stellar contract deploy \
  --wasm contracts/zkpass-verifier/target/wasm32-unknown-unknown/release/zkpass_verifier.optimized.wasm \
  --source deployer \
  --network testnet

6. Initialize the contract

python3 -c "
import json
vk = {
    'alpha_g1': '00'*64,
    'beta_g2': '00'*64,
    'delta_g2': '00'*64,
    'gamma_g2': '00'*64,
    'ic': ['00'*64]*4
}
open('/tmp/vk.json','w').write(json.dumps(vk))
print('done')
"

stellar contract invoke \
  --id <YOUR_CONTRACT_ID> \
  --source deployer \
  --network testnet \
  -- initialize \
  --admin <YOUR_ADDRESS> \
  --vk-file-path /tmp/vk.json

7. Call verify_kyc

python3 -c "
import json
proof = {'pi_a':'00'*64,'pi_b':'00'*64,'pi_c':'00'*64}
signals = {'valid':1,'min_age':18,'min_kyc_score':70,'commitment':'00'*32}
open('/tmp/proof.json','w').write(json.dumps(proof))
open('/tmp/signals.json','w').write(json.dumps(signals))
print('done')
"

stellar contract invoke \
  --id <YOUR_CONTRACT_ID> \
  --source deployer \
  --network testnet \
  -- verify_kyc \
  --user <YOUR_ADDRESS> \
  --proof-file-path /tmp/proof.json \
  --signals-file-path /tmp/signals.json

Expected output: true + kyc/verified event emitted on-chain.


Honest Status

This was built during a hackathon. Here's what's real and what's mocked:

Component Status Notes
Circom circuit ✅ Real Three actual ZK constraints + Poseidon commitment
Groth16 local verification ✅ Real snarkjs verifies proof before submission
Soroban contract ✅ Deployed Live on testnet, initialized, verify_kyc returns true on-chain
kyc/verified event ✅ Real Emitted on-chain, visible on stellar.expert
is_verified query ✅ Real Persistent on-chain record after successful call
BN254 pairing check 🔄 MVP Contract validates circuit output signal; full cryptographic pairing verification is the production upgrade path using Stellar Protocol 26 BN254 host functions
KYC data source 🔄 Mock Real provider integration (e.g. Smile Identity) is the production next step
Sanctions list 🔄 MVP 4 hardcoded country codes; production uses a Merkle non-membership proof

The ZK circuit is real. The proof system is real. The Stellar contract is deployed and responding. The mock data is clearly labelled everywhere.


Why This Matters (The Nigeria Context)

This project was built from Lagos, Nigeria — a country currently on the FATF grey list. The effect on real people: Nigerian users are blocked from global payment rails not because they failed compliance, but because their country code did.

ZKPass proposes a different model: compliance at the individual level, not the country level. A Nigerian user who genuinely passes KYC can generate a proof that says exactly that — without revealing their nationality, their score, or anything else — and get access to financial infrastructure that currently excludes them by default.

Stellar's mission is financial access for everyone. ZKPass tries to mean that literally.


Demo

📹 Watch the demo video (link added at submission)

Contract on Stellar testnet: CBWVJJ2CZSGRG43TSISDQUSD3LCDXUDPDF4TM6SCZOJIXGPYHU5SUAIO

verify_kyc transaction: 33efd191...


Resources Used


License

MIT — see LICENSE

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors