Account-Based Anonymous Rollup

(authors: Alexandre Belling, Olivier Bégassat, Nicolas Liochon)

This extends the rollup proposal (On-chain scaling to potentially ~500 tx/sec through mass tx validation) to support anonymous transactions. By anonymous we mean (1) participants in a transaction are unlinkable (2) transferred amounts are unknown (3) participants’ balances are unknown. Optionally, (4) participants are hidden even to the other participants in the transaction.

Some familiarity with rollups and zCash is recommended :-).

Main ideas

The main concepts are:

Operators and blockchain have the same responsibilities as in a standard rollup

Accounts are not stored in clear like in a standard rollup: to update their accounts users send only states’ hashes (old value, new value), plus a ZKP to prove that the transition is valid.

To send money, users use money orders. Operators register money orders. Once registered, a money order cannot be repudiated.

Like zCash, there is a Merkle tree to store the created money orders, and another Merkle tree to store the nullified money orders. These Merkle trees are not global but per account.

The rollup state furthermore includes a Merkle tree to keep track of previous root hashes. This allows proving that a money order was previously registered.

Data structure

State

The state is the highest level representation of the roll-up state

Type Description Accounts MerkleTree[Account] Contains the hash of all accounts. Has depth 24. PreviousStates MerkleTree[State] A Merkle tree containing the previous states of the roll-up. It is filled sequentially. When the tree is full, we loop back and overwrite the oldest state root with the newest one. This ensures the previous state roots stay accessible to the roll-up for some duration (it stays available for ~8years).

Accounts

Accounts contains user’s private data

Type Description AccountData AccountData MoneyOrderSent MerkleTree[MoneyOrder] One-time list of created money orders. It is reset at each MoneyOrder creation. MoneyOrderConsumed SparseMerkleTree[Hash(MoneyOrder)] The insert-only list of nullifiers. Essentially, this Merkle tree keeps track of the transactions received by the account. Its depth is 64. ExtraData string A placeholder that can be used for more complex custom roll-ups, for instance: cross-roll-up transfers

AccountData

AccountData contains plaintext values, that we don’t want to be disclosed when the sender creates a Merkle proof of inclusion of a specific money order.

Type Description Balance int Balance of the account PublicKey int The public key of the account owner Randomness int Some secret randomness to hide the account data

MoneyOrder

A money order describes the intent of doing a transfer of money. Its view is restricted to the transaction participant.

Type Description To int Recipient’s public key Amount int Amount of token transferred Id int Unique id for each transaction. Id = hash(PubFrom Amount, To, Salt) ExtraData string Extra data for real-world use cases. For instance a command identifier.

Additionally, we describe below the structure of a Money Order Receipt (MOR).

MoneyOrderReceipt

A money order receipt is a message sent by the sender to the receiver to let him that he created a money order for him and a proof that he did.

Type Description MoneyOrder MoneyOrder Money whose this receipt is about ProofOfRegistration MerklePath a Merkle Path to a state root hash where the Money Order was registered. Can be a ZKP to hide the sender account from the receiver.

ZKP description

Technically, there should be a single ZKP for registering and using money orders. For clarity we detail two independent ZKP:

creationZKP

Public Inputs

Description AccountHashBefore AccountHash before the transaction AccountHashAfter AccountHash after the transaction

Constraint Summary

Name Description RangePBalance Proof that the resulting balance is positive RangeProofAmount Proof that the transferred amount is positive MKP_MoneyOrder Proof that the claimed MoneyOrder root is the claimed one OpenAccountBefore Proof of that the claimed account content matches the public account hash OpenAccountAfter Proof that the new account Hash matches the new account content Possession of private key Proof that the sender actually owns the account

receiptZKP

Public Inputs

To The receiver PublicKey or temporary key Amount The amount of token transferred Id Id of the transaction, see MoneyOrder.Id StateHash A valid former roll-up state Hash

Constraint Summary

Name Description OpenMonerOrder Get the hash of the claimed money order MKP_Account_sender Proof of inclusion of the claimed sender account hash MKP_MoneyOrder Proof of inclusion of the claimed money order OpenAccount Proof of that the claimed account content matches the public account hash

The receiptZKP is optional. It allows the money order creator to hide his accountID to the recipient of the money order. However, doing it implies a second level of recursion and thus, use costly MNT4-6 curves cycles or Cock-Pinchs curves.

MoneyOrderRedemptionZKP

Public Input

Description AccountHashBefore AccountHash before the transaction AccountHashAfter AccountHash after the transaction CurrentStateRootHash State root hash used to prove correctness the used money order receipt. By current, we mean the time at which the time the redeemed user creates the proof.

Constraint Summary

Name Description Verify MoneyOrder Verify the correctness of the money order MKP_Nullifier_0 Proof that the position at which we will insert the new nullifier is empty. (IE: the transaction wasn’t already consumed) MKP_Nullier_1 Proof that the claimed Nullifier root after insertion is the claimed one OpenAccountBefore Proof of that the claimed account content matches the public account hash OpenAccountAfter Proof that the new account Hash matches the new account content OldStateProof Proof that the money order was verified with a correct previous state root

Operator Proof (valid for both receiver and sender execution)

Public Inputs

Description AccountHashBefore AccountHash before the transaction AccountHashAfter AccountHash after the transaction rollupStateBefore The Root hash of the roll-up state before rollupStateAfter The root hash of the roll-up state after oldStateInclusion* MerkleProof of inclusion of the root proof

Needed for Money Order Redemption execution

Constraint Summary

MKP_Account_before Proof of inclusion of the claimed sender account hash MKP_account_after Proof of correctness of the resulting root hash MKP_old_transition Proof of inclusion of the old root Verify transition Verify the correctness of account hash transition

Workflow

The full workflow from user S to send money to a user R is:

R creates its account info AI : hash(random number saltX, private key)

R sends this information to S, off-chain

S (1) creates a money order (2) adds its to its created orders accumulator (3) calculate the new hash value for its account (4) generate the creationZKP

S sends the update transaction to the operator, with the old hash, the new hash and the creationZKP

The operator generates a rollupUpdateZKP for all the transactions received, and sends a global update transaction to the blockchain.

S watch the blockchain to see if is account was updated. Once it’s done it can generate a receiptZKP to link its update to the global rollup state Sx. S creates a Money Order receipt and sends it to R.

R checks receiptZKP, and checks on the blockchain that the root hash Sx provided correspond to an existing rollup update. If so, R knows that the money order is registered and the payment final (with caveat relating to blockhain’s finality as in a standard rollup).

For R to transfer the amount of the money order to its own account, with a receipt for a money order M, going to a historical state Sx of the rollup.

R gets a Merkle Path from a recent version of the rollup, called Sc, to Sx.

R generates a ZKP, which includes: proof that M was included in the history of Sc, with the orderReceiptZKP and the Merkle Path from Sc to Sx. proves that M was not in R’s nullifiers.

R send the update transaction to the operator.

Practicalities

If we want to hide the sender from the receiver, we need to use a ZKP, hence a curve that supports 3 levels of recursion (eg. MNT4 or 6).

The users should connect to the operator with a protocol such as Tor to hide their ip addresses.

The operator knows the Merkle Tree of the historical states. The users need to access this data to create the proof of past inclusion for the receipt. To prevent the operator to correlate such request to account updates the user can first get the data from the operator, then watch the blockchain for any amount of time to update the Merkle Path.

Money orders can be checked until the proof of registration leads to a state known in the state history. In other words is money order is limited in time. With 2^24 states kept and 10 seconds between two rollup update a money order is valid for more than 5 years.

Performances

With MiMC as the hash function, a cost of 20K constraints to check a proof, and 2^24 accounts, we can estimate the operator cost for a standard rollup and compare it with this anonymous rollup:

Standard:

Transfer: 24 * 2 * 2 hashes to verify + signature (~2000 constraints) = ~70k constraints

Anonymous:

Money order registration/utilisation/both by a single account: 24 * 2 hashes + ZKP to verify ~70k constraints. Sender’s anonymity for the receiver requires specific curves (MNT4) slower and currently less optimized than BN256.

Anonymous rollups also support batching, i.e. it’s possible to register/use multiple money orders in a single operator transaction. In the data structure proposed above, it’s possible to create 2^8 money orders in a single transaction, while there is no limit for the operator on the number of money orders used in a single transaction.