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