diff --git a/executor/programs/rust/ef_io_demo/.cargo/config.toml b/executor/programs/rust/ef_io_demo/.cargo/config.toml new file mode 100644 index 000000000..ca99a3f45 --- /dev/null +++ b/executor/programs/rust/ef_io_demo/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv64im-lambda-vm-elf] +rustflags = [ + "--cfg", "getrandom_backend=\"custom\"", + "-C", "passes=lower-atomic" +] diff --git a/executor/programs/rust/ef_io_demo/Cargo.toml b/executor/programs/rust/ef_io_demo/Cargo.toml new file mode 100644 index 000000000..f1c6f812a --- /dev/null +++ b/executor/programs/rust/ef_io_demo/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "ef_io_demo" +version = "0.1.0" +edition = "2024" + +[dependencies] +lambda-vm-syscalls = { path = "../../../../syscalls" } diff --git a/executor/programs/rust/ef_io_demo/src/main.rs b/executor/programs/rust/ef_io_demo/src/main.rs new file mode 100644 index 000000000..ef0690398 --- /dev/null +++ b/executor/programs/rust/ef_io_demo/src/main.rs @@ -0,0 +1,22 @@ +// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output`). +// +// 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. +use lambda_vm_syscalls as syscalls; + +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); + } + + if buf_size > 0 { + let half = buf_size / 2; + unsafe { + syscalls::ef_io::write_output(buf_ptr, half); + syscalls::ef_io::write_output(buf_ptr.add(half), buf_size - half); + } + } +} diff --git a/executor/programs/rust/ef_io_multi_write/.cargo/config.toml b/executor/programs/rust/ef_io_multi_write/.cargo/config.toml new file mode 100644 index 000000000..ca99a3f45 --- /dev/null +++ b/executor/programs/rust/ef_io_multi_write/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv64im-lambda-vm-elf] +rustflags = [ + "--cfg", "getrandom_backend=\"custom\"", + "-C", "passes=lower-atomic" +] diff --git a/executor/programs/rust/ef_io_multi_write/Cargo.lock b/executor/programs/rust/ef_io_multi_write/Cargo.lock new file mode 100644 index 000000000..f0b43add4 --- /dev/null +++ b/executor/programs/rust/ef_io_multi_write/Cargo.lock @@ -0,0 +1,331 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "ef_io_multi_write" +version = "0.1.0" +dependencies = [ + "lambda-vm-syscalls", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "lambda-vm-syscalls" +version = "0.1.0" +dependencies = [ + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "rand", + "riscv", + "thiserror", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/executor/programs/rust/ef_io_multi_write/Cargo.toml b/executor/programs/rust/ef_io_multi_write/Cargo.toml new file mode 100644 index 000000000..ec11f0991 --- /dev/null +++ b/executor/programs/rust/ef_io_multi_write/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "ef_io_multi_write" +version = "0.1.0" +edition = "2024" + +[dependencies] +lambda-vm-syscalls = { path = "../../../../syscalls" } diff --git a/executor/programs/rust/ef_io_multi_write/src/main.rs b/executor/programs/rust/ef_io_multi_write/src/main.rs new file mode 100644 index 000000000..39d2be61c --- /dev/null +++ b/executor/programs/rust/ef_io_multi_write/src/main.rs @@ -0,0 +1,14 @@ +// EF IO edge case: many small write_output calls. +// +// Emits "abcdefghij" one byte at a time via 10 separate `write_output` +// calls to stress multi-call concatenation beyond the two-call demo. +use lambda_vm_syscalls as syscalls; + +pub fn main() { + let data = b"abcdefghij"; + for &byte in data.iter() { + unsafe { + syscalls::ef_io::write_output(&byte as *const u8, 1); + } + } +} diff --git a/executor/programs/rust/ef_io_no_input/.cargo/config.toml b/executor/programs/rust/ef_io_no_input/.cargo/config.toml new file mode 100644 index 000000000..ca99a3f45 --- /dev/null +++ b/executor/programs/rust/ef_io_no_input/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv64im-lambda-vm-elf] +rustflags = [ + "--cfg", "getrandom_backend=\"custom\"", + "-C", "passes=lower-atomic" +] diff --git a/executor/programs/rust/ef_io_no_input/Cargo.lock b/executor/programs/rust/ef_io_no_input/Cargo.lock new file mode 100644 index 000000000..e9964a48d --- /dev/null +++ b/executor/programs/rust/ef_io_no_input/Cargo.lock @@ -0,0 +1,331 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "ef_io_no_input" +version = "0.1.0" +dependencies = [ + "lambda-vm-syscalls", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "lambda-vm-syscalls" +version = "0.1.0" +dependencies = [ + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "rand", + "riscv", + "thiserror", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/executor/programs/rust/ef_io_no_input/Cargo.toml b/executor/programs/rust/ef_io_no_input/Cargo.toml new file mode 100644 index 000000000..c7efcc7d9 --- /dev/null +++ b/executor/programs/rust/ef_io_no_input/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "ef_io_no_input" +version = "0.1.0" +edition = "2024" + +[dependencies] +lambda-vm-syscalls = { path = "../../../../syscalls" } diff --git a/executor/programs/rust/ef_io_no_input/src/main.rs b/executor/programs/rust/ef_io_no_input/src/main.rs new file mode 100644 index 000000000..d5c8e0419 --- /dev/null +++ b/executor/programs/rust/ef_io_no_input/src/main.rs @@ -0,0 +1,22 @@ +// EF IO edge case: no private input supplied. +// +// Calls `read_input` expecting `buf_size == 0`, then emits a hardcoded +// byte via `write_output` to prove the output path works independently. +use lambda_vm_syscalls as syscalls; + +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); + } + + // Per spec: if buf_size is 0, buf_ptr is unspecified — don't dereference it. + assert_eq!(buf_size, 0, "expected no private input"); + + // Emit a hardcoded output to verify write_output works without read_input data. + let output = b"ok"; + unsafe { + syscalls::ef_io::write_output(output.as_ptr(), output.len()); + } +} diff --git a/executor/programs/rust/ef_io_zero_write/.cargo/config.toml b/executor/programs/rust/ef_io_zero_write/.cargo/config.toml new file mode 100644 index 000000000..ca99a3f45 --- /dev/null +++ b/executor/programs/rust/ef_io_zero_write/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv64im-lambda-vm-elf] +rustflags = [ + "--cfg", "getrandom_backend=\"custom\"", + "-C", "passes=lower-atomic" +] diff --git a/executor/programs/rust/ef_io_zero_write/Cargo.lock b/executor/programs/rust/ef_io_zero_write/Cargo.lock new file mode 100644 index 000000000..ff9e67742 --- /dev/null +++ b/executor/programs/rust/ef_io_zero_write/Cargo.lock @@ -0,0 +1,331 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "ef_io_zero_write" +version = "0.1.0" +dependencies = [ + "lambda-vm-syscalls", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "lambda-vm-syscalls" +version = "0.1.0" +dependencies = [ + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "rand", + "riscv", + "thiserror", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/executor/programs/rust/ef_io_zero_write/Cargo.toml b/executor/programs/rust/ef_io_zero_write/Cargo.toml new file mode 100644 index 000000000..5a451e635 --- /dev/null +++ b/executor/programs/rust/ef_io_zero_write/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "ef_io_zero_write" +version = "0.1.0" +edition = "2024" + +[dependencies] +lambda-vm-syscalls = { path = "../../../../syscalls" } diff --git a/executor/programs/rust/ef_io_zero_write/src/main.rs b/executor/programs/rust/ef_io_zero_write/src/main.rs new file mode 100644 index 000000000..16ca072d0 --- /dev/null +++ b/executor/programs/rust/ef_io_zero_write/src/main.rs @@ -0,0 +1,16 @@ +// EF IO edge case: zero-size write_output followed by a real write. +// +// Verifies that a zero-length `write_output` call doesn't corrupt state +// and the subsequent call produces correct output. +use lambda_vm_syscalls as syscalls; + +pub fn main() { + let data = b"hello"; + + unsafe { + // Zero-size write — should be a no-op. + syscalls::ef_io::write_output(data.as_ptr(), 0); + // Real write — should produce "hello" as public output. + syscalls::ef_io::write_output(data.as_ptr(), data.len()); + } +} diff --git a/executor/src/vm/instruction/execution.rs b/executor/src/vm/instruction/execution.rs index 04502645b..219414745 100644 --- a/executor/src/vm/instruction/execution.rs +++ b/executor/src/vm/instruction/execution.rs @@ -304,7 +304,7 @@ impl Instruction { // It is not the correct implementation of ecall/ebreak let pointer = registers.read(10)?; let len = registers.read(11)?; - let bytes = memory.load_bytes(pointer, len); + let bytes = memory.load_bytes(pointer, len)?; let value = str::from_utf8(&bytes).map_err(|_| ExecutionError::IncorrectMessage)?; println!("PRINT VM: {}", value); @@ -313,7 +313,7 @@ impl Instruction { // panic let pointer = registers.read(10)?; let len = registers.read(11)?; - let bytes = memory.load_bytes(pointer, len); + let bytes = memory.load_bytes(pointer, len)?; let value = str::from_utf8(&bytes).map_err(|_| ExecutionError::IncorrectMessage)?; return Err(ExecutionError::Panic(value.to_owned())); diff --git a/executor/src/vm/memory.rs b/executor/src/vm/memory.rs index b1f047ee1..ecf5e33ee 100644 --- a/executor/src/vm/memory.rs +++ b/executor/src/vm/memory.rs @@ -38,9 +38,10 @@ impl BuildHasher for U64BuildHasher { pub type U64HashMap = HashMap; -// TODO: Correctly define this -const MAX_PUBLIC_OUTPUT_COMMIT_SIZE: u64 = 1024; -const PUBLIC_OUTPUT_START_INDEX: u64 = 0; +/// Total cap on public output bytes across all `commit_public_output` calls. +/// The COMMIT AIR concatenates calls via the running `x254` index, so this +/// is enforced as a running-total budget rather than a per-call limit. +pub const MAX_PUBLIC_OUTPUT_TOTAL_SIZE: u64 = 1024 * 1024; /// Maximum size of the private input memory region (in bytes). pub const MAX_PRIVATE_INPUT_SIZE: u64 = 6700000; /// Fixed high address where private input is mapped. Guest programs can read @@ -50,19 +51,30 @@ pub const MAX_PRIVATE_INPUT_SIZE: u64 = 6700000; pub const PRIVATE_INPUT_START_INDEX: u64 = 0xFF000000; #[derive(Default, Debug)] -pub struct Memory(U64HashMap<[u8; 4]>); +pub struct Memory { + cells: U64HashMap<[u8; 4]>, + /// Bytes committed to public output via `commit_public_output`. The + /// COMMIT AIR doesn't write to a fixed memory region (it streams bytes + /// onto the Commit bus by `index`), so this buffer is purely the + /// executor's view used by `read_return_value` and CLI display. + public_output: Vec, +} impl Memory { pub fn load_byte(&self, address: u64) -> u8 { let aligned_address = address - address % 4; - let value = self.0.get(&aligned_address).cloned().unwrap_or_default(); + let value = self + .cells + .get(&aligned_address) + .cloned() + .unwrap_or_default(); value[(address % 4) as usize] } pub fn store_byte(&mut self, address: u64, value: u8) { let aligned_address = address - address % 4; let entry = self - .0 + .cells .entry(aligned_address) .or_insert_with(|| [0, 0, 0, 0]); entry[(address % 4) as usize] = value; @@ -72,7 +84,7 @@ impl Memory { if !address.is_multiple_of(4) { return Err(MemoryError::UnalignedAccess); } - let bytes = self.0.get(&address).cloned().unwrap_or_default(); + let bytes = self.cells.get(&address).cloned().unwrap_or_default(); Ok(u32::from_le_bytes(bytes)) } @@ -81,7 +93,7 @@ impl Memory { return Err(MemoryError::UnalignedAccess); } let bytes = value.to_le_bytes(); - self.0.insert(address, bytes); + self.cells.insert(address, bytes); Ok(()) } @@ -90,8 +102,8 @@ impl Memory { if !address.is_multiple_of(8) { return Err(MemoryError::UnalignedAccess); } - let low_bytes = self.0.get(&address).cloned().unwrap_or_default(); - let high_bytes = self.0.get(&(address + 4)).cloned().unwrap_or_default(); + let low_bytes = self.cells.get(&address).cloned().unwrap_or_default(); + let high_bytes = self.cells.get(&(address + 4)).cloned().unwrap_or_default(); let low = u32::from_le_bytes(low_bytes) as u64; let high = u32::from_le_bytes(high_bytes) as u64; Ok(low | (high << 32)) @@ -104,8 +116,8 @@ impl Memory { } let low = (value & 0xFFFFFFFF) as u32; let high = (value >> 32) as u32; - self.0.insert(address, low.to_le_bytes()); - self.0.insert(address + 4, high.to_le_bytes()); + self.cells.insert(address, low.to_le_bytes()); + self.cells.insert(address + 4, high.to_le_bytes()); Ok(()) } @@ -117,7 +129,11 @@ impl Memory { ); } let aligned_address = address - address % 4; - let bytes = self.0.get(&aligned_address).cloned().unwrap_or_default(); + let bytes = self + .cells + .get(&aligned_address) + .cloned() + .unwrap_or_default(); let value = &bytes[(address % 4) as usize..(address % 4) as usize + 2]; Ok(u16::from_le_bytes( value.try_into().map_err(|_| MemoryError::LoadHalf)?, @@ -130,7 +146,7 @@ impl Memory { } let aligned_address = address - address % 4; let entry = self - .0 + .cells .entry(aligned_address) .or_insert_with(|| [0, 0, 0, 0]); let bytes = value.to_le_bytes(); @@ -139,19 +155,25 @@ impl Memory { Ok(()) } + /// Append `length` bytes from guest memory starting at `address` to the + /// public output. The COMMIT AIR concatenates calls via the running + /// `x254` index, and the trace builder accumulates `commit_ops` into + /// `VmProof.public_output`; this method maintains the executor's view + /// of the same byte stream so `read_return_value` matches. pub fn commit_public_output(&mut self, address: u64, length: u64) -> Result<(), MemoryError> { - if length > MAX_PUBLIC_OUTPUT_COMMIT_SIZE { + let new_total = (self.public_output.len() as u64) + .checked_add(length) + .ok_or(MemoryError::CommitSizeExceeded)?; + if new_total > MAX_PUBLIC_OUTPUT_TOTAL_SIZE { return Err(MemoryError::CommitSizeExceeded); } - self.store_word(PUBLIC_OUTPUT_START_INDEX, length as u32)?; - let inputs = self.load_bytes(address, length); - self.set_bytes_aligned(PUBLIC_OUTPUT_START_INDEX + 4, &inputs)?; + let bytes = self.load_bytes(address, length)?; + self.public_output.extend_from_slice(&bytes); Ok(()) } pub fn read_return_value(&self) -> Result, MemoryError> { - let size = self.load_word(PUBLIC_OUTPUT_START_INDEX)?; - Ok(self.load_bytes(PUBLIC_OUTPUT_START_INDEX + 4, size as u64)) + Ok(self.public_output.clone()) } /// Pre-loads private input bytes at `PRIVATE_INPUT_START_INDEX` as a @@ -164,23 +186,29 @@ impl Memory { if inputs.len() as u64 > MAX_PRIVATE_INPUT_SIZE { return Err(MemoryError::PrivateInputSizeExceeded); } - self.store_word(PRIVATE_INPUT_START_INDEX, inputs.len() as u32)?; + let len_u32 = + u32::try_from(inputs.len()).map_err(|_| MemoryError::PrivateInputSizeExceeded)?; + self.store_word(PRIVATE_INPUT_START_INDEX, len_u32)?; self.set_bytes_aligned(PRIVATE_INPUT_START_INDEX + 4, &inputs)?; Ok(()) } - pub fn load_bytes(&self, mut addr: u64, len: u64) -> Vec { - let mut result = Vec::with_capacity(len as usize); - let end = addr + len; + pub fn load_bytes(&self, mut addr: u64, len: u64) -> Result, MemoryError> { + let end = addr.checked_add(len).ok_or(MemoryError::AddressOverflow)?; + let len_usize = usize::try_from(len).map_err(|_| MemoryError::AllocationFailed)?; + let mut result = Vec::new(); + result + .try_reserve_exact(len_usize) + .map_err(|_| MemoryError::AllocationFailed)?; while addr < end { let aligned = addr - (addr % 4); - let bytes = self.0.get(&aligned).cloned().unwrap_or_default(); + let bytes = self.cells.get(&aligned).cloned().unwrap_or_default(); let offset = (addr % 4) as usize; let take = std::cmp::min(4 - offset, (end - addr) as usize); result.extend_from_slice(&bytes[offset..offset + take]); addr += take as u64; } - result + Ok(result) } /// Helper method to store a given input at an aligned address. It may also overwrite existing bytes with zero if inputs is not divisible by 4 @@ -192,7 +220,7 @@ impl Memory { for chunk in inputs.chunks(4) { let mut bytes = [0u8; 4]; bytes[..chunk.len()].copy_from_slice(chunk); - self.0.insert(addr, bytes); + self.cells.insert(addr, bytes); addr += 4; } Ok(()) @@ -209,6 +237,10 @@ pub enum MemoryError { CommitSizeExceeded, #[error("Private input size exceeded")] PrivateInputSizeExceeded, + #[error("Address range exceeds u64::MAX")] + AddressOverflow, + #[error("Failed to allocate memory for load_bytes")] + AllocationFailed, } #[cfg(test)] @@ -234,7 +266,7 @@ mod tests { } #[test] - fn test_commit_public_output_overwrites() { + fn test_commit_public_output_appends() { let mut memory = Memory::default(); memory.store_byte(0x100, b'a'); memory.store_byte(0x101, b'b'); @@ -248,19 +280,141 @@ mod tests { .commit_public_output(0x104, 2) .expect("second commit should succeed"); - // Overwrite semantics: second commit replaces first + // Append semantics: calls concatenate (EF zkVM IO interface). + assert_eq!( + memory + .read_return_value() + .expect("public output should be readable"), + b"abcd".to_vec() + ); + } + + #[test] + fn test_commit_public_output_empty_is_ok() { + let mut memory = Memory::default(); + memory + .commit_public_output(0, 0) + .expect("zero-length commit should succeed"); + assert!( + memory + .read_return_value() + .expect("public output should be readable") + .is_empty() + ); + } + + #[test] + fn test_commit_public_output_address_overflow() { + let mut memory = Memory::default(); + let err = memory + .commit_public_output(u64::MAX, 2) + .expect_err("address overflow must error, not panic"); + assert!(matches!(err, super::MemoryError::AddressOverflow)); + } + + #[test] + fn test_load_bytes_huge_len_returns_alloc_error() { + let memory = Memory::default(); + // A multi-petabyte allocation request from a guest must fail cleanly, + // not abort the host process via OOM. `addr=0` and `len=1<<50` keep + // `checked_add` happy so the path reaches the allocation. + let huge = 1u64 << 50; + let err = memory + .load_bytes(0, huge) + .expect_err("huge alloc must error, not abort"); + assert!(matches!(err, super::MemoryError::AllocationFailed)); + } + + #[test] + fn test_load_bytes_overflow_errors() { + let memory = Memory::default(); + let err = memory + .load_bytes(u64::MAX, 2) + .expect_err("address overflow must error, not panic"); + assert!(matches!(err, super::MemoryError::AddressOverflow)); + } + + #[test] + fn test_commit_public_output_total_cap() { + let mut memory = Memory::default(); + // Seed enough source bytes for two 512 KB writes. + let chunk = vec![0xAB; 512 * 1024]; + memory + .set_bytes_aligned(0x1_0000, &chunk) + .expect("seed should succeed"); + + memory + .commit_public_output(0x1_0000, 512 * 1024) + .expect("first 512 KB commit should succeed"); + memory + .commit_public_output(0x1_0000, 512 * 1024) + .expect("second 512 KB commit should succeed (total = 1 MB)"); + + // One more byte exceeds the 1 MB total cap. + let err = memory.commit_public_output(0x1_0000, 1).unwrap_err(); + assert!(matches!(err, super::MemoryError::CommitSizeExceeded)); + } + + #[test] + fn test_commit_zero_length_after_data() { + let mut memory = Memory::default(); + memory.store_byte(0x100, b'x'); + memory + .commit_public_output(0x100, 1) + .expect("first commit should succeed"); + // Zero-length commit must be a no-op. + memory + .commit_public_output(0x100, 0) + .expect("zero-length commit should succeed"); assert_eq!( memory .read_return_value() .expect("public output should be readable"), - b"cd".to_vec() + b"x".to_vec() ); } #[test] - fn test_commit_public_output_size_exceeded() { + fn test_commit_zero_length_at_cap() { let mut memory = Memory::default(); - let err = memory.commit_public_output(0x100, 1025); - assert!(err.is_err()); + let chunk = vec![0xAB; 512 * 1024]; + memory + .set_bytes_aligned(0x1_0000, &chunk) + .expect("seed should succeed"); + // Fill to exactly 1 MB. + memory + .commit_public_output(0x1_0000, 512 * 1024) + .expect("first half should succeed"); + memory + .commit_public_output(0x1_0000, 512 * 1024) + .expect("second half should succeed"); + // Zero-length commit at the cap boundary must still succeed. + memory + .commit_public_output(0x1_0000, 0) + .expect("zero-length commit at cap should succeed"); + } + + #[test] + fn test_commit_cap_exceeded_does_not_modify_output() { + let mut memory = Memory::default(); + memory.store_byte(0x100, b'a'); + memory.store_byte(0x101, b'b'); + memory + .commit_public_output(0x100, 2) + .expect("initial commit should succeed"); + + let before = memory + .read_return_value() + .expect("public output should be readable"); + + // Attempt a commit that exceeds the cap. + let _ = memory.commit_public_output(0x100, super::MAX_PUBLIC_OUTPUT_TOTAL_SIZE); + + let after = memory + .read_return_value() + .expect("public output should be readable"); + + // The failed commit must not have modified the output buffer. + assert_eq!(before, after); } } diff --git a/executor/tests/rust.rs b/executor/tests/rust.rs index fab183571..c217c9cf0 100644 --- a/executor/tests/rust.rs +++ b/executor/tests/rust.rs @@ -160,6 +160,52 @@ fn test_commit() { ); } +#[test] +fn test_ef_io_demo_concatenates_writes() { + // Demo guest reads its private input via EF `read_input`, then emits it + // back as the public output via TWO `write_output` calls (split in halves). + // The COMMIT AIR concatenates the two calls; the executor's + // `commit_public_output` appends in the same order. + let input: Vec = b"hello world!".to_vec(); + run_program_and_check_public_output( + "./program_artifacts/rust/ef_io_demo.elf", + input.clone(), + input, + ); +} + +#[test] +fn test_ef_io_no_input() { + // Guest calls read_input with no private input (buf_size should be 0), + // then emits a hardcoded "ok" via write_output. + run_program_and_check_public_output( + "./program_artifacts/rust/ef_io_no_input.elf", + b"ok".to_vec(), + vec![], + ); +} + +#[test] +fn test_ef_io_zero_write() { + // Guest calls write_output(ptr, 0) then write_output(ptr, 5). + // The zero-length call must be a no-op; output should be "hello". + run_program_and_check_public_output( + "./program_artifacts/rust/ef_io_zero_write.elf", + b"hello".to_vec(), + vec![], + ); +} + +#[test] +fn test_ef_io_multi_write() { + // Guest emits "abcdefghij" one byte at a time via 10 write_output calls. + run_program_and_check_public_output( + "./program_artifacts/rust/ef_io_multi_write.elf", + b"abcdefghij".to_vec(), + vec![], + ); +} + #[test] fn test_commit_sum() { run_program_and_check_public_output( diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 6c9a07488..254c37834 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -651,6 +651,11 @@ pub fn prove_with_options_and_inputs( .filter(|c| c.is_private_input) .count(); + debug_assert_eq!( + traces.public_output_bytes, result.return_values.memory_values, + "public output diverged between executor view and trace reconstruction" + ); + Ok(VmProof { proof, runtime_page_ranges, diff --git a/prover/src/tests/prove_elfs_tests.rs b/prover/src/tests/prove_elfs_tests.rs index e8e79f80f..e3f6761b6 100644 --- a/prover/src/tests/prove_elfs_tests.rs +++ b/prover/src/tests/prove_elfs_tests.rs @@ -2172,6 +2172,55 @@ fn test_prove_private_input_different_values() { assert_eq!(proof.public_output, input[4..12].to_vec()); } +/// End-to-end: EF zkVM IO interface — demo guest reads its private input via +/// `read_input` and emits it back through TWO `write_output` calls. The +/// COMMIT AIR's running `x254` index concatenates them; the resulting proof's +/// `public_output` must equal the original input. +#[test] +fn test_prove_ef_io_demo_concatenates() { + let workspace_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("workspace root") + .to_path_buf(); + let elf_bytes = + std::fs::read(workspace_root.join("executor/program_artifacts/rust/ef_io_demo.elf")) + .expect("ef_io_demo.elf not found — run `make compile-programs-rust`"); + let input: &[u8] = b"hello world!"; + let proof = crate::prove_with_inputs(&elf_bytes, input).expect("prove should succeed"); + assert!( + crate::verify(&proof, &elf_bytes).expect("verify should not error"), + "ef_io_demo should verify" + ); + assert_eq!( + proof.public_output, input, + "two write_output calls must concatenate" + ); +} + +/// End-to-end: EF IO with no private input — guest emits a hardcoded "ok" +/// via `write_output`. Proves the output path works when `read_input` +/// returns `buf_size == 0`. +#[test] +fn test_prove_ef_io_no_input() { + let workspace_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("workspace root") + .to_path_buf(); + let elf_bytes = + std::fs::read(workspace_root.join("executor/program_artifacts/rust/ef_io_no_input.elf")) + .expect("ef_io_no_input.elf not found — run `make compile-programs-rust`"); + let proof = crate::prove(&elf_bytes).expect("prove should succeed"); + assert!( + crate::verify(&proof, &elf_bytes).expect("verify should not error"), + "ef_io_no_input should verify" + ); + assert_eq!( + proof.public_output, + b"ok".to_vec(), + "output should be hardcoded 'ok'" + ); +} + /// End-to-end: Rust std program with private input. #[test] fn test_prove_commit_sum() { diff --git a/syscalls/src/ef_io.rs b/syscalls/src/ef_io.rs new file mode 100644 index 000000000..dabf7818d --- /dev/null +++ b/syscalls/src/ef_io.rs @@ -0,0 +1,84 @@ +//! EF zkVM IO interface: +//! +//! Two 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. +//! +//! 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. + +#[cfg(target_arch = "riscv64")] +use core::arch::asm; + +#[cfg(target_arch = "riscv64")] +use crate::syscalls::{PRIVATE_INPUT_START, SyscallNumbers}; + +/// EF IO: return a zero-copy pointer and size for the private input. +/// +/// Per the spec this function is idempotent, callable multiple times, and +/// cannot fail. If `buf_size` is 0, the value of `buf_ptr` is unspecified. +/// Privacy of the input is the guest's responsibility; the VM does not +/// enforce it. +/// +/// # Safety +/// +/// `buf_ptr` and `buf_size` must be valid, writable pointers. +#[cfg(target_arch = "riscv64")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn read_input(buf_ptr: *mut *const u8, buf_size: *mut usize) { + unsafe { + let len_ptr = PRIVATE_INPUT_START as *const u32; + let len = core::ptr::read_volatile(len_ptr) as usize; + *buf_ptr = (PRIVATE_INPUT_START + 4) as *const u8; + *buf_size = len; + } +} + +/// EF IO: append `size` bytes from `output` to the public output. +/// +/// Multiple calls concatenate. Per the spec this function cannot fail; in +/// practice the executor enforces a total-output cap (see +/// `MAX_PUBLIC_OUTPUT_TOTAL_SIZE` in `executor::vm::memory`). Exceeding it +/// causes the executor to return an error and abort proving — not a graceful +/// failure mode at the C boundary, but consistent with "cannot fail" for +/// well-formed programs that stay under the limit. +/// +/// # Safety +/// +/// `output` must point to `size` readable bytes within guest memory. +#[cfg(target_arch = "riscv64")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write_output(output: *const u8, size: usize) { + unsafe { + asm!( + "ecall", + in("a0") 1usize, // fd = 1 (stdout) — required by the COMMIT chip + in("a1") output, + in("a2") size, + in("a7") SyscallNumbers::Commit as 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. +#[cfg(not(target_arch = "riscv64"))] +pub fn read_input(_buf_ptr: *mut *const u8, _buf_size: *mut usize) { + unimplemented!("read_input 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_output(_output: *const u8, _size: usize) { + unimplemented!("write_output is only implemented for riscv64 targets"); +} diff --git a/syscalls/src/lib.rs b/syscalls/src/lib.rs index 378257d18..79a420181 100644 --- a/syscalls/src/lib.rs +++ b/syscalls/src/lib.rs @@ -1,4 +1,5 @@ pub mod allocator; +pub mod ef_io; pub mod entrypoint; pub mod random; pub mod syscalls; diff --git a/syscalls/src/syscalls.rs b/syscalls/src/syscalls.rs index 14d5b2e6f..6451828c6 100644 --- a/syscalls/src/syscalls.rs +++ b/syscalls/src/syscalls.rs @@ -6,10 +6,10 @@ use core::arch::asm; /// The host pre-loads the input; the guest reads directly (no ecall). /// Must match `executor::vm::memory::PRIVATE_INPUT_START_INDEX`. #[cfg(target_arch = "riscv64")] -const PRIVATE_INPUT_START: usize = 0xFF000000; +pub const PRIVATE_INPUT_START: usize = 0xFF000000; #[cfg(target_arch = "riscv64")] -enum SyscallNumbers { +pub enum SyscallNumbers { Print = 1, Panic = 2, Commit = 64,