A thread for me and @xuyang to sync work on the protocol adapter.
-
Verify Cairo proofs on EVM
- evm-verifier-contract - Cryptographic STARK verifier elements
- We need to further investigate the verifier-contract and proving system(lambda-cairo-prover) to ensure format and API compatibility. Ideally, we should create tests to verify the pipeline.
-
Verify Risc0 proofs on EVM
- risc0-ethereum: RISC Zero’s Ethereum contracts, including the on-chain verifier for all RISC Zero Groth16 proofs
- We will use risc0’s toolchain to construct tests verifying risc0 proof validation on EVM.
This is the Starknet docs page, where all deployments of the Starknet solidity verifier are listed Solidity verifier :: Starknet documentation.
The GpsStatementVerifier
implementation behind this proxy and the verifyProofAndRegister
insides seems to be the main entry point:
function verifyProofAndRegister(
uint256[] calldata proofParams,
uint256[] calldata proof,
uint256[] calldata taskMetadata,
uint256[] calldata cairoAuxInput,
uint256 cairoVerifierId
) external;
The last input argument uint256 cairoVerifierId
seems to indicate the verifier layout that should be used. Apparently, there are eight:
- “plain”
- “small”
- “dex”
- “recursive”
- “starknet”
- “recursive_large_output”
- “all_solidity”
- “starknet_with_keccak”
I guess the main challenge (mostly for you @xuyang) is figuring out what formats and which layout we have to use.
I can create a Solidity project in which we can play with the deployed verifier on mainnet locally (so that we don’t need to pay gas for trying things out).
It would be good to have an example STARK proof in the right format from your shielded RM implementation.
Probably it makes also sense to connect with the Starknet people @xuyang.
Protocol Adaptor meeting Notes Dec 16 , 2024
settlement only protocol adaptor
we have a transaction object which gets submitted to an EVM contract and EVM contract needs to
- run usual verification checking validity + balance
- if in fact valid then apply the state changes (commitments and nullifiers + potentially storage)
Is there a Cairo verifier we can just re-use. There are a bunch of contracts deployed and one that contains verifier proof and register function, but think this is the entry point, but unclear what the format is. There are different layouts, main difficulty is to figure out how the data has to go in.
First thing we are trying to build is settlement only Cairo Protocol adaptor. Taking tx object validating proofs and checking the state changes. Only proofs will be Cairo proofs, all proofs must align with Cairo RM. Poseidon Hash function. Did we find a Poseidon implementation on the EVM. Can look into solidity contract from Starkware.
Would hashing happen in the contract or locally?
- PA contract needs to implement is valid and balanced as defined in resource machine specs
- Implement verify
Do we need to compute hashes if we are just verifying a transaction? If we want to open the commitment from the commitment tree, we need the hash.
If we store the state off-chain, can build the Merkel tree off-chain.
We should just implement a Merkel tree in Solidity. What you would store in the mapping is previous Merkel tree roots and commitments.
Solidity storage
- commitments
- nullifiers
- Previous Merkel tree roots (which can be used for proofs)
- Store Binary data (which can put in mapping) if instructed by tx
In terms of storage, comprehensive?
Yes
Is it clear what to do for validity checking, balance checking, and state updates?
Yes
EVM interop
EVM state changes and resource state changes can be coupled so you can send 10 WETH to the PA contract mint 10 WETH resource, do stuff with the resource then someone can later withdraw a total of 10 WETH.
For ERC-20s
-
we want to be able to write a resource logic which says new resources can only be created when some ERC-20 tokens are transferred to the PA, and that ERC-20 tokens can be transferred from PA when a resource is consumed. (counting for the balance).
-
We want ERC-20 transfers to count as the balance delta. A simple way to do it is to special case ERC-20 transfers in the contract
-
We could also, hardcode the ERC-20 transfer logic in the PA, pass as input to resource logics what ERC-20 transfers have happened, they can accept or reject also PA needs to be careful about enforcing unauthorized erc-20 transfers
-
Wrapper contracts for each asset (need to call the PA and allow creation of a resource somehow, move the logic of the ERC-20 token becoming a resource move it out of the PA contract) - wrapper contracts for each mapping. Wrapper contract holds tokens and non PA, and somehow check the right resource is created.
-
Another way of going about this would be to somehow convert the EVM state changes into a delta and add the deltas to the tx s.t. the tx can only be balanced if certain resource state changes happen, might be compatible with both things. by default EVM doesn’t have concept of balance but if we know the transfers we can calculate the balance first through transfers then calculate the EVM delta add this. we would have to associate each ERC-20, 721, 1155, or w/e transfer with a specific kind to turn it into a delta. Maybe we make a sort of ERC-20, ERC-721, etc. wrapped resource logic and then tokens could be converted too and from these wrapper resources in the transaction.
- somewhere we have logic which enforces the correspondence, kind of like putting this more in the resource machine, makes it more clear from the perspective of RM programs what exactly is going on
-
When executing a transaction PA also takes EVM action instructions (transfer ERC-20 from amount or transfer ERC-721 from amount, then at the start of executing the transaction the pa contract will execute the ERC-20 contract transfer from, and it will track an EVM delta corresponding to the total transfer amount where each contract is associated with a particular resource kind, this is an arbitrary decision we can enforce, which means we can have resource logic corresponding to resource kind do whatever we want it to - can enforce - allow themselves to be converted to native Anoma token resources or similar.
For example, we can have ERC-20 wrapped resource logic put the contract address in the resource label then derive the kind. So we need only one for each semantically unique Ethereum thing we are trying to wrap. Reasonably elegant. You could just transfer around ERC-20 wrapped tokens in the resource machine without doing anything else. We can also allow ERC-20 wrapped to be converted to and from some corresponding Anoma token.
If we limit kudos to ERC-20 resource logic and no, you say there is a wrapper contract, and it ensures that tokens being transferred create an equivalent. What I am really suggesting is the PA supports making arbitrary EVM state changes. You specify some EVM state changes you would like to happen.
Send to PA so it can track what is going on. If the PA is executing transfer from on ERC-20 one can imagine generalizing this to arbitrary call data (need to think about security properties) combine these to a delta. PA enforces the balance check, if you want to deposit 10 WETH in rm state in your tx before executed on EVM need to create 10 WETH ERC-20 resources so when its executed on EVM tx will be balances, using balance check to enforce state change correspondence.
This seems like a reasonably good place to start, with just ERC-20, and seeing if we can get the pattern to work.
Discussion
Do we expect or want to have this first version? I can’t work on the Cairo part, but can work on the interop part and Merkel tree part. When doing this, will understand all the details better. What part should we start with?
I would start just writing the state and data structures you need to process, writing validity check, and balance check, one EVM action for ERC-20 transfers and getting Cairo verifier to work. All of this is pretty well-defined just need to get stuff to work on the EVM. It would be cool to have an initial prototype by early January or something.
I think for Xuyang’s work it would be good to test different inputs, would like to set up a solidity project which allows us to fork Starknet to see if we can verify simple stark proofs. Having a solidity project in which you can easily input a proof and see if it works or reverts - Xuyang - I will figure out how to verify a Cairo proof in EVM. there are 8 different verifier layouts, I will take care of it.
Do you by chance know where to find Poseidon Hash implementation, or is there something you have in mind? There might be several, not sure of the formats of the concrete implementations are compatible, but we can figure it out, do some test for performance to see if large Merkel tree is available in EVM. For now, we also verify each proof separately and don’t combine into one. We don’t have the proof of aggregation. We can test the prototype locally first.
Yulia / Enrique would benefit from diagrams. Details for enforcing that some token has been transferred are not clear to me yet. Michael and I know the EVM. If you want to understand in detail, just read the EVM yellow paper.
How does something become a resource in the first place?
If here the idea to ensure some tokens have been transferred to a particular smart contract - contract has signature of user, couldn’t resource logic try to check the signature. It could be one way of implementing the RL. Are you proposing RL try to verify actual signature? problem with this is it does not cleanly interoperate with how the EVM works (multi-sig, abstract account with different signature scheme, just have to sue the fact that transfer from succeeded, and how it was authenticated we don’t know.)
Linking discussion from Hacker house where we discuss settlement only and full feature EVM contracts.
https://research.anoma.net/t/anoma-protocol-adaptor-discussion/878
I’ve pushed an initial draft for the EVM protocol adapter https://github.com/anoma/evm-protocol-adapter/blob/main/src/ProtocolAdapter.sol.
I’ll write some explanation tomorrow and we can discuss it in our next meeting.
EVM Protocol Adapter Architecture Draft
Transaction flow for the settlement-only EVM protocol adapter interacting with a resource wrapper contract and associated ERC20 contract.
Component Overview
The graphic shows 5 components
- Juvix App: Contains a wrapper resource definition and transaction functions producing transaction objects. The wrapper resource logic requires a signed authorization message by the owner on ephemeral and non-ephemeral consumption. The signature is stored in
action.appData
(as done in Kudos) and transaction functions are responsible to put them in place. - Cairo Backend: Called by Juvix to compute proofs populating the transaction object.
- Protocol Adapter (PA) contract: A solidity contract containing
- A Merkle tree implementation (to accumulate commitments)
- A set implementation (to accumulate nullifiers)
- A
verify
function and a reference to the Starknet Solidity verifier contract allowing Cario proof verification - An
execute
function that updates the commitments and nullifiers and can make external calls to wrapper contracts when processing wrapper resources
- Resource Wrapper contract
- Contains a reference to the wrapper resource kind.
- Owns wrapped ERC20 tokens
- Can only be called by the protocol adapter contract
- Contains a
wrap
andunwrap
function calling the target contract (e.g.,transferFrom
andtransfer
on an ERC20 token)
- Target contract: E.g., a pre-existing ERC20 token containing the balances of owners.
Transaction Flow (ERC20 Wrap Example)
User Alice owns 100 ABC ERC-20 tokens and wants to wrap 20 of them in a resource.
-
Alice calls
approve
on the ABC contract to allow the associated resource wrapper contract to spend 20 ABC on her behalf. Alternatively,Permit2
signatures can be used to eliminate this step. -
Alice calls the
initialize
transaction function in the ERC20 Juvix app populating a transaction object. This consumes a ephemeral ABC resource and creates a non-ephemeral ABC ERC20 resource, both with quantity 20 and Alice as the owner. The ERC20 resource contains the ABC ERC20 wrapper contract address in its label (thus influencing its kind) and requires theaction.appData
map to contain the consumed, ephemeral resource object with its nullifier as the lookup key. -
The transaction function calls the Juvix RM interface to prove the transaction object. In this example, proofs are computed using the Cairo backend since this is the only one we support in this prototype. In future versions, different proving systems can be selected through the information flow control predicate in the in transaction object.
-
The Cairo backend computes the various proofs and returns them. For compliance proofs, it checks the EVM protocol adapter state, i.e., the on-chain commitment accumulator and nullifier set maintained inside.
-
Alice (or any other Ethereum account) sends the balanced and valid
initialize
transaction object to the Ethereum mempool*. -
The protocol adapter verifies all proofs (i.e., delta, compliance, logic proofs) in the transaction object using the STARK verifier contract. If a proof is invald, the transaction reverts.
-
The PA updates its state by iterating over the
tx.actions
and adding the commitments and nullifiers and adding them to the commitment accumulator and nullifier set, respectively. -
For each nullifier and commitment, it checks if the corresponding resource object is part of the
action.appData
with the nullifer and commitment as the lookup key, respectively. On successful lookup, the PA checks- if the resource is ephemeral
- if the resource reproduces the nullifier** or commitment
- has a contract address stored in its label
- if the contract is a wrapper contract and contains a reference to the resource’s kind.
If the label doesn’t contain a contract address, the next step is skipped.
If the kind referenced in the wrapper contract doesn’t match the resource, the call reverts.
In this example, all criteria from above are met for the consumed, ephemeral ABC ERC20 resource being part of theinitialize
transaction object. The PA then calls thewrap
function in the wrapper contract with the nullifier, the resource object, and theaction.appData
as inputs.
-
The wrapper contract
wrap
function extracts the resource quantity and owner (stored inresource.value
) of the passed resource object and callstransferFrom({from: owner, to: address(this), value: quantity})
on the ABC ERC20 token contract. This transfers 20 ABC tokens from Alice to the wrapper contract. After processing the remainingtx.actions
(see step 6.) the call succeeds. In case of failure, the entire transaction reverts.
* Unbalanced transactions would be submitted to an intent pool to be matched by solvers. The solver can then send and execute the balanced transaction (and has to be cautious to not get frontrun for profitable intents).
** The universal nullifier key must be used to nullify ephemeral resources triggering EVM state changes. Since EVM state is visible anyway, no information is leaked here.
Below, the PA and the wrapper contract interfaces are shown:
interface IProtocolAdapter {
function verify(Transaction calldata transaction) external;
function execute(Transaction calldata transaction) external;
}
interface IResourceWrapper {
event ResourceWrapped(bytes32 indexed nullifier, Resource resource);
event ResourceUnwrapped(bytes32 indexed commitment, Resource resource);
function kind() external view returns (bytes32);
function wrap(bytes32 nullifier, Resource calldata resource, Map.KeyValuePair[] calldata appData) external;
function unwrap(bytes32 commitment, Resource calldata resource, Map.KeyValuePair[] calldata appData) external;
}
Conclusion
This design decouples the PA and wrapper contract, supports intents, maintains information flow control (except for external EVM state changes), and is not limited to ERC20 tokens. Arbitrary logic and external contract calls can be executed in each deployed wrapper contract.
Could you elaborate on how the Cairo backend checks the on-chain state? I might overlook something obvious here but I stumbled over this step unsure how this is done exactly