The execution proof performs local and global checks expected from the execution in a verifiable manner by producing a succinct proof of knowledge. This post aims to further develop the idea described here.
Proof stages
We can differentiate between three proof stages characterized by their relationship with the RM:
- Raw RM proofs - the proofs produced directly by RM for each transaction. Include compliance proofs, logic proofs, and the delta proof
- Aggregated proof - combines the raw RM proofs into a fixed number of proofs [1]. It can either be a part of internal RM functionality or performed over an RM transaction with raw proofs. For simplicity we assume it is a part of the RM functionality.
- Execution proof - a proof over multiple transactions that verifies the global execution checks in a verifiable manner
The table below compares some properties of these proofs.
Raw proofs | Aggregated proof | Execution proof | |
---|---|---|---|
Scope | CU, Action, Transaction | Transaction | Multiple transactions |
Recursive | No | Yes | Yes |
Created by | RM | RM* | Not RM |
Created by | Users, Solvers | Users, Solvers | Controller |
Resource Plasma Assumptions
We discuss the execution proof in the context of Resource Plasma. For the rest of the post we assume the following:
- Commitments are stored in a Merkle tree
CM
- Nullifiers are stored in a sorted Merkle tree
NF
- The Anoma Contract stores the hash of the current root of the commitment and nullifier trees:
currentState = hash(CMroot, NFroot)
[2] - The underlying RM is the RISC0 RM
The execution circuit
The circuit is parametrised over n
- the number of transaction it processes at once. For each transaction, it performs the out-of-circuit RM checks and then verifies the total state transition induced by applying these transactions.
Out-of-circuit checks required by RM
The following out-of-circuit checks are required in addition to in-circuit checks to ensure that the state transition is computed correctly:
currentState
is a valid historical state – guaranteed by receiving it from the Anoma Contract [3][4]- CU are disjoint within an Action:
- The same resource is not consumed in two different CUs
- The same resource is not created in two different CUs [5]
- Actions are disjoint within a Transaction:
- The same resource is not consumed in two different actions of the transaction
- The same resource is not created in two different actions of the transaction [6]
- RM proofs are valid:
- The aggregated SNARK proof are valid
- Delta proof is valid
- Nullifier non-existence proof for consumed resources
- Commitment non-existence proof for created resources [7]
Global checks in a circuit
Out-of-circuit RM checks include both local and global checks that require access to global structures. By definition, it isn’t possible to put global checks in a circuit, therefore we assume that the global state doesn’t change between the moment of proving against the currentState
value and applying the changes associated with the created execution proof.
Inputs
Instance:
currentState
- a commitment to the current state stored in the Anoma ContractnewState
- a commitment to the proposed state
Witness:
n
transactions TX_in + 1
CM root (init state →tx_1
updated state →tx_2
updated state → … →tx_n
updated state) CR_in + 1
NF root NR_i
Constraints
The execution circuit constraints verify each transaction individually and ensure that they correctly update the current state into the new proposed state.
- hash(CR_0, NR_0) = currentState
- hash(CR_n, NR_n) = newState
For each transaction:
- Action.consumed are disjoint for all actions in TX_i
- CU.consumed are disjoint for all CUs for all actions in TX_i
- Verify RM proofs:
- Aggregator.verify(TX_i)
- ECDSA.verify(TX_i.deltaProof, deltaVerifyingKey)[8]
- Non-inclusion proof for consumed in TX_i resources and NR_{i - 1}
- CR_{i+1} = CR_i + TX_i.created – CR_{i+1} is produced from CR_i by adding commitments of resources created in TX_i
- NR_{i+1} = NR_i + TX_i.consumed – NR_{i+1} is produced from NR_i by adding commitments of resources created in TX_i
Questions
- Find a more efficient way to verify non-inclusion for nullifiers
- Currently the tree update constraint is underspecified. It has to be specified further and the list of inputs might have to be possibly updated
Potentially relevant links
- Reckle Trees: Updatable Merkle Batch Proofs with Applications
- Vector Commitments Study
- Zero-Knowledge Sets
- MPT
- Taiko rollup
- Scroll rollup
- Starknet SNOS
- ZKSync
Currently two: compliance and logic proofs are aggregated into one and the delta proof is left as is ↩︎
The hash function used to compute it must have a binding property ↩︎
Strictly speaking, the specification requires the CMtree root to be a valid historical root, but in the context of Resource Plasma, we have two trees and store their hash ↩︎
When verifying the execution proof from the past, the validity of the historical state can be verified by checking the history and making sure that the state used to prove the statement against is indeed valid ↩︎
This statement follows from 2.1 and the fact that we use the nullifier of the old resource as a nonce of the new resource ↩︎
This follows from 3.1 and the fact the same resources have the same nonces ↩︎
Implicitly follows from the fact that all consumed resources are unique (points 2, 3, and 5 ensure that) ↩︎
Delta verifying key is computed from the transaction components ↩︎