risc0_zkvm_platform/
syscall.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[cfg(target_os = "zkvm")]
16use core::arch::asm;
17use core::{cmp::min, ffi::CStr, ptr::null_mut, slice, str::Utf8Error};
18
19use num_enum::{FromPrimitive, IntoPrimitive};
20use paste::paste;
21
22use crate::WORD_SIZE;
23
24pub mod ecall {
25    pub const HALT: u32 = 0;
26    pub const INPUT: u32 = 1;
27    pub const SOFTWARE: u32 = 2;
28    pub const SHA: u32 = 3;
29    pub const BIGINT: u32 = 4;
30    pub const USER: u32 = 5;
31    pub const BIGINT2: u32 = 6;
32    pub const POSEIDON2: u32 = 7;
33}
34
35pub mod halt {
36    pub const TERMINATE: u32 = 0;
37    pub const PAUSE: u32 = 1;
38    pub const SPLIT: u32 = 2;
39}
40
41pub mod reg_abi {
42    pub const REG_ZERO: usize = 0; // zero constant
43    pub const REG_RA: usize = 1; // return address
44    pub const REG_SP: usize = 2; // stack pointer
45    pub const REG_GP: usize = 3; // global pointer
46    pub const REG_TP: usize = 4; // thread pointer
47    pub const REG_T0: usize = 5; // temporary
48    pub const REG_T1: usize = 6; // temporary
49    pub const REG_T2: usize = 7; // temporary
50    pub const REG_S0: usize = 8; // saved register
51    pub const REG_FP: usize = 8; // frame pointer
52    pub const REG_S1: usize = 9; // saved register
53    pub const REG_A0: usize = 10; // fn arg / return value
54    pub const REG_A1: usize = 11; // fn arg / return value
55    pub const REG_A2: usize = 12; // fn arg
56    pub const REG_A3: usize = 13; // fn arg
57    pub const REG_A4: usize = 14; // fn arg
58    pub const REG_A5: usize = 15; // fn arg
59    pub const REG_A6: usize = 16; // fn arg
60    pub const REG_A7: usize = 17; // fn arg
61    pub const REG_S2: usize = 18; // saved register
62    pub const REG_S3: usize = 19; // saved register
63    pub const REG_S4: usize = 20; // saved register
64    pub const REG_S5: usize = 21; // saved register
65    pub const REG_S6: usize = 22; // saved register
66    pub const REG_S7: usize = 23; // saved register
67    pub const REG_S8: usize = 24; // saved register
68    pub const REG_S9: usize = 25; // saved register
69    pub const REG_S10: usize = 26; // saved register
70    pub const REG_S11: usize = 27; // saved register
71    pub const REG_T3: usize = 28; // temporary
72    pub const REG_T4: usize = 29; // temporary
73    pub const REG_T5: usize = 30; // temporary
74    pub const REG_T6: usize = 31; // temporary
75    pub const REG_MAX: usize = 32; // maximum number of registers
76}
77
78pub mod keccak_mode {
79    pub const KECCAK_PERMUTE: u32 = 0;
80    pub const KECCAK_PROVE: u32 = 1;
81}
82
83pub const DIGEST_WORDS: usize = 8;
84pub const DIGEST_BYTES: usize = WORD_SIZE * DIGEST_WORDS;
85
86pub const KECCACK_STATE_BYTES: usize = 200;
87pub const KECCACK_STATE_WORDS: usize = 200 / WORD_SIZE;
88pub const KECCACK_STATE_DWORDS: usize = 200 / 8;
89
90/// Number of words in each cycle received using the SOFTWARE ecall
91pub const IO_CHUNK_WORDS: usize = 4;
92
93// Limit syscall buffers so that the Executor doesn't get into an infinite
94// split situation.
95pub const MAX_BUF_BYTES: usize = 4 * 1024;
96pub const MAX_BUF_WORDS: usize = MAX_BUF_BYTES / WORD_SIZE;
97pub const MAX_SHA_COMPRESS_BLOCKS: usize = 1000;
98
99pub mod bigint {
100    pub const OP_MULTIPLY: u32 = 0;
101
102    /// BigInt width, in bits, handled by the BigInt accelerator circuit.
103    pub const WIDTH_BITS: usize = 256;
104
105    /// BigInt width, in bytes, handled by the BigInt accelerator circuit.
106    pub const WIDTH_BYTES: usize = WIDTH_BITS / 8;
107
108    /// BigInt width, in words, handled by the BigInt accelerator circuit.
109    pub const WIDTH_WORDS: usize = WIDTH_BYTES / crate::WORD_SIZE;
110}
111
112/// A UTF-8 NUL-terminated name of a syscall with static lifetime.
113#[derive(Clone, Copy, Debug)]
114#[repr(transparent)]
115pub struct SyscallName(*const u8);
116
117/// Construct a SyscallName declaration at compile time.
118///
119/// ```rust
120/// use risc0_zkvm_platform::declare_syscall;
121///
122/// declare_syscall!(SYS_MY_SYSTEM_CALL);
123/// ```
124#[macro_export]
125macro_rules! declare_syscall {
126    (
127        $(#[$meta:meta])*
128        $vis:vis $name:ident
129    ) => {
130        // Go through `CStr` to avoid `unsafe` in the caller.
131        $(#[$meta])*
132        $vis const $name: $crate::syscall::SyscallName = match ::core::ffi::CStr::from_bytes_until_nul(
133            concat!(module_path!(), "::", stringify!($name), "\0").as_bytes(),
134        ) {
135            Ok(c_str) => match $crate::syscall::SyscallName::from_c_str(c_str) {
136                Ok(name) => name,
137                Err(_) => unreachable!(),
138            },
139            Err(_) => unreachable!(),
140        };
141    };
142}
143
144pub mod nr {
145    declare_syscall!(pub SYS_ARGC);
146    declare_syscall!(pub SYS_ARGV);
147    declare_syscall!(pub SYS_CYCLE_COUNT);
148    declare_syscall!(pub SYS_EXIT);
149    declare_syscall!(pub SYS_FORK);
150    declare_syscall!(pub SYS_GETENV);
151    declare_syscall!(pub SYS_KECCAK);
152    declare_syscall!(pub SYS_LOG);
153    declare_syscall!(pub SYS_PANIC);
154    declare_syscall!(pub SYS_PIPE);
155    #[deprecated]
156    pub const SYS_PROVE_KECCAK: crate::syscall::SyscallName = unsafe {
157        crate::syscall::SyscallName::from_bytes_with_nul(
158            c"risc0-zkvm-platform::syscall::SYS_PROVE_KECCAK".as_ptr() as *const u8,
159        )
160    };
161    declare_syscall!(pub SYS_PROVE_ZKR);
162    declare_syscall!(pub SYS_RANDOM);
163    declare_syscall!(pub SYS_READ);
164    declare_syscall!(pub SYS_VERIFY_INTEGRITY);
165    declare_syscall!(pub SYS_VERIFY_INTEGRITY2);
166    declare_syscall!(pub SYS_WRITE);
167}
168
169#[repr(usize)]
170#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, FromPrimitive)]
171pub enum Syscall {
172    #[num_enum(catch_all)]
173    Unknown(usize) = 0,
174    Argc = 1,
175    Argv = 2,
176    CycleCount = 3,
177    Exit = 4,
178    Fork = 5,
179    Getenv = 6,
180    Keccak = 7,
181    Log = 8,
182    Panic = 9,
183    Pipe = 10,
184    Random = 11,
185    Read = 12,
186    User = 13,
187    VerifyIntegrity = 14,
188    VerifyIntegrity2 = 15,
189    Write = 16,
190    ProveZkr = 17,
191}
192
193impl SyscallName {
194    /// Converts a static C string to a system call name, if it is UTF-8.
195    #[inline]
196    pub const fn from_c_str(c_str: &'static CStr) -> Result<Self, Utf8Error> {
197        match c_str.to_str() {
198            Ok(_) => Ok(unsafe { Self::from_bytes_with_nul(c_str.as_ptr().cast()) }),
199            Err(error) => Err(error),
200        }
201    }
202
203    /// Converts a raw UTF-8 C string pointer to a system call name.
204    ///
205    /// # Safety
206    ///
207    /// The pointer must reference a static null-terminated UTF-8 string.
208    pub const unsafe fn from_bytes_with_nul(ptr: *const u8) -> Self {
209        Self(ptr)
210    }
211
212    pub fn as_ptr(&self) -> *const u8 {
213        self.0
214    }
215
216    pub fn as_str(&self) -> &str {
217        core::str::from_utf8(unsafe { core::ffi::CStr::from_ptr(self.as_ptr().cast()).to_bytes() })
218            .unwrap()
219    }
220}
221
222impl AsRef<str> for SyscallName {
223    fn as_ref(&self) -> &str {
224        self.as_str()
225    }
226}
227
228/// Returned registers (a0, a1) from a syscall invocation.
229#[repr(C)]
230pub struct Return(pub u32, pub u32);
231
232macro_rules! impl_syscall {
233    ($func_name:ident
234        // Ugh, unfortunately we can't make this a regular macro list since the asm macro
235        // doesn't expand register names so in($register) doesn't work.
236        $(, $a0:ident $(, $a1:ident $(, $a2: ident $(, $a3: ident $(, $a4: ident )? )? )? )? )?
237    ) => {
238        /// Invoke a raw system call
239        ///
240        /// # Safety
241        ///
242        /// `from_host` must be aligned and dereferenceable.
243        #[cfg_attr(feature = "export-syscalls", unsafe(no_mangle))]
244        #[deprecated]
245        pub unsafe extern "C" fn $func_name(
246            syscall_name: SyscallName,
247            from_host: *mut u32,
248            from_host_words: usize
249            $(,$a0: u32 $(,$a1: u32 $(,$a2: u32 $(,$a3: u32 $(,$a4: u32)? )? )? )? )?
250        ) -> Return {
251            unimplemented!();
252        }
253
254
255        paste! {
256            /// Invoke a raw system call
257            ///
258            /// # Safety
259            ///
260            /// `from_host` must be aligned and dereferenceable.
261            #[cfg_attr(feature = "export-syscalls", unsafe(no_mangle))]
262            pub unsafe extern "C" fn [<$func_name _nr>] (
263                syscall: usize,
264                syscall_name: SyscallName,
265                from_host: *mut u32,
266                from_host_words: usize
267                $(,$a0: u32 $(,$a1: u32 $(,$a2: u32 $(,$a3: u32 $(,$a4: u32)? )? )? )? )?
268            ) -> Return {
269                #[cfg(target_os = "zkvm")] {
270                    let a0: u32;
271                    let a1: u32;
272                    unsafe {
273                        ::core::arch::asm!(
274                            "ecall",
275                            in("t0") $crate::syscall::ecall::SOFTWARE,
276                            in("t6") syscall,
277                            inlateout("a0") from_host => a0,
278                            inlateout("a1") from_host_words => a1,
279                            in("a2") syscall_name.as_ptr()
280                            $(,in("a3") $a0 $(,in("a4") $a1 $(,in("a5") $a2 $(,in("a6") $a3 $(,in("a7") $a4 )? )? )? )? )?
281                        );
282                    }
283                    Return(a0, a1)
284                }
285                #[cfg(not(target_os = "zkvm"))]
286                unimplemented!()
287            }
288        }
289    }
290}
291
292impl_syscall!(syscall_0);
293impl_syscall!(syscall_1, a3);
294impl_syscall!(syscall_2, a3, a4);
295impl_syscall!(syscall_3, a3, a4, a5);
296impl_syscall!(syscall_4, a3, a4, a5, a6);
297impl_syscall!(syscall_5, a3, a4, a5, a6, a7);
298
299fn ecall_1(t0: u32, a0: u32, a1: u32) {
300    #[cfg(target_os = "zkvm")]
301    unsafe {
302        asm!(
303            "ecall",
304            in("t0") t0,
305            in("a0") a0,
306            in("a1") a1,
307        )
308    };
309    #[cfg(not(target_os = "zkvm"))]
310    {
311        core::hint::black_box((t0, a0, a1));
312        unimplemented!()
313    }
314}
315
316fn ecall_4(t0: u32, a0: u32, a1: u32, a2: u32, a3: u32, a4: u32) {
317    #[cfg(target_os = "zkvm")]
318    unsafe {
319        asm!(
320            "ecall",
321            in("t0") t0,
322            in("a0") a0,
323            in("a1") a1,
324            in("a2") a2,
325            in("a3") a3,
326            in("a4") a4,
327        )
328    };
329    #[cfg(not(target_os = "zkvm"))]
330    {
331        core::hint::black_box((t0, a0, a1, a2, a3, a4));
332        unimplemented!()
333    }
334}
335
336/// # Safety
337///
338/// `out_state` must be aligned and dereferenceable.
339// [inline(never)] is added to mitigate potentially leaking information about program execution
340// through the final value of the program counter (pc) on halt where there is more than one
341// location in the program where `sys_halt` is called. As long as the halt instruction only exists
342// in one place within the program, the pc will always be the same invariant with input.
343#[inline(never)]
344#[cfg_attr(feature = "export-syscalls", no_mangle)]
345pub extern "C" fn sys_halt(user_exit: u8, out_state: *const [u32; DIGEST_WORDS]) -> ! {
346    ecall_1(
347        ecall::HALT,
348        halt::TERMINATE | ((user_exit as u32) << 8),
349        out_state as u32,
350    );
351    unreachable!();
352}
353
354/// # Safety
355///
356/// `out_state` must be aligned and dereferenceable.
357// [inline(never)] is added to mitigate potentially leaking information about program execution
358// through the final value of the program counter (pc) on pause where there is more than one
359// location in the program where `sys_pause` is called. As long as the pause instruction only exists
360// in one place within the program, the pc will always be the same invariant with input.
361#[inline(never)]
362#[cfg_attr(feature = "export-syscalls", no_mangle)]
363pub unsafe extern "C" fn sys_pause(user_exit: u8, out_state: *const [u32; DIGEST_WORDS]) {
364    ecall_1(
365        ecall::HALT,
366        halt::PAUSE | ((user_exit as u32) << 8),
367        out_state as u32,
368    );
369}
370
371#[cfg_attr(feature = "export-syscalls", no_mangle)]
372pub extern "C" fn sys_input(index: u32) -> u32 {
373    let t0 = ecall::INPUT;
374    let index = index & 0x07;
375    #[cfg(target_os = "zkvm")]
376    unsafe {
377        let a0: u32;
378        asm!(
379            "ecall",
380            in("t0") t0,
381            inlateout("a0") index => a0,
382        );
383        a0
384    }
385    #[cfg(not(target_os = "zkvm"))]
386    {
387        core::hint::black_box((t0, index));
388        unimplemented!()
389    }
390}
391
392/// # Safety
393///
394/// `out_state`, `in_state`, `block1_ptr`, and `block2_ptr` must be aligned and
395/// dereferenceable.
396#[cfg_attr(feature = "export-syscalls", no_mangle)]
397#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
398pub unsafe extern "C" fn sys_sha_compress(
399    out_state: *mut [u32; DIGEST_WORDS],
400    in_state: *const [u32; DIGEST_WORDS],
401    block1_ptr: *const [u32; DIGEST_WORDS],
402    block2_ptr: *const [u32; DIGEST_WORDS],
403) {
404    ecall_4(
405        ecall::SHA,
406        out_state as u32,
407        in_state as u32,
408        block1_ptr as u32,
409        block2_ptr as u32,
410        1,
411    );
412}
413
414/// # Safety
415///
416/// `out_state`, `in_state`, and `buf` must be aligned and dereferenceable.
417#[cfg_attr(feature = "export-syscalls", no_mangle)]
418#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
419pub unsafe extern "C" fn sys_sha_buffer(
420    out_state: *mut [u32; DIGEST_WORDS],
421    in_state: *const [u32; DIGEST_WORDS],
422    buf: *const u8,
423    count: u32,
424) {
425    let mut ptr = buf;
426    let mut count_remain = count;
427    let mut in_state = in_state;
428    while count_remain > 0 {
429        let count = min(count_remain, MAX_SHA_COMPRESS_BLOCKS as u32);
430        ecall_4(
431            ecall::SHA,
432            out_state as u32,
433            in_state as u32,
434            ptr as u32,
435            ptr.add(DIGEST_BYTES) as u32,
436            count,
437        );
438        count_remain -= count;
439        ptr = ptr.add(2 * DIGEST_BYTES * count as usize);
440        in_state = out_state;
441    }
442}
443
444/// # Safety
445///
446/// `state_addr`, `in_buf_addr`, and `out_buf_addr` must be word-aligned and
447/// dereferenceable.
448#[cfg_attr(feature = "export-syscalls", no_mangle)]
449#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
450pub unsafe extern "C" fn sys_poseidon2(
451    _state_addr: *mut [u32; DIGEST_WORDS],
452    _in_buf_addr: *const u8,
453    _out_buf_addr: *mut [u32; DIGEST_WORDS],
454    _bits_count: u32,
455) {
456    unimplemented!()
457}
458
459/// # Safety
460///
461/// `result`, `x`, `y`, and `modulus` must be aligned and dereferenceable.
462#[cfg_attr(feature = "export-syscalls", no_mangle)]
463#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
464pub unsafe extern "C" fn sys_bigint(
465    result: *mut [u32; bigint::WIDTH_WORDS],
466    op: u32,
467    x: *const [u32; bigint::WIDTH_WORDS],
468    y: *const [u32; bigint::WIDTH_WORDS],
469    modulus: *const [u32; bigint::WIDTH_WORDS],
470) {
471    ecall_4(
472        ecall::BIGINT,
473        result as u32,
474        op,
475        x as u32,
476        y as u32,
477        modulus as u32,
478    );
479}
480
481/// # Safety
482///
483/// `recv_buf` must be aligned and dereferenceable.
484#[cfg_attr(feature = "export-syscalls", no_mangle)]
485pub unsafe extern "C" fn sys_rand(recv_buf: *mut u32, words: usize) {
486    unsafe { syscall_0_nr(Syscall::Random.into(), nr::SYS_RANDOM, recv_buf, words) };
487}
488
489/// # Safety
490///
491/// `msg_ptr` must be aligned and dereferenceable.
492#[cfg_attr(feature = "export-syscalls", no_mangle)]
493pub unsafe extern "C" fn sys_panic(msg_ptr: *const u8, len: usize) -> ! {
494    unsafe {
495        syscall_2_nr(
496            Syscall::Panic.into(),
497            nr::SYS_PANIC,
498            null_mut(),
499            0,
500            msg_ptr as u32,
501            len as u32,
502        )
503    };
504
505    // As a fallback for non-compliant hosts, issue an illegal instruction.
506    #[cfg(target_os = "zkvm")]
507    asm!("sw x0, 1(x0)");
508    unreachable!()
509}
510
511/// # Safety
512///
513/// `msg_ptr` must be aligned and dereferenceable.
514#[cfg_attr(feature = "export-syscalls", no_mangle)]
515pub unsafe extern "C" fn sys_log(msg_ptr: *const u8, len: usize) {
516    unsafe {
517        syscall_2_nr(
518            Syscall::Log.into(),
519            nr::SYS_LOG,
520            null_mut(),
521            0,
522            msg_ptr as u32,
523            len as u32,
524        )
525    };
526}
527
528#[cfg_attr(feature = "export-syscalls", no_mangle)]
529pub extern "C" fn sys_cycle_count() -> u64 {
530    let Return(hi, lo) = unsafe {
531        syscall_0_nr(
532            Syscall::CycleCount.into(),
533            nr::SYS_CYCLE_COUNT,
534            null_mut(),
535            0,
536        )
537    };
538    ((hi as u64) << 32) + lo as u64
539}
540
541#[allow(dead_code)]
542fn print(msg: &str) {
543    let msg = msg.as_bytes();
544    unsafe {
545        sys_log(msg.as_ptr(), msg.len());
546    }
547}
548
549/// Reads the given number of bytes into the given buffer, posix-style.  Returns
550/// the number of bytes actually read.  On end of file, returns 0.
551///
552/// Like POSIX read, this is not guaranteed to read all bytes
553/// requested.  If we haven't reached EOF, it is however guaranteed to
554/// read at least one byte.
555///
556/// Users should prefer a higher-level abstraction.
557///
558/// # Safety
559///
560/// `recv_ptr` must be aligned and dereferenceable.
561#[cfg_attr(feature = "export-syscalls", unsafe(no_mangle))]
562pub unsafe extern "C" fn sys_read(fd: u32, recv_ptr: *mut u8, nbytes: usize) -> usize {
563    let Return(nbytes_read, final_word) = unsafe {
564        syscall_2_nr(
565            Syscall::Read.into(),
566            nr::SYS_READ,
567            recv_ptr as *mut u32,
568            nbytes,
569            fd,
570            nbytes as u32,
571        )
572    };
573
574    nbytes_read as usize
575}
576
577/// Reads up to the given number of words into the buffer [recv_buf,
578/// recv_buf + nwords).  Returns the number of bytes actually read.
579/// sys_read_words is a more efficient interface than sys_read, but
580/// varies from POSIX semantics.  Notably:
581///
582/// * The read length is specified in words, not bytes.  (The output
583///   length is still returned in bytes)
584///
585/// * If not all data is available, `sys_read_words` will return a short read.
586///
587/// * recv_buf must be word-aligned.
588///
589/// * Return a short read in the case of EOF mid-way through.
590///
591/// # Safety
592///
593/// `recv_ptr' must be a word-aligned pointer and point to a region of
594/// `nwords' size.
595#[cfg_attr(feature = "export-syscalls", no_mangle)]
596pub unsafe extern "C" fn sys_read_words(fd: u32, recv_ptr: *mut u32, nwords: usize) -> usize {
597    let nbytes = nwords * WORD_SIZE;
598    let Return(nbytes_read, final_word) = unsafe {
599        syscall_2_nr(
600            Syscall::Read.into(),
601            nr::SYS_READ,
602            recv_ptr,
603            nbytes,
604            fd,
605            nbytes as u32,
606        )
607    };
608    nbytes_read as usize
609}
610
611/// # Safety
612///
613/// `write_ptr` must be aligned and dereferenceable.
614#[cfg_attr(feature = "export-syscalls", no_mangle)]
615pub unsafe extern "C" fn sys_write(fd: u32, write_ptr: *const u8, nbytes: usize) {
616    unsafe {
617        syscall_3_nr(
618            Syscall::Write.into(),
619            nr::SYS_WRITE,
620            null_mut(),
621            0,
622            fd,
623            write_ptr as u32,
624            nbytes as u32,
625        )
626    };
627}
628
629// Some environment variable names are considered safe by default to use in the guest, provided by
630// the host, and are included in this list. It may be useful to allow guest developers to register
631// additional variable names as part of their guest program.
632const ALLOWED_ENV_VARNAMES: &[&[u8]] = &[
633    b"RUST_BACKTRACE",
634    b"RUST_LIB_BACKTRACE",
635    b"RISC0_KECCAK_PO2",
636];
637
638/// Retrieves the value of an environment variable, and stores as much
639/// of it as it can it in the memory at [out_words, out_words +
640/// out_nwords).
641///
642/// Returns the length of the value, in bytes, or usize::MAX if the variable is
643/// not set.
644///
645/// This is normally called twice to read an environment variable:
646/// Once to get the length of the value, and once to fill in allocated
647/// memory.
648///
649/// NOTE: Repeated calls to sys_getenv are not guaranteed to result in the same
650/// data being returned. Returned data is entirely in the control of the host.
651///
652/// # Safety
653///
654/// `out_words` and `varname` must be aligned and dereferenceable.
655#[cfg_attr(feature = "export-syscalls", no_mangle)]
656pub unsafe extern "C" fn sys_getenv(
657    out_words: *mut u32,
658    out_nwords: usize,
659    varname: *const u8,
660    varname_len: usize,
661) -> usize {
662    if cfg!(not(feature = "sys-getenv")) {
663        let mut allowed = false;
664        for allowed_varname in ALLOWED_ENV_VARNAMES {
665            let varname_buf = unsafe { slice::from_raw_parts(varname, varname_len) };
666            if *allowed_varname == varname_buf {
667                allowed = true;
668                break;
669            }
670        }
671        if !allowed {
672            const MSG_1: &[u8] = "sys_getenv not enabled for var".as_bytes();
673            unsafe { sys_log(MSG_1.as_ptr(), MSG_1.len()) };
674            unsafe { sys_log(varname, varname_len) };
675            const MSG_2: &[u8] = "sys_getenv is disabled; can be enabled with the sys-getenv feature flag on risc0-zkvm-platform".as_bytes();
676            unsafe { sys_panic(MSG_2.as_ptr(), MSG_2.len()) };
677        }
678    }
679    let Return(a0, _) = unsafe {
680        syscall_2_nr(
681            Syscall::Getenv.into(),
682            nr::SYS_GETENV,
683            out_words,
684            out_nwords,
685            varname as u32,
686            varname_len as u32,
687        )
688    };
689    if a0 == u32::MAX {
690        usize::MAX
691    } else {
692        a0 as usize
693    }
694}
695
696/// Retrieves the count of arguments provided to program execution.
697///
698/// NOTE: Repeated calls to sys_argc are not guaranteed to result in the same
699/// data being returned. Returned data is entirely in the control of the host.
700#[cfg_attr(feature = "export-syscalls", no_mangle)]
701pub extern "C" fn sys_argc() -> usize {
702    if cfg!(not(feature = "sys-args")) {
703        const MSG: &[u8] = "sys_argc is disabled; can be enabled with the sys-args feature flag on risc0-zkvm-platform".as_bytes();
704        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
705    }
706    let Return(a0, _) = unsafe { syscall_0_nr(Syscall::Argc.into(), nr::SYS_ARGC, null_mut(), 0) };
707    a0 as usize
708}
709
710/// Retrieves the argument with arg_index, and stores as much
711/// of it as it can it in the memory at [out_words, out_words +
712/// out_nwords).
713///
714/// Returns the length, in bytes, of the argument string. If the requested
715/// argument index does not exist (i.e. `arg_index` >= argc) then this syscall
716/// will not return.
717///
718/// This is normally called twice to read an argument: Once to get the length of
719/// the value, and once to fill in allocated memory.
720///
721/// NOTE: Repeated calls to sys_argv are not guaranteed to result in the same
722/// data being returned. Returned data is entirely in the control of the host.
723///
724/// # Safety
725///
726/// `out_words` must be aligned and dereferenceable.
727#[cfg_attr(feature = "export-syscalls", no_mangle)]
728pub unsafe extern "C" fn sys_argv(
729    out_words: *mut u32,
730    out_nwords: usize,
731    arg_index: usize,
732) -> usize {
733    if cfg!(not(feature = "sys-args")) {
734        const MSG: &[u8] = "sys_argv is disabled; can be enabled with the sys-args feature flag on risc0-zkvm-platform".as_bytes();
735        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
736    }
737    let Return(a0, _) = unsafe {
738        syscall_1_nr(
739            Syscall::Argv.into(),
740            nr::SYS_ARGV,
741            out_words,
742            out_nwords,
743            arg_index as u32,
744        )
745    };
746    a0 as usize
747}
748
749#[cfg_attr(feature = "export-syscalls", no_mangle)]
750#[deprecated]
751pub extern "C" fn sys_alloc_words(nwords: usize) -> *mut u32 {
752    unsafe { sys_alloc_aligned(WORD_SIZE * nwords, WORD_SIZE) as *mut u32 }
753}
754
755/// # Safety
756///
757/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
758#[cfg(all(feature = "export-syscalls", not(target_os = "zkvm")))]
759#[no_mangle]
760pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
761    unimplemented!("sys_alloc_aligned called outside of target_os = zkvm");
762}
763
764/// # Safety
765///
766/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
767#[cfg(all(
768    feature = "export-syscalls",
769    feature = "heap-embedded-alloc",
770    target_os = "zkvm"
771))]
772#[no_mangle]
773pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
774    use core::alloc::GlobalAlloc;
775    crate::heap::embedded::HEAP.alloc(core::alloc::Layout::from_size_align(bytes, align).unwrap())
776}
777
778/// # Safety
779///
780/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
781#[cfg(all(
782    feature = "export-syscalls",
783    not(feature = "heap-embedded-alloc"),
784    target_os = "zkvm"
785))]
786#[no_mangle]
787pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
788    crate::heap::bump::alloc_aligned(bytes, align)
789}
790
791/// Send a ReceiptClaim digest to the host to request verification.
792///
793/// A cooperative prover will only return if there is a verifying proof
794/// associated with that claim digest, and will always return a result code
795/// of 0 to register a0. The caller must encode the claim_digest into a
796/// public assumptions list for inclusion in the guest output.
797///
798/// # Safety
799///
800/// `claim_digest` must be aligned and dereferenceable.
801/// `control_root` must be aligned and dereferenceable.
802#[cfg(feature = "export-syscalls")]
803#[no_mangle]
804pub unsafe extern "C" fn sys_verify_integrity(
805    claim_digest: *const [u32; DIGEST_WORDS],
806    control_root: *const [u32; DIGEST_WORDS],
807) {
808    let mut to_host = [0u32; DIGEST_WORDS * 2];
809    to_host[..DIGEST_WORDS].copy_from_slice(claim_digest.as_ref().unwrap_unchecked());
810    to_host[DIGEST_WORDS..].copy_from_slice(control_root.as_ref().unwrap_unchecked());
811
812    // Send the claim_digest to the host via software ecall.
813    let Return(a0, _) = unsafe {
814        syscall_2_nr(
815            Syscall::VerifyIntegrity.into(),
816            nr::SYS_VERIFY_INTEGRITY,
817            null_mut(),
818            0,
819            to_host.as_ptr() as u32,
820            (DIGEST_BYTES * 2) as u32,
821        )
822    };
823
824    // Check to ensure the host indicated success by returning 0.
825    // This should always be the case. This check is included for
826    // forwards-compatibility.
827    if a0 != 0 {
828        const MSG: &[u8] = "sys_verify_integrity returned error result".as_bytes();
829        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
830    }
831}
832
833/// TODO: Send a ReceiptClaim digest to the host to request verification. Meant for proofs that use union.
834///
835/// # Safety
836///
837/// `claim_digest` must be aligned and dereferenceable.
838/// `control_root` must be aligned and dereferenceable.
839#[cfg(feature = "export-syscalls")]
840#[no_mangle]
841pub unsafe extern "C" fn sys_verify_integrity2(
842    claim_digest: *const [u32; DIGEST_WORDS],
843    control_root: *const [u32; DIGEST_WORDS],
844) {
845    let mut to_host = [0u32; DIGEST_WORDS * 2];
846    to_host[..DIGEST_WORDS].copy_from_slice(claim_digest.as_ref().unwrap_unchecked());
847    to_host[DIGEST_WORDS..].copy_from_slice(control_root.as_ref().unwrap_unchecked());
848
849    // Send the claim_digest to the host via software ecall.
850    let Return(a0, _) = unsafe {
851        syscall_2_nr(
852            Syscall::VerifyIntegrity2.into(),
853            nr::SYS_VERIFY_INTEGRITY2,
854            null_mut(),
855            0,
856            to_host.as_ptr() as u32,
857            (DIGEST_BYTES * 2) as u32,
858        )
859    };
860
861    // Check to ensure the host indicated success by returning 0.
862    // This should always be the case. This check is included for
863    // forwards-compatibility.
864    if a0 != 0 {
865        const MSG: &[u8] = "sys_verify_integrity2 returned error result".as_bytes();
866        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
867    }
868}
869// Make sure we only get one of these since it's stateful.
870#[cfg(not(feature = "export-syscalls"))]
871extern "C" {
872    pub fn sys_alloc_aligned(nwords: usize, align: usize) -> *mut u8;
873}
874
875/// `sys_fork()` creates a new process by duplicating the calling process. The
876/// new process is referred to as the child process. The calling process is
877/// referred to as the parent process.
878///
879/// The child process and the parent process run in separate memory spaces. At
880/// the time of `sys_fork()` both memory spaces have the same content.
881///
882/// # Return Value
883///
884/// On success, the PID of the child process (1) is returned in the parent, and
885/// 0 is returned in the child. On failure, -1 is returned in the parent, no
886/// child process is created.
887#[cfg(feature = "export-syscalls")]
888#[no_mangle]
889pub extern "C" fn sys_fork() -> i32 {
890    let Return(a0, _) = unsafe { syscall_0_nr(Syscall::Fork.into(), nr::SYS_FORK, null_mut(), 0) };
891    a0 as i32
892}
893
894/// `sys_pipe()` creates a pipe, a unidirectional data channel that can be used
895/// for interprocess communication. The pointer `pipefd` is used to return two
896/// file descriptors referring to the ends of the pipe. `pipefd[0]` refers to
897/// the read end of the pipe. `pipefd[1]` refers to the write end of the pipe.
898/// Data written to the write end of the pipe is buffered by the host until it
899/// is read from the read end of the pipe.
900///
901/// # Return Value
902///
903/// On success, zero is returned.  On error, -1 is returned, and `pipefd` is
904/// left unchanged.
905///
906/// # Safety
907///
908/// `pipefd` must be aligned, dereferenceable, and have capacity for 2 u32
909/// values.
910#[cfg(feature = "export-syscalls")]
911#[no_mangle]
912pub unsafe extern "C" fn sys_pipe(pipefd: *mut u32) -> i32 {
913    let Return(a0, _) = unsafe { syscall_0_nr(Syscall::Pipe.into(), nr::SYS_PIPE, pipefd, 2) };
914    a0 as i32
915}
916
917/// `sys_exit()` causes normal process termination.
918///
919/// Currently the `status` is unused and ignored.
920#[cfg(feature = "export-syscalls")]
921#[no_mangle]
922pub extern "C" fn sys_exit(status: i32) -> ! {
923    let Return(a0, _) = unsafe { syscall_0_nr(Syscall::Exit.into(), nr::SYS_EXIT, null_mut(), 0) };
924    #[allow(clippy::empty_loop)]
925    loop {
926        // prevent dishonest provers from relying on the ability to prove the
927        // child process rather than the intended parent process.
928    }
929}
930
931/// Executes a `ZKR' in the recursion circuit, specified by control
932/// ID.  The control ID must be registered in the host's index of ZKRs.
933///
934/// This only triggers the execution of the ZKR; it does not add any
935/// assumptions.  In order to prove that the ZKR executed correctly,
936/// users must calculate the claim digest and add it to the list of
937/// assumptions.
938///
939/// # Safety
940///
941/// `claim_digest` must be aligned and dereferenceable.
942/// `control_id` must be aligned and dereferenceable.
943/// `control_root` must be aligned and dereferenceable.
944/// `input` must be aligned and have `input_len` u32s dereferenceable
945#[cfg_attr(all(feature = "export-syscalls", feature = "unstable"), no_mangle)]
946#[stability::unstable]
947pub unsafe extern "C" fn sys_prove_zkr(
948    claim_digest: *const [u32; DIGEST_WORDS],
949    control_id: *const [u32; DIGEST_WORDS],
950    control_root: *const [u32; DIGEST_WORDS],
951    input: *const u32,
952    input_len: usize,
953) {
954    let Return(a0, _) = unsafe {
955        syscall_5_nr(
956            Syscall::ProveZkr.into(),
957            nr::SYS_PROVE_ZKR,
958            null_mut(),
959            0,
960            claim_digest as u32,
961            control_id as u32,
962            control_root as u32,
963            input as u32,
964            input_len as u32,
965        )
966    };
967
968    // Check to ensure the host indicated success by returning 0.
969    // Currently, this should always be the case. This check is
970    // included for forwards-compatibility.
971    if a0 != 0 {
972        const MSG: &[u8] = "sys_prove_zkr returned error result".as_bytes();
973        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
974    }
975}
976
977/// Permute the keccak state on the host
978///
979/// # Safety
980#[cfg_attr(feature = "export-syscalls", no_mangle)]
981pub unsafe extern "C" fn sys_keccak(
982    in_state: *const [u64; KECCACK_STATE_DWORDS],
983    out_state: *mut [u64; KECCACK_STATE_DWORDS],
984) -> i32 {
985    let Return(a0, _) = unsafe {
986        syscall_3_nr(
987            Syscall::Keccak.into(),
988            nr::SYS_KECCAK,
989            out_state as *mut u32,
990            KECCACK_STATE_WORDS,
991            keccak_mode::KECCAK_PERMUTE,
992            in_state as u32,
993            0,
994        )
995    };
996    a0 as i32
997}
998
999/// Executes the keccak circuit, and then executes the lift predicate
1000/// in the recursion circuit.
1001///
1002/// This only triggers the execution of the circuits; it does not add
1003/// any assumptions.  In order to prove that it executed correctly,
1004/// users must calculate the claim digest and add it to the list of
1005/// assumptions.
1006///
1007/// # Safety
1008///
1009/// `claim_digest` must be aligned and dereferenceable.
1010/// `control_root` must be aligned and dereferenceable.
1011/// `input` must be aligned and have `input_len` u32s dereferenceable
1012#[cfg_attr(feature = "export-syscalls", no_mangle)]
1013pub unsafe extern "C" fn sys_prove_keccak(
1014    claim_digest: *const [u32; DIGEST_WORDS],
1015    control_root: *const [u32; DIGEST_WORDS],
1016) {
1017    let Return(a0, _) = unsafe {
1018        syscall_3_nr(
1019            Syscall::Keccak.into(),
1020            nr::SYS_KECCAK,
1021            null_mut(),
1022            0,
1023            keccak_mode::KECCAK_PROVE,
1024            claim_digest as u32,
1025            control_root as u32,
1026        )
1027    };
1028
1029    // Check to ensure the host indicated success by returning 0.
1030    // Currently, this should always be the case. This check is
1031    // included for forwards-compatibility.
1032    if a0 != 0 {
1033        const MSG: &[u8] = "sys_prove_keccak returned error result".as_bytes();
1034        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
1035    }
1036}
1037
1038#[repr(C)]
1039pub struct BigIntBlobHeader {
1040    pub nondet_program_size: u32,
1041    pub verify_program_size: u32,
1042    pub consts_size: u32,
1043    pub temp_size: u32,
1044}
1045
1046macro_rules! impl_sys_bigint2 {
1047    ($func_name:ident, $a1:ident
1048        $(, $a2: ident
1049            $(, $a3: ident
1050                $(, $a4: ident
1051                    $(, $a5: ident
1052                        $(, $a6: ident)?
1053                    )?
1054                )?
1055            )?
1056        )?
1057    ) => {
1058        /// Invoke a bigint2 program.
1059        ///
1060        /// # Safety
1061        ///
1062        /// `blob_ptr` and all arguments must be aligned and dereferenceable.
1063        #[cfg_attr(feature = "export-syscalls", no_mangle)]
1064        pub unsafe extern "C" fn $func_name(blob_ptr: *const u8, a1: *const u32
1065            $(, $a2: *const u32
1066                $(, $a3: *const u32
1067                    $(, $a4: *const u32
1068                        $(, $a5: *const u32
1069                            $(, $a6: *const u32)?
1070                        )?
1071                    )?
1072                )?
1073            )?
1074        ) {
1075            #[cfg(target_os = "zkvm")]
1076            {
1077                let header = blob_ptr as *const $crate::syscall::BigIntBlobHeader;
1078                let nondet_program_ptr = (header.add(1)) as *const u32;
1079                let verify_program_ptr = nondet_program_ptr.add((*header).nondet_program_size as usize);
1080                let consts_ptr = verify_program_ptr.add((*header).verify_program_size as usize);
1081                let temp_space = ((*header).temp_size as usize) << 2;
1082
1083                ::core::arch::asm!(
1084                    "sub sp, sp, {temp_space}",
1085                    "ecall",
1086                    "add sp, sp, {temp_space}",
1087                    temp_space = in(reg) temp_space,
1088                    in("t0") ecall::BIGINT2,
1089                    in("t1") nondet_program_ptr,
1090                    in("t2") verify_program_ptr,
1091                    in("t3") consts_ptr,
1092                    in("a0") blob_ptr,
1093                    in("a1") a1,
1094                    $(in("a2") $a2,
1095                        $(in("a3") $a3,
1096                            $(in("a4") $a4,
1097                                $(in("a5") $a5,
1098                                    $(in("a6") $a6)?
1099                                )?
1100                            )?
1101                        )?
1102                    )?
1103                );
1104            }
1105
1106            #[cfg(not(target_os = "zkvm"))]
1107            unimplemented!()
1108        }
1109    }
1110}
1111
1112impl_sys_bigint2!(sys_bigint2_1, a1);
1113impl_sys_bigint2!(sys_bigint2_2, a1, a2);
1114impl_sys_bigint2!(sys_bigint2_3, a1, a2, a3);
1115impl_sys_bigint2!(sys_bigint2_4, a1, a2, a3, a4);
1116impl_sys_bigint2!(sys_bigint2_5, a1, a2, a3, a4, a5);
1117impl_sys_bigint2!(sys_bigint2_6, a1, a2, a3, a4, a5, a6);