Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

31 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Zero Knowledge Mixer Workshop πŸŒͺ️
| Build Your Own Tornado Cash |

Table of Contents

Resources & Setup
Understanding Privacy & Mixers
Workshop Tasks
Getting Help & Next Steps

Resources & Prerequisites πŸ“‹

Essential Reading

Requirements & Setup Options

You can choose between two setup methods:

Option A: Local Installation

Requirements:

  • Node.js >= 18
  • Circom
  • Basic familiarity with terminal
  • Basic understanding of:
    • Cryptographic hash functions
    • Smart contracts (basic)

Setup:

# Clone the repository
git clone https://github.com/vaniiiii/zk-workshop
cd zk-workshop

# Install dependencies
npm install

# Verify installation
npx hardhat help
npx hardhat zkit
npx hardhat compile
npx hardhat zkit compile
npx hardhat clean

# Verify Circom installation
circom --help
circom --version

Option B: Docker Setup (Recommended for Windows)

Requirements:

  • Docker installed on your system
  • Git
  • Basic familiarity with terminal

Setup:

# Clone the repository
git clone https://github.com/vaniiiii/zk-workshop
cd zk-workshop

Start Docker container:

For macOS/Linux:

docker run -it --rm -v $(pwd):/workspace vani0xff/zk-workshop bash

For Windows PowerShell:

docker run -it --rm -v ${PWD}:/workspace vani0xff/zk-workshop bash

If Windows path issues occur, use full path:

docker run -it --rm -v C:\full\path\to\workshop:/workspace vani0xff/zk-workshop bash

Working with Docker:

  • Edit files locally in your preferred editor
  • Run all commands in the Docker terminal
  • To exit Docker: type exit
  • To restart: run the docker run command again
  • Verify setup inside Docker container:
    circom --help
    circom --version

Background 🎯

What is a Mixer?

A mixer is a privacy protocol that enables users to break the on-chain link between source and destination addresses. It works like a pool where many users deposit fixed amounts of tokens, and later withdraw them to different addresses, making it impossible to trace which deposit corresponds to which withdrawal.

How Does it Work?

1. Deposit Phase

  • User generates two random values: nullifier and secret
  • Creates a commitment (hash of both values)
  • Deposits tokens along with the commitment
  • Commitment gets added to a Merkle tree

2. Withdrawal Phase

  • User provides a zero-knowledge proof showing:
    • They know a secret corresponding to a commitment in the tree
    • They haven't withdrawn these tokens before (using nullifier)
  • If proof verifies, tokens are sent to a new address

3. Privacy Protection

  • Commitments hide the actual secret values
  • Merkle tree proves membership without revealing which leaf
  • Nullifiers prevent double-spending without linking to deposit

Technical Overview

Our implementation consists of four main components:

  1. CommitmentHasher: Creates commitment and nullifier hashes
  2. HashLeftRight: Hash two values using MiMc hash function
  3. MerkleTreeChecker: Verifies membership proofs
  4. Withdraw: Main circuit combining all components

Circuit File Structure

circuits/
β”œβ”€β”€ lib/                          # Template implementations (edit these)
β”‚   β”œβ”€β”€ commitment_hasher.circom
β”‚   β”œβ”€β”€ hash_left_right.circom
β”‚   β”œβ”€β”€ merkletree_checker.circom
β”‚   └── withdraw.circom
β”œβ”€β”€ commitment_hasher.circom      # Entry points (do not edit)
β”œβ”€β”€ hash_left_right.circom
β”œβ”€β”€ merkletree_checker.circom
└── withdraw.circom

All workshop tasks are completed in the circuits/lib/ directory. The entry point files in circuits/ handle circuit instantiation.

Workshop Structure

The workshop consists of two parts:

Part 1: Circuit Implementation (Current)

In this part, we'll implement the core zero-knowledge circuits that power the mixer. You'll build each component step by step, with tests to verify your implementation.

Part 2: Smart Contract Integration (Following)

After completing the circuits, we'll explore the smart contract side. This includes:

  • Deposit and withdrawal mechanics
  • On-chain verification
  • Complete system integration

The full implementation can be found in scripts/withdraw.ts.

Workshop Tasks πŸ› οΈ

1. CommitmentHasher

πŸ“ circuits/lib/commitment_hasher.circom

What we're building:

A circuit that creates two different hashes:

  • A commitment that securely combines nullifier and secret
  • A nullifier hash that serves as a public identifier

Technical Details:

  • Uses Pedersen Hash, a specialized commitment scheme that:
    • Maps inputs to points on an elliptic curve
    • Combines points using curve arithmetic
    • Provides:
      • Perfect hiding: Output reveals nothing about inputs
      • Computational binding: Practically impossible to find different inputs with same output
      • Efficient for ZK circuits
πŸ’‘ Need a hint for CommitmentHasher?
  1. Structure:
component nullifierBits = Num2Bits(248);
component secretBits = Num2Bits(248);
component commitmentHasher = Pedersen(496);  // Why 496?
component nullifierHasher = Pedersen(248);
  1. Think about:
  • How to connect bits between components
  • Why nullifierHash only needs nullifier bits
  • Order of bits in commitment

Testing Your Implementation:

npx hardhat zkit compile
npx hardhat test test/commitmentHasher.t.ts

2. HashLeftRight

πŸ“ circuits/lib/hash_left_right.circom

What we're building:

A circuit that combines two inputs into one hash, used as the building block for our Merkle tree.

Technical Details:

  • Uses MiMCSponge hash function:
    • Optimized for ZK circuits
    • Fewer constraints than Pedersen
    • Perfect for binary tree structures
πŸ’‘ Need a hint for HashLeftRight?
  1. Structure:
component hasher = MiMCSponge(2, 220, 1);
// Think about:
// - How to connect inputs
// - Which output to use
// - What the k parameter means

Testing Your Implementation:

npx hardhat zkit compile
npx hardhat test test/hashLeftRight.t.ts

3. MerkleTreeChecker

πŸ“ circuits/lib/merkletree_checker.circom

What we're building:

A circuit that verifies a value exists in a Merkle tree without revealing which leaf it is.

Technical Details:

  • Uses path verification
  • Implements selective hash combination
  • Handles dynamic positioning with DualMux
πŸ’‘ Need a hint for MerkleTreeChecker?
  1. Structure:
component selectors[levels];
component hashers[levels];
// Think about:
// - How to initialize components
// - How to connect between levels
// - Final root comparison

Testing Your Implementation:

npx hardhat zkit compile
npx hardhat test test/merkleTreeChecker.t.ts

4. Withdraw Circuit

πŸ“ circuits/lib/withdraw.circom

What we're building:

The main circuit that ties everything together to enable private withdrawals.

Technical Details:

  • Combines commitment verification
  • Implements membership proof
  • Ensures single-use through nullifiers
  • Maintains zero-knowledge properties
πŸ’‘ Need a hint for Withdraw?
  1. Structure:
component hasher = CommitmentHasher();
component tree = MerkleTreeChecker(levels);
// Think about:
// - How to verify nullifierHash
// - How to connect commitment to tree
// - Why we need recipient constraint

Testing Your Implementation:

npx hardhat zkit compile
npx hardhat zkit verifiers
npx hardhat test test/withdraw.t.ts

Getting Help 🀝

Use logging for debugging:

// In circuits
log("Value of signal:", signal);
log("Debug point reached");
// In tests
console.log("Witness output:", witness);
console.log("Circuit inputs:", input);

Clear cache and compile again:

npx hardhat clean
npx hardhat zkit compile

Going Further πŸš€

To test your circuit with the smart contract implementation:

# Generate Solidity verifier
npx hardhat zkit verifiers

# Deploy and test the contract
npx hardhat run scripts/withdraw.ts

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages