---
# System prepended metadata

title: FLAX Privacy Layer - Updated

---

# FLAX Privacy Layer - Updated

## Introduction

FLAX is a drop-in-place privacy layer for EVM chains that is compatible with existing protocols and tokens. FLAX enables:
- Anonymous interaction with any on-chain application using ERC20/721/1155 assets
- Confidential payments (hidden sender, recipient, and amount)


FLAX enables easy and usable privacy, focusing on three core properties---flexibility, composability, and cost.

#### Flexibility
FLAX is a dapp that can be deployed to any EVM chain. There is no need to bridge to a separate L1 or L2 for privacy. FLAX serves as a drop-in-place privacy layer for existing ecosystems.

#### Composability 
FLAX is backwards-compatible with all existing assets and on-chain applications. No wrapped assets and no smart contract modifications---the original token contracts remain untouched. The accounting is simply moved up one layer to the FLAX protocol.

#### Cost
Through transaction batching and zk proof aggregation, we will eventually be able to achieve private usage of funds at an overhead cost of roughly ~150-200k gas per operation.


## High Level Architecture

FLAX uses one large multi-asset pool, known as the Vault, to store every user's funds. All user interactions with dapps are initiated from the FLAX Wallet contract without revealing the identity of the user.
* Vault.sol - Responsible for holding all of the funds and is only callable by the Wallet 
* Wallet.sol
    * Manages all of the note commitments and therefore keeps track (cryptographically) of which users own which notes
    * Makes the external contract calls for the user
    * The entry point for users when they want to perform a DeFi transaction or a smart contract call

The Wallet will request the funds needed from the Vault, and then perform the contract call. Once the contract call is made, the Wallet will send back any remaining or new funds back to the Vault. The Wallet will then create new note commitments for these values for the user. The following diagram illustrates a high level view of the workflow:

![](https://i.imgur.com/Rq7h5S0.png)


### FLAX API

#### Wallet.sol

**[1]** `DepositFunds(Deposit deposit)`
* Creates new note commitments for the deposited amount for the user calling this function.
* Caller must be the owner of the deposit tokens.
    * This is to prevent any user from making your deposits for you if you have approved the vault to spend the tokens
* User must first approve the **Vault** to spend the deposited amount.
    * The Vault is the one making the transfer to prevent users from using processBundle with a malicious action to transfer the funds how they like
* Calls the Vault.makeDeposit(deposit), and the Vault will call (assetType).transfer(deposit.spender, address(this), deposit.value).

**[2]** `ProcessBundle(Bundle bundle) returns (bool[] successes, bytes[][] results)`
* Performs every Operation inside the bundle
* For each Operation, handles:
    * Spend Txs - Verifies user owns the notes listed in the spend tx. Gathers specified funds from the Vault for each valid spend tx.
    * Actions - Makes an external call to the user-defined smart contract with the user-defined data for each Action. Note: An action can not call the Vault.
    * Refund Txs - Creates new note commitments for the user for each asset type with positive balance. Sends the funds to the Vault. Asset types must be known in advance.
* The Wallet will contain 0 funds before this call, and 0 funds after. 

**[3]** `ConfidentialTransfer(...) returns (bool success)`
- Takes list of spend proofs, an asset address, an amount, and a recipient address
- Nullifies the spent note commitments
- Enqueues new note commitments for the spent assets that now belonging to the new recipient


**[4]** `Commit8FromQueue()`
* Commits 8 leaves in the leafQueue to the noteCommitmentTree. The sequencer is responsible for calling this function periodically. Note: User's cannot spend note commitments in the queue. They must be commited to the tree via this function in order to be spent.

#### Vault.sol
These are the exposed functions that only the Wallet can call.

**[1]** `RequestFunds(uint256[] values, address[] assetTypes) onlyWallet`
* Transfers the given value for each asset type to the Wallet.
* Called by the Wallet after the Wallet verified valid SpendTxs corresponding to the given values and asset types.
* Not callable by processBundles since actions cannot call the Vault.

**[2]** `makeDeposit(IFLAXTeller.Deposit calldata deposit) onlyWallet returns (bool)`
* Calls assetType.transfer(deposit.spender, address(this), deposit.value) - essentially transfering approved funds to this contract.
* Returns the success status of the transfer call.


### Batching
In order to get the lowest gas cost per user interaction, FLAX will batch multiple transactions into one and submit them on-chain via a sequencer. This may initially add a mild latency to user transactions (parametrizable based on user preferences). Under early low volume, FLAX will likely subsidize costs to ensure low latency. In the future, higher volume will make batching more time and cost effective.

### Proof Aggregation
In order to further reduce gas costs, we will aggregate multiple users' spend proofs into single aggregate proofs. This is possible through recursive composition or [other aggregation schemes](https://eprint.iacr.org/2021/529.pdf).


## FLAX Usage

Using FLAX privacy is extremely simple. Once you deposit assets into the pool, you are free to make private transfers or contract calls as usual. The underlying ERC20/721/1155 assets remain unchanged (no wrapped assets). All that changes is that the accounting of said assets is moved to our protocol layer.

Note: Both confidential payments and anonymous contract calls will be usable manually through our browser extension (likely a MetaMask snap) or programatically through our SDK. The SDK contains all logic for tracking and spending private balances. The browser extension wraps the SDK functionality in a simple UI.

### Confidential Payments
1. Deposit funds to the shielded pool via the wallet's deposit methods.
2. Attain a shielded address for your recipient. Given knowledge of your counterparty's address base (an identifier privately shared with you by the recipient), our SDK will automatically handle generating the shielded address.
3. Call an exposed `confidentialTransfer` method on the wallet, entering an asset type, amount, and shielded recipient address.


### Anonymous Contract Calls
1. Deposit funds to the shielded pool via the wallet's deposit methods.
2. Specify your desired assets, contract calldata, and contract address.
3. Format the contract call into a FLAX operation  and call `performOperation`(handled by SDK or browser extension).


## Compliance
While we seek to empower users with on-chain privacy, we will also take measures to be regulatorily compliant where needed.

### Per-User Viewing Keys
Each user has their own viewing key. Knowledge of a user’s viewing key allows one to link together the user’s private transactions. Usage of specific assets within FLAX can be made contingent on sharing one’s viewing key with a privileged party (e.g. an asset issuer). 

### Travel Rule (and other custom logic)
As an extension of per-user viewing keys, we can encode custom conditions for requiring sharing of viewing keys. For example, we could require users to share viewing keys only for transactions that use more than 1,000,000 USDC.

### Privacy-Preserving Blacklists 
Services such as Circle or Chainalysis provide blacklists of illicit addresses. In order to draw on such blacklists while still preserving privacy, we can deploy a custom zk blacklist. For each FLAX call, we could enables users to provide a zk proof that their shielded address is not included in the blacklist without revealing their address.

## Appendix

### Other Future Features
* **Note joining** - We can currently only spend one note at a time. We want to enable joining notes together via a single spend proof. This will reduce spend costs and enable easier confidential payments.
* **Easier confidential payments** - Given UX benefits provided by note joining (above), we will likely build out a whole set of tooling specifically around confidential payments.

### Gas Estimates

Note: Got these from Wei Jie's [slides](https://docs.google.com/presentation/d/1G1zQjTKPKclUtwaYidek07eceL7BB1rZ0nMH39dU3yw/edit#slide=id.p) and [research post](https://ethresear.ch/t/gas-and-circuit-constraint-benchmarks-of-binary-and-quinary-incremental-merkle-trees-using-the-poseidon-hash-function/7446) from 0xPARC group.

#### Cost By On-Chain Op
* Groth16 proof verification: ~200k gas + ~21k gas per public input
* Aggregate proof (8 aggregated spend proofs) verification: ~350-400k gas
* Poseidon (2 inputs): 53189 gas, 240 constraints
* Poseidon (5 inputs): 121984 gas, ??? constraints
* SHA256 (2 inputs): 2179 gas, 409926 constraints

#### Prover Costs
* Proof generation (1.1M constraints):
  * ~ 7 seconds on an Intel i7 1.80GHz laptop with rapidsnark (optimised for Intel x86)
  * ~ 17 seconds with zkutil (Rust/bellman)
  * Minutes with snarkjs (Nodejs)
* Binary Merkle Tree (Poseidon Hash)
    * Depth = 32, Gas Cost = 1.32 million (for entire transaction)
* Quinary Merkle Tree (Poseidon Hash)
    * Depth = 15, Gas Cost = 1.78 million (for entire transaction)

#### `Operation` On-Chain Op Costs
* 1 merkle tree insert per spend ~1.3 million gas
* 1 batch merkle tree insertion for 8 note commitments ~1.4 mil
* 1 batch merkle tree insertion for 16 note commitments is ~1.6mil
* 1 proof verification per spend ~200k gas + 21k per pub input
* 1 Poseidon hash per refund tx ~50k gas
* 1 merkle tree insert per refund tx ~1.3 million gas
* 2 contract.balanceOf calls per ERC20 interacted with

#### Costs for *n* `Operation`, *m* spends, and *p* refunds
* 1 proof verification per spend ~m*(200k + 7*21k)
* 1 poseidon hash per refund ~p*50k
* 2 transfer and balance calls per asset involved ~4.1k for transfer, ~1.4k for balanceOf (depends on ERC20 impl)
* Total estimated overhead (up to 8 note commitments, (m + p) <= 8): ~1.4mil + m*(200k + 7* 21k) + p*50k

#### `Operation` Costs In Different Situations:
* Assuming **4 operations batched** (n = 4) --- 1 spend and 1 refund per operation (8 total potential note commitments commited in a batch to the merkle tree):
  * 1.4 million + 4*(200k + 147k) + 4*50k
  * = ~2.988 million gas for the bundle
  * = ~747k gas per user operation
* Assuming **8 operations batched** (n = 8) --- 1 spend and 1 refund per operation (16 total potential note commitments commited in a batch to the merkle tree):
  * 1.6 million + 8*(200k + 147k) + 8*50k
  * = ~4.776 million gas for the bundle
  * = ~597k gas per user operation
* Assuming **8 operations batched** (n=8) with **spend proof aggregation** --- 1 aggregate spend proof, 1 refund per operation (still 16 potential note commitments added to tree between spends and refunds):
  * 1.6 million + ~350k + 8*50k
  * = ~2.35 million gas for the bundle
  * = ~293k gas per user operation

* Assuming **8 operations batched** (n=8) with **zk merkle tree updates** and **spend proof aggregation** --- 1 merkle tree update proof, 1 aggregate spend proof, 1 refund per operation:
  * ~350k + ~350k + 8*50k
  * = ~1.1 million gas for the bundle
  * = ~138k gas per user operation

### Proving time estimates

Approach 1: 12k constraints, <5s with wasmsnark, per numbers provided here https://github.com/tornadocash/tornado-core/tree/master/circuits
Approach 2: 16k constraints, ~5s with wasmsnark

### References
* Original FLAX Paper - https://eprint.iacr.org/2021/1249.pdf
* If you have questions or are interested jamming together, please reach out to luketchang00@gmail.com
