ChronoVault

Web3 wallet with TOTP 2 factor authentication and zero-knowledge proofs.

ChronoVault

Created At

ETHOnline 2025

Winner of

ETHGlobal

ETHGlobal - 🏆 ETHOnline 2025 Finalist

Hardhat

Hardhat - Best projects built using Hardhat 3 2nd place

Project Description

ChronoVault is a self-custody Web3 wallet that brings bank-grade transaction security to cryptocurrency through zero-knowledge proof-based two-factor authentication. The core innovation is requiring users to prove possession of a valid Time-based One-Time Password (TOTP) for every transaction without ever exposing the authentication secret on-chain or to any third party.

The system works by having users generate a TOTP secret during wallet creation, similar to setting up Authy or Google Authenticator. When initiating a transaction, the user's device generates a zero-knowledge proof demonstrating they know the correct TOTP code for the current timestamp. This proof is submitted alongside the transaction to the smart contract wallet, which verifies the proof's validity before executing the transaction. The smart contract never sees the TOTP secret or the actual code - only the cryptographic proof that the user possesses a valid code.

Built on ERC-4337 account abstraction, ChronoVault implements a custom account contract that integrates Groth16 zero-knowledge proof verification directly into the transaction validation flow. The wallet enforces that every UserOperation must include a valid ZK proof corresponding to the current block timestamp, with a tolerance window to account for clock drift and network latency. This creates a replay-attack resistant authentication layer where each proof is valid only for a brief time window and can be used exactly once.

The privacy guarantees are substantial - the TOTP secret never leaves the user's device, the blockchain never sees the 6-digit codes, and observers can only see cryptographic proofs being submitted. This maintains the self-custody ethos of Web3 while adding a practical security layer that users already understand from Web2 banking apps. If a user's private key is compromised, an attacker still cannot execute transactions without also possessing the TOTP secret, creating a true two-factor security model for on-chain assets.

The implementation includes comprehensive testing with 35 test cases covering happy paths, edge cases, timestamp validation, replay attack prevention, and the full integration between circuit-generated proofs and smart contract verification. The project demonstrates that zero-knowledge proofs can be practical, performant, and user-friendly for everyday cryptocurrency transactions.

How it's Made

The architecture consists of three tightly integrated components: zero-knowledge circuits, smart contracts, and a frontend application, all managed in a pnpm monorepo.

The ZK circuit layer uses Circom 2.2.1 to define the TOTP verification logic. The circuit takes three private inputs - the TOTP secret, current timestamp, and the calculated TOTP code - and proves their correctness without revealing any of them. The circuit implements the full RFC 6238 TOTP algorithm including HMAC-SHA1 computation and the dynamic truncation to produce 6-digit codes. We use the Groth16 proving system with a Powers of Tau ceremony for 2^14 constraints, which our 492-constraint circuit fits comfortably within. The snarkjs library handles proof generation client-side and automatically generates a Solidity verifier contract named TOTPVerifier.sol that we rename and integrate into our contract compilation pipeline.

The smart contract layer implements ERC-4337 account abstraction with custom validation logic. The TOTPWallet contract inherits from a base account contract and overrides the validateUserOp function to require ZK proof verification. Each UserOperation must include the proof as calldata, along with the public timestamp input. The contract verifies the proof using the generated TOTPVerifier.sol contract, then checks that the timestamp falls within an acceptable window of the current block timestamp - we use a 60-second tolerance in each direction to handle clock drift. We track used timestamps in a mapping to prevent replay attacks, ensuring each proof can only be used once even if it's still within the valid time window.

The particularly hacky and interesting part was the build orchestration. Because snarkjs generates a generic Groth16Verifier contract but our Solidity code imports TOTPVerifier, we added a post-generation step in the circuit setup script that reads the generated Solidity file and does a string replacement to rename the contract. This happens automatically during the circuit build, so when Hardhat compiles the blockchain package, the correctly-named verifier is already in place. The CI pipeline builds circuits first, then blockchain, then frontend, with careful caching of the 18MB Powers of Tau file to avoid repeated downloads.

Testing was done with Node.js's built-in test runner rather than Mocha, using Hardhat 3.0's native support for the modern testing framework. Every test generates real ZK proofs using the actual circuit, wasm witness generator, and proving keys - no mocks or shortcuts. This caught several edge cases around timestamp validation and proof format that wouldn't have surfaced with simulated proofs. The test suite generates over 35 different proofs during execution, proving the system works end-to-end.

The frontend uses Next.js 15 with React 19 and wagmi v2 for Web3 integration. We use wagmi's CLI to auto-generate TypeScript types from the compiled contracts, ensuring type safety between the contracts and frontend code. The UI is built with shadcn/ui components on Tailwind CSS with full dark mode support via CSS variables.

One technical challenge was handling proof generation asynchronously in tests without hanging the Node.js process - we had to carefully manage process.exit calls and use process.nextTick to ensure all async operations completed before termination. Another was coordinating the three-package build where the frontend depends on blockchain types which depend on generated circuits. The solution was workspace-level scripts that orchestrate the correct build order and watch mode for development.

The entire stack runs on Node.js 22, uses TypeScript throughout with strict type checking, and maintains sub-second proof generation times on standard hardware. No external APIs or centralized services are required - everything runs locally or on-chain.

background image mobile

Join the mailing list

Get the latest news and updates