Proof that a real, unique human stands behind a piece of content — anonymous, sybil-resistant, and verified on Stellar with zero-knowledge.
🔗 Live demo: https://web-seven-eta-40.vercel.app · 🎬 Narrated walkthrough: demo/humanvouch-demo-narrated.mp4
Built for Stellar Hacks: Real-World ZK.
You cannot prove, from content alone, that a human and not an AI wrote it. The bytes are identical, and AI detectors are unreliable and trivially evaded (Google's Dipper paraphraser dropped one detector from 70% → 4.6% accuracy). HumanVouch does not attempt this.
Instead it proves something real and achievable: a verified, unique human privately vouches for a piece of content — putting their (anonymous) personhood behind it, in a way bots and duplicate accounts cannot fake. It's attribution, not authorship; accountability, not AI-detection.
- Vouch — a verified human stakes a private, anonymous vouch on a piece of content. A Groth16 zero-knowledge proof is generated in the browser (the identity secret never leaves the device) and the attestation is recorded on Stellar.
- Verify — anyone can check how many unique verified humans stand behind a piece of content.
- Shareable credential — vouching returns a
/?v=<contentHashField>link (the SHA-256 content hash reduced into the BLS12-381 field, recomputable by anyone holding the content) + a paste-able badge you drop on X, Medium, anywhere; opening it resolves the attestation on-chain. - For agents (x402) — an HTTP endpoint where an AI agent pays a micropayment over x402 (HTTP 402) and gets a verifiable, on-chain answer to "does a real human stand behind this content?" — HumanVouch as infrastructure for the agent era.
- Proof of personhood — a real Cloudflare Turnstile human check gates getting an identity (anti-bot); production upgrades to World ID for true uniqueness.
- Sybil-resistance — one human, one vouch per content, enforced on-chain by a nullifier.
The circuit proves, without revealing the human:
commitment = Poseidon(identitySecret)
MerkleInclusion(commitment, path) == root // membership in a personhood registry
nullifierHash = Poseidon(identitySecret, contentHash) // one human, one vouch per content
Public signals: [root, nullifierHash, contentHash]. The on-chain AttestContract verifies the
Groth16 proof, requires the root be a valid registry root (Groth16 validity alone ≠ membership),
rejects nullifier replay, and records the vouch.
On Stellar this means BLS12-381 (Soroban's native pairing). The circuit is compiled with
circom --prime bls12381 and hashes with Poseidon over BLS12-381
— a Poseidon-Merkle membership proof verified on Stellar, which (as far as we found) had not been
demonstrated before.
- AttestContract:
CDPDQJB7HX5XVOUHEDQKV6T7KJXNGVTVH3VDCXMFEE7GPIIINOVO5YZT(set_vk,set_root/is_valid_root,attest,get_vouches). - Built on the official
soroban-examples/groth16_verifier(BLS12-381) + CircomStellar's proof encoder.
packages/
zk/ Circom membership + content-nullifier circuit (Poseidon-bls12381) + off-chain witness
contracts/ Soroban: groth16 verifier + AttestContract (registry + attest + replay guard)
web/ Nuxt app: built-in wallet, browser proving, verify, shareable links,
x402 agent endpoint (/api/v1/attestation), Turnstile personhood
circuits/ Plan-01 circuit lib (BN254, TDD) — superseded on-chain by the bls12381 build
docs/ design spec, BLS12-381 migration addendum, on-chain status
Stack: Circom 2 + snarkjs (Groth16) · Soroban (Rust) on Stellar testnet · Nuxt 3 / Vue + Tailwind ·
@stellar/stellar-sdk · Cloudflare Turnstile · deployed on Vercel.
# the full live app
cd packages/web && yarn install && yarn dev # http://127.0.0.1:58273
# rebuild the ZK circuit + proof (needs the circom 2 compiler on PATH)
cd packages/zk/circuits && mkdir -p build
circom attestation255.circom --r1cs --wasm --prime bls12381 -o build
# trusted setup + proof: see docs/ONCHAIN-STATUS.mdThe live demo is deployed to Vercel with packages/web/scripts/deploy.sh, the only reproducible
deployment path:
cd packages/web && ./scripts/deploy.shWhat the script does:
- Builds with the Vercel preset (
VERCEL=1 yarn build), producing.vercel/output/. - Copies the complete
@stellar/stellar-sdkpackage from the rootnode_modulesinto every serverless function under.vercel/output/functions/*.func/node_modules/@stellar/stellar-sdk. - Uploads the prebuilt output to production with
npx --yes vercel deploy --prebuilt --prod --yes.
Do not remove step 2. stellar-sdk 16's ESM build ships nested vendored dependencies that Vercel's
file tracer misses. Without the copy, the server routes (for example the x402 endpoint
/api/v1/attestation) return a 500 on a missing js-xdr file.
Requirements: the Vercel CLI must be authenticated (npx vercel login, or a VERCEL_TOKEN in the
environment) and the directory linked to the Vercel project (npx vercel link).
- Not AI detection. Attribution to an accountable unique human, not a claim about who typed the bytes.
- Demo trusted setup is non-production (deterministic entropy → forgeable). Production needs a real MPC.
- Personhood is Cloudflare Turnstile (anti-bot) in the demo; production = World ID (unique-personhood).
- The demo personhood registry is operated by us (real Poseidon-bls12381 commitments + Merkle root published on-chain) — real cryptography, standing in for World ID / passport.
Real zero-knowledge, real on-chain, for the humans who create — and the agents that increasingly read what we write.