Skip to content

Commit 20e7a86

Browse files
authored
EVM PA Documentation Update (#387)
Make PA documentation correspond to main RM specs. Introduce a separate page for EVM integration.
2 parents 4be22ff + 9ed46a4 commit 20e7a86

File tree

11 files changed

+709
-376
lines changed

11 files changed

+709
-376
lines changed

docs/arch/integrations/adapters/evm.md

Lines changed: 0 additions & 374 deletions
This file was deleted.
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
---
2+
icon: material/devices
3+
search:
4+
exclude: false
5+
boost: 2
6+
tags:
7+
- evm
8+
- resource-machine
9+
- protocol-adapter
10+
markdown_extensions:
11+
- def_list
12+
---
13+
14+
# EVM Resource Machine Datatypes
15+
16+
This section contains the descriptions of the RM datatype implementations and their interfaces.
17+
18+
The "Proof" sections try to describe the explicit implementation details that may differ from the original RM specification.
19+
20+
## Resource
21+
22+
A [resource](https://github.com/anoma/evm-protocol-adapter/blob/b807463b96c12743e20a07fbafef0b895c1c969f/contracts/src/Types.sol#L16) is implemented as the following Solidity struct:
23+
24+
```solidity
25+
struct Resource {
26+
bytes32 logicRef;
27+
bytes32 labelRef;
28+
bytes32 valueRef;
29+
bytes32 nullifierKeyCommitment;
30+
bytes32 nonce;
31+
bytes32 randSeed;
32+
uint128 quantity;
33+
bool ephemeral;
34+
}
35+
```
36+
37+
### Resource Computable Components
38+
39+
Commitment
40+
41+
: A commitment of resource `resource` is computed as packed bytes of the resource fields alongside with hardcoded 17 bytes prepending randomness values:
42+
43+
```solidity
44+
function commitment(Resource memory resource) internal pure returns (bytes32 cm) {
45+
cm = sha256(
46+
abi.encodePacked(
47+
resource.logicRef,
48+
resource.labelRef,
49+
resource.quantity,
50+
resource.valueRef,
51+
resource.ephemeral,
52+
resource.nonce,
53+
resource.nullifierKeyCommitment,
54+
rcm(resource)
55+
)
56+
);
57+
}
58+
59+
function rcm(Resource memory resource) internal pure returns (bytes32 randCm) {
60+
bytes17 prfExpandPersonalization = 0x52495343305f457870616e645365656401;
61+
randCm = sha256(abi.encodePacked(prfExpandPersonalization, resource.randSeed, resource.nonce));
62+
}
63+
```
64+
65+
Nullifier
66+
67+
: A nullifier of a resource `resource` with nullifier key `nullifierKey` is computed as packed bytes of the nullifier key, nonce, hardcoded 17 bytes with randomness fields of a resource, as well as the commitment of a resource as described above.
68+
69+
```solidity
70+
function nullifier(Resource memory resource, bytes32 nullifierKey) internal pure returns (bytes32 nf) {
71+
nf = sha256(abi.encodePacked(nullifierKey, resource.nonce, psi(resource), commitment(resource)));
72+
}
73+
74+
function psi(Resource memory resource) internal pure returns (bytes32 randNf) {
75+
bytes17 prfExpandPersonalization = 0x52495343305f457870616e645365656400;
76+
randNf = sha256(abi.encodePacked(prfExpandPersonalization, resource.randSeed, resource.nonce));
77+
}
78+
```
79+
80+
Kind
81+
82+
: A kind of a resource `resource` with `logicRef = resource.logicRef` and `labelRef = resource.labelRef` is computed as `sha256(abi.encode(logicRef, labelRef))`.
83+
84+
Delta
85+
86+
: The delta of a resource `resource` is computed as the multiple of the resource kind and quantity seen as scalars and Pedersen-committed to a 2D point on the K256 curve. However, we do not use them for verification purposes. For details one can consult the [compliance circuit](https://github.com/anoma/arm-risc0/blob/73bb97640e24a3533587051fcf58c3ed1a12e8e8/arm/src/compliance.rs#L170).
87+
88+
## Compliance Unit
89+
90+
The compliance unit is defined as the `VerifierInput` struct:
91+
92+
```solidity
93+
struct VerifierInput {
94+
bytes proof;
95+
Instance instance;
96+
}
97+
```
98+
99+
where the instance is the expected compliance unit instance type:
100+
101+
```solidity
102+
struct Instance {
103+
ConsumedRefs consumed;
104+
CreatedRefs created;
105+
bytes32 unitDeltaX;
106+
bytes32 unitDeltaY;
107+
}
108+
```
109+
110+
where
111+
112+
```solidity
113+
struct ConsumedRefs {
114+
bytes32 nullifier;
115+
bytes32 logicRef;
116+
bytes32 commitmentTreeRoot;
117+
}
118+
```
119+
120+
and
121+
122+
```solidity
123+
struct CreatedRefs {
124+
bytes32 commitment;
125+
bytes32 logicRef;
126+
}
127+
```
128+
129+
As mentioned in [[EVM Primitive Interfaces]] page, the unit delta is represented as a 2D point.
130+
131+
## Action
132+
133+
The action datatype is described as
134+
135+
```solidity
136+
struct Action {
137+
Logic.VerifierInput[] logicVerifierInputs;
138+
Compliance.VerifierInput[] complianceVerifierInputs;
139+
}
140+
```
141+
142+
Where the compliance verifier input time is defined above, while the logic verifier input is defined as:
143+
144+
```solidity
145+
struct VerifierInput {
146+
bytes32 tag;
147+
bytes32 verifyingKey;
148+
AppData appData;
149+
bytes proof;
150+
}
151+
```
152+
153+
The `AppData` stands for the 4 different payloads we posess:
154+
155+
```solidity
156+
struct AppData {
157+
ExpirableBlob[] resourcePayload;
158+
ExpirableBlob[] discoveryPayload;
159+
ExpirableBlob[] externalPayload;
160+
ExpirableBlob[] applicationPayload;
161+
}
162+
```
163+
164+
with `ExpirableBlob` defined as
165+
166+
```solidity
167+
struct ExpirableBlob {
168+
DeletionCriterion deletionCriterion;
169+
bytes blob;
170+
}
171+
```
172+
173+
There are only two deletion criteria we support:
174+
175+
```solidity
176+
enum DeletionCriterion {
177+
Immediately,
178+
Never
179+
}
180+
```
181+
182+
The storage of the criteria is designated to the EVM event history logs. Once the transaction is executed, the event will be transmitted, including the blobs, which will then be recoverable by an interested party through indexing services.
183+
184+
The `actionTreeRoot` is computed as the root of a merkle tree of depth 4 with leaves provided by the `tag`s in the corresponding Action.
185+
186+
## Transaction
187+
188+
The transaction is defined as a list of actions with the delta prove attesting to the balancing of the action alongside an aggregation proof, attesting to the verification of all the logic and compliance verifying keys present in the transaction given the instances:
189+
190+
```solidity
191+
struct Transaction {
192+
Action[] actions;
193+
bytes deltaProof;
194+
bytes aggregationProof;
195+
}
196+
```
197+
198+
If an aggregation proof is present, the PA only checks the delta and the aggregation proofs.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
icon: material/devices
3+
search:
4+
exclude: false
5+
boost: 2
6+
tags:
7+
- evm
8+
- resource-machine
9+
- protocol-adapter
10+
markdown_extensions:
11+
- def_list
12+
---
13+
14+
# Execution Flow of the EVM Protocol Adapter
15+
16+
This page describes at a high-level the Protocol Adapter implementation on the EVM. This page in particular explains how we address all the out of circuit checks and any additional ones implemented.
17+
18+
## Resource Machine
19+
20+
The resource machine functionality is implemented via the `execute` function, processing the verification of the transaction as per the RM specification and updating state consisting of a commitment accumulator, set of historical tree roots, and the nullifier set.
21+
22+
The `execute` function updates the commitment accumulator and the nullifier set if and only if the following two criteria are fulfilled:
23+
24+
- All checks specified by the RM specification pass. In particular:
25+
+ All compliance proofs are valid.
26+
- All resource logic proofs are valid.
27+
- The delta proof is valid.
28+
- For each action, its compliance units partition its tags.
29+
- Verifying keys of tags match the logic references in the compliance units.
30+
- Historical roots defined in the compliance units exist in the set of historical roots.
31+
- There are no repeated commitments or nullifiers in the transaction.
32+
- Each commitment in a transaction has not been added to the commitment accumulator before.
33+
- Each nullifier in a transaction has not been added to the nullifier set before.
34+
- All forwarder calls in each `externalPayload` return the expected outcomes and do not revert.
35+
36+
Given all of this is done, all the commitments in a transaction are added to the commitment Merkle tree and the nullifiers are added to the nullifier set. We also emit several events relevant to the transaction during the execution.
37+
38+
Below, we go into details regarding verification and event-emission details.
39+
40+
### Verification
41+
42+
Verification can be split into two conceptual components as specified above:
43+
44+
#### RM Checks
45+
46+
We go through the list explicating how each check is made:
47+
48+
- All compliance proofs are valid.
49+
50+
We grant that by iterating over the compliance units and verifying their proofs by calling the trusted Risc0 Verifier.
51+
52+
- All resource logic proofs are valid.
53+
54+
When iterating over compliance units, for each tag present we fetch the corresponding proofs from the action, verifying it by calling the trusted Risc0 Verifier.
55+
56+
- The delta proof is valid.
57+
58+
At the end of the transaction, we run ECDSA signature verification, which corresponds to verifying a delta proof.
59+
60+
- For each action, its compliance units partition its tags.
61+
62+
This check is split into four components.
63+
64+
1. When iterating over the compliance units of the action, we call `lookup` on every nullifier and commitment. This function iterates over the logic verifier inputs in a given action and reverts if no matching tag has been found. This ensures that the set of tags in compliance unit is a subset of a set of tags of the action.
65+
66+
2. When adding nullifiers to the nullifier set, the operation reverts on a repeating nullifier insertion. This grants that nullifiers across the compliance units are unique.
67+
68+
3. Our compliance circuit ensures that if a resource is committed, it uses a nullifier of a consumed resource present in the same compliance unit as its nonce. Since nullifiers are unique, this statistically grants uniqueness of commitments.
69+
70+
4. During the initial allocation of array sizes for tags, we compute the number of the overall tags by `countTags` function. It in particular ensures that the number of tags in the compliance units and logic verifier inputs match.
71+
72+
- Verifying keys of tags match the logic references in the compliance units.
73+
74+
When doing `lookup` we check that these match explicitly. Note that in this implementation, the hashing function to get the logic reference from the verifying key is just the identity function, hence we check for explicit equality.
75+
76+
- Historical roots defined in the compliance units exist in the set of historical roots.
77+
78+
When iterating over the compliance units, we explicitly check containment of all roots provided in the set of roots we store on-chain.
79+
80+
- There are no repeated commitments or nullifiers in the transaction.
81+
82+
As mentioned, the uniqueness of commitments and nullifiers are granted by the semantics of nullifier set and the compliance circuit constraints.
83+
84+
- Each commitment in a transaction has not been added to the commitment accumulator before.
85+
86+
Same here.
87+
88+
- Each nullifier in a transaction has not been added to the nullifier set before.
89+
90+
Same here.
91+
92+
#### External Call Checks
93+
94+
After verifying a logic proof of a resource, we iterate over the `externalPayload`in its application data. In each position, the verifier expects to be encoded a tuple with:
95+
96+
1. External address
97+
2. Input bytes
98+
3. Output bytes
99+
100+
The external address is assumed to be a forwarder contract which can hence be called via `forwardCall` interface, giving the `logicRef` of the resource and the input bytes as the arguments.
101+
102+
The call ought to return exactly the output bytes, otherwise, the verifier reverts with the appropriate error.
103+
104+
105+
### Events
106+
107+
For each payload blob with deletion criterion `Never`, we emit it as an event to be recorded in Ethereum's history and picked up by apporpriate indexers:
108+
109+
```solidity
110+
event ResourcePayload(bytes32 indexed tag, uint256 index, bytes blob);
111+
112+
event DiscoveryPayload(bytes32 indexed tag, uint256 index, bytes blob);
113+
114+
event ExternalPayload(bytes32 indexed tag, uint256 index, bytes blob);
115+
116+
event ApplicationPayload(bytes32 indexed tag, uint256 index, bytes blob);
117+
```
118+
119+
Regardless of the deletion criterion, we also emit an event signaling that a forwarder call has been made:
120+
121+
```solidity
122+
event ForwarderCallExecuted(address indexed untrustedForwarder, bytes input, bytes output);
123+
```
124+
125+
Alongside that, we have separate events informing that a specific action has executed with specified root:
126+
127+
```solidity
128+
event ActionExecuted(bytes32 actionTreeRoot, uint256 actionTagCount);
129+
```
130+
131+
And the final transaction event listing all tags and their verification keys in the order provided by the compliance units:
132+
133+
```solidity
134+
event TransactionExecuted(bytes32[] tags, bytes32[] logicRefs);
135+
```
136+
137+
138+
## User
139+
140+
From the perspective of the user, there are two things they are concerned about:
141+
142+
1) Getting to know which resources are relevant to them
143+
144+
2) Composing and submitting transactions
145+
146+
### Resource State Sync
147+
148+
For the former, the user can listen to the incoming `TransactionExecuted` events and inspect individual `appData` fields. Each user is assumed to have some sets of keypairs. Specifically some sets for decoding the `discoveryPayload` and `resourcePayload` contained in the `appData` of the given `tag`.
149+
150+
While these fields are permissionless, they are incentivized (and frequently required by the resource logics) to contain specific sort of info. Specifically, the user expects that if the `tag` is relevant to them, the `discoveryPayload` decodes to a single byte with their private discovery key. This way the user understands that `tag` is a resource they are interested in. Afterwards, they can use their resource payload decryption key to decrypt `resourcePayload` which they can expect to contain the resource plaintext and other relevant information.
151+
152+
If the resource needs to make a forwarder call, the user is expected to put the plaintext in a universally decodable format into the head of the `resourcePayload` alongside with the nullifier key afterwards in the array if the resource making a call is consumed. The PA decided on the validity of the call based on the provided information in these predetermined locations.
153+
154+
Given the plaintext, the user can then check that the plaintext actually corresponds to the `tag` by committing or nullifying (given that the nullifier key is provided) the resource. That way the user gets to know what resource was created or consumed in a shielded context.
155+
156+
### Transaction Generation
157+
158+
There are only two non-application specific pieces of data that a user needs to know regarding the global state if they want to generate a transaction. Both of them are needed to nullify a given resource `R`. These are:
159+
160+
- Some root at which `R` has been created.
161+
- The path to `R` at that root.
162+
163+
If `R` has indeed been created, it exists at the latest root. The EVM PA supports getting the information about the latest root by using the
164+
165+
```solidity
166+
function latestCommitmentTreeRoot() external view returns (bytes32 root);
167+
```
168+
169+
The Merkle proofs are not availiable on-chain for gas reasons.
170+
171+
## Solvers
172+
173+
Solvers can also get information regarding resources interesting to them similarly to the users: from inspecting the `discoveryPayload`.
174+
175+
## Executor
176+
177+
For the EVM Protocol Adapter the role of the executor depends on the chain on which it has been deployed. For example, for the PA deployed on Ethereum the executor will be the validator set of the Ethereum chain.

0 commit comments

Comments
 (0)