A beginner-friendly Rate-Limited Nullifier (RLN) implementation using Circom 2, Groth16 zk-SNARKs, and Ethereum smart contracts for privacy-preserving spam prevention. This zero-knowledge proof tutorial demonstrates anonymous rate limiting with cryptoeconomic incentives.
Keywords: circom tutorial
zksnark example
zero-knowledge proofs
groth16
merkle trees
poseidon hash
privacy-preserving cryptography
ethereum
smart contracts
anti-spam
beginner-friendly
Portfolio Project Status: This is a demonstration project showcasing RLN implementation fundamentals. The circuit compiles successfully and core functionality is working, with some test implementations incomplete by design.
This Circom tutorial project demonstrates zero-knowledge proof development and zkSNARK circuit design through an RLN (Rate-Limited Nullifier) system. Perfect for developers learning privacy-preserving cryptography, Ethereum smart contract integration, and zk-SNARK fundamentals.
This zkSNARK example enables:
- Anonymous Rate Limiting: Users can post messages anonymously but are limited to one message per epoch
- Spam Detection: Publishing multiple messages in the same epoch reveals the user's secret key
- Cryptoeconomic Security: Users stake deposits that can be slashed if they spam
- Zero-Knowledge Proofs: Membership and rate limiting are enforced without revealing user identity
RLN (Rate-Limited Nullifier) is a zero-knowledge cryptographic primitive that enables privacy-preserving spam prevention in anonymous environments. This zk-SNARK application uses Shamir's Secret Sharing and Merkle tree proofs to enforce rate limits without revealing user identities.
The key insight: users generate zero-knowledge proofs to prove they are rate-limited without revealing their identity. Violating the rate limit cryptographically reveals their secret key through polynomial interpolation, enabling cryptoeconomic punishment.
- Identity: Each user has a secret key
a0
and derives an identity commitmentHash(a0)
- Linear Polynomial: For each epoch, user knows a line
y = a1 * x + a0
wherea1 = Hash(a0, epoch)
- Shares: Each message includes a point
(x, y)
on this line wherex = Hash(message)
- Secret Recovery: Two points reveal the line equation, exposing
a0
and enabling slashing
This Groth16 zkSNARK circuit built with Circom 2 enforces the RLN protocol using constraint programming and finite field arithmetic:
- Merkle Tree Membership: User's identity commitment exists in the registry
- Nullifier Generation:
nullifier = Hash(a1, messageId)
prevents double-spending - Share Computation:
y = a1 * x + a0
wherex = Hash(signal)
anda1 = Hash(a0, epoch)
- Rate Limiting: Each epoch allows only one message per identity
The Solidity smart contract integrates with the zkSNARK verifier to provide:
- Identity Registration: Stake ETH deposits and commit to the Merkle tree
- Message Posting: Submit messages with Groth16 zero-knowledge proofs
- Spam Detection: Identify duplicate nullifiers within epochs
- Cryptoeconomic Slashing: Recover secrets and slash spammers' deposits
The browser-compatible SDK provides a complete zero-knowledge proof toolkit:
- Identity Management: Create and manage RLN identities with Poseidon hashing
- Proof Generation: Generate zk-SNARKs for anonymous message posting
- Merkle Tree Operations: Maintain sparse Merkle tree identity registry
- WASM Integration: WebAssembly-based proof generation for browsers
This zero-knowledge proof tutorial demonstrates:
Cryptography & Math:
- Shamir's Secret Sharing schemes
- Polynomial interpolation for secret recovery
- Poseidon hash function (zk-SNARK friendly)
- Merkle tree membership proofs
- Finite field arithmetic
zkSNARK Development:
- Circom 2 circuit programming
- Groth16 proving system
- Constraint system design
- Trusted setup procedures
- WASM compilation for browsers
Ethereum Integration:
- Solidity smart contracts
- Gas optimization techniques
- Event emission and indexing
- Cryptoeconomic mechanism design
Software Engineering:
- JavaScript/TypeScript SDK development
- Test-driven development with Hardhat
- Performance benchmarking
- Browser compatibility with WebAssembly
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
# Install Circom directly via cargo
cargo install --git https://github.com/iden3/circom.git
npm install
npm run compile
This will:
- Compile the RLN circuit (✅ Working)
- Generate trusted setup parameters (
⚠️ Uses dummy file for portfolio demo) - Create WASM files for the browser (✅ Working)
- Export Solidity verifier contract (
⚠️ Skipped due to dummy setup)
npm test
Current Test Status: 16/27 tests passing (59% pass rate)
Tests verify:
- ✅ Identity registration and commitment generation - Working
- ✅ Circuit compilation and constraint validation - Working
- ✅ Cryptographic primitives (Poseidon, SSS) - Working
⚠️ Message posting with rate limiting - Partially implemented⚠️ Spam detection and slashing mechanisms - Contract methods incomplete⚠️ Proof generation and verification - Core working, edge cases need handling- ✅ Gas usage optimization - Basic benchmarks working
# Basic demo (note: CLI implementation is incomplete)
npm run demo
Note: CLI demo is a placeholder. The core functionality is demonstrated through the test suite.
npm run deploy
Note: Deployment script exists but may need adjustment for production use.
circom-zksnark-rln-demo/
├── circuits/
│ └── rln.circom # Main RLN circuit
├── contracts/
│ ├── RLN.sol # Main RLN contract
│ └── RLNVerifier.sol # Auto-generated verifier
├── packages/
│ └── sdk/
│ ├── index.js # JavaScript SDK
│ └── wasm/ # WASM files for browser
├── test/
│ └── rln.spec.js # Comprehensive tests
├── cli/
│ └── demo.js # CLI demonstration tool
├── scripts/
│ └── compile.js # Circuit compilation script
└── build/ # Compilation artifacts
- One message per epoch per identity
- Configurable epoch length (default: 1 hour)
- Cryptographic enforcement via nullifiers
- Duplicate nullifiers in the same epoch indicate spam
- Automatic secret key recovery from multiple shares
- Slashing mechanism with economic incentives
- Anonymous message posting
- Zero-knowledge membership proofs
- Identity commitments hide actual secrets
- Groth16 proofs for efficiency and security
- Merkle tree membership verification
- Cryptoeconomic slashing deterrent
Actual Compiled Circuit Stats:
- Merkle Tree Height: 20 levels (supports 2^20 identities)
- Message Limit: 1 per epoch per identity
- Template Instances: 216
- Non-linear Constraints: 5,893
- Linear Constraints: 6,497
- Total Constraints: 12,390
- Public Inputs: 2 (x, externalNullifier)
- Private Inputs: 43 (identitySecret, pathElements, pathIndices, messageId)
- Public Outputs: 3 (y, nullifier, root)
- Wires: 12,413
Actual Test Results:
Operation | Gas Usage |
---|---|
MockVerifier Deployment | 171,117 gas |
RLN Contract Deployment | 929,760 gas |
Identity Registration | 95,339 gas |
Message Posting | Not yet implemented |
Spam Detection | Not yet implemented |
Slashing | Not yet implemented |
- Compressed: < 1.2 MB gzipped
- Includes: WASM circuit, snarkjs, utilities
- Browser Compatible: Works in modern browsers
- WebWorker Support: Non-blocking proof generation
- Trusted Setup: Powers of Tau ceremony should involve multiple contributors
- Deposit Security: Stake amounts should exceed spam profit incentives
- Epoch Management: Epoch length affects both UX and security
- Secret Storage: Users must securely store their identity secrets
- Merkle Tree Updates: Registry updates should be carefully managed
The RLN system is based on Shamir's Secret Sharing:
Given a linear polynomial: y = a1 * x + a0
- a0 is the secret key
- a1 = Hash(a0, epoch) ensures epoch-specific shares
- x = Hash(message) ensures message-specific shares
- y is the public share value
Two shares (x1, y1) and (x2, y2) reveal:
a0 = (y1 * x2 - y2 * x1) / (x2 - x1)
This mathematical property enables automatic secret recovery when users violate rate limits.
-
Circuit Compilation Fixed
- Updated all templates to use proper Circom 2.x syntax
- Fixed component instantiation patterns
- Resolved invalid template syntax errors
-
SDK Improvements
- Fixed MerkleTree empty tree handling
- Added proper string-to-BigInt conversion for signals
- Updated external nullifier calculation for string inputs
-
Contract & Test Updates
- Added MockVerifier for testing
- Updated ethers.js v6 compatibility
- Fixed deployment and event handling
-
Dependencies
- Rust toolchain updated to 1.88.0
- Circom 2.2.2 installed and working
- All npm dependencies resolved
This is a portfolio demonstration project with some intentional limitations:
- Powers of Tau: Uses dummy file instead of real trusted setup
- Verifier Contract: Mock implementation for testing only
- Contract Methods: Some slashing/recovery methods incomplete
- CLI Demo: Placeholder implementation
- Edge Cases: Some test scenarios need additional handling
- Circuit Compilation: ✅ Successful
- Test Suite: 16/27 tests passing (59%)
- Gas Efficiency: Basic benchmarks working
- SDK Functionality: Core features operational
Circuit Compilation Times:
- Circuit compilation: ~2-3 seconds (on M1 Mac)
- Constraint generation: 12,390 total constraints
- WASM generation: ~1 second
Test Performance:
- Identity commitment generation: ~10ms
- Basic proof generation: Haven't timed this yet (TODO!)
- Merkle tree operations: Pretty fast, but should measure properly
Gas Usage (From Test Results):
- Contract deployment: 929,760 gas (seems high?)
- Identity registration: 95,339 gas (reasonable)
- Message posting: Not implemented yet
Things I want to measure when I have more time:
- Proof generation time across different message sizes
- Memory usage during proof generation
- Constraint count optimization opportunities
- Browser vs Node.js performance differences
- Batch proof generation efficiency
Note: These are just rough measurements from my development machine. Real benchmarking would need proper testing infrastructure!
Cryptographic Foundations:
- Using Poseidon hash (designed for zk-SNARKs)
- Proper Shamir's Secret Sharing implementation
- Merkle tree membership proofs
- Nullifier uniqueness enforcement
Circuit Security:
- Input validation for path indices (must be 0 or 1)
- Range checking for message limits
- Proper component instantiation (finally!)
Things I'm Worried About:
- Trusted Setup: Using dummy Powers of Tau file (big red flag!)
- Mock Verifier: Always returns true (obviously insecure)
- No Input Validation: Contract doesn't validate proof inputs thoroughly
- Secret Storage: No guidance on secure key management
- Replay Protection: Basic nullifier checking but needs more testing
Things I'm Not Sure About:
- Are my constraint counts reasonable for this circuit size?
- Is the merkle tree depth (20) secure enough?
- Should I be using different hash functions for different purposes?
- Are there timing attacks I should worry about?
What I Want to Learn More About:
- How to properly validate elliptic curve points
- Best practices for zkSNARK circuit security
- Common attack vectors against RLN systems
- Proper trusted setup procedures
Disclaimer: This is a learning project! Don't use this in production without proper security audits and fixes.
Better Testing:
- Add performance benchmarking tests
- Test edge cases and error conditions
- Add fuzzing tests with random inputs
- Measure constraint count scaling
Documentation:
- Add more code comments explaining the math
- Create simple diagrams showing the RLN flow
- Write a tutorial for beginners like me
- Document all the gotchas I encountered
Development Experience:
- Add pre-commit hooks for linting
- Automate circuit compilation in CI
- Add better error messages
- Create development scripts for common tasks
Circuit Optimizations:
- Reduce constraint count where possible
- Add support for different tree depths
- Implement batch verification
- Optimize Poseidon usage
Production Readiness:
- Proper trusted setup integration
- Real verifier contract generation
- Comprehensive input validation
- Rate limiting per user/epoch tracking
Developer Experience:
- Browser-based demo interface
- Interactive circuit debugger
- Performance profiling tools
- Better error handling and logging
Advanced Features:
- Support for different proving systems (PLONK?)
- Integration with existing identity systems
- Decentralized trusted setup coordination
- Cross-chain compatibility
Research Directions:
- Explore different rate limiting strategies
- Investigate privacy-preserving optimizations
- Study scaling solutions for large user bases
- Research post-quantum security implications
Reality Check: This is a portfolio project, so most of these are just ideas to show I'm thinking about the bigger picture!
npm run lint
npm run typecheck
npm run build:sdk
MIT
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
RLN & Rate Limiting:
- RLN Documentation - Official Rate-Limited Nullifier docs
- Privacy & Scaling Explorations - Ethereum Foundation research team
Circom & zkSNARK Development:
- Circom 2 Documentation - Official Circom circuit programming guide
- snarkjs Library - JavaScript zkSNARK toolkit
- Circom Tutorial - Getting started guide
- zkSNARK Examples - Circuit library and examples
Cryptography Papers:
- Groth16 Paper - "On the Size of Pairing-based Non-interactive Arguments"
- Poseidon Hash - zkSNARK-friendly hash function
- Merkle Trees - Cryptographic commitment schemes
Zero-Knowledge Proof Resources:
- ZK Learning Resources - Comprehensive ZK learning materials
- ZK Whiteboard Sessions - Video tutorials
- Awesome Zero Knowledge - Curated ZK resources
Perfect for:
circom tutorial
,zksnark example
,zero-knowledge proof learning
,groth16 implementation
,ethereum privacy
,anti-spam cryptography