Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

How to Implement Privacy-Preserving KYC for On-Chain Pools

A technical guide for developers to architect systems that verify participant identity for regulated DeFi pools using zero-knowledge proofs and decentralized identity, preserving user privacy.
Chainscore © 2026
introduction
GUIDE

How to Implement Privacy-Preserving KYC for On-Chain Pools

This guide explains how to integrate privacy-preserving KYC mechanisms into on-chain liquidity pools, balancing regulatory compliance with user anonymity using zero-knowledge proofs and selective disclosure.

Privacy-preserving KYC (Know Your Customer) is a critical component for permissioned DeFi pools that must comply with regulations like FATF Travel Rule or MiCA, without forcing users to fully dox their on-chain activity. Traditional KYC leaks sensitive personal data to pool operators and creates centralized honeypots. The goal is to verify user eligibility—such as residency or accredited investor status—while keeping their identity and wallet history private. This is achieved through cryptographic primitives like zero-knowledge proofs (ZKPs) and verifiable credentials, allowing users to prove statements about their identity without revealing the underlying data.

The core architecture involves three parties: the user, a trusted identity verifier (like an on-chain KYC provider), and the smart contract pool. First, a user undergoes KYC with a verifier off-chain (e.g., Fractal ID or Polygon ID). Upon successful verification, the issuer provides a verifiable credential (VC), a cryptographically signed attestation. The user then generates a zero-knowledge proof—using circuits from libraries like circom or snarkjs—that demonstrates they hold a valid VC meeting the pool's specific criteria, such as countryCode != OFAC-sanctioned or accreditedInvestor == true.

The on-chain pool contract, deployed on networks like Ethereum or Polygon, contains the verification key corresponding to the ZKP circuit. A user submits their proof along with a public nullifier to prevent double-spending the credential. The contract verifies the proof in a gas-efficient manner, often using precompiles like EIP-196 or EIP-197 for pairing operations. If valid, the user's address is approved for deposit. Popular implementation frameworks include Semaphore for anonymous signaling or zkKYC modules from identity protocols like iden3.

Developers must carefully design the credential schema and circuit logic. For example, a circuit for a US-only pool might prove a credential's issuerSignature is valid and that the country field equals "US", without revealing the user's name or date of birth. The circuit code, written in a domain-specific language, is compiled and trusted setup is performed. The resulting verification key is hardcoded into the pool factory. It's crucial to use audited libraries and ensure the verifier's attestation root is securely stored and updatable by governance.

Key challenges include managing revocation of credentials and preventing Sybil attacks. Revocation can be handled via accumulators or sparse Merkle trees, where the verifier updates a revocation list and users must prove non-membership. To link deposits to a single identity without exposing it, systems use deterministic but unlinkable nullifiers. For production, consider gas costs; verification on Ethereum mainnet can exceed 200k gas, making Layer 2 solutions like zkRollups ideal. Always conduct a third-party audit of both the ZKP circuits and the integrating smart contracts before launch.

prerequisites
PREREQUISITES AND SYSTEM ARCHITECTURE

How to Implement Privacy-Preserving KYC for On-Chain Pools

This guide outlines the technical foundation required to build a system that verifies user credentials without exposing sensitive identity data on-chain.

A privacy-preserving KYC system for on-chain pools requires a fundamental architectural shift from traditional models. Instead of storing verified user data on a public ledger, the system relies on zero-knowledge proofs (ZKPs). A user proves to a verifier contract that they possess a valid credential from a trusted issuer—such as being over 18 or accredited—without revealing the underlying data. The core components are: a trusted issuer (e.g., a KYC provider), a user wallet capable of generating ZKPs, and a verifier smart contract on the destination chain. This architecture ensures compliance is enforced at the protocol level while preserving user privacy.

Before development, you must select a ZK proving system. zk-SNARKs (like those used by Tornado Cash) offer succinct proofs and fast verification but require a trusted setup. zk-STARKs provide quantum resistance and no trusted setup but generate larger proofs. For most EVM-based pools, Circom with the Groth16 prover is a common choice due to its mature tooling and efficient on-chain verification. You will also need an identity attestation standard. The Verifiable Credentials (VC) data model, as defined by the W3C, paired with EIP-712 for structured signing, provides a robust framework for issuers to create tamper-proof credentials.

The issuer's role is to authenticate users off-chain and issue a signed Verifiable Credential. This credential contains the user's claims (e.g., "isAccreditedInvestor": true) and is stored privately in the user's wallet, such as a browser extension or mobile app. The user never sends this credential on-chain. Instead, when interacting with a gated pool, their wallet uses a ZK circuit to generate a proof. This circuit takes the credential as a private input and outputs a public signal stating, for instance, "credentialIsValid AND userIsAccredited". Only this proof and the public signal are sent to the verifier contract.

The on-chain verifier is a lightweight smart contract containing the verification key for the ZK circuit. Its primary function is to call a verifyProof() function, which checks the cryptographic proof against the public signals. If valid, the contract can grant access—for example, by minting a non-transferable access token (like an ERC-721) to the user's address. This token serves as a privacy-preserving pass, proving the user is verified without linking multiple transactions to their identity. The pool's core logic then simply checks for ownership of this access token before allowing deposits or swaps.

Key prerequisites for developers include familiarity with Circom or Halo2 for circuit writing, a framework like SnarkJS for proof generation, and a smart contract development environment like Hardhat or Foundry. You must also design the off-chain issuer backend, which can leverage existing KYC providers' APIs. Security audits for both the ZK circuits and the verifier contract are non-negotiable, as subtle bugs can compromise the entire system's privacy or correctness. Start by forking proven templates, such as those from the Semaphore or ZK-KYC repositories, to accelerate development.

In summary, the architecture decentralizes trust: users trust the issuer to validate them correctly, and the pool trusts the cryptographic soundness of the ZK verification. This model enables compliant DeFi pools that do not leak user data, moving beyond the current paradigm of centralized whitelists stored on-chain. The next steps involve writing the specific Circom circuit for your compliance logic and deploying the verifier contract to your target chain.

key-concepts-text
TUTORIAL

How to Implement Privacy-Preserving KYC for On-Chain Pools

A technical guide to using zero-knowledge proofs for decentralized identity verification, enabling regulatory compliance without exposing user data.

Privacy-preserving KYC (Know Your Customer) uses zero-knowledge proofs (ZKPs) to verify user credentials without revealing the underlying data. For on-chain pools, this allows protocols to comply with regulations—like proving a user is not from a sanctioned jurisdiction or is over 18—while maintaining user privacy. Instead of submitting a passport hash, a user generates a zk-SNARK or zk-STARK proof that cryptographically attests their credentials meet the pool's policy, submitting only the proof and a public nullifier to prevent double-spending. This shifts the trust model from a centralized validator to the cryptographic soundness of the ZKP circuit and the integrity of the identity attestor.

The implementation stack typically involves three core components: an identity verifier, a ZK circuit, and an on-chain verifier contract. The identity verifier (e.g., a government agency or accredited provider like Civic or Veramo) issues a Verifiable Credential (VC) to the user's wallet. The user then uses a client-side prover (e.g., with Circom or Halo2) to generate a proof against a circuit that encodes the pool's rules. A sample rule in Circom might check that a credential's countryCode is not in a banned list and that birthDate is more than 18 years ago, outputting a true/false signal without leaking the actual values.

Deploying this requires writing and compiling the ZK circuit. For example, using the Circom language and snarkjs, you define templates for your checks. After compiling the circuit (.wasm) and generating a trusted setup (.zkey), you create a Solidity verifier contract. The on-chain pool contract then integrates this verifier, requiring users to submit a valid proof and a unique nullifier (often hash(secret, poolId)) with each interaction. The contract checks the proof via the verifier and records the nullifier to prevent reuse. This setup ensures only verified, unique users can access the pool.

Key challenges include managing trusted setup ceremonies for zk-SNARKs, ensuring the identity issuer is reputable, and handling credential revocation. For production, consider using Semaphore for anonymous signaling or zkEmail for proof-of-email-based verification to simplify development. Always audit both the ZK circuits and the smart contract integration, as bugs can either break privacy or allow forged proofs. This approach future-proofs DeFi pools for regulatory evolution while upholding the core Web3 tenets of user sovereignty and data minimization.

ARCHITECTURE OVERVIEW

Comparison of Privacy-Preserving KYC Architectures

A technical comparison of three primary architectures for implementing privacy-preserving KYC, focusing on their trade-offs for on-chain liquidity pools.

Feature / MetricZero-Knowledge Proofs (ZKPs)Trusted Execution Environments (TEEs)Federated Attestation

Privacy Model

Cryptographic (ZK-SNARKs/STARKs)

Hardware-based isolation

Off-chain credential issuance

On-Chain Verification Cost

High (10k-100k+ gas)

Low (< 50k gas)

Low (< 30k gas)

Trust Assumption

Trustless (cryptographic soundness)

Trusted hardware vendor

Trusted federation of issuers

Proof Generation Latency

2-10 seconds

< 1 second

Off-chain (pre-generated)

Sybil Resistance

Strong (unique identity binding)

Moderate (hardware binding)

Strong (issuer verification)

Revocation Mechanism

Complex (nullifier sets)

Simple (attestation update)

Simple (issuer blacklist)

Developer Complexity

High (circuit design)

Medium (enclave programming)

Low (API integration)

Example Protocols

Semaphore, zkKYC

Oasis Labs, Secret Network

Verite, Polygon ID

step-1-verifier-contract
ARCHITECTURE

Step 1: Design the On-Chain Verifier Contract

The core of a privacy-preserving KYC system is a smart contract that verifies zero-knowledge proofs without exposing user data. This step defines its critical functions and security model.

The on-chain verifier is a smart contract that performs one primary task: validating a zero-knowledge proof (ZKP). When a user submits a proof, the contract runs a verification algorithm against it and a public verification key. The proof cryptographically asserts that the user's off-chain data (e.g., passport details, residency) satisfies the KYC rules, but the contract only learns the binary result: true or false. This separation of proof verification from data disclosure is the foundation of privacy. For Ethereum, this is typically implemented using the Groth16 or PLONK proving systems via libraries like snarkjs and circom.

The contract must be designed with clear, permissioned interfaces. A core function like verifyProof(bytes calldata _proof, uint256[] calldata _publicSignals) will be called by users or a relayer. The _publicSignals are the non-sensitive data that both the prover and verifier agree upon, such as a nullifier hash to prevent double-registration and a commitment to the user's verified status. The contract must store these nullifiers on-chain to enforce sybil resistance, ensuring one KYC attestation cannot be used to create multiple anonymous identities.

Security considerations are paramount. The verification key is a critical parameter that must be set correctly at deployment, often via a constructor or a privileged setVerifier function controlled by a multi-sig or DAO. The contract should also include emergency pause functionality and upgradeability mechanisms (using transparent proxies like OpenZeppelin's) to respond to cryptographic breakthroughs or bugs. Gas optimization is crucial; verification functions can be expensive, so the contract logic should be minimal and use efficient data types like uint256 for the proof inputs.

A practical implementation involves generating the verifier contract from the ZKP circuit. Using the circom and snarkjs toolchain, you compile your circuit (.circom file) and perform a trusted setup to generate a verification_key.json. The command snarkjs zkey export solidityverifier then outputs a Solidity contract. This auto-generated contract contains the verification logic and must be integrated into your broader protocol, inheriting from it or calling its verifyProof function.

Finally, the verifier contract must be integrated with the broader application. It typically works in tandem with a registry contract that maps verified nullifiers to user entitlements, like a whitelist for a private pool. Events should be emitted upon successful verification (e.g., VerificationSuccessful(address indexed sender, uint256 nullifierHash)) for off-chain indexing. By completing this step, you establish the trustless, privacy-preserving gatekeeper for your on-chain system.

step-2-proof-generation
ZK CIRCUIT INTEGRATION

Step 2: Implement Client-Side Proof Generation

This step involves generating a zero-knowledge proof on the user's device to verify KYC status without revealing private data.

Client-side proof generation is the core privacy mechanism. Instead of submitting raw documents, the user's wallet runs a zero-knowledge circuit locally. This circuit takes private inputs—like a hashed government ID and a verified credential from a provider like Veriff or Persona—and public inputs, such as the pool's required jurisdiction whitelist. The circuit's logic proves the user is KYC-verified and eligible for the specific pool, outputting a zk-SNARK proof. Popular libraries for this include SnarkJS for Groth16 proofs or Circom for circuit design.

The implementation typically involves a pre-compiled .wasm file (the circuit) and a .zkey file (the proving key). Your dApp frontend loads these files and uses them with the proving library. For example, using SnarkJS in a React component, you would call snarkjs.groth16.fullProve with the witness (computed private inputs) and the proving key. The resulting proof object contains the actual proof (pi_a, pi_b, pi_c) and public signals, which are then sent to the blockchain verifier. This keeps all sensitive computation off-chain.

A critical detail is trusted setup dependency. Most zk-SNARK circuits require a one-time, multi-party ceremony to generate the proving and verification keys. If you're using a common circuit for KYC (e.g., proving membership in a Merkle tree of verified users), you may rely on a public ceremony. For custom logic, you must conduct your own. The alternative is using zk-STARKs or other proof systems without trusted setups, though they often have larger proof sizes.

Here is a simplified code snippet for generating a proof in a browser context, assuming the circuit files are hosted and the snarkjs library is available as a module or via CDN.

javascript
async function generateKYCProof(privateInputs, circuitWasmUrl, zkeyUrl) {
    const { proof, publicSignals } = await snarkjs.groth16.fullProve(
        privateInputs,
        circuitWasmUrl,
        zkeyUrl
    );
    // Format for Ethereum calldata
    const calldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals);
    return { proof, publicSignals, calldata };
}

The privateInputs object would contain the secret data known only to the user, such as their secret nullifier and the Merkle path proving their KYC credential is in the approved root.

After generating the proof, the client must send the proof data and public signals to the on-chain verifier contract. The public signals often include a commitment hash of the user's identity (to prevent double-spending access) and the identifier for the specific liquidity pool. The smart contract then calls the verifyProof function. If verification passes, the contract grants the user permission to interact with the pool, all while the contract logic never learns the user's actual identity or KYC details.

step-3-attestation-integration
PRIVACY-PRESERVING KYC

Step 3: Integrate with Decentralized Identity Attestations

This guide explains how to implement privacy-preserving KYC for on-chain liquidity pools using decentralized identity attestations, moving beyond simple wallet whitelists to verified, anonymous compliance.

Decentralized identity attestations enable selective disclosure of user credentials without revealing the underlying identity. For on-chain pools, this means you can verify a user meets specific criteria—like being a non-sanctioned entity or passing a basic KYC check—while preserving their privacy. Protocols like Worldcoin's World ID (for proof of personhood), Verite (for credential standards), or Ethereum Attestation Service (EAS) provide the infrastructure. Instead of storing sensitive data on-chain, you verify a cryptographic proof (like a zero-knowledge proof or a signed attestation) that the user possesses a valid credential from a trusted issuer.

The integration flow typically involves three parties: the user, an attestation issuer (a KYC provider), and your smart contract. First, a user completes an off-chain KYC process with a compliant provider, who issues a verifiable credential. The user then generates a proof of possession, often using a zero-knowledge proof (ZKP) to hide the credential details. Your pool's smart contract must include a verification function that checks the proof against the issuer's public key or a verification registry on-chain. For example, you could use the EAS schema bytes32 kycSchema = 0x123... and verify an attestation's validity via EAS.isAttestationValid(attestationUID).

Here is a basic Solidity example for a pool that gates entry based on a valid KYC attestation from a specific issuer using EAS. The contract stores the trusted issuer's address and the required schema identifier. The deposit function checks for a valid, non-revoked attestation before allowing the transaction to proceed.

solidity
import { IEAS, Attestation } from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol";

contract KYCGatedPool {
    IEAS public immutable eas;
    address public trustedIssuer;
    bytes32 public requiredSchema;

    constructor(IEAS _eas, address _issuer, bytes32 _schema) {
        eas = _eas;
        trustedIssuer = _issuer;
        requiredSchema = _schema;
    }

    function deposit(uint amount, bytes32 attestationUID) external {
        Attestation memory attestation = eas.getAttestation(attestationUID);
        require(attestation.issuer == trustedIssuer, "Untrusted issuer");
        require(attestation.schema == requiredSchema, "Incorrect schema");
        require(!attestation.revoked, "Attestation revoked");
        require(attestation.recipient == msg.sender, "Attestation not for sender");
        // ... proceed with deposit logic
    }
}

For enhanced privacy, integrate zero-knowledge proofs (ZKPs). Instead of submitting the attestation UID (which is linkable on-chain), users can generate a ZK-SNARK proof, via a circuit, that they own a valid, unrevoked attestation from the trusted issuer. The contract then verifies this proof. Frameworks like Circom and SnarkJS can be used to build the circuit, while Semaphore or RLN offer libraries for anonymous signaling. This approach ensures users can interact with the pool multiple times without creating an on-chain link between their transactions, achieving true anonymous compliance.

Key considerations for production include managing attestation revocation, handling issuer key rotation, and deciding on the trust model. You must have a mechanism to check if an attestation has been revoked, which may require integrating with a revocation registry or listening for on-chain revocation events. Issuer keys may need to be updated, requiring a governance process. Finally, you are placing trust in the attestation issuer; using a decentralized issuer network or requiring attestations from multiple issuers can reduce this single point of failure.

This architecture enables regulatory-compliant DeFi pools without sacrificing user sovereignty. It shifts the paradigm from identity disclosure to credential verification, allowing for pools that are accessible globally while filtering for specific jurisdictional or risk-based requirements. The end result is a more inclusive and secure financial system where access is based on verified attributes, not exposed personal data.

step-4-pool-gating-logic
PRIVACY ENGINE

Step 4: Implement Pool Gating and Access Control

This step integrates the verified identity proof from the previous stage to create a gated, privacy-preserving pool. We'll implement a smart contract that checks a user's credentials without exposing their personal data.

The core mechanism for pool gating is a verifier contract that validates a user's zero-knowledge proof (ZKP) against the public verification key. When a user attempts to join or interact with a private pool, they submit a ZKP generated by their Semaphore identity. The contract's verifyProof function checks this proof cryptographically, confirming the user is a verified member of the correct group and meets the pool's specific criteria (e.g., countryCode or accreditationStatus), all without learning who the user is.

Access control logic is then layered on top of the verification. A typical pattern uses a modifier like onlyVerifiedUsers. This modifier would call the verifier contract, and if the proof is valid, allow the transaction to proceed to mint a pool token, record a vote, or permit a deposit. For example, a private DeFi pool's deposit() function would be gated by this check, ensuring only accredited investors from permitted jurisdictions can participate, as defined by the pool's gating criteria set during initialization.

The gating criteria themselves are encoded into the nullifier and signal of the Semaphore proof. The nullifier prevents double-spending of a verification (a user joining twice), while the signal can contain a hash of the specific pool's address or rules. This means one ZKP from an identity contract can be re-used for multiple pools that share the same base requirements, but the signal ensures it's only valid for the intended destination, maintaining strict compartmentalization.

For developers, the implementation involves integrating a library like @semaphore-protocol/proof or snarkjs for on-chain verification. The contract must store the Semaphore group's Merkle root and the verifier's public key. A critical best practice is to use a proxy contract or upgradeable pattern for the verifier logic, allowing the verification key or group to be updated if the credential schema changes, without needing to migrate the entire pool.

Finally, consider gas optimization. Verifying a ZKP on-chain is computationally expensive. Strategies to manage this include using a relayer network to sponsor transactions for users, implementing meta-transactions, or utilizing layer-2 solutions like zkEVM rollups where verification is inherently cheaper. The cost must be factored into the pool's design to ensure a smooth user experience.

tools-and-libraries
PRIVACY-PRESERVING KYC

Essential Tools and Libraries

Implementing KYC for on-chain liquidity pools without compromising user privacy requires specialized cryptographic tools. This guide covers the core libraries and protocols for zero-knowledge identity verification.

DEVELOPER FAQ

Frequently Asked Questions

Common technical questions and solutions for implementing privacy-preserving KYC in on-chain liquidity pools.

Privacy-preserving KYC (Know Your Customer) is a method for verifying user identities without exposing their sensitive personal data on-chain. Unlike traditional KYC, which submits raw documents to a centralized database, this approach uses zero-knowledge proofs (ZKPs) or secure multi-party computation.

Key differences:

  • Data Minimization: Only proves a claim (e.g., "user is over 18") is true, without revealing the underlying data (the birth date).
  • User Sovereignty: The proof is generated client-side; the verifier never sees the original documents.
  • On-Chain Compliance: A cryptographic proof of verification (like a ZK-SNARK) is submitted to the smart contract, allowing the pool to enforce access controls while preserving privacy.

Protocols implementing this include Polygon ID, zkPass, and Sismo.

conclusion
IMPLEMENTATION ROADMAP

Conclusion and Next Steps

This guide has outlined the core components for building privacy-preserving KYC for on-chain liquidity pools. The next step is to integrate these concepts into a production-ready system.

To implement this system, you must first choose your core privacy primitives. For zero-knowledge proof (ZKP) verification, consider using Circom for circuit design with the SnarkJS proving system, or Halo2 for more complex logic. For the trusted execution environment (TEE), options include Intel SGX or AMD SEV. Your choice will dictate the architecture of your attestation service and the format of the privacy-preserving credentials issued to users.

The development workflow typically follows these stages: 1) Design the ZK circuit to validate that a user's credentials satisfy pool requirements without revealing them. 2) Build a secure off-chain attestation service inside a TEE to mint credentials. 3) Create smart contracts for pool governance that can verify ZK proofs (using libraries like verifier.sol). 4) Develop the user-facing dApp to handle proof generation and submission. Tools like Hardhat or Foundry are essential for testing the entire flow, especially the gas costs of on-chain proof verification.

Key challenges in production include managing the trust assumptions of your TEE provider, ensuring the user experience for proof generation is not prohibitive, and maintaining regulatory compliance as laws evolve. It's critical to conduct thorough audits on both your ZK circuits (e.g., with zkSecurity) and your smart contracts. Furthermore, consider the long-term maintainability of your system, as ZKP schemes and TEE architectures are rapidly advancing.

For further learning, explore existing implementations and research. The Semaphore protocol by Privacy & Scaling Explorations offers a framework for anonymous signaling. Projects like Aztec Network demonstrate private smart contract execution. The IEEE Standard for TEEs provides foundational specifications. Engaging with the ZKProof Community Standards and following developments from teams like Ingonyama and RiscZero will keep your knowledge current.

As a next step, we recommend building a minimal viable prototype. Start by forking a simple Circom tutorial circuit, modifying it to prove a simple credential claim (e.g., age > 18). Integrate it with a Hardhat project to test on-chain verification. This hands-on experience is invaluable for understanding the practical constraints and opportunities of privacy-preserving systems before committing to a full architecture.

How to Implement Privacy-Preserving KYC for On-Chain Pools | ChainScore Guides