Anonymity Points claimed as rewards for anonymity mining are stored in "shielded" accounts within a Merkle Tree, using many of the same patterns as in the core deposit contract and Tornado Trees.
The special property that the accounts tree brings to the table is that only the owner of the account knows its balance. Each time that an account's balance is updated by claiming additional rewards, or withdrawing from it, a new account is created, and the old account is nullified.
The balance of an account is stored as a component of the commitment hash that is inserted into the tree. The balance of an account can only by known by having the private key used to produce an encrypted backup of its commitment inputs.
An account commitment consists of three components:
- The 31-byte amount of AP in the account
- A 31-byte random secret
- A 31-byte random nullifier
An account commitment hash is the Poseidon hash of these three components
Claiming a reward
Inputs to a reward proof
The total set of public inputs for a reward proof are:
- The block reward rate for the claim
- The fee that you're paying the relayer (or zero)
- The Tornado instance address associated with the note
- The reward nullifier (Poseidon hash of the note nullifier)
- The args hash
- The root of the account tree pre-claim (input)
- The nullifier of the existing account being added to
- The root of the account tree after updating it (output)
- The path indices to the new account, as an integer, left-padded with zeroes
- The commitment for the new account
- The root of the deposit tree
- The root of the withdrawal tree
The total set of private inputs for a reward proof are:
- The secret of the note being claimed
- The nullifier of the note being claimed
- The amount coming from an existing account
- An array of path elements to the existing account commitment in the tree
- The path indices to the existing account, as an integer, left-padded with zeroes
- The balance of the new account
- The secret of the new account
- The nullifier of the new account
- An array of path elements to the new account
- The block number when the claimed note was deposited
- The path indices to the deposit in the Tornado Trees deposit tree, as an integer, left-padded with zeroes
- An array of path elements to the deposit in the Tornado Trees deposit tree
- The block number when the claimed note was withdrawn
- The path indices to the withdrawal in the Tornado Trees withdrawal tree, as an integer, left-padded with zeroes
- An array of path elements to the withdrawal in the Tornado Trees withdrawal tree
Rewards are claimed by proving that:
- You know the path to a deposit and withdrawal event pair
- The deposit and withdrawal correspond to the same note whose secret and nullifier you know
- The withdrawal event's block number is a specified duration after the deposit event's block number
- The amount you're claiming is then a multiple of the agreed-upon reward rate, times the specified block duration
- The disclosed "reward nullifier" is the Poseidon hash of the original note nullifier
An invariant constraint is first created showing that the amount that is coming from an existing account, plus the block reward rate times the block duration, is equal to the new account's balance plus the relayer fee.
An overflow check is then performed on the input, output, and block duration values to ensure that they fit within 248 bits, 248 bits, and 32 bits, respectively, without overflowing.
Input account validation
The input account balance, secret, and nullifier are hashed to obtain the input account commitment, and then the path elements and path indices are used to verify that such a commitment exists within the specified input root, but in such a way that if the input account balance is 0, the check passes regardless.
This scheme allows for new accounts to be created without publicly disclosing that they are new.
The public input nullifier hash is checked against the input account's nullifier component.
Output account validation
The output account balance, secret, and nullifier are hashed to obtain the output account commitment, and are checked against the public output commitment hash.
The account tree update is validated using the input root, output root, output commitment as a new leaf, and the specified output path elements and indices.
Tornado trees validation
The Tornado Commitment used for the note being claimed is computed using the note secret and nullifier, and then the corresponding commitment in the Tornado Trees deposit tree is computed using the Tornado instance address, note commitment, and deposit block number as components.
The deposit commitment is then verified to exist at the specified deposit path beneath the deposit tree root.
The withdrawal commitment in the Tornado Trees withdrawal tree is then computed using the Tornado instance address, note nullifier hash, and withdrawal block number as components. The withdrawal commitment is verified to exist at the specified withdrawal path beneat the withdrawal tree root.
The reward nullifier for the provided note is then computed by generating the Poseidon hash of the note's nullifier. This is compared to the reward nullifier specified as a public input.
Args hash square
As a last step, the args hash is squared into a public output, to ensure that the reward transaction parameters cannot be tampered with relative to the proof.
Completing the reward transaction
Using the generated proof, we can now call the
reward method of the Miner contract.
The reward method takes the proof, and the public inputs to the proof, and verifies:
- The input account nullifier has not already been used
- The output account path corresponds to the input root leaf index
- The deposit and withdrawal roots are the current or previous roots of the Tornado Trees contract (are known roots)
- The args hash is correct with respect to the extData args
- The relayer fee is within a valid 248-bit integer range
- The reward rate equals what is expected for the Tornado instance specified
- The reward has not already been claimed according to the reward nullifier
- The proof is valid according to the Reward verifier, using the public inputs provided
If these preconditions are met, then:
- The input account is nullified
- The reward is nullified
- The account tree root is updated to the specified output account root
- If applicable, the relayer is rewarded TORN using the fee AP at the current AMM rate
- A New Account event is emitted
There is an alternate method in the Miner contract which deals with "batch rewards". While most transactions claim rewards individually, batch reward transactions claim multiple rewards at once. This avoids having to wait for the next block for each reward claim, since each claim has to insert a new leaf from the last account root.
A batch reward transaction specifies a sequence of
reward method parameter sets, which are executed incrementally.