github
--> zkrom
--> zkvmpil
--> zkproverjs
zkprover:executor
:git clone https://github.com/hermeznetwork/zkrom.git
cd zkrom
npm i && npm run build
cd ..
git clone https://github.com/hermeznetwork/zkvmpil.git
cd zkvmpil
npm i && npm run build
git clone https://github.com/hermeznetwork/zkproverjs.git
cd zkproverjs
npm i
Detailed explanation:
zkrom
main/*
: contains assembly codebuild
: compiled assembly. code ready to the executorzkvmpil
src
: state-machinesbuild
: compiled state-machines. code ready to the executorzkproverjs
src/main_executor.js
: cli to run executor easily
zkrom/build
& zkvm pil/build
input.json
Run executor (in zkproverjs
repository)
to just test the executor, the output is not needed
node src/main_executor.js ./testvectors/input.json -r ../zkrom/build/rom.json -p ../zkvmpil/build/zkevm.pil.json -o ./testvectors/poly.bin
Basically, the executor runs the program that is specified by the ROM.
The program can be seen in the rom.json
file, which includes some debugging information.
Let's see an example of assembly code
builded into the rom.json
:
ASSEMBLY: 1 => B
JSON FILE:
{
"CONST": 1,
"neg": 0,
"setB": 1,
"line": 51,
"fileName": "../zkrom/main/main.zkasm"
}
All operations are defined in the JSON file, plus line
& fileName
where the assembly code is.
This JSON file is ready to be interpreted by the executor
In the zkproverjs
repository you can find an example of launch.json
to debug the executor code: https://github.com/hermeznetwork/zkproverjs/blob/main/.vscode/launch.json#L8
l
is the rom.json that is going to be executed: https://github.com/hermeznetwork/zkproverjs/blob/main/src/executor.js#L61ctx(context)
, registers and op
you will see all the states changes made by the executorctx.input
contins all the variables loaded from input.json
storage
makes refrence to the merkle-treeinput.json
are pre-processed and store it on ctx.pTxs
. Besides, globalHash
is computed given all the inputs.json
according to specification (TO_BE_UPDATED)NAME | DESCRIPTION | EXECUTION |
---|---|---|
MLOAD | memory load | op = mem(addr) |
MSTORE | memory storage | mem(addr) = op |
SLOAD | storage load | op = storage.get(SR, H[A0 , A1 , A2 , B0 , C0 , C1 , C2 , C3, 0...0])) where storage.get(root, key) -> value |
SSTORE | storage store | op = storage.set(SR, (H[A0 , A1 , A2 , B0 , C0 , C1 , C2 , C3, 0...0], D0 + D1 * 2^64 + D2 * 2^128 + D3 * 2^192 ) where storage.set(oldRoot, key, newValue) -> newRoot |
HASHW | hash write bytes | hash[addr].push(op[0..D-1]) |
HASHE | hash end | hash[addr].end() |
HASHR | hash read | op = hash[addr].result |
ARITH | arithmetic operation | AB + C = D OR op |
SHL | shift left | op = A << D |
SHR | shift right | op = A >> D |
ECRECOVER | signature recover | op = ECRECOVER( A: HASH, B: R, C:S, D: V) |
ASSERT | assertion | A = op |
A :MSTORE(sequencerAddr)
{
"inA": 1,
"neg": 0,
"offset": 4,
"mWR": 1,
"line": 9,
"offsetLabel": "sequencerAddr",
"useCTX": 0,
"fileName": ".../zkrom/main/main.zkasm"
}
A
register in op
, write in memory position 4 (offset
) the op
value$ => A : MLOAD(pendingTxs)
{
"freeInTag": {
"op": ""
},
"inFREE": 1,
"neg": 0,
"setA": 1,
"offset": 1,
"mRD": 1,
"line": 25,
"offsetLabel": "pendingTxs",
"useCTX": 0,
"fileName": ".../zkrom/main/main.zkasm"
}
offset
) into op
(action marked by inFREE
), set op
in A
register
$ => A :MLOAD(sequencerAddr)
0 => B,C
$ => A :SLOAD
{
"freeInTag": {
"op": ""
},
"inFREE": 1,
"neg": 0,
"setA": 1,
"offset": 4,
"mRD": 1,
"line": 47,
"offsetLabel": "sequencerAddr",
"useCTX": 0,
"fileName": ".../zkrom/main/main.zkasm"
},
{
"CONST": 0,
"neg": 0,
"setB": 1,
"setC": 1,
"line": 48,
"fileName": ".../zkrom/main/main.zkasm"
},
{
"freeInTag": {
"op": ""
},
"inFREE": 1,
"neg": 0,
"setA": 1,
"sRD": 1,
"line": 49,
"fileName": ".../zkrom/main/main.zkasm"
}
sequencerAccValue
) into op
, store op
on D
registersequencerAddr
) into op
, store op
on A
registerop
, store it in registers B
and C
storage.get(SR, H[A0 , A1 , A2 , B0 , C0 , C1 , C2 , C3, 0...0]))
SR
is the current state-root saved in register SR
A0, A1, A2
has the sequencer addressB0
is set to 0
pointing out that the balance
is going to be readC0,C1,C2,C3
are set to 0 since they are not used when reading balance from merkle-treeop
(marked by inFREE
tag), set op
to register A
$ => A :MLOAD(sequencerAddr)
0 => B,C
$ => SR :SSTORE
{
"freeInTag": {
"op": ""
},
"inFREE": 1,
"neg": 0,
"setA": 1,
"offset": 4,
"mRD": 1,
"line": 56,
"offsetLabel": "sequencerAddr",
"useCTX": 0,
"fileName": ".../zkrom/main/main.zkasm"
},
{
"CONST": 0,
"neg": 0,
"setB": 1,
"setC": 1,
"line": 57,
"fileName": ".../zkrom/main/main.zkasm"
},
{
"freeInTag": {
"op": ""
},
"inFREE": 1,
"neg": 0,
"setSR": 1,
"sWR": 1,
"line": 58,
"fileName": ".../zkrom/main/main.zkasm"
}
sequencerAddr
) and store it on op
, set op
to register Aop
, store op
in registers B
and C
storage.set(SR, (H[A0 , A1 , A2 , B0 , C0 , C1 , C2 , C3, 0...0], D0 + D1 * 2^64 + D2 * 2^128 + D3 * 2^192 )
SR
is the current state-root saved in register SR
A0, A1, A2
has the sequencer addressB0
is set to 0
pointing out that the balance
is going to be readC0,C1,C2,C3
are set to 0 since they are not used when reading balance from merkle-treeD0, D1, D2, D3
is the value writen in the merkle-tree pointed out by H[A0 , A1 , A2 , B0 , C0 , C1 , C2 , C3, 0...0]
–> in this example register D
has the balance of the seqAddr
SR
register