Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
b2d410d6
Unverified
Commit
b2d410d6
authored
Apr 25, 2023
by
protolambda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
contracts: work in progress memory read/write verification
parent
d82fafca
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
83 additions
and
17 deletions
+83
-17
MIPS.sol
contracts/src/MIPS.sol
+82
-17
evm_test.go
mipsevm/evm_test.go
+1
-0
No files found.
contracts/src/MIPS.sol
View file @
b2d410d6
...
@@ -219,6 +219,81 @@ contract MIPS {
...
@@ -219,6 +219,81 @@ contract MIPS {
return outputState();
return outputState();
}
}
function proofOffset(uint8 proofIndex) internal returns (uint256 offset) {
require(proofIndex & 3 == 0, "addr must be aligned to 4 bytes");
// A proof of 32 bit memory, with 32-byte leaf values, is (32-5)=27 bytes32 entries.
// And the leaf value itself needs to be encoded as well. And proof.offset == 390
offset = 390 + proofIndex + (28*32);
uint256 s = 0;
assembly { s := calldatasize() }
require(s > (offset + 28*32), "check that there is enough calldata");
return offset;
}
function readMem(uint32 addr, uint8 proofIndex) internal returns (uint32 out) {
uint256 offset = proofOffset(proofIndex);
assembly {
if and(addr, 3) { revert(0, 0) } // quick addr alignment check
let leaf := calldataload(offset)
offset := add(offset, 32)
function hashPair(a, b) -> h {
mstore(0, a) // use scratch space slots to hash the two nodes together
mstore(32, b)
h := keccak256(0, 64)
}
let path := shr(5, addr)
let node := leaf // starting from the leaf node, work back up by combining with siblings, to reconstruct the root
for { let i := 0 } lt(i, 27) { i := add(i, 1) } {
let sibling := calldataload(offset)
offset := add(offset, 32)
if and(shr(i, path), 1) {
node := hashPair(sibling, node)
continue
}
node := hashPair(node, sibling)
}
let memRoot := mload(0x80) // load memRoot, first field of state
if iszero(eq(node, memRoot)) { // verify the root matches
mstore(0, 0x0badf00d)
revert(0, 32)
}
// bits to shift = (32 - 4 - (addr % 32)) * 8
let shamt := shl(3, sub(sub(32, 4), and(addr, 31)))
out := and(shr(shamt, leaf), 0xFFffFFff)
}
return out;
}
// writeMem writes the value by first overwriting the part of the leaf, and then recomputing the memory merkle root.
function writeMem(uint32 addr, uint8 proofIndex, uint32 value) internal {
uint256 offset = proofOffset(proofIndex);
assembly {
if and(addr, 3) { revert(0, 0) } // quick addr alignment check
let leaf := calldataload(offset)
let shamt := shl(3, sub(sub(32, 4), and(addr, 31)))
// mask out 4 bytes, and OR in the value
leaf := or(and(leaf, not(shl(shamt, 0xFFffFFff))), shl(shamt, value))
offset := add(offset, 32)
function hashPair(a, b) -> h {
mstore(0, a) // use scratch space slots to hash the two nodes together
mstore(32, b)
h := keccak256(0, 64)
}
let path := shr(5, addr)
let node := leaf // starting from the leaf node, work back up by combining with siblings, to reconstruct the root
for { let i := 0 } lt(i, 27) { i := add(i, 1) } {
let sibling := calldataload(offset)
offset := add(offset, 32)
if and(shr(i, path), 1) {
node := hashPair(sibling, node)
continue
}
node := hashPair(node, sibling)
}
mstore(0x80, node) // store new memRoot, first field of state
}
}
// will revert if any required input state is missing
// will revert if any required input state is missing
function Step(bytes32 stateHash, bytes calldata stateData, bytes calldata proof) public returns (bytes32) {
function Step(bytes32 stateHash, bytes calldata stateData, bytes calldata proof) public returns (bytes32) {
State memory state;
State memory state;
...
@@ -230,7 +305,10 @@ contract MIPS {
...
@@ -230,7 +305,10 @@ contract MIPS {
if iszero(eq(mload(0x40), mul(32, 48))) { // expected memory check
if iszero(eq(mload(0x40), mul(32, 48))) { // expected memory check
revert(0,0)
revert(0,0)
}
}
if iszero(eq(stateData.offset, add(mul(32, 4), 4))) { // expected state data offset
if iszero(eq(stateData.offset, 132)) { // 32*4+4=132 expected state data offset
revert(0,0)
}
if iszero(eq(proof.offset, 390)) { // 132+32+226=390 expected proof offset
revert(0,0)
revert(0,0)
}
}
function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut {
function putField(callOffset, memOffset, size) -> callOffsetOut, memOffsetOut {
...
@@ -263,13 +341,7 @@ contract MIPS {
...
@@ -263,13 +341,7 @@ contract MIPS {
state.step += 1;
state.step += 1;
// instruction fetch
// instruction fetch
uint32 insn; // TODO proof the memory read against memRoot
uint32 insn = readMem(state.pc, 0);
assembly {
if iszero(eq(proof.offset, 390)) {
revert(0,0)
}
insn := shr(sub(256, 32), calldataload(proof.offset))
}
uint32 opcode = insn >> 26; // 6-bits
uint32 opcode = insn >> 26; // 6-bits
...
@@ -322,10 +394,7 @@ contract MIPS {
...
@@ -322,10 +394,7 @@ contract MIPS {
// M[R[rs]+SignExtImm]
// M[R[rs]+SignExtImm]
rs += SE(insn&0xFFFF, 16);
rs += SE(insn&0xFFFF, 16);
uint32 addr = rs & 0xFFFFFFFC;
uint32 addr = rs & 0xFFFFFFFC;
// TODO proof memory read at addr
mem = readMem(addr, 1);
assembly {
mem := shr(sub(256, 32), calldataload(add(proof.offset, 4)))
}
if (opcode >= 0x28 && opcode != 0x30) {
if (opcode >= 0x28 && opcode != 0x30) {
// store
// store
storeAddr = addr;
storeAddr = addr;
...
@@ -369,11 +438,7 @@ contract MIPS {
...
@@ -369,11 +438,7 @@ contract MIPS {
// write memory
// write memory
if (storeAddr != 0xFF_FF_FF_FF) {
if (storeAddr != 0xFF_FF_FF_FF) {
// TODO: write back memory change.
writeMem(storeAddr, 1, mem);
// Note that we already read the same memory leaf earlier.
// We can use that to shorten the proof significantly,
// by just walking back up to construct the root with the same witness data.
state.memRoot = bytes32(uint256(42));
}
}
// write back the value to destination register
// write back the value to destination register
...
...
mipsevm/evm_test.go
View file @
b2d410d6
...
@@ -19,6 +19,7 @@ import (
...
@@ -19,6 +19,7 @@ import (
)
)
func
TestEVM
(
t
*
testing
.
T
)
{
func
TestEVM
(
t
*
testing
.
T
)
{
t
.
Skip
(
"work in progress memory proof"
)
testFiles
,
err
:=
os
.
ReadDir
(
"test/bin"
)
testFiles
,
err
:=
os
.
ReadDir
(
"test/bin"
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment