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 │ 2. Aggregate &
▼ │ Store Proofs
┌─────────────────────┐ │
│ │ ▼
│ Relayer Service │ ┌─────────────────────┐
│ (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 calls
privateTransfer()with verified proof - Contract transfers ETH from pool → Recipient
On-chain observers see:
- Transaction 1: User A → Contract (deposit)
- Transaction 2: Relayer → Contract → Recipient (transfer)
- No direct link between User A and Recipient
The repository is organized into three main directories:
app: Contains the Node.js application that serves as the frontend and handles proof generationcircuit: Contains the zk-SNARK circuit written in Circom for proving commitment ownershipcontracts: Contains the Solidity smart contracts managed with Foundry
Before you begin, ensure you have the following installed:
git clone [email protected]:Poly-pay/polypay.git
cd polypay
# Install Node.js dependencies
cd app
npm install
cd ..cd circuit/
makeThis command will:
- Compile the
circuit.circomfile - Perform a local trusted setup (for demonstration purposes, do not use in production)
- Generate the proving key (
circuit_final.zkey), verification key (verification_key.json), and WebAssembly version of the circuit (circuit.wasm) - Place the generated files in the
setupdirectory
Create .env.secret file in app/ directory with your private keys and zkVerify credentials.
cd app/
node ./src/get_vkhash.jsThis requires .env.secret to be properly configured.
Copy the generated vkHash to .env file in contracts/ directory.
cd contracts
forge script script/PrivateTransferContract.s.sol:ZkvVerifierContractScript \
--rpc-url wss://ethereum-sepolia-rpc.publicnode.com \
--private-key=YOUR_PRIVATE_KEY \
--broadcast- Save the deployed contract address to
.envinapp/directory - Update the relayer private key in
app.js:
this.relayerWallet = new ethers.Wallet(
"YOUR_PRIVATE_KEY", // relayer private key (change this to something for your own use)
provider
);cd app/
node ./src/app.js- Generate a Proof: The user interacts with the DApp. The DApp uses the compiled circuit (
circuit.wasm) and proving key (circuit_final.zkey) to generate a proof based on the user's commitment - 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 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: