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