The problem NULLIUS solves

When your wallet shows you a balance, that number came from a company.

Your wallet asked a server how much ETH an address holds. The server answered. Your wallet drew the answer on the screen. That is the entire exchange. There is no signature, no proof, and no check anywhere in it. The number you are looking at is a claim, and your wallet believes it the way you believe a stranger who tells you the time.

This is true of nearly everything you see in crypto: your balance, your token holdings, whether a transaction went through, what a contract currently holds. It all arrives as an unverified assertion from a server you do not control, and every interface presents it as fact.

Three things make that worse than it sounds. A very large share of Ethereum reads flow through a handful of companies. A lying server looks exactly like an honest one, because there is no error and no warning, and the interface renders a lie in the same font as the truth. And if an attacker can make your wallet believe a false balance or a false allowance, they can walk you into signing something you never would have signed. Challenges I ran into

  1. A local chain made valid proofs prove the wrong state

A stray Anvil process on our RPC port reported a different chain (31337), causing our anchor selection to use an invalidly old block. Proofs were technically valid but against the wrong chain. We fixed this by validating chain IDs and anchoring to the highest block confirmed by at least two independent endpoints. We also correctly distinguish proven zero/absence from missing data.

  1. Our best demo moment was invisible

Our malicious RPC was correctly detected, but the UI only showed the failure as text because the first valid proof won the race. We now retain and visualize failed proof paths, making the exact point where a forged proof breaks visible to the audience.

  1. Public RPCs and local forks couldn't reliably serve proofs

Anvil forks inherited upstream eth_getProof rate limits, while some public endpoints throttled or refused proofs. We moved to two independent public mainnet endpoints plus our malicious proxy, giving us real mainnet proofs and cross-provider corroboration. Verified results are also cached by address + state root.

  1. Noir's API changed underneath our ZK circuit

The Poseidon/Keccak hashing APIs we planned to use had changed in Noir 1.0. We switched to Poseidon2 and built a self-consistent, tested circuit, while explicitly marking its JavaScript-side integration as incomplete rather than claiming unsupported ZK functionality.

  1. Making the proof visualization consistent was harder than expected

Proofs have different depths, making naive scaling distort the visualization. After three iterations, we fixed the row height and scale limits and used browser debug measurements to guarantee consistent rendering across proof states.

Share this project:

Updates

Submission history