Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tip/local preimage support temp #3

Closed
51 changes: 35 additions & 16 deletions rvgo/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ func loadPreimageOracleContractCode(t *testing.T) *Contract {
return &outDat
}

func loadPreimageKeyLibCode(t *testing.T) *Contract {
dat, err := os.ReadFile("../rvsol/out/PreimageKeyLib.sol/PreimageKeyLib.json")
require.NoError(t, err)
var outDat Contract
err = json.Unmarshal(dat, &outDat)
require.NoError(t, err)
return &outDat
}

type Contract struct {
DeployedBytecode struct {
Object hexutil.Bytes `json:"object"`
Expand All @@ -84,15 +93,17 @@ func (c *Contract) SourceMap(sourcePaths []string) (*srcmap.SourceMap, error) {
}

type Contracts struct {
RISCV *Contract
Oracle *Contract
RISCV *Contract
Oracle *Contract
PreimageKeyLib *Contract
}

type Addresses struct {
RISCV common.Address
Oracle common.Address
Sender common.Address
FeeRecipient common.Address
RISCV common.Address
Oracle common.Address
PreimageKeyLib common.Address
Sender common.Address
FeeRecipient common.Address
}

func newEVMEnv(t *testing.T, contracts *Contracts, addrs *Addresses) *vm.EVM {
Expand All @@ -109,24 +120,28 @@ func newEVMEnv(t *testing.T, contracts *Contracts, addrs *Addresses) *vm.EVM {
env := vm.NewEVM(blockContext, vm.TxContext{}, state, chainCfg, vmCfg)
env.StateDB.SetCode(addrs.RISCV, contracts.RISCV.DeployedBytecode.Object)
env.StateDB.SetCode(addrs.Oracle, contracts.Oracle.DeployedBytecode.Object)
env.StateDB.SetState(addrs.RISCV, common.Hash{}, addrs.Oracle.Hash()) // set storage slot pointing to preimage oracle

env.StateDB.SetCode(addrs.PreimageKeyLib, contracts.PreimageKeyLib.DeployedBytecode.Object)
env.StateDB.SetState(addrs.RISCV, common.Hash{}, addrs.Oracle.Hash()) // set storage slot pointing to preimage oracle
env.StateDB.SetState(addrs.RISCV, common.Hash{1}, addrs.PreimageKeyLib.Hash()) // set slot pointing to preimageKeyLib

rules := env.ChainConfig().Rules(header.Number, true, header.Time)
env.StateDB.Prepare(rules, addrs.Sender, addrs.FeeRecipient, &addrs.RISCV, vm.ActivePrecompiles(rules), nil)
return env
}

var testAddrs = &Addresses{
RISCV: common.HexToAddress("0x1337"),
Oracle: common.HexToAddress("0xf00d"),
Sender: common.HexToAddress("0x7070"),
FeeRecipient: common.HexToAddress("0xbd69"),
RISCV: common.HexToAddress("0x1337"),
Oracle: common.HexToAddress("0xf00d"),
PreimageKeyLib: common.HexToAddress("0x7331"),
Sender: common.HexToAddress("0x7070"),
FeeRecipient: common.HexToAddress("0xbd69"),
}

func testContracts(t *testing.T) *Contracts {
return &Contracts{
RISCV: loadStepContractCode(t),
Oracle: loadPreimageOracleContractCode(t),
RISCV: loadStepContractCode(t),
Oracle: loadPreimageOracleContractCode(t),
PreimageKeyLib: loadPreimageKeyLibCode(t),
}
}

Expand All @@ -137,9 +152,13 @@ func addTracer(t *testing.T, env *vm.EVM, addrs *Addresses, contracts *Contracts
require.NoError(t, err)
b, err := contracts.Oracle.SourceMap([]string{"../rvsol/src/PreimageOracle.sol"})
require.NoError(t, err)
c, err := contracts.PreimageKeyLib.SourceMap([]string{"../rvsol/src/PreimageKeyLib.sol"})
require.NoError(t, err)

env.Config.Tracer = srcmap.NewSourceMapTracer(map[common.Address]*srcmap.SourceMap{
addrs.RISCV: a,
addrs.Oracle: b,
addrs.RISCV: a,
addrs.Oracle: b,
addrs.PreimageKeyLib: c,
}, os.Stdout)
}

Expand Down
1 change: 1 addition & 0 deletions rvgo/fast/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ var (
CheatBytes4 = crypto.Keccak256([]byte("cheat(uint256,bytes32,bytes32,uint256)"))[:4]
CheatLocalKeyBytes4 = crypto.Keccak256([]byte("cheatLocalKey(uint256,bytes32,bytes32,uint256,bytes32)"))[:4]
LoadKeccak256PreimagePartBytes4 = crypto.Keccak256([]byte("loadKeccak256PreimagePart(uint256,bytes)"))[:4]
LoadLocalDataBytes4 = crypto.Keccak256([]byte("loadLocalData(uint256,bytes32,bytes32,uint256,uint256)"))[:4]
)
19 changes: 10 additions & 9 deletions rvgo/fast/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,20 @@ func (wit *StepWitness) EncodePreimageOracleInput(localContext LocalContext) ([]

switch preimage.KeyType(wit.PreimageKey[0]) {
case preimage.LocalKeyType:
// We have no on-chain form of preparing the bootstrap pre-images onchain yet.
// So instead we cheat them in.
// In production usage there should be an on-chain contract that exposes this,
// rather than going through the global keccak256 oracle.
if len(wit.PreimageValue) > 32+8 {
return nil, fmt.Errorf("local pre-image exceeds maximum size of 32 bytes with key 0x%x", wit.PreimageKey)
}
var input []byte
input = append(input, CheatLocalKeyBytes4...)
input = append(input, uint64ToBytes32(wit.PreimageOffset)...)
input = append(input, LoadLocalDataBytes4...)
input = append(input, wit.PreimageKey[:]...)
input = append(input, common.Hash(localContext).Bytes()...)

preimagePart := wit.PreimageValue[8:]
var tmp [32]byte
copy(tmp[:], wit.PreimageValue[wit.PreimageOffset:])
copy(tmp[:], preimagePart)
input = append(input, tmp[:]...)
input = append(input, uint64ToBytes32(uint64(len(wit.PreimageValue))-8)...)
input = append(input, common.Hash(localContext).Bytes()...)
input = append(input, uint64ToBytes32(uint64(len(wit.PreimageValue)-8))...)
input = append(input, uint64ToBytes32(wit.PreimageOffset)...)
// Note: we can pad calldata to 32 byte multiple, but don't strictly have to
return input, nil
case preimage.Keccak256KeyType:
Expand Down
59 changes: 59 additions & 0 deletions rvsol/src/PreimageKeyLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

/// @title PreimageKeyLib
/// @notice Shared utilities for localizing local keys in the preimage oracle.
library PreimageKeyLib {
/// @notice Generates a context-specific local key for the given local data identifier.
/// @dev See `localize` for a description of the localization operation.
/// @param _ident The identifier of the local data. [0, 32) bytes in size.
/// @param _localContext The local context for the key.
/// @return key_ The context-specific local key.
function localizeIdent(uint256 _ident, bytes32 _localContext) internal view returns (bytes32 key_) {
assembly {
// Set the type byte in the given identifier to `1` (Local). We only care about
// the [1, 32) bytes in this value.
key_ := or(shl(248, 1), and(_ident, not(shl(248, 0xFF))))
}
// Localize the key with the given local context.
key_ = localize(key_, _localContext);
}

/// @notice Localizes a given local data key for the caller's context.
/// @dev The localization operation is defined as:
/// localize(k) = H(k .. sender .. local_context) & ~(0xFF << 248) | (0x01 << 248)
/// where H is the Keccak-256 hash function.
/// @param _key The local data key to localize.
/// @param _localContext The local context for the key.
/// @return localizedKey_ The localized local data key.
function localize(bytes32 _key, bytes32 _localContext) internal view returns (bytes32 localizedKey_) {
assembly {
// Grab the current free memory pointer to restore later.
let ptr := mload(0x40)
// Store the local data key and caller next to each other in memory for hashing.
mstore(0, _key)
mstore(0x20, caller())
mstore(0x40, _localContext)
// Localize the key with the above `localize` operation.
localizedKey_ := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1))
// Restore the free memory pointer.
mstore(0x40, ptr)
}
}

/// @notice Computes and returns the key for a global keccak pre-image.
/// @param _preimage The pre-image.
/// @return key_ The pre-image key.
function keccak256PreimageKey(bytes memory _preimage) internal pure returns (bytes32 key_) {
assembly {
// Grab the size of the `_preimage`
let size := mload(_preimage)

// Compute the pre-image keccak256 hash (aka the pre-image key)
let h := keccak256(add(_preimage, 0x20), size)

// Mask out prefix byte, replace with type 2 byte
key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
}
}
}
68 changes: 51 additions & 17 deletions rvsol/src/PreimageOracle.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol";
import { PreimageKeyLib } from "./PreimageKeyLib.sol";

contract PreimageOracle {
contract PreimageOracle is IPreimageOracle {
mapping(bytes32 => uint256) public preimageLengths;
mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts;
mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk;
Expand Down Expand Up @@ -32,32 +34,60 @@ contract PreimageOracle {
preimageLengths[key] = size;
}

function localize(bytes32 _key, bytes32 _localContext) internal view returns (bytes32 localizedKey_) {
assembly {
// Grab the current free memory pointer to restore later.
let ptr := mload(0x40)
// Store the local data key and caller next to each other in memory for hashing.
mstore(0, _key)
mstore(0x20, caller())
mstore(0x40, _localContext)
// Localize the key with the above `localize` operation.
localizedKey_ := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1))
// Restore the free memory pointer.
mstore(0x40, ptr)
}
}

// temporary method for localizeation
function cheatLocalKey(uint256 partOffset, bytes32 key, bytes32 part, uint256 size, bytes32 localContext) external {
// sanity check key is local key using prefix
require(uint8(key[0]) == 1, "must be used for local key");

bytes32 localizedKey = localize(key, localContext);
bytes32 localizedKey = PreimageKeyLib.localize(key, localContext);
preimagePartOk[localizedKey][partOffset] = true;
preimageParts[localizedKey][partOffset] = part;
preimageLengths[localizedKey] = size;
}

function loadLocalData(
uint256 _ident,
bytes32 _localContext,
bytes32 _word,
uint256 _size,
uint256 _partOffset
) external returns (bytes32 key_) {
// Compute the localized key from the given local identifier.
key_ = PreimageKeyLib.localizeIdent(_ident, _localContext);

// Revert if the given part offset is not within bounds.
if (_partOffset > _size + 8 || _size > 32) {
// Revert with "PartOffsetOOB()"
assembly {
// Store "PartOffsetOOB()"
mstore(0, 0xfe254987)
// Revert with "PartOffsetOOB()"
revert(0x1c, 4)
}
// TODO: remove with revert PartOffsetOOB();
}

// Prepare the local data part at the given offset
bytes32 part;
assembly {
// Clean the memory in [0x20, 0x40)
mstore(0x20, 0x00)

// Store the full local data in scratch space.
mstore(0x00, shl(192, _size))
mstore(0x08, _word)

// Prepare the local data part at the requested offset.
part := mload(_partOffset)
}

// Store the first part with `_partOffset`.
preimagePartOk[key_][_partOffset] = true;
preimageParts[key_][_partOffset] = part;
// Assign the length of the preimage at the localized key.
preimageLengths[key_] = _size;
}

// loadKeccak256PreimagePart prepares the pre-image to be read by keccak256 key,
// starting at the given offset, up to 32 bytes (clipped at preimage length, if out of data).
function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external {
Expand Down Expand Up @@ -94,4 +124,8 @@ contract PreimageOracle {
preimageParts[key][_partOffset] = part;
preimageLengths[key] = size;
}

function loadSha256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external {

}
}
73 changes: 57 additions & 16 deletions rvsol/src/Step.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { IPreimageOracle } from "./interfaces/IPreimageOracle.sol";
import { PreimageKeyLib } from "./PreimageKeyLib.sol";


contract Step {
IPreimageOracle public preimageOracle;

address public preimageOracle;

constructor(address _preimageOracle) {
constructor(IPreimageOracle _preimageOracle) {
preimageOracle = _preimageOracle;
}

// we must use public scope because solidity compiler optimizes out
function localize(bytes32 _key, bytes32 _localContext) public view returns (bytes32 localizedKey_) {
return PreimageKeyLib.localize(_key, _localContext);
}

// Executes a single RISC-V instruction, starting from
function step(bytes calldata stateData, bytes calldata proof, bytes32 localContext) public returns (bytes32) {
assembly {
Expand All @@ -21,7 +27,6 @@ contract Step {
function preimageOraclePos() -> out { // slot of preimageOraclePos field
out := 0
}

//
// Yul64 - functions to do 64 bit math - see yul64.go
//
Expand Down Expand Up @@ -764,27 +769,63 @@ contract Step {
mstore(add(memPtr, 0x04), key)
mstore(add(memPtr, 0x24), offset)
let cgas := 100000 // TODO change call gas

// CALL: msg.sender: this contract,
// DELEGATECALL: msg.sender: tx.origin

let res := call(cgas, addr, 0, memPtr, 0x44, 0x00, 0x40) // output into scratch space
if res { // 1 on success
dat := mload(0x00)
datlen := mload(0x20)
leave
}
revertWithCode(0xbadf00d0)
revertWithCode(0x4321dead)
}

function localize(preImageKey, localContext_) -> localizedKey {
// TODO: deduplicate definition of localize using lib
// Grab the current free memory pointer to restore later.
let ptr := mload(0x40)
// Store the local data key and caller next to each other in memory for hashing.
mstore(0, preImageKey)
mstore(0x20, caller())
mstore(0x40, localContext_)
// Localize the key with the above `localize` operation.
localizedKey := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1))
// Restore the free memory pointer.
mstore(0x40, ptr)
// calling address(this).localize(bytes32,bytes32)
// eventually calling PreimageKeyLib.localize(bytes32,bytes32)
let memPtr := mload(0x40) // get pointer to free memory for preimage interactions
mstore(memPtr, shl(224, 0x1aae47f0)) // (32-4)*8=224: right-pad the function selector, and then store it as prefix
mstore(add(memPtr, 0x04), preImageKey)
mstore(add(memPtr, 0x24), localContext_)
let cgas := 100000 // TODO change call gas

let res := delegatecall(cgas, address(), memPtr, 0x44, 0x00, 0x20) // output into scratch space
if res { // 1 on success
localizedKey := mload(0x00)
leave
}
revertWithCode(0x1234dead)

// let addr := sload(preimageKeyLibPos()) // calling PreimageKeyLib.localize(bytes32,bytes32)
// let memPtr := mload(0x40) // get pointer to free memory for preimage interactions
// mstore(memPtr, shl(224, 0x1aae47f0)) // (32-4)*8=224: right-pad the function selector, and then store it as prefix
// mstore(add(memPtr, 0x04), preImageKey)
// mstore(add(memPtr, 0x24), localContext_)
// let cgas := 100000 // TODO change call gas

// // CALL: msg.sender: this contract
// // DELEGATECALL: msg.sender: tx.origin

// let res := call(cgas, addr, 0, memPtr, 0x44, 0x00, 0x20) // output into scratch space
// if res { // 1 on success
// localizedKey := mload(0x00)
// leave
// }
// revertWithCode(0x1234dead)

// // TODO: deduplicate definition of localize using lib
// // Grab the current free memory pointer to restore later.
// let ptr := mload(0x40)
// // Store the local data key and caller next to each other in memory for hashing.
// mstore(0, preImageKey)
// mstore(0x20, caller())
// mstore(0x40, localContext_)
// // Localize the key with the above `localize` operation.
// localizedKey := or(and(keccak256(0, 0x60), not(shl(248, 0xFF))), shl(248, 1))
// // Restore the free memory pointer.
// mstore(0x40, ptr)
}

function readPreimageValue(addr, count, localContext_) -> out {
Expand Down
Loading
Loading