Skip to content

Commit 62bfbb9

Browse files
ColoCarlettidiegokingstonMauroToscano
authored
feat(prover): Memory-mapped private input for guest programs (#512)
* fix executor bugs * add private inputs feature * merge * change page size * fmt * fix_tests * fix * fix_merge * fmt * fix_comments * change verifier privete_inputs handle * fmt * add max private input size * fmt * fmt * fmt * test: add security tests for private input tamper scenarios Verify the verifier rejects proofs with tampered num_private_input_pages (zeroed, inflated, exceeds max) and tampered public_output when private input is present. Also confirm the proof struct does not carry raw private input bytes. * clippy + fmt * clippy + fmt --------- Co-authored-by: Diego K <43053772+diegokingston@users.noreply.github.com> Co-authored-by: MauroFab <maurotoscano2@gmail.com>
1 parent 61a1eca commit 62bfbb9

16 files changed

Lines changed: 516 additions & 147 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.attribute 5, "rv64i2p1"
2+
.globl main
3+
main:
4+
# Read private input directly from 0xFF000000 (memory-mapped).
5+
# Layout: [len:u32 LE] [data...]
6+
# Commits 8 bytes of data.
7+
#
8+
# Note: lui in RV64 sign-extends to 64 bits. lui with 0xFF000 would give
9+
# 0xFFFFFFFFFF000000. To get 0xFF000000 we need to construct it differently:
10+
# lui x, 0x100000 gives 0x100000000 (53 upper bits), too high.
11+
# Instead: load 0x0FF00000 and shift left by 4 bits, OR similar tricks.
12+
# Simplest: use li macro and let the assembler handle it.
13+
14+
li t0, 0xFF000000 # 1: t0 = 0xFF000000 (private input base)
15+
16+
# Read length at 0xFF000000
17+
lw t3, 0(t0) # 2: t3 = length
18+
19+
# Load 8 bytes of data at 0xFF000008 (aligned, 4 bytes into data region)
20+
ld t1, 8(t0) # 3
21+
22+
# Commit 8 bytes from 0xFF000008
23+
addi a1, t0, 8 # 4: buf_addr = 0xFF000008
24+
li a0, 1 # 5: fd = 1
25+
li a2, 8 # 6: count = 8
26+
li a7, 64 # 7: syscall = Commit
27+
ecall # 8: commit
28+
29+
# Halt
30+
li a0, 0 # 9: exit_code = 0
31+
li a7, 93 # 10: syscall = Halt
32+
ecall # 11: halt
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use lambda_vm_syscalls as syscalls;
22

33
pub fn main() {
4-
let input: Vec<u8> = syscalls::syscalls::get_private_input().unwrap();
4+
let input: Vec<u8> = syscalls::syscalls::get_private_input();
55
syscalls::syscalls::print_string(format!("Private input received: {:?}\n", input).as_str());
66
syscalls::syscalls::commit(&input);
77
}

executor/programs/rust/commit_sum/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use lambda_vm_syscalls as syscalls;
22

33
pub fn main() {
4-
let input: Vec<u8> = syscalls::syscalls::get_private_input().unwrap();
4+
let input: Vec<u8> = syscalls::syscalls::get_private_input();
55
let a = input[0];
66
let b = input[1];
77
syscalls::syscalls::commit((a + b).to_le_bytes().as_ref());

executor/programs/rust/ethrex/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use guest_program::{execution::execution_program, input::ProgramInput};
22
use rkyv::rancor::Error;
33
use lambda_vm_syscalls as syscalls;
44
pub fn main() {
5-
let input = syscalls::syscalls::get_private_input().unwrap();
5+
let input = syscalls::syscalls::get_private_input();
66
let input = rkyv::from_bytes::<ProgramInput, Error>(&input).unwrap();
77
let output = execution_program(input).unwrap();
88
let output_bytes = output.encode();

executor/programs/rust/memory/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use lambda_vm_syscalls as syscalls;
22

33
pub fn main() {
4-
let input = syscalls::syscalls::get_private_input().unwrap();
4+
let input = syscalls::syscalls::get_private_input();
55
let size = u32::from_be_bytes(input.try_into().unwrap());
66
let mut vector = vec![];
77
for i in 0..size {

executor/programs/rust/pure_commit/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

executor/src/vm/instruction/execution.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const REGULAR_PC_UPDATE: u64 = 4;
1010
pub enum SyscallNumbers {
1111
Print = 1,
1212
Panic = 2,
13-
GetPrivateInputs = 4,
1413
Commit = 64,
1514
Halt = 93,
1615
}
@@ -21,7 +20,6 @@ impl TryFrom<u64> for SyscallNumbers {
2120
match value {
2221
1 => Ok(SyscallNumbers::Print),
2322
2 => Ok(SyscallNumbers::Panic),
24-
4 => Ok(SyscallNumbers::GetPrivateInputs),
2523
64 => Ok(SyscallNumbers::Commit),
2624
93 => Ok(SyscallNumbers::Halt),
2725
_ => Err(()),
@@ -326,14 +324,6 @@ impl Instruction {
326324
src2_val = buf_addr;
327325
dst_val = count;
328326
}
329-
SyscallNumbers::GetPrivateInputs => {
330-
// get private inputs
331-
let pointer = registers.read(10)?;
332-
let private_inputs = memory.load_private_inputs()?;
333-
for (i, byte) in private_inputs.iter().enumerate() {
334-
memory.store_byte(pointer + i as u64, *byte);
335-
}
336-
}
337327
SyscallNumbers::Halt => {
338328
// halt
339329
return Ok(Log {

executor/src/vm/memory.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ pub type U64HashMap<V> = HashMap<u64, V, U64BuildHasher>;
4141
// TODO: Correctly define this
4242
const MAX_PUBLIC_OUTPUT_COMMIT_SIZE: u64 = 1024;
4343
const PUBLIC_OUTPUT_START_INDEX: u64 = 0;
44-
// Ported from main: increased from 1024 to support larger inputs (ethrex)
45-
const MAX_PRIVATE_INPUT_SIZE: u64 = 6700000;
46-
// Ported from main: fixed high address to avoid overlap with program memory
47-
const PRIVATE_INPUT_START_INDEX: u64 = 0xFF000000;
44+
/// Maximum size of the private input memory region (in bytes).
45+
pub const MAX_PRIVATE_INPUT_SIZE: u64 = 6700000;
46+
/// Fixed high address where private input is mapped. Guest programs can read
47+
/// directly from this address (ZisK-style memory-mapped input).
48+
/// Layout: 4-byte LE length prefix at `PRIVATE_INPUT_START_INDEX`, then data at +4.
49+
/// Must match `PRIVATE_INPUT_START` in `syscalls/src/syscalls.rs`.
50+
pub const PRIVATE_INPUT_START_INDEX: u64 = 0xFF000000;
4851

4952
#[derive(Default, Debug)]
5053
pub struct Memory(U64HashMap<[u8; 4]>);
@@ -151,7 +154,13 @@ impl Memory {
151154
Ok(self.load_bytes(PUBLIC_OUTPUT_START_INDEX + 4, size as u64))
152155
}
153156

157+
/// Pre-loads private input bytes at `PRIVATE_INPUT_START_INDEX` as a
158+
/// 4-byte LE length prefix followed by the raw data. The guest reads these
159+
/// bytes directly via normal RISC-V loads (ZisK-style memory-mapped input).
154160
pub fn store_private_inputs(&mut self, inputs: Vec<u8>) -> Result<(), MemoryError> {
161+
if inputs.is_empty() {
162+
return Ok(());
163+
}
155164
if inputs.len() as u64 > MAX_PRIVATE_INPUT_SIZE {
156165
return Err(MemoryError::PrivateInputSizeExceeded);
157166
}
@@ -160,13 +169,6 @@ impl Memory {
160169
Ok(())
161170
}
162171

163-
pub fn load_private_inputs(&self) -> Result<Vec<u8>, MemoryError> {
164-
let size = self.load_word(PRIVATE_INPUT_START_INDEX)?;
165-
let mut inputs = size.to_le_bytes().to_vec();
166-
inputs.extend_from_slice(&self.load_bytes(PRIVATE_INPUT_START_INDEX + 4, size as u64));
167-
Ok(inputs)
168-
}
169-
170172
pub fn load_bytes(&self, mut addr: u64, len: u64) -> Vec<u8> {
171173
let mut result = Vec::with_capacity(len as usize);
172174
let end = addr + len;

executor/tests/asm.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ fn run_program(elf_path: &str) {
2121
);
2222
}
2323

24+
/// Test that the memory-mapped private input region is readable by guest programs.
25+
/// The ASM program reads from 0xFF000000 and commits 8 bytes of data.
26+
#[test]
27+
fn test_private_input_memory_mapped() {
28+
let elf_data = std::fs::read("./program_artifacts/asm/test_private_input_xpage.elf").unwrap();
29+
let program = Elf::load(&elf_data).unwrap();
30+
let input: Vec<u8> = (0u8..16).collect();
31+
let executor = Executor::new(&program, input.clone()).unwrap();
32+
let result = executor.run().unwrap();
33+
// Committed bytes are at 0xFF000008 = data bytes [4..12]
34+
assert_eq!(result.return_values.memory_values, input[4..12].to_vec());
35+
}
36+
2437
#[test]
2538
fn test_basic_program() {
2639
run_program("./program_artifacts/asm/basic_program.elf");
1.83 KB
Binary file not shown.

0 commit comments

Comments
 (0)