---
# System prepended metadata

title: ZK Validator Proof of Reserves

---

*A way for liquid staking providers to verifiably report their total reserves backing their LSTs. Referencing Yi Sun's (Axiom) [post](https://hackmd.io/@axiom/rkfPDbNCs) which was labeled Rocket Pool but detailed a Lido implementation. For use at [Ion](https://twitter.com/ionprotocol) Protocol.* 

## Goal

Liquid staking providers today rely on centralized parties to report total reserve, or total redeemable value that backs an LST. Notably for oDAO, it is a small number of whitelisted clients who reports reward accrual on-chain to determine the rETH exchange rate. This process should be standardized and provable.   

Multiple factors goes into considering a provider's *total reserve*, in both execution and consensus layer states. 

This includes execution layer states such as 
- treasuries allocated for liquid backstops
- reward accrual addresses
- idle ETH in a waiting queue not yet deployed into validators

And consensus layer state of validator beaconchain balances that are actively being used as slashable security. 

This information in combination with circuits can then be trustlessly reported on-chain.

##  Proof
Three basic steps are necessary for trustlessly committing to a total reserve value on chain. First, maintain and upkeep relevant data to be used as input witnesses, second, construct the operations that needs verifiability in-circuit, and last, perform verification on a smart contract. 

**Querying Inputs** 
1. For execution layer state
    - maintain and upkeep storage slots for relevant contracts that holds balances that contribute to the reward. 
3. For consensus layer state 
    - maintain and upkeep validator public keys and their beaconchain balances that belong to a node operator set of a provider (registry maintained on execution layer).  
    - build a merkle branch for performing merkle inclusion proof for these validator pubkeys and balances. 

**Prover Operations**

Our circuit must prove three things: 
1. *Validator Inclusion*: that the supplied validator public keys belong to a specified provider.  
    - The circuit takes in the supplied storage slots that hold the list of relevant validator pubkeys. 
3. *Validator Balances*: that the supplied balances belong to the specified validator public key.
    - The circuit constructs the Simple Serialize hash for each validator pubkey and balance pair, then generates a SHA-256 merkle root by hashing the SSZ nodes with the supplied merkle branch.
    - This merkle inclusion proof should verify that the supplied balances belong to each of the specified validator pubkeys. 
5. *Total Sum*: that the supplied balances were correctly summed together to report a total balance. 
    - The circuit takes in the storage slots that hold additional execution layer reserves and its values. 
    - The circuit sums up the above with the balances reported for each validator pubkey and reports a singular sum. 

**On-Chain Verification**
- First check the output zkp through the generated verifier on chain.
    - For validator inclusion, this tells us that all of the supplied validator pubkeys belonged to that provider. 
    - For validator balalnces, this tells us that the merkle root outputted was correctly generated from the input validator pubkey-balance pairs and the supplied merkle branch. Note that this does not yet complete the merkle inclusion proof as it has not been compared to the real beaconchain root. 
    - For total sum, this tells us that the sum correctly includes balances at the proposed storage slots as well as balances tied to each validator pubkeys.
- If the above passes, then we compare the output merkle root from the *Validator Balances* step with the beaconchain root. 
    - With EIP-4788 in the upcoming upgrade, the beaconchain merkle root should be available for trustless query in the execution layer either from the precompile or actual bytecode. 
    - This tells us that the supplied ETH balances for each pubkey in the beaconchain was correct. 
- Combining all of the above, we now have a provable sum of beaconchain balances that belong to a node operator set within a given provider.

**Trust Assumptions**
- This does guarantee that the reported total balance is a correct summation of the balances that verifiably belong to each of the supplied validator public keys. 
- This does guarantee that the supplied validator public keys are part of the on-chain registry's predefined storage slots.
- But if providers add or change where in a contract storage their validator public keys are stored: 
    - Can underestimate the total reserve if not all relevant public keys are reflected in the proof. 
    - Cannot overestimate the total reserve as any pubkeys not included in the defined slots would fail the verification step. 

## Rocket Pool Spec
Handling the off-chain queries that are specific to each providers. The prover operation and on-chain verification step should be standardized across providers. 
### Querying Inputs

**Execution Layer Reserves**

Addresses should be added here if keeping redeemable reserves.

**Validator Pubkey Upkeep**
- `WatchMinipoolCreated`: to watch for new validator being added
    - This listens to the event `MinipoolCreated` being emitted in the `createMinipool` function in the `RocketMinipoolManager.sol`. 
    - However, this function is being used in `createVacantMinipool` function and when the event is being emitted, it’s just an empty object with no balance or pubkey being set as shown below. We just have to watch for it to register the validator with pubkey/balance etc. and probe it until it does.
- `WatchMinipoolDestroyed`: to watch for new validator being destroyed

**Relevant Addresses**
```solidity
ROCKET_MINI_POOL_MANAGER_CONTRACT_ADDRESS="0x6d010C43d4e96D74C422f2e27370AF48711B49bF"
ROCKET_NODE_MANAGER_CONTRACT_ADDRESS="0x89F478E6Cc24f052103628f36598D4C14Da3D287"
ROCKET_TOKEN_RETH_CONTRACT_ADDRESS="0xae78736Cd615f374D3085123A210448E74Fc6393"
ROCKET_REWARDS_POOL_CONTRACT_ADDRESS="0xA805d68b61956BC92d556F2bE6d18747adAeEe82"
ROCKET_DEPOSIT_POOL_CONTRACT_ADDRESS="0xDD3f50F8A6CafbE9b31a427582963f465E745AF8"
ROCKET_VAULT_CONTRACT_ADDRESS="0x3bDC69C4E5e13E52A65f5583c23EFB9636b469d6"
ROCKET_SMOOTHING_POOL_CONTRACT="0xd4E96eF8eee8678dBFf4d535E033Ed1a4F7605b7"
```