Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion executor/programs/rust/ef_io_demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output`).
// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output` /
// `write_log`).
//
// Reads the private input via the EF zero-copy `read_input` shim, then emits it
// back as the public output in TWO `write_output` calls (split in halves) to
// exercise the multi-call concatenation requirement of the EF spec.
//
// Also makes one `write_log` call. That channel is a no-op on Lambda VM, so this
// proves the symbol contract rather than any output: a portable guest calling
// `write_log` links and runs. It is the only thing that does — the symbol is
// dead-stripped from guests that never call it.
use lambda_vm_syscalls as syscalls;

/// Diagnostic text for the `write_log` call. Must be valid UTF-8 per the EF spec.
const LOG_MESSAGE: &str = "ef_io_demo: echoing private input to public output\n";

pub fn main() {
let mut buf_ptr: *const u8 = core::ptr::null();
let mut buf_size: usize = 0;
unsafe {
syscalls::ef_io::read_input(&mut buf_ptr, &mut buf_size);
syscalls::ef_io::write_log(LOG_MESSAGE.as_ptr(), LOG_MESSAGE.len());
}

if buf_size > 0 {
Expand Down
41 changes: 39 additions & 2 deletions syscalls/src/ef_io.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
//! EF zkVM IO interface: <https://github.com/eth-act/zkvm-standards/blob/main/standards/io-interface/README.md>
//!
//! Two C-callable functions that match the EF standard so portable applications
//! compile unchanged across zkVMs:
//! Three C-callable functions that match the EF standard so portable
//! applications compile unchanged across zkVMs:
//!
//! - `read_input`: returns a zero-copy pointer + size to the private input.
//! - `write_output`: appends bytes to the public output. Multiple calls
//! concatenate.
//! - `write_log`: diagnostic (`println`-style) UTF-8 text for the host. Not
//! part of the statement being proven. Specified by
//! <https://github.com/eth-act/zkvm-standards/pull/27>, which amends the
//! interface above from two functions to three; until it merges, the linked
//! README documents only the first two.
//!
//! On Lambda VM these map to:
//! - `read_input` → memory-mapped private input region at `0xFF000000`
//! (4-byte LE length prefix at base, data at `+4`).
//! - `write_output` → ECALL #64 (Commit). The trace builder maintains a
//! running commitment index in synthetic register `x254`, so multiple
//! ECALLs naturally concatenate at the proof level.
//! - `write_log` → nothing at all. The standard lets proving environments that
//! don't expose diagnostics ignore these calls, and the `Print` ecall it
//! would otherwise use has no Ecall-bus receiver (see `write_log`'s doc
//! comment and `syscalls::print_string`).

#[cfg(target_arch = "riscv64")]
use core::arch::asm;
Expand Down Expand Up @@ -67,6 +76,26 @@ pub unsafe extern "C" fn write_output(output: *const u8, size: usize) {
}
}

/// EF IO: emit `utf8_len` bytes of UTF-8 diagnostic text (`println`-style) for
/// the host. Intentionally a no-op: it emits no ecall and reads no memory.
///
/// The natural lowering would be the `Print` ecall (a7=1), but that ecall has
/// no receiver on the Ecall bus, so emitting it unbalances the LogUp argument
/// and every proof using it fails to verify — the same reason
/// `syscalls::print_string` is a no-op. Ignoring the call is conforming: per
/// the spec the log text "does not contribute to the public output, is not part
/// of the statement being proven", and calls "may be ignored by production
/// proving environments that do not expose diagnostics". Like the rest of the
/// interface it cannot fail; there is no error code.
///
/// # Safety
///
/// `utf8_bytes` must point to `utf8_len` readable bytes of guest memory. The
/// requirement is nominal — the no-op never dereferences the pointer.
#[cfg(target_arch = "riscv64")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn write_log(_utf8_bytes: *const u8, _utf8_len: usize) {}

/// Host-side stub — Lambda VM's IO interface is only implemented for the
/// `riscv64` guest target. Not exported with C linkage on host so the
/// generic name doesn't collide with C dependencies in test builds.
Expand All @@ -82,3 +111,11 @@ pub fn read_input(_buf_ptr: *mut *const u8, _buf_size: *mut usize) {
pub fn write_output(_output: *const u8, _size: usize) {
unimplemented!("write_output is only implemented for riscv64 targets");
}

/// Host-side stub — Lambda VM's IO interface is only implemented for the
/// `riscv64` guest target. Not exported with C linkage on host so the
/// generic name doesn't collide with C dependencies in test builds.
#[cfg(not(target_arch = "riscv64"))]
pub fn write_log(_utf8_bytes: *const u8, _utf8_len: usize) {
unimplemented!("write_log is only implemented for riscv64 targets");
}
Loading