Created on 1st March 2025
•
People who used tornadocash got censored from using popular front-ends, simply because they wanted to protect their privacy. This is because tornadocash "publicly announces" which public wallets are transferring money privately. In simple terms they see them making a transaction depositing into the protocol.
UltraAnon solves this by enabling full plausible plausible deniability. Inspired by EIP7503 (zkwormholes) but with even stronger guarantees. Since UltraAnons "burn-address" isnt actually a burn address and can still publicly spend. Which means we achieve maximum plausible deniability!
Achieved by having a single address type where its balance is tracked using a zk proof system. This system tracks balances differently, instead of a total balance, it tracks two states: a total amount recieved publicly, and the total amount spent privately. This allows accounts to recieve money publicly and show it on their balance, but once they make a private transaction, it looks like nothing happend to their account. And it just looks like the tokens stayed in their account while it actually is privately spent. The real balance (total_incoming_amount- total_outgoing_amount ) can only be seen by the user. While the rest of the world sees only the total_incoming_amout.
Furthermore, UltraAnon also has a anonymity set of every account. Even if they only make public transfers. Unlike is systems like zcash where users who only use public accounts, never contribute to the anonymity.
It also inherits the property of EIP-7503 (zkwormholes) where proof time is O(1). Unlike UTXO based privacy protocols like zcash and railgun where the proof time to spends increases for every transaction you recieve
We modified the ERC20 standard to enable both public and private transactions. Users can choose to send a transaction publicly or privately. We hosted a UI on IPFS and ENS for users to interact with.
We modified the ERC20 standard in solidity and wrote ZK circuits in Noir. The user creates a private or public transaction and attatches a proof generated on the client-side with NoirJs. The proof along with the transaction arguments are sent to a relayer who executes the transaction on the user's behalf.
The smart contract updates users' balances after the proof has been verified on-chain.
We also added 2 merkle trees to track the public and private state in. This merkle tree updates on every transfer.
We intended to deploy on ZK sync but we didn't realize they don't yet have the precompile needed to deploy our Noir verifier. This was undocumented.
We ended up using sepolia but we missed out on experimenting with zksync native account abstraction and paymaster, which we possibly could have used as relayer.
Full plausible deniability.
A new state model that mixes public and private state.
O(1) proof time, indepedent of the amount of transfers recieved.
The state design was complex so we were left with little time to construct a front-end. Additionally, the relayer is self-hosted and acting out of good faith. In a full implementation there should be an incentive for relayers to execute transactions. We would also like to explorer more offchain compliance guarentees.
This gas fees are also incredibly high, especially for public transfer, which cost the same amount of gas as private transfers since it also uses almost the same zk-ciruit. And needs to update 2 merkle tree.
We think we can significantly reduce some of the gascost by using only one merkle tree or even better storage proofs. But fundamentally public transfer will cost far more than a normal erc20 since it uses the same expensive zkp as private transfer
The project was entirely built during the buildathon. It was inspired by EIP7503 and some previous research on account based nullifier schemes: https://github.com/jimjimvalkema/scrollZkWormholes/blob/main/docs/notes.md#partial-spends-address-reusability
Tracks Applied (2)
zkSync ∎