Polkadot uses an account-based Tx model, which easily enables linking activity between accounts. To preserve on-chain anonymity, the options available to the user at the moment are limited to using centralised exchanges. It requires transferring their funds to an exchange-controlled account and withdrawing them at a later point in time, to a different account.
While it works to an extent, there are major drawbacks:
- Users need to trust the exchange in the first place
- There is a fee associated with each transaction, usually both withdrawal and deposit, and sometimes also with creating an account
- The entire process is cumbersome
- If the user wants to mix the whole amount, analysis tools can still correlate similar-looking values (especially taking the fees into account).
Conceptually, the project is similar to an Ethereum-based tornado.cash. We won't describe the mechanics of that project in this application, but rather outline the differences in implementation and how it will translate to a Polkadot deployment.
- ZK Proof system
Tornado cash uses a Groth16 proving scheme, whereas this proposal will leverage a more modern and efficient scheme Plonk. The three main advantages of Plonk over Groth16 are:
- Faster prover time
- Smaller number of circuit constraints1, leading to smaller program sizes
- Trusted setup, while still required, is universal (not relevant for this project)
- Hash function
The advantage of building an end-product after Tornado is the opportunity to use newer hash functions, too. Specifically, we will be using a Poseidon hash function, which is more suited for ZK proof systems than Pedersen hash used in Tornado, using up to 8x less constraints per message bit 2.
- Implementation
While we could build the smart contract with solidity and deploy on an EVM-compatible parachain, we believe that using a rust-based smart contract language brings unquestionable security benefits to the table.
There are Rust-based implementations already available of both Plonk and Poseidon Hash. The challenge will be integrating these into an ink!
contract.
Most importantly the Dusk lib uses custom data structures, that will need to be:
- initially for testing, wrapped in local data structures and have the Spread/PackedLayout derive'd on them (due to the orphan rule you can't have non-local both struct and derive macro)
- later, for optimisation, custom PackedLayout needs to be implemented for taking advantage of efficient storage
Aside from the core protocol (smart contracts), there are two more critical components to make it fully functional:
- Tx Relayer: the whole idea of a mixer is that the withdrawing account can't be linked to the deposit account. This means withdrawing from an empty account which can't cover its Tx fees. This is where the relayer comes in: a user would generate the proof in the frontend portal with their own payout address and relayer's fee address as parameters, so that the relayer can't steal the funds. The relayer submits the Tx to the smart contract, which, upon verifying the correctness, would pay the amount to the user and a subtract the fee for the relayer.
- Frontend: users should have a friendly interface (akin to Tornado.cash) to:
- create commitments and submit them to the mixer. They should be presented with a "note" that allows them to withdraw the amount later
- select the relayer
- spend the notes, by submitting the necessary data to the contract for it to verify the ownership of the deposited coin
The project closest to ours is anon. They are building a mixer as a substrate module as opposed to smart contracts. Furthermore, they are utilizing the Bulletproofs system in contrast to Plonk.
- Total Estimated Duration: 6.5 months
- Full-Time Equivalent (FTE): 1
- Estimated duration: 0.5 month
- FTE: 1
- Implement an append-only
MerkleTree
struct on 32-byte arrays as leafs, that can be imported into anink!
contract. Use anink!
built-in hash function (Blake2x256) for now. - Create an
ink!
contract withdeposit
function, that uses the MerkleTree as the underlying data storage and appends the commitments "deposited" to it. - Add the signature for the
withdraw
function. Verify that the submitted nullifier hash hasn't been "spent" yet. - Emit the
Deposit
andWithdraw
events.
- Estimated duration: 0.5 month
- FTE: 1
Replace Blake2x256 with Poseidon hash algorithm.
- Estimated duration: 2 months
- FTE: 1
Create a CLI tool that takes as inputs:
- the prover key
d_p
(hardcoded for now; later generated from a trusted setup ceremony) - leaf index
l
- the merkle tree opening for leaf
l
and a rootR
from history - nullifier
k
- randomness
r
- payout address
A
- relayer address
t
- relayer fee
f
And produces a ZK proof P
. The tool can be compiled to WASM for future inclusion in the frontend component.
Utilise Dusk's Plonk implementation as much as possible.
- Estimated duration: 2 month
- FTE: 1
To withdraw a coin, a user needs to submit a proof that they know the randomness & nullifier, i.e. they are the true owners of the coin.
The withdraw
method of the mixer ensure that the nullifier k
has not been spent yet (by storing the previously spent nullifier hashes).
Critically, the method delegates the checking of the proof to the verifier (another method), which takes as inputs:
- the verifier key
d_v
(hardcoded for now; later generated from a trusted setup ceremony) - ZK Proof
P
generated by the prover - Root
R
- hash
h
of the nullifierk
- payout address
A
- relayer address
t
- relayer fee
f
It returns true if the proof P
satisfies the public values (the rest of the inputs)
Utilise Dusk's Plonk implementation as much as possible.
- Estimated duration: 0.5 month
- FTE: 1
An offline tool (written in Rust or TypeScript, TBD) that is pre-funded and accepts incoming requests.
It verifies the validity of the submitted proof & Tx. If correct, it submits the Tx to the ink!
contract.
Also here we will write out the smart contract logic for paying out the fee f
to relayer address t
, once a valid Tx has been submitted. This will fall under the withdraw
method.
- Estimated duration: 1 month
- FTE: 1
- Estimated duration: 0.5 month
- FTE: 1
- Supporting arbitrary tokens
- ?? Wallet ??
- Incentive layer with a token: This is akin to Tornado.cash's TORN token. This is not strictly necessary for the functioning of the project, but it serves in users' best interest. The longer they keep the tokens in the smart contract (a.k.a anonymity pool), the more tokens they accumulate, thus keeping a high anonymity set and preserving privacy. This is also the most likely way to monetize the project, e.g.:
- 10% allocated to the founding team
- 20% for early investors
- 20% for public sale
- 50% for anonymity pool
Footnotes
-
for a concrete circuit proving leaf knowledge in a Merkle tree, which is essential for any mixer ↩