monerochan_runtime/
lib.rs

1#[cfg(all(target_os = "zkvm", feature = "embedded"))]
2use syscalls::MAX_MEMORY;
3
4#[cfg(target_os = "zkvm")]
5use {
6    cfg_if::cfg_if,
7    syscalls::{syscall_hint_len, syscall_hint_read},
8};
9
10extern crate alloc;
11
12#[cfg(target_os = "zkvm")]
13pub mod allocators;
14
15pub mod syscalls;
16
17#[cfg(feature = "lib")]
18pub mod io {
19    pub use monerochan_lib::io::*;
20}
21
22#[cfg(feature = "lib")]
23pub mod lib {
24    pub use monerochan_lib::*;
25}
26
27#[cfg(all(target_os = "zkvm", feature = "libm"))]
28mod libm;
29
30/// The number of 32 bit words that the public values digest is composed of.
31pub const PV_DIGEST_NUM_WORDS: usize = 8;
32pub const POSEIDON_NUM_WORDS: usize = 8;
33
34/// Size of the reserved region for input values with the embedded allocator.
35#[cfg(all(target_os = "zkvm", feature = "embedded"))]
36pub(crate) const EMBEDDED_RESERVED_INPUT_REGION_SIZE: usize = 1024 * 1024 * 1024;
37
38/// Start of the reserved region for inputs with the embedded allocator.
39#[cfg(all(target_os = "zkvm", feature = "embedded"))]
40pub(crate) const EMBEDDED_RESERVED_INPUT_START: usize =
41    MAX_MEMORY - EMBEDDED_RESERVED_INPUT_REGION_SIZE;
42
43/// Pointer to the current position in the reserved region for inputs with the embedded allocator.
44#[cfg(all(target_os = "zkvm", feature = "embedded"))]
45static mut EMBEDDED_RESERVED_INPUT_PTR: usize = EMBEDDED_RESERVED_INPUT_START;
46
47#[repr(C)]
48pub struct ReadVecResult {
49    pub ptr: *mut u8,
50    pub len: usize,
51    pub capacity: usize,
52}
53
54/// Read a buffer from the input stream.
55///
56/// The buffer is read into uninitialized memory.
57///
58/// When the `bump` feature is enabled, the buffer is read into a new buffer allocated by the
59/// program.
60///
61/// When the `embedded` feature is enabled, the buffer is read into the reserved input region.
62///
63/// When there is no allocator selected, the program will fail to compile.
64///
65/// If the input stream is exhausted, the failed flag will be returned as true. In this case, the
66/// other outputs from the function are likely incorrect, which is fine as `monerochan-lib` always panics
67/// in the case that the input stream is exhausted.
68#[no_mangle]
69pub extern "C" fn read_vec_raw() -> ReadVecResult {
70    #[cfg(not(target_os = "zkvm"))]
71    unreachable!("read_vec_raw should only be called on the zkvm target.");
72
73    #[cfg(target_os = "zkvm")]
74    {
75        // Get the length of the input buffer.
76        let len = syscall_hint_len();
77
78        // If the length is u32::MAX, then the input stream is exhausted.
79        if len == usize::MAX {
80            return ReadVecResult { ptr: std::ptr::null_mut(), len: 0, capacity: 0 };
81        }
82
83        // Round up to multiple of 4 for whole-word alignment.
84        let capacity = (len + 3) / 4 * 4;
85
86        cfg_if! {
87            if #[cfg(feature = "embedded")] {
88                // Get the existing pointer in the reserved region which is the start of the vec.
89                // Increment the pointer by the capacity to set the new pointer to the end of the vec.
90                let ptr = unsafe { EMBEDDED_RESERVED_INPUT_PTR };
91                if ptr.saturating_add(capacity) > MAX_MEMORY {
92                    panic!("Input region overflowed.")
93                }
94
95                // SAFETY: The VM is single threaded.
96                unsafe { EMBEDDED_RESERVED_INPUT_PTR += capacity };
97
98                // Read the vec into uninitialized memory. The syscall assumes the memory is
99                // uninitialized, which is true because the input ptr is incremented manually on each
100                // read.
101                syscall_hint_read(ptr as *mut u8, len);
102
103                // Return the result.
104                ReadVecResult {
105                    ptr: ptr as *mut u8,
106                    len,
107                    capacity,
108                }
109            } else {
110                // Allocate a buffer of the required length that is 4 byte aligned.
111                let layout = std::alloc::Layout::from_size_align(capacity, 4).expect("vec is too large");
112
113                // SAFETY: The layout was made through the checked constructor.
114                let ptr = unsafe { std::alloc::alloc(layout) };
115
116                // Read the vec into uninitialized memory. The syscall assumes the memory is
117                // uninitialized, which is true because the bump allocator does not dealloc, so a new
118                // alloc is always fresh.
119                syscall_hint_read(ptr as *mut u8, len);
120
121                // Return the result.
122                ReadVecResult {
123                    ptr: ptr as *mut u8,
124                    len,
125                    capacity,
126                }
127            }
128        }
129    }
130}
131
132#[cfg(target_os = "zkvm")]
133mod zkvm {
134    use crate::syscalls::syscall_halt;
135
136    use cfg_if::cfg_if;
137    use sha2::{Digest, Sha256};
138
139    cfg_if! {
140        if #[cfg(feature = "verify")] {
141            use p3_baby_bear::BabyBear;
142            use p3_field::AbstractField;
143
144            pub static mut DEFERRED_PROOFS_DIGEST: Option<[BabyBear; 8]> = None;
145        }
146    }
147
148    cfg_if::cfg_if! {
149        if #[cfg(feature = "blake3")] {
150            pub static mut PUBLIC_VALUES_HASHER: Option<blake3::Hasher> = None;
151        }
152        else {
153            pub static mut PUBLIC_VALUES_HASHER: Option<Sha256> = None;
154        }
155    }
156
157    #[no_mangle]
158    unsafe extern "C" fn __start() {
159        {
160            #[cfg(all(target_os = "zkvm", feature = "embedded"))]
161            crate::allocators::init();
162
163            cfg_if::cfg_if! {
164                if #[cfg(feature = "blake3")] {
165                    PUBLIC_VALUES_HASHER = Some(blake3::Hasher::new());
166                }
167                else {
168                    PUBLIC_VALUES_HASHER = Some(Sha256::new());
169                }
170            }
171
172            #[cfg(feature = "verify")]
173            {
174                DEFERRED_PROOFS_DIGEST = Some([BabyBear::zero(); 8]);
175            }
176
177            extern "C" {
178                fn main();
179            }
180            main()
181        }
182
183        syscall_halt(0);
184    }
185
186    static STACK_TOP: u32 = 0x0020_0400;
187
188    core::arch::global_asm!(include_str!("memset.s"));
189    core::arch::global_asm!(include_str!("memcpy.s"));
190
191    core::arch::global_asm!(
192        r#"
193    .section .text._start;
194    .globl _start;
195    _start:
196        .option push;
197        .option norelax;
198        la gp, __global_pointer$;
199        .option pop;
200        la sp, {0}
201        lw sp, 0(sp)
202        call __start;
203    "#,
204        sym STACK_TOP
205    );
206
207    pub fn zkvm_getrandom_v2(s: &mut [u8]) -> Result<(), getrandom_v2::Error> {
208        unsafe {
209            crate::syscalls::sys_rand(s.as_mut_ptr(), s.len());
210        }
211
212        Ok(())
213    }
214
215    getrandom_v2::register_custom_getrandom!(zkvm_getrandom_v2);
216
217    #[no_mangle]
218    unsafe extern "Rust" fn __getrandom_v03_custom(
219        dest: *mut u8,
220        len: usize,
221    ) -> Result<(), getrandom_v3::Error> {
222        unsafe {
223            crate::syscalls::sys_rand(dest, len);
224        }
225
226        Ok(())
227    }
228}
229
230#[macro_export]
231macro_rules! entrypoint {
232    ($path:path) => {
233        const ZKVM_ENTRY: fn() = $path;
234
235        mod zkvm_generated_main {
236
237            #[no_mangle]
238            fn main() {
239                // Link to the actual entrypoint only when compiling for zkVM, otherwise run a
240                // simple noop. Doing this avoids compilation errors when building for the host
241                // target.
242                //
243                // Note that, however, it's generally considered wasted effort compiling zkVM
244                // programs against the host target. This just makes it such that doing so wouldn't
245                // result in an error, which can happen when building a Cargo workspace containing
246                // zkVM program crates.
247                if cfg!(target_os = "zkvm") {
248                    super::ZKVM_ENTRY()
249                } else {
250                    eprintln!("Not running in zkVM, skipping entrypoint");
251                }
252            }
253        }
254    };
255}