Zero-Knowledge Proofs for DeFi Reputation: How We Built It
When we set out to build a reputation layer for DeFi, we hit a fundamental tension: protocols need to verify creditworthiness, but users shouldn't have to broadcast their exact scores to the world. Zero-knowledge proofs solve this elegantly.
This post walks through how CrowdProof uses Groth16 SNARKs to enable private reputation verification — the tradeoffs we considered, the circuit design, and the performance characteristics.
The Privacy Problem
Consider a typical scenario: a lending protocol wants to offer under-collateralized loans to users with strong repayment history. Without ZK proofs, the flow looks like this:
User → "My score is 782" → Lending Protocol
This leaks the exact score to the protocol, and potentially to anyone monitoring the transaction. Worse, if the score is checked on-chain, it becomes permanently public.
Why Groth16?
We evaluated three ZK proof systems:
| System | Proof Size | Verification Gas | Trusted Setup | Prover Time |
|---|---|---|---|---|
| Groth16 | 192 bytes | ~250K gas | Required | ~2s |
| PLONK | ~500 bytes | ~300K gas | Universal | ~5s |
| STARKs | ~50 KB | ~1M gas | None | ~10s |
We chose Groth16 because:
- Smallest proof size — 192 bytes is practical for on-chain storage
- Cheapest verification — 250K gas is affordable on L2s ($0.01–0.10)
- Fastest prover — 2-second proof generation is acceptable for API-driven flows
The tradeoff is the trusted setup ceremony, which we mitigate with a multi-party computation (100+ participants). As long as one participant is honest, the system is secure.
Circuit Design
Our circuit proves the statement: "I know a score s for category c and wallet w, committed in Merkle root R, such that s ≥ threshold."
Public Inputs (known to verifier)
R— Merkle root of the latest score batch (stored on-chain inReputationOracle)threshold— The minimum score being provencategory— Which score category (DeFi Lending, Governance, etc.)timestamp_bound— Ensures the score is recent
Private Inputs (known only to prover)
score— The actual score valuemerkle_proof— Path from the score leaf to the rootwallet_address— The user's wallet
Constraints
// 1. Score is in valid range
assert(score >= 0 && score <= 1000);
// 2. Score meets the claimed threshold
assert(score >= threshold);
// 3. Score is included in the committed Merkle tree
leaf = hash(wallet_address, category, score, timestamp);
assert(verify_merkle_proof(leaf, merkle_proof, R));
// 4. Score is fresh enough
assert(timestamp >= timestamp_bound);
The circuit has approximately 15,000 constraints — small enough for fast proving, large enough to enforce all security properties.
The Merkle Commitment
Every time scores are recalculated, the backend commits a Merkle root to the ReputationOracle contract:
function commitScores(bytes32 merkleRoot, uint256 batchId) external onlyRole(ORACLE_ROLE) {
latestRoot = merkleRoot;
emit ScoresCommitted(merkleRoot, batchId, block.timestamp);
}
This creates an on-chain anchor that ZK proofs reference. The verifier checks the proof against this root, confirming the score was genuinely computed by CrowdProof — not fabricated by the user.
API Integration
Generating a proof is a single API call:
curl -X POST https://crowdproof-api.azurewebsites.net/api/v1/reputation/prove \
-H "Content-Type: application/json" \
-d '{
"subject": "0x1234...",
"proofType": "ScoreAboveThreshold",
"category": "DEFI_LENDING",
"threshold": 700
}'
Response:
{
"proof": "0x1a2b3c4d...",
"proofType": "ScoreAboveThreshold"
}
The proof can then be verified either via the API or on-chain:
bool valid = reputationOracle.verifyProof(proof, 700, ScoreCategory.DEFI_LENDING);
Performance Characteristics
We benchmarked on a standard cloud VM (4 vCPU, 16 GB RAM):
| Operation | P50 | P99 |
|---|---|---|
| Proof generation | 1.8s | 3.2s |
| API verification | 8ms | 15ms |
| On-chain verification | 250K gas | 250K gas |
The proof generation time is dominated by the elliptic curve operations in the Groth16 prover. This is fast enough for an API-driven flow but would be noticeable in a synchronous UX — which is why we're also working on client-side WASM proof generation for future releases.
What's Next
- Client-side proving — WASM build of the prover for browser-based proof generation
- Recursive proofs — Prove multiple categories in a single proof
- Cross-chain verification — Verify proofs on any EVM chain regardless of where the root was committed
ZK proofs are the key to making reputation composable without sacrificing privacy. If you're building a protocol that needs trustworthy identity signals, check out our ZK Proofs guide or dive into the API reference.