A privacy-preserving Ethereum transfer system that uses zero-knowledge proofs to enable anonymous ETH transfers through a pool-based mechanism.
This system allows users to transfer ETH privately by breaking the on-chain link between sender and recipient through a pool-based approach combined with zero-knowledge proofs.
βββββββββββββββββββββββ 1. Generate ZK Proof βββββββββββββββββββββββ
β β βββββββββββββββββββββββββββΆ β β
β User Application β β zkVerify β
β (Privacy Client) β β (Proof Verifier) β
β β βββββββββββ 4. Get ββββββββ β β
βββββββββββββββββββββββ Aggregation ID βββββββββββββββββββββββ
β β
β β
β 5. Call via Relayer Wallet β 2. Aggregate &
βΌ β Store Proofs
βββββββββββββββββββββββ β
β β βΌ
β Relayer Wallet β βββββββββββββββββββββββ
β (Anonymous Proxy) β β β
β β β zkVerify Chain β
βββββββββββββββββββββββ β (Proof Registry) β
β β β
β 6. Submit Transaction βββββββββββββββββββββββ
βΌ β
βββββββββββββββββββββββ 8. Query Proof Status β
β β ββββββββββββββββββββββββββββββββββββββ β
β Smart Contract β β
β (Privacy Pool) β 7. Verify Aggregated Proof β
β β βββββββββββββββββββββββββββββββββββββββΆβ
βββββββββββββββββββββββ β
β β
β 9. Transfer ETH β
βΌ β
βββββββββββββββββββββββ β
β β β
β Recipient β β
β β β
βββββββββββββββββββββββ β
β
βββββββββββββββββββββββ 3. Submit & Wait β
β β ββββββββββββββββββββββββββββββββββββββββ
β Ethereum Chain β
β (Settlement Layer) β
βββββββββββββββββββββββ
- Acts as an ETH pool that holds deposited funds
- Tracks commitments and their associated amounts
- Verifies zero-knowledge proofs via zkVerify integration
- Executes transfers from pool to recipients
- Proves ownership of a commitment without revealing the private key
- Validates that the user knows the secret behind a specific commitment
- Generates proofs that are verified on-chain through zkVerify
- Submits transactions on behalf of users to hide the actual sender
- Pays gas fees for transaction execution
- Ensures the transaction caller is not the original depositor
User A deposits ETH β Smart Contract Pool
- Generates commitment = hash(privateKey, nonce)
- Contract stores:
commitmentAmounts[commitment] = depositAmount - Public info: Someone deposited X ETH with commitment Y
User A (or someone with A's private key) initiates transfer:
- Generate ZK proof proving ownership of commitment
- Submit proof to zkVerify for verification
- Relayer Wallet calls
privateTransfer()with verified proof - Contract transfers ETH from pool β Recipient
On-chain observers see:
- Transaction 1: User A β Contract (deposit)
- Transaction 2: Relayer Wallet β Contract β Recipient (transfer)
- No direct link between User A and Recipient
The repository is organized into three main directories:
app/: Node.js application that serves as the frontend interface and handles zero-knowledge proof generationgenerate_proof/: Noir circuits for generating proofs of commitment ownershipcontracts/: Solidity smart contracts for the privacy pool, managed with Foundry framework
Before you begin, ensure you have the following tools installed:
-
Node.js: JavaScript runtime environment
-
Foundry: Ethereum development toolkit for smart contracts
-
Noirup: Noir toolchain installer
Important: You must use version
1.0.0-beta.12specifically:noirup -v 1.0.0-beta.12
Newer versions will not work with the current circuit implementation.
git clone git@github.com:Poly-pay/polypay_noir.git
cd polypay_noir
# Install Node.js dependencies
cd app
npm install
cd ..cd generate_proof/
nargo compileThis will generate target/generate_proof.json, which the application uses during proof generation.
Navigate to the app/ directory and set up your environment:
cd app
cp .env.template .envEdit the .env file and configure the following variables:
- RELAYER_ZKVERIFY_API_KEY: Get your API key from the appropriate relayer service:
- For Testnet: Visit https://relayer-testnet.horizenlabs.io/
- For Mainnet: Visit https://relayer.horizenlabs.io/
- ETH_SECRET_KEY: Your Ethereum private key
From the app/ directory, register the verification key:
npm run registerVKNote: This step requires your .env file to be properly configured. The command will register the verification key and output a vkHash value.
Copy the generated vkHash to the .env file in the contracts/ directory.
Navigate to the contracts directory and deploy the smart contract:
cd contracts
forge script script/PrivateTransferContract.s.sol:ZkvVerifierContractScript \
--rpc-url wss://ethereum-sepolia-rpc.publicnode.com \
--private-key=YOUR_PRIVATE_KEY \
--broadcastImportant: Replace YOUR_PRIVATE_KEY with your actual private key.
Complete the configuration by updating these values:
- Save the deployed contract address to the
.envfile in theapp/directory - Update the relayer wallet private key in
app.js:
this.relayerWallet = new ethers.Wallet(
"YOUR_PRIVATE_KEY", // Replace with your relayer wallet private key
provider
);Start the application:
cd app
npm run start- Generate a Proof: The user interacts with the app, which uses the compiled Noir circuit and proving artifacts to generate a zero-knowledge proof.
- Submit Proof to zkVerify: The DApp sends the generated proof and public inputs to zkVerify for verification
- Receive Proof ID: zkVerify verifies the proof and returns proof
- Execute Private Transfer: The relayer wallet calls the smart contract with the proof, enabling anonymous transfer from pool to recipient
- On-Chain Attestation: The smart contract verifies the proof through zkVerify's attestation contract
Check out zkVerify documentation for additional info and tutorials: