diff --git a/rust/verify_steps/.gitignore b/rust/verify_steps/.gitignore new file mode 100644 index 000000000..283c2a3d2 --- /dev/null +++ b/rust/verify_steps/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +Cargo.lock +methods/guest/Cargo.lock +target/ +.vscode + diff --git a/rust/verify_steps/Cargo.toml b/rust/verify_steps/Cargo.toml new file mode 100644 index 000000000..63b3854ee --- /dev/null +++ b/rust/verify_steps/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = ["host", "methods"] + +# Always optimize; building and running the guest takes much longer without optimization. +[profile.dev] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true diff --git a/rust/verify_steps/host/Cargo.toml b/rust/verify_steps/host/Cargo.toml new file mode 100644 index 000000000..58f90a8d7 --- /dev/null +++ b/rust/verify_steps/host/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "host" +version = "0.1.0" +edition = "2021" + +[dependencies] +memmap2 = "0.3" +methods = { path = "../methods" } +risc0-zkvm = { version = "0.21.0" } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +hex = "0.4" +sha2 = "0.10" diff --git a/rust/verify_steps/host/src/main.rs b/rust/verify_steps/host/src/main.rs new file mode 100644 index 000000000..5faec3089 --- /dev/null +++ b/rust/verify_steps/host/src/main.rs @@ -0,0 +1,74 @@ +use std::env; +use memmap2::MmapOptions; +use std::fs::{File}; +use methods::{ + TESTE1_ELF, TESTE1_ID +}; +use risc0_zkvm::{ + default_prover, + ExecutorEnv }; + + + /* + How to run this: + 1) create a step log + cartesi-machine.lua --max-mcycle=0 --log-step=1,/tmp/step.bin + Logging step of 1 cycles to /tmp/step.bin + 0: 5b4d6e46f7c024a1c108dcd2d7c174ec8ed2259a7ca53e362c611c2476791cb0 + 1: f66a12a3601c991025f7658226220f8346365f98958f5859a3add8954c1b1dd4 + + 2) pass hashes, log file and mcycle_count to the host + cargo run 5b4d6e46f7c024a1c108dcd2d7c174ec8ed2259a7ca53e362c611c2476791cb0 /tmp/step.bin 1 f66a12a3601c991025f7658226220f8346365f98958f5859a3add8954c1b1dd4 + */ + fn main() { + fn parse_hash(hex: &str) -> [u8; 32] { + let bytes = hex::decode(hex).expect("Invalid hex string"); + let mut array = [0; 32]; + array.copy_from_slice(&bytes); + array + } + + let args: Vec = env::args().collect(); + if args.len() != 5 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + let root_hash_before = parse_hash(&args[1]); + let log_file_path = &args[2]; + let mcycle_count: u64 = args[3].parse().expect("Invalid step count"); + let root_hash_after = parse_hash(&args[4]); + assert_eq!(root_hash_before.len(), 32); + assert_eq!(root_hash_after.len(), 32); + + // mmap the step log file + let log_file = File::open(log_file_path).expect("Could not open log file"); + let log_file_len = log_file.metadata().expect("Could not get metadata").len(); + let log_file = unsafe { + MmapOptions::new() + .len(log_file_len as usize) + .map(&log_file) + .expect("Could not memory map log file") + }; + + // build, run and verify the prover + let mut builder = ExecutorEnv::builder(); + builder.write(&mcycle_count).unwrap(); + builder.write(&root_hash_before).unwrap(); + builder.write(&root_hash_after).unwrap(); + builder.write(&log_file_len).unwrap(); + for i in (0..log_file_len).step_by(1) { + builder.write(&log_file[i as usize]).unwrap(); + } + let env = builder.build().unwrap(); + let prover = default_prover(); + let receipt = prover + .prove(env, TESTE1_ELF) + .unwrap(); + + //todo result to be hashes and mcycle_count + let result: bool = receipt.journal.decode().unwrap(); + println!("host: result from guest: {:?}", result); + receipt + .verify(TESTE1_ID) + .unwrap(); +} diff --git a/rust/verify_steps/methods/Cargo.toml b/rust/verify_steps/methods/Cargo.toml new file mode 100644 index 000000000..2c120e462 --- /dev/null +++ b/rust/verify_steps/methods/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "methods" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "0.21.0" } + +[package.metadata.risc0] +methods = ["guest"] diff --git a/rust/verify_steps/methods/build.rs b/rust/verify_steps/methods/build.rs new file mode 100644 index 000000000..08a8a4eb7 --- /dev/null +++ b/rust/verify_steps/methods/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/rust/verify_steps/methods/guest/Cargo.toml b/rust/verify_steps/methods/guest/Cargo.toml new file mode 100644 index 000000000..5ebe56326 --- /dev/null +++ b/rust/verify_steps/methods/guest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "teste1" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +# If you want to try (experimental) std support, add `features = [ "std" ]` to risc0-zkvm +risc0-zkvm = { version = "0.21.0", default-features = true } +tiny-keccak = { version = "2.0", features = ["keccak"] } + +[build-dependencies] +cc = "1.0" diff --git a/rust/verify_steps/methods/guest/build.rs b/rust/verify_steps/methods/guest/build.rs new file mode 100644 index 000000000..ff1fda1db --- /dev/null +++ b/rust/verify_steps/methods/guest/build.rs @@ -0,0 +1,14 @@ +// fn main() { +// cc::Build::new() +// .object("../../../../zkarch/zkarch-replay-steps.o") +// .compile("loxa"); +// } + +fn main() { + const OBJ_PATH: &str = "../../../../zkarch/zkarch-replay-steps.o"; + + println!("cargo:rerun-if-changed={}", OBJ_PATH); + cc::Build::new() + .object(OBJ_PATH) + .compile("cartesi_zkarch_replay_steps"); +} diff --git a/rust/verify_steps/methods/guest/src/main.rs b/rust/verify_steps/methods/guest/src/main.rs new file mode 100644 index 000000000..f82430152 --- /dev/null +++ b/rust/verify_steps/methods/guest/src/main.rs @@ -0,0 +1,89 @@ +#![no_main] +// If you want to try std support, also update the guest Cargo.toml file +//#![no_std] // std support is experimental + +use risc0_zkvm::guest::env; +use std::ffi::{CStr}; +risc0_zkvm::guest::entry!(main); +use std::os::raw::{c_char, c_ulong, c_ulonglong}; +use tiny_keccak::{Hasher, Keccak}; + +#[no_mangle] +pub extern "C" fn interop_print(text: *const c_char) { + let str = unsafe { CStr::from_ptr(text).to_string_lossy().into_owned() }; + println!("print_from_c: {}", str); +} + +#[no_mangle] +pub extern "C" fn interop_merkle_tree_hash(data: *const c_char, size: c_ulong, hash: *mut c_char) { + let mut hasher = Keccak::v256(); + if size > 32 { + unsafe { + let half_size = size / 2; + let left_hash = [0u8; 32]; + interop_merkle_tree_hash(data, half_size, left_hash.as_ptr() as *mut c_char); + let right_hash = [0u8; 32]; + interop_merkle_tree_hash(data.add(half_size as usize) as *const c_char, half_size, right_hash.as_ptr() as *mut c_char); + hasher.update(left_hash.as_ref()); + hasher.update(right_hash.as_ref()); + let mut result_bytes = [0u8; 32]; + hasher.finalize(&mut result_bytes); + std::ptr::copy(result_bytes.as_ptr(), hash as *mut u8, 32); + } + } else{ + let mut hasher = Keccak::v256(); + hasher.update(unsafe { std::slice::from_raw_parts(data as *const u8, size as usize) }); + let mut result_bytes = [0u8; 32]; + hasher.finalize(&mut result_bytes); + unsafe { + std::ptr::copy(result_bytes.as_ptr(), hash as *mut u8, 32); + } + } +} + +#[no_mangle] +pub extern "C" fn interop_concat_hash(left: *const c_char, right: *const c_char, result: *mut c_char) { + let mut hasher = Keccak::v256(); + hasher.update(unsafe { std::slice::from_raw_parts(left as *const u8, 32) }); + hasher.update(unsafe { std::slice::from_raw_parts(right as *const u8, 32) }); + let mut result_bytes = [0u8; 32]; + hasher.finalize(&mut result_bytes); + unsafe { + std::ptr::copy(result_bytes.as_ptr(), result as *mut u8, 32); + } +} + +#[no_mangle] +pub extern "C" fn interop_abort_with_msg(msg: *const c_char) { + let str = unsafe { CStr::from_ptr(msg).to_string_lossy().into_owned() }; + panic!("abort_with_msg: {}", str); +} + +extern "C" { + pub fn zkarch_replay_steps(root_hash_before: *const c_char, raw_log_data: *const c_char, raw_log_size: c_ulonglong, mcycle_count: c_ulonglong, root_hash_after: *const c_char) -> c_ulonglong; +} + + +fn main() { + let mcycle_count: u64 = env::read(); + let root_hash_before : [u8; 32] = env::read(); + let root_hash_after : [u8; 32] = env::read(); + let raw_log_length : u64 = env::read(); + println!("guest: mcycle_count: {:?}", mcycle_count); + println!("guest: root_hash_before: {:?}", root_hash_before); + println!("guest: root_hash_after: {:?}", root_hash_after); + println!("guest: raw_log_length: {:?}", raw_log_length); + let mut raw_log: Vec = vec![0; raw_log_length as usize]; + for i in (0..raw_log_length).step_by(1) { + raw_log[i as usize] = env::read(); + } + println!("guest: before zkarch_replay_steps"); + unsafe { + zkarch_replay_steps(root_hash_before.as_ptr() as *const c_char, raw_log.as_ptr() as *const c_char, raw_log_length, mcycle_count, root_hash_after.as_ptr() as *const c_char); + } + println!("guest: after zkarch_replay_steps"); + let result : bool = true; // TODO: result -> root_hash_before, mcycle_count and root_hash_after + println!("guest: commiting"); + env::commit(&result); + println!("guest: committed"); +} diff --git a/rust/verify_steps/methods/src/lib.rs b/rust/verify_steps/methods/src/lib.rs new file mode 100644 index 000000000..1bdb3085f --- /dev/null +++ b/rust/verify_steps/methods/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/rust/verify_steps/rust-toolchain.toml b/rust/verify_steps/rust-toolchain.toml new file mode 100644 index 000000000..36614c30c --- /dev/null +++ b/rust/verify_steps/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "rust-src"] +profile = "minimal" diff --git a/src/interpret.cpp b/src/interpret.cpp index c320da7e2..c6c5a2115 100644 --- a/src/interpret.cpp +++ b/src/interpret.cpp @@ -17,9 +17,11 @@ #include #include -#ifdef MICROARCHITECTURE +#if defined(MICROARCHITECTURE) #include "uarch-machine-state-access.h" #include "uarch-runtime.h" +#elif defined(ZKARCHITECTURE) +#include "replay-step-state-access.h" #else #include "record-step-state-access.h" #include "replay-step-state-access.h" @@ -5674,8 +5676,10 @@ interpreter_break_reason interpret(STATE_ACCESS &a, uint64_t mcycle_end) { } } -#ifdef MICROARCHITECTURE +#if defined(MICROARCHITECTURE) template interpreter_break_reason interpret(uarch_machine_state_access &a, uint64_t mcycle_end); +#elif defined(ZKARCHITECTURE) +template interpreter_break_reason interpret(replay_step_state_access &a, uint64_t mcycle_end); #else // Explicit instantiation for state_access template interpreter_break_reason interpret(state_access &a, uint64_t mcycle_end); diff --git a/src/interpret.h b/src/interpret.h index a28db95c9..48a0526bf 100644 --- a/src/interpret.h +++ b/src/interpret.h @@ -56,10 +56,13 @@ enum class interpreter_break_reason { template interpreter_break_reason interpret(STATE_ACCESS &a, uint64_t mcycle_end); -#ifdef MICROARCHITECTURE +#if defined(MICROARCHITECTURE) class uarch_machine_state_access; // Declaration of explicit instantiation in module interpret.cpp when compiled with microarchitecture extern template interpreter_break_reason interpret(uarch_machine_state_access &a, uint64_t mcycle_end); +#elif defined(ZKARCHITECTURE) +class replay_step_state_access; +extern template interpreter_break_reason interpret(replay_step_state_access &a, uint64_t mcycle_end); #else // Forward declarations class state_access; diff --git a/src/os.cpp b/src/os.cpp index ecc74fd19..8528ea03b 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -689,6 +689,8 @@ uint64_t os_get_concurrency() { #endif } +#ifndef ZKARCHITECTURE + bool os_parallel_for(uint64_t n, const std::function &task) { #ifdef HAVE_THREADS if (n > 1) { @@ -717,6 +719,8 @@ bool os_parallel_for(uint64_t n, const std::function lock; @@ -141,6 +142,8 @@ using os_select_after_callback = std::function. +// + +#define ZKARCHITECTURE 1 + +#include "zkarch-runtime.h" +#include "interpret.h" +#include "replay-step-state-access.h" +#include + +using namespace cartesi; + +extern "C" void zkarch_replay_steps( + interop_hash_type root_hash_before, + unsigned char* step_log_image, + uint64_t step_log_image_size, + uint64_t mcycle_count, + interop_hash_type root_hash_after) +{ + replay_step_state_access a(step_log_image, step_log_image_size, *reinterpret_cast(root_hash_before)); + uint64_t mcycle_end{}; + (void) __builtin_add_overflow(a.read_mcycle(), mcycle_count, &mcycle_end); + interpret(a, mcycle_end); + a.finish(*reinterpret_cast(root_hash_after)); +} diff --git a/zkarch/zkarch-runtime.cpp b/zkarch/zkarch-runtime.cpp new file mode 100644 index 000000000..c9cb5ec93 --- /dev/null +++ b/zkarch/zkarch-runtime.cpp @@ -0,0 +1,88 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#include +#include + +#include "zkarch-runtime.h" +#include "replay-step-state-access-interop.h" +#include "os.h" + +using namespace cartesi; + +extern "C" void __cxa_pure_virtual() {} +void operator delete(void *, unsigned long) {} +void operator delete(void *, unsigned int) {} + +extern "C" void __assert_func(const char *file, int line, const char *, const char *e) {} + +extern "C" void __assert_fail(const char *__assertion, const char *__file, unsigned int __line, + const char *__function) { + abort(); + +} + +extern "C" NO_RETURN void abort(void) { + interop_abort_with_msg("abort() called"); + __builtin_trap(); +} + +namespace cartesi { + +void os_open_tty(void) {} + +void os_close_tty(void) {} + +bool os_poll_tty(uint64_t timeout_us) { + (void) timeout_us; + return false; +} + +int os_getchar(void) { + return -1; +} + +void os_putchar(uint8_t ch) { +} + +#include + +// The __ucmpdi2 function performs an unsigned comparison between two 64-bit integers. +// It returns 0 if a == b, a positive value if a > b, and a negative value if a < b. +extern "C" int __ucmpdi2(uint64_t a, uint64_t b) { + uint32_t a_high = a >> 32; + uint32_t b_high = b >> 32; + if (a_high > b_high) { + return 1; + } else if (a_high < b_high) { + return -1; + } + uint32_t a_low = (uint32_t)a; + uint32_t b_low = (uint32_t)b; + if (a_low > b_low) { + return 1; + } else if (a_low < b_low) { + return -1; + } + return 0; +} + +extern "C" int __clzdi2(uint64_t a) { + abort(); // TODO +} + + +} // namespace cartesi diff --git a/zkarch/zkarch-runtime.h b/zkarch/zkarch-runtime.h new file mode 100644 index 000000000..9367ce8ab --- /dev/null +++ b/zkarch/zkarch-runtime.h @@ -0,0 +1,37 @@ +// Copyright Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with this program (see COPYING). If not, see . +// + +#ifndef ZKUARCH_RUNTIME_H +#define ZKUARCH_RUNTIME_H + +#include "compiler-defines.h" + +#include +#include + + +// Methods implemented inrisc0 guest +extern "C" void print_from_c(const char *text); +extern "C" void abort_from_c(); + +#define assert(a) \ + if (!(a)) { \ + abort(); \ + } + +extern "C" NO_RETURN void abort(void); + +#endif