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