Skip to main content

Zero-Knowledge Proofs for DeFi Reputation: How We Built It

· 4 min read
CrowdProof Team
Protocol Engineering

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:

SystemProof SizeVerification GasTrusted SetupProver Time
Groth16192 bytes~250K gasRequired~2s
PLONK~500 bytes~300K gasUniversal~5s
STARKs~50 KB~1M gasNone~10s

We chose Groth16 because:

  1. Smallest proof size — 192 bytes is practical for on-chain storage
  2. Cheapest verification — 250K gas is affordable on L2s ($0.01–0.10)
  3. 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 in ReputationOracle)
  • threshold — The minimum score being proven
  • category — Which score category (DeFi Lending, Governance, etc.)
  • timestamp_bound — Ensures the score is recent

Private Inputs (known only to prover)

  • score — The actual score value
  • merkle_proof — Path from the score leaf to the root
  • wallet_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):

OperationP50P99
Proof generation1.8s3.2s
API verification8ms15ms
On-chain verification250K gas250K 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.