Skip to Content
All

A Tornado

 — #cryptography#privacy#cryptocurrency

Tornado Cash is a privacy tool.

A piece of software, running on decentralized networks such as Ethereum.

It uses a technology based on cryptography: zkSNARKs.

zkSNARKs

Stands for Zero-Knowledge Succinct Non-Interactive Argument of Knowledge.

It means that it allows a party to send a message, such as a deposit. And to then prove it possesses certain information without revealing that information to send another message. This proof is made possible using a secret key created before the subsequent message is sent.

How?

It uses a smart contract that accepts say ETH deposits that can be withdrawn with a different address. Whenever a new address withdraws ETH, there is no way to link the withdrawal to the deposit, which makes the procedure private, by "Mixing" transactions.

Mixing?

It mixes/remaps transactions. Bob isn't the only person depositing and withdrawing.

The contract is a DAO.

DAO

Trust-less Decentralized Autonomous Organization.

It is trust-less. Instead of having to trust someone to do something for you, it's some piece of code that does that thing. And that code is running on a decentralized network. And..

DAO which means it is an entity, a structure or a thing, not a person. Could be a group in control of it, or a single person, but the DAO is a thing. A smart contract.

No particular central governing body exists. Instead, the members of the DAO will decide about how to run the organization. The decision making takes place using TORN token pursuant to the governance rules that was defined by the developing team.

TORN token?

Those tokens can be sent from one address to another.

An ETH token is an ERC-20 token native to the Etherum network.

A TORN token the ERC-20 token issued by the Tornado smart contract. It's here.

Those Tornado issued tokens are used for the 'governance' of the smart contract. Maybe one person is in possession of all the tokens. But maybe that's a few thousands. Who knows. But one address nearly holds half of them right now. It's here.

Governance is used by many non privacy-focused smart contracts too.

It's just a function of the contract that allows changing the behavior of the contract.

Tornado Governance

So If I grab all those tokens I can change how that Tornado behaves? Or as a minority holder casts some votes or something?

No.

Only address: 0x000000000000000000000000000000000000 can. Which means nobody. It's a dead address.

The dev team has given up the governance control. It makes that contract immutable. It's here

Note: ETH miners though could find a consensus and break the rules performed by this smart contract on some transaction(s), they did it before, and it split the miner community into two, ETH as we know it today, and ETC which kept the code execution as law.

How does it work then, from a user perspective?

Here is how this (roughly) goes:

  • Bob deposits funds using an ETH compliant wallet, and gets a "note" in response that acts as the money transfer control number
  • Bob waits till some other deposits are made
  • Bob withdraw funds by providing the "note" with a wallet address (or relayer) he would like the funds transferred to.

Bob needs the note. Or can't withdraw: the contract wants the note to validate that a matching deposit was made.

Can the note be used for connecting the deposit to the withdrawal?

Yes it can, and that is what Tornado is providing as their "compliance mechanic". Such a report could be used by whoever has the "note" which is the person who sent the deposit transaction, only.

OFAC not too happy about this

Can it not just double/triple spend?

What if Bob sends the a second transaction to withdraw, with the same note? Would it not syphon the funds in the pool?

It could, but no.

It's single use. It uses something called Nullifiers.

Nullifiers?

A Nullifier is a piece of data that ensures a message, the note, is only used once.

When attempting to withdraw, the user provides a zk proof of the withdrawal circuit validating the nullifierHash is actually the hash of the private nullifier. The contract code checks the nullifierHash hasn't been used before, then marks it as already transferred (withdrawn).

Circuit?

It's what the contract execution does. The contract circuit, or function. The Nullifier check is part of that circuitry.

Nullifier check in the withdraw function:

contract Tornado {
  mapping(bytes32 => bool) public nullifierHashes;
  function withdraw(... bytes32 _nullifierHash, ...)  {

    // Verify note

    require(!nullifierHashes[_nullifierHash], "The note has been already spent");

    // ... performs withdrawal

    nullifierHashes[_nullifierHash] = true;
  }
}

So what if Alice tries to withdraw?

Nullifiers guarantee single use of the messages. The next question is who is authorized to send that single valid nullifier in the first place.

The nullifier comes with its counterpart, the commitment. Also referred to as “commitment-nullifier scheme” which powers many other Zero-knowledge proof applications.

A commitment is a published hash of secrets, and it determines who is allowed to create a valid nullifier. As a result, the commitment hash and the nullifier hash share a common data field so that the zk prover can cross-validate that data field.

  • Bob deposits funds and the secret named “nullifier/whatever”.

The zk proof proves to the smart contract that Bob has deposited that amount before by validating the commitment hash and nullifier hash.

The same amount?

Yes.

While being trust-less and mixing things up, Tornado does not provide transactional amount confidentiality.

The general idea of Tornado is using a zero-knowledge Merkle-tree based membership proof to prove the ownership of some deposit. The zero-knowledge membership proof does provide anonymity for the involved parties in the Tornado transaction, however transactional amount confidentiality is not guaranteed since the fund value is sent in plaintext.

Merkle-tree?

Verifying the validity of transactions or proving ownership of a particular piece of data would likely checking each hash individually. This process would be compute intensive, especially as the number of transactions or pieces of data grows.

Rather than iterating through all those hashes, a more efficient approach is to assert "I know the preimage of one of the hashes" and "the hash is inside the Merkle Tree." This achieves the same result as referencing an extensive array of hashes but with improved efficiency.

Sorry, what?

Merkle Proofs exhibit logarithmic complexity in the tree size, making them relatively efficient compared to alternative methods.

In a transaction, let's say a transfer of tokens, two secret numbers are generates, concatenated, the result get hashed, that hash is sent over (to be added to the "Merkle tree").

During withdrawal, the withdrawer provides a leaf hash pre-image and proves that the leaf hash is present in the tree using a Merkle proof.

While this ties the depositor to the withdrawer, employing both the Merkle proof and the leaf pre-image verification in a zero-knowledge manner breaks this link.

Zero-knowledge proofs enable the proof of a valid Merkle proof against the public Merkle root and the leaf preimage without disclosing the computational process.

Merely providing a zero-knowledge proof of having a Merkle proof and producing the root is insufficient for security; the withdrawer must also demonstrate knowledge of the leaf pre-image.

As the Merkle Tree's leaves are public, anyone can generate a Merkle proof for any leaf and subsequently prove, in a zero-knowledge manner, the validity of their Merkle proof.

Hence, proving a leaf's presence in the tree alone is insufficient to prevent theft by proof forgery. The withdrawer must additionally prove knowledge of the preimage of the leaf in question without revealing the leaf itself.

In the provided code snippet for depositing funds, the commitment argument is public, while the commitment variable represents the leaf being added to the tree, which is the hash of two secret numbers, kept private by the depositor.

For withdrawal, the proof consists of validating the computation:

processMerkleProof(merkleProof, hash(concat(secret1, secret2))) == root

In the context of Tornado Cash, the verifier is the Tornado Cash smart contract that releases funds upon receiving a valid proof. The prover is the withdrawer who proves the execution of a hash computation to produce one of the leaves.

The withdrawer conducts the computation (Merkle proof and leaf hash generation), produces a zk-proof of its correctness, and submits this proof to the smart contract. The merkleProof and secret{1,2} are concealed in the proof, and with the proof of computation, the verifier can confirm that the withdrawer correctly ran the computation to produce the leaf and Merkle root.

In summary

Chaotic Waves

  1. Depositor
  • Generates two secret numbers and creates a commitment hash from their concatenation.
  • Submits a commitment hash.
  • Transfers cryptocurrency to Tornado Cash.
  1. Tornado Cash (During Deposit):
  • Adds the commitment hash to the Merkle Tree.
  1. Withdrawer:
  • Generates a valid Merkle proof for the Merkle root.
  • Generates the commitment hash from the two secret numbers.
  • Generates a zk-proof of the above computations.
  • Submits the proof to Tornado Cash.
  1. Tornado Cash (During Withdraw):
  • Verifies the proofs against the Merkle root.
  • Transfers cryptocurrency to the withdrawer.