diff --git a/crates/preimage/src/hint.rs b/crates/preimage/src/hint.rs new file mode 100644 index 00000000..4fc1df32 --- /dev/null +++ b/crates/preimage/src/hint.rs @@ -0,0 +1,62 @@ +use crate::PipeHandle; +use alloc::vec; +use anyhow::{bail, Result}; + +/// A [HintWriter] is a high-level interface to the hint pipe. It provides a way to write hints to the host. +#[derive(Debug, Clone, Copy)] +pub struct HintWriter { + pipe_handle: PipeHandle, +} + +impl HintWriter { + /// Create a new [HintWriter] from a [PipeHandle]. + pub fn new(pipe_handle: PipeHandle) -> Self { + Self { pipe_handle } + } + + /// Write a hint to the host. This will overwrite any existing hint in the pipe, and block until all data has been + /// written. + pub fn write(&self, hint: &str) -> Result<()> { + // Form the hint into a byte buffer. The format is a 4-byte big-endian length prefix followed by the hint + // string. + let mut hint_bytes = vec![0u8; hint.len() + 4]; + hint_bytes[0..4].copy_from_slice(u32::to_be_bytes(hint.len() as u32).as_ref()); + hint_bytes[4..].copy_from_slice(hint.as_bytes()); + + // Write the hint to the host. + let mut written = 0; + loop { + match self.pipe_handle.write(&hint_bytes[written..]) { + Ok(0) => break, + Ok(n) => { + written += n as usize; + continue; + } + Err(e) => bail!("Failed to write preimage key: {}", e), + } + } + + // Read the hint acknowledgement from the host. + let mut hint_ack = [0u8; 1]; + self.read_exact(&mut hint_ack)?; + + Ok(()) + } + + /// Reads bytes into `buf` and returns the number of bytes read. + fn read(&self, buf: &mut [u8]) -> Result { + let read = self.pipe_handle.read(buf)?; + Ok(read as usize) + } + + /// Reads exactly `buf.len()` bytes into `buf`, blocking until all bytes are read. + fn read_exact(&self, buf: &mut [u8]) -> Result<()> { + let mut read = 0; + while read < buf.len() { + let chunk_read = self.read(&mut buf[read..])?; + read += chunk_read; + } + + Ok(()) + } +} diff --git a/crates/preimage/src/hint_writer.rs b/crates/preimage/src/hint_writer.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/preimage/src/lib.rs b/crates/preimage/src/lib.rs index 0a8eff05..48ab8d7b 100644 --- a/crates/preimage/src/lib.rs +++ b/crates/preimage/src/lib.rs @@ -17,5 +17,8 @@ pub use key::{PreimageKey, PreimageKeyType}; mod oracle; pub use oracle::OracleReader; +mod hint; +pub use hint::HintWriter; + mod pipe; pub use pipe::PipeHandle; diff --git a/examples/simple-revm/src/main.rs b/examples/simple-revm/src/main.rs index 0866de57..6dfd3628 100644 --- a/examples/simple-revm/src/main.rs +++ b/examples/simple-revm/src/main.rs @@ -4,7 +4,7 @@ use alloc::vec::Vec; use anyhow::{anyhow, bail, Result}; use kona_common::{io, FileDescriptor}; -use kona_preimage::{OracleReader, PipeHandle, PreimageKey, PreimageKeyType}; +use kona_preimage::{HintWriter, OracleReader, PipeHandle, PreimageKey, PreimageKeyType}; use revm::{ db::{CacheDB, EmptyDB}, primitives::{ @@ -27,15 +27,20 @@ const CODE_KEY: B256 = b256!("00000000000000000000000000000000000000000000000000 static CLIENT_PREIMAGE_PIPE: PipeHandle = PipeHandle::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite); +static CLIENT_HINT_PIPE: PipeHandle = + PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); #[no_mangle] pub extern "C" fn _start() { kona_common::alloc_heap!(HEAP_SIZE); + let mut oracle = OracleReader::new(CLIENT_PREIMAGE_PIPE); + let hint_writer = HintWriter::new(CLIENT_HINT_PIPE); + io::print("Booting EVM and checking hash...\n"); - let (input, digest, code) = boot().expect("Failed to boot"); + let (digest, code) = boot(&mut oracle).expect("Failed to boot"); - match run_evm(input, digest, code) { + match run_evm(&mut oracle, &hint_writer, digest, code) { Ok(_) => io::print("Success, hashes matched!\n"), Err(e) => { let _ = io::print_err(alloc::format!("Error: {}\n", e).as_ref()); @@ -47,20 +52,28 @@ pub extern "C" fn _start() { } /// Boot the program and load bootstrap information. -fn boot() -> Result<(Vec, [u8; 32], Vec)> { - let mut oracle = OracleReader::new(CLIENT_PREIMAGE_PIPE); - let input = oracle.get(PreimageKey::new(*INPUT_KEY, PreimageKeyType::Local))?; +fn boot(oracle: &mut OracleReader) -> Result<([u8; 32], Vec)> { let digest = oracle .get(PreimageKey::new(*DIGEST_KEY, PreimageKeyType::Local))? .try_into() .map_err(|_| anyhow!("Failed to convert digest to [u8; 32]"))?; let code = oracle.get(PreimageKey::new(*CODE_KEY, PreimageKeyType::Local))?; - Ok((input, digest, code)) + Ok((digest, code)) } /// Call the SHA-256 precompile and assert that the input and output match the expected values -fn run_evm(input: Vec, digest: [u8; 32], code: Vec) -> Result<()> { +fn run_evm( + oracle: &mut OracleReader, + hint_writer: &HintWriter, + digest: [u8; 32], + code: Vec, +) -> Result<()> { + // Send a hint for the preimage of the digest to the host so that it can prepare the preimage. + hint_writer.write(&alloc::format!("sha2-preimage {}", hex::encode(digest)))?; + // Get the preimage of `digest` from the host. + let input = oracle.get(PreimageKey::new(*INPUT_KEY, PreimageKeyType::Local))?; + let mut cache_db = CacheDB::new(EmptyDB::default()); // Insert EVM identity contract into database. diff --git a/fpvm-tests/asterisc-tests/simple_revm_test.go b/fpvm-tests/asterisc-tests/simple_revm_test.go index 108f244f..14b0837e 100644 --- a/fpvm-tests/asterisc-tests/simple_revm_test.go +++ b/fpvm-tests/asterisc-tests/simple_revm_test.go @@ -3,6 +3,7 @@ package asterisc_test import ( "crypto/sha256" "debug/elf" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -14,11 +15,13 @@ import ( func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr string) { images := make(map[[32]byte][]byte) + sha2Preimages := make(map[[32]byte][]byte) + input := []byte("facade facade facade") shaHash := sha256.Sum256(input) - // shaHash[0] = 0x01 - images[preimage.LocalIndexKey(0).PreimageKey()] = input images[preimage.LocalIndexKey(1).PreimageKey()] = shaHash[:] + sha2Preimages[shaHash] = input + // CALLDATASIZE // PUSH0 // PUSH0 @@ -30,7 +33,16 @@ func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr stri oracle := &testOracle{ hint: func(v []byte) { - // no-op + hintStr := string(v) + hintParts := strings.Split(hintStr, " ") + + switch hintParts[0] { + case "sha2-preimage": + hash := common.HexToHash(hintParts[1]) + images[preimage.LocalIndexKey(0).PreimageKey()] = sha2Preimages[hash] + default: + t.Fatalf("unknown hint: %s", hintStr) + } }, getPreimage: func(k [32]byte) []byte { p, ok := images[k] diff --git a/fpvm-tests/cannon-go-tests/minimal_test.go b/fpvm-tests/cannon-go-tests/minimal_test.go index 7578a82b..89ee32c1 100644 --- a/fpvm-tests/cannon-go-tests/minimal_test.go +++ b/fpvm-tests/cannon-go-tests/minimal_test.go @@ -25,12 +25,10 @@ func TestMinimal(t *testing.T) { us := mipsevm.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) for i := 0; i < 200_000; i++ { - wit, err := us.Step(true) + _, err := us.Step(true) require.NoError(t, err) - // hack: state isn't exposed in `InstrumentedState`, so we generate the - // state witness each step and check for the exit condition - if wit != nil && wit.State[89] == 1 { - fmt.Printf("exited @ step #%d\n", i) + if state.Exited { + fmt.Printf("exited @ step #%d\n", state.Step) break } } diff --git a/fpvm-tests/cannon-go-tests/simple_revm_test.go b/fpvm-tests/cannon-go-tests/simple_revm_test.go index cbaf8a7f..b3fa6a0a 100644 --- a/fpvm-tests/cannon-go-tests/simple_revm_test.go +++ b/fpvm-tests/cannon-go-tests/simple_revm_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "strings" "testing" "github.com/ethereum-optimism/optimism/cannon/mipsevm" @@ -38,11 +39,13 @@ var _ PreimageOracle = (*testOracle)(nil) func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr string) { images := make(map[[32]byte][]byte) + sha2Preimages := make(map[[32]byte][]byte) + input := []byte("facade facade facade") shaHash := sha256.Sum256(input) - // shaHash[0] = 0x01 - images[preimage.LocalIndexKey(0).PreimageKey()] = input images[preimage.LocalIndexKey(1).PreimageKey()] = shaHash[:] + sha2Preimages[shaHash] = input + // CALLDATASIZE // PUSH0 // PUSH0 @@ -54,7 +57,16 @@ func rustTestOracle(t *testing.T) (po PreimageOracle, stdOut string, stdErr stri oracle := &testOracle{ hint: func(v []byte) { - // no-op + hintStr := string(v) + hintParts := strings.Split(hintStr, " ") + + switch hintParts[0] { + case "sha2-preimage": + hash := common.HexToHash(hintParts[1]) + images[preimage.LocalIndexKey(0).PreimageKey()] = sha2Preimages[hash] + default: + t.Fatalf("unknown hint: %s", hintStr) + } }, getPreimage: func(k [32]byte) []byte { p, ok := images[k] @@ -81,12 +93,10 @@ func TestSimpleRevm(t *testing.T) { us := mipsevm.NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr)) for i := 0; i < 200_000; i++ { - wit, err := us.Step(true) + _, err := us.Step(false) require.NoError(t, err) - // hack: state isn't exposed in `InstrumentedState`, so we generate the - // state witness each step and check for the exit condition - if wit != nil && wit.State[89] == 1 { - fmt.Printf("exited @ step #%d\n", i) + if state.Exited { + fmt.Printf("exited @ step #%d\n", state.Step) break } } diff --git a/fpvm-tests/cannon-rs-tests/src/simple_revm_test.rs b/fpvm-tests/cannon-rs-tests/src/simple_revm_test.rs index 164cf772..763e48be 100644 --- a/fpvm-tests/cannon-rs-tests/src/simple_revm_test.rs +++ b/fpvm-tests/cannon-rs-tests/src/simple_revm_test.rs @@ -2,9 +2,7 @@ use alloy_primitives::hex; use anyhow::Result; -use cannon_mipsevm::{ - load_elf, patch_stack, InstrumentedState, PreimageOracle, -}; +use cannon_mipsevm::{load_elf, patch_stack, InstrumentedState, PreimageOracle}; use preimage_oracle::{Hint, Key, LocalIndexKey}; use sha2::{Digest, Sha256}; use std::{collections::HashMap, io::BufWriter}; @@ -40,6 +38,7 @@ fn test_simple_revm() { pub struct RevmTestOracle { images: HashMap<[u8; 32], Vec>, + sha2_preimages: HashMap<[u8; 32], Vec>, } impl RevmTestOracle { @@ -50,25 +49,46 @@ impl RevmTestOracle { let input_hash = hasher.finalize(); let mut images = HashMap::new(); - images.insert((0 as LocalIndexKey).preimage_key(), INPUT.to_vec()); images.insert((1 as LocalIndexKey).preimage_key(), input_hash.to_vec()); images.insert( (2 as LocalIndexKey).preimage_key(), hex!("365f5f37365ff3").to_vec(), ); - Self { images } + let mut sha2_preimages = HashMap::new(); + sha2_preimages.insert(input_hash.try_into().unwrap(), INPUT.to_vec()); + + Self { + images, + sha2_preimages, + } } } impl PreimageOracle for RevmTestOracle { - fn hint(&mut self, _: impl Hint) -> Result<()> { - // no-op - Ok(()) + fn hint(&mut self, hint: impl Hint) -> Result<()> { + let hint_str = std::str::from_utf8(hint.hint())?; + let hint_parts = hint_str.split_whitespace().collect::>(); + + match hint_parts[0] { + "sha2-preimage" => { + let hash: [u8; 32] = hex::decode(hint_parts[1])? + .try_into() + .map_err(|_| anyhow::anyhow!("Failed to parse hash"))?; + self.images.insert( + (0 as LocalIndexKey).preimage_key(), + self.sha2_preimages + .get(&hash) + .ok_or(anyhow::anyhow!("No preimage for hash"))? + .to_vec(), + ); + Ok(()) + } + _ => anyhow::bail!("Unknown hint: {}", hint_str), + } } fn get(&mut self, key: [u8; 32]) -> Result> { - dbg!(&key); Ok(self .images .get(&key)