Skip to content

Commit

Permalink
feat: one cycle input (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
ctian1 authored Mar 30, 2024
1 parent 14bd4b2 commit fe8df5e
Show file tree
Hide file tree
Showing 40 changed files with 856 additions and 215 deletions.
11 changes: 6 additions & 5 deletions book/writing-programs/inputs-and-outputs.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Inputs and Outputs

In real world applications of zero-knowledge proofs, you almost always want to verify your proof in the context of some inputs and outputs. For example:

- **Rollups**: Given a list of transactions, prove the new state of the blockchain.
- **Coprocessors**: Given a block header, prove the historical state of some storage slot inside a smart contract.
- **Attested Images**: Given a signed image, prove that you made a restricted set of image transformations.
Expand All @@ -17,11 +18,10 @@ let b = sp1_zkvm::io::read::<u64>();
let c = sp1_zkvm::io::read::<String>();
```

Note that `T` must implement the `serde::Serialize` and `serde::Deserialize` trait. If you want to read bytes directly, you can also use the `sp1_zkvm::io::read_slice` method.
Note that `T` must implement the `serde::Serialize` and `serde::Deserialize` trait. If you want to read bytes directly, you can also use the `sp1_zkvm::io::read_vec` method.

```rust,noplayground
let mut my_slice = [0_u8; 32];
sp1_zkvm::io::read_slice(&mut my_slice);
let my_vec = sp1_zkvm::io::read_vec();
```

## Writing Data
Expand All @@ -34,7 +34,7 @@ sp1_zkvm::io::write::<u64>(&b);
sp1_zkvm::io::write::<String>(&c);
```

Note that `T` must implement the `Serialize` and `Deserialize` trait. If you want to write bytes directly, you can also use `sp1_zkvm::io::write_slice` method:
Note that `T` must implement the `Serialize` and `Deserialize` trait. If you want to write bytes directly, you can also use `sp1_zkvm::io::write_slice` method:

```rust,noplayground
let mut my_slice = [0_u8; 32];
Expand All @@ -44,6 +44,7 @@ sp1_zkvm::io::write_slice(&my_slice);
## Creating Serializable Types

Typically, you can implement the `Serialize` and `Deserialize` traits using a simple derive macro on a struct.

```rust,noplayground
use serde::{Serialize, de::Deserialize};
Expand All @@ -63,4 +64,4 @@ Here is a basic example of using inputs and outputs with more complex types.

```rust,noplayground
{{#include ../../examples/io/program/src/main.rs}}
```
```
11 changes: 10 additions & 1 deletion core/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ pub fn criterion_benchmark(c: &mut Criterion) {
};
group.bench_function(
format!("main:{}:{}", p.split('/').last().unwrap(), cycles),
|b| b.iter(|| run_and_prove(black_box(program.clone()), &[], BabyBearPoseidon2::new())),
|b| {
b.iter(|| {
run_and_prove(
black_box(program.clone()),
#[allow(deprecated)]
sp1_core::SP1Stdin::new(),
BabyBearPoseidon2::new(),
)
})
},
);
}
group.finish();
Expand Down
14 changes: 7 additions & 7 deletions core/src/cpu/air/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,15 @@ impl CpuChip {
ecall_cols.is_halt.result
};

// Constrain EcallCols.is_lwa.result == syscall_id is LWA.
let is_lwa = {
// Constrain EcallCols.is_hint_len.result == syscall_id is HINT_LEN.
let is_hint_len = {
IsZeroOperation::<AB::F>::eval(
builder,
syscall_id - AB::Expr::from_canonical_u32(SyscallCode::LWA.syscall_id()),
ecall_cols.is_lwa,
syscall_id - AB::Expr::from_canonical_u32(SyscallCode::HINT_LEN.syscall_id()),
ecall_cols.is_hint_len,
is_ecall_instruction.clone(),
);
ecall_cols.is_lwa.result
ecall_cols.is_hint_len.result
};

// When syscall_id is ENTER_UNCONSTRAINED, the new value of op_a should be 0.
Expand All @@ -329,10 +329,10 @@ impl CpuChip {
.when(is_ecall_instruction.clone() * is_enter_unconstrained)
.assert_word_eq(local.op_a_val(), zero_word);

// When the syscall is not one of ENTER_UNCONSTRAINED, LWA, or HALT, op_a shouldn't change.
// When the syscall is not one of ENTER_UNCONSTRAINED, HINT_LEN, or HALT, op_a shouldn't change.
builder
.when(is_ecall_instruction.clone())
.when_not(is_enter_unconstrained + is_lwa + is_halt)
.when_not(is_enter_unconstrained + is_hint_len + is_halt)
.assert_word_eq(local.op_a_val(), local.op_a_access.prev_value);

(
Expand Down
4 changes: 2 additions & 2 deletions core/src/cpu/columns/ecall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pub struct EcallCols<T> {
/// Whether the current ecall is ENTER_UNCONSTRAINED.
pub is_enter_unconstrained: IsZeroOperation<T>,

/// Whether the current ecall is LWA.
pub is_lwa: IsZeroOperation<T>,
/// Whether the current ecall is HINT_LEN.
pub is_hint_len: IsZeroOperation<T>,

/// Whether the current ecall is HALT.
pub is_halt: IsZeroOperation<T>,
Expand Down
6 changes: 3 additions & 3 deletions core/src/cpu/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,9 +511,9 @@ impl CpuChip {
- F::from_canonical_u32(SyscallCode::ENTER_UNCONSTRAINED.syscall_id()),
);

// Populate `is_lwa`.
ecall_cols.is_lwa.populate_from_field_element(
syscall_id - F::from_canonical_u32(SyscallCode::LWA.syscall_id()),
// Populate `is_hint_len`.
ecall_cols.is_hint_len.populate_from_field_element(
syscall_id - F::from_canonical_u32(SyscallCode::HINT_LEN.syscall_id()),
);

// Populate `is_halt`.
Expand Down
34 changes: 25 additions & 9 deletions core/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::utils::Buffer;

/// Standard input for the prover.
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct SP1Stdin {
pub buffer: Buffer,
/// Input stored as a vec of vec of bytes. It's stored this way because the read syscall reads
/// a vec of bytes at a time.
pub buffer: Vec<Vec<u8>>,
pub ptr: usize,
}

/// Standard output for the prover.
Expand All @@ -18,35 +21,47 @@ impl SP1Stdin {
/// Create a new `SP1Stdin`.
pub fn new() -> Self {
Self {
buffer: Buffer::new(),
buffer: Vec::new(),
ptr: 0,
}
}

/// Create a `SP1Stdin` from a slice of bytes.
pub fn from(data: &[u8]) -> Self {
Self {
buffer: Buffer::from(data),
buffer: vec![data.to_vec()],
ptr: 0,
}
}

/// Read a value from the buffer.
pub fn read<T: Serialize + DeserializeOwned>(&mut self) -> T {
self.buffer.read()
let result: T =
bincode::deserialize(&self.buffer[self.ptr]).expect("failed to deserialize");
self.ptr += 1;
result
}

/// Read a slice of bytes from the buffer.
pub fn read_slice(&mut self, slice: &mut [u8]) {
self.buffer.read_slice(slice);
slice.copy_from_slice(&self.buffer[self.ptr]);
self.ptr += 1;
}

/// Write a value to the buffer.
pub fn write<T: Serialize>(&mut self, data: &T) {
self.buffer.write(data);
let mut tmp = Vec::new();
bincode::serialize_into(&mut tmp, data).expect("serialization failed");
self.buffer.push(tmp);
}

/// Write a slice of bytes to the buffer.
pub fn write_slice(&mut self, slice: &[u8]) {
self.buffer.write_slice(slice);
self.buffer.push(slice.to_vec());
}

pub fn write_vec(&mut self, vec: Vec<u8>) {
self.buffer.push(vec);
}
}

Expand Down Expand Up @@ -126,7 +141,7 @@ pub mod proof_serde {
#[cfg(test)]
mod tests {
use crate::{
utils::{tests::FIBONACCI_IO_ELF, BabyBearPoseidon2},
utils::{setup_logger, tests::FIBONACCI_IO_ELF, BabyBearPoseidon2},
SP1ProofWithIO, SP1Prover, SP1Stdin, SP1Verifier,
};

Expand All @@ -144,6 +159,7 @@ pub mod proof_serde {
/// Tests serialization with a binary encoding
#[test]
fn test_bincode_roundtrip() {
setup_logger();
let mut stdin = SP1Stdin::new();
stdin.write(&3u32);
let proof = SP1Prover::prove(FIBONACCI_IO_ELF, stdin).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl SP1Prover {
pub fn execute(elf: &[u8], stdin: SP1Stdin) -> Result<SP1Stdout> {
let program = Program::from(elf);
let mut runtime = Runtime::new(program);
runtime.write_stdin_slice(&stdin.buffer.data);
runtime.write_vecs(&stdin.buffer);
runtime.run();
Ok(SP1Stdout::from(&runtime.state.output_stream))
}
Expand All @@ -77,7 +77,7 @@ impl SP1Prover {
let config = BabyBearPoseidon2::new();

let program = Program::from(elf);
let (proof, stdout) = run_and_prove(program, &stdin.buffer.data, config);
let (proof, stdout) = run_and_prove(program, stdin.clone(), config);
let stdout = SP1Stdout::from(&stdout);
Ok(SP1ProofWithIO {
proof,
Expand All @@ -103,7 +103,7 @@ impl SP1Prover {
{
let program = Program::from(elf);
let mut runtime = Runtime::new(program);
runtime.write_stdin_slice(&stdin.buffer.data);
runtime.write_vecs(&stdin.buffer);
runtime.run();
let stdout = SP1Stdout::from(&runtime.state.output_stream);
let proof = prove_core(config, runtime);
Expand Down
2 changes: 1 addition & 1 deletion core/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct MemoryInitializeFinalizeEvent {
}

impl MemoryInitializeFinalizeEvent {
pub fn intialize(addr: u32, value: u32, used: bool) -> Self {
pub fn initialize(addr: u32, value: u32, used: bool) -> Self {
// All memory initialization happen at shard 0, timestamp 0.
Self {
addr,
Expand Down
10 changes: 8 additions & 2 deletions core/src/runtime/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ impl Runtime {
pub fn write_stdin<T: Serialize>(&mut self, input: &T) {
let mut buf = Vec::new();
bincode::serialize_into(&mut buf, input).expect("serialization failed");
self.state.input_stream.extend(buf);
self.state.input_stream.push(buf);
}

pub fn write_stdin_slice(&mut self, input: &[u8]) {
self.state.input_stream.extend(input);
self.state.input_stream.push(input.to_vec());
}

pub fn write_vecs(&mut self, inputs: &[Vec<u8>]) {
for input in inputs {
self.state.input_stream.push(input.clone());
}
}

pub fn read_stdout<T: DeserializeOwned>(&mut self) -> T {
Expand Down
51 changes: 24 additions & 27 deletions core/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ impl Runtime {
}

// If it's the first time accessing this address, initialize previous values as zero.
if addr != 0 {
if let Entry::Vacant(_) = entry {
self.record
.memory_initialize_events
.push(MemoryInitializeFinalizeEvent::initialize(addr, 0, true))
}
}
let record = entry.or_default();
let value = record.value;
let prev_shard = record.shard;
Expand Down Expand Up @@ -241,6 +248,13 @@ impl Runtime {
}

// If it's the first time accessing this address, initialize previous values as zero.
if addr != 0 {
if let Entry::Vacant(_) = entry {
self.record
.memory_initialize_events
.push(MemoryInitializeFinalizeEvent::initialize(addr, 0, true))
}
}
let record = entry.or_default();
let prev_value = record.value;
let prev_shard = record.shard;
Expand Down Expand Up @@ -685,7 +699,6 @@ impl Runtime {
if let Some(syscall_impl) = syscall_impl {
// Executing a syscall optionally returns a value to write to the t0 register.
// If it returns None, we just keep the syscall_id in t0.
// Only the "LWA" syscall actually writes to t0, most syscalls don't return a value.
let res = syscall_impl.execute(&mut precompile_rt, b, c);
if let Some(val) = res {
a = val;
Expand Down Expand Up @@ -852,6 +865,11 @@ impl Runtime {
);
}

// Create init event for register 0 because it needs to be the first row in MemoryInit.
self.record
.memory_initialize_events
.push(MemoryInitializeFinalizeEvent::initialize(0, 0, true));

tracing::info!("starting execution");
}

Expand Down Expand Up @@ -923,11 +941,10 @@ impl Runtime {
program_memory_map.insert(key, (*value, true));
}

let mut memory_initialize_events = Vec::new();
let mut memory_finalize_events = Vec::new();
let memory_finalize_events = &mut self.record.memory_finalize_events;

// We handle the addr = 0 case separately, as we constraint it to be 0 in the first row
// of the memory initialize/finalize table so it must be first in the array of events.
// We handle the addr = 0 case separately, as we constrain it to be 0 in the first row
// of the memory finalize table so it must be first in the array of events.
let addr_0_record = self.state.memory.get(&0u32);

let addr_0_final_record = match addr_0_record {
Expand All @@ -938,8 +955,6 @@ impl Runtime {
timestamp: 0,
},
};

memory_initialize_events.push(MemoryInitializeFinalizeEvent::intialize(0, 0, true));
memory_finalize_events.push(MemoryInitializeFinalizeEvent::finalize_from_record(
0,
addr_0_final_record,
Expand All @@ -959,14 +974,6 @@ impl Runtime {
continue;
}

// If the memory addr was accessed, we only add it to "memory_initialize_events" if it was
// not in the program_memory_image, otherwise it'll be accounted from the
// program_memory_image table.
if !self.program.memory_image.contains_key(addr) {
memory_initialize_events
.push(MemoryInitializeFinalizeEvent::intialize(*addr, 0, true));
}

memory_finalize_events.push(MemoryInitializeFinalizeEvent::finalize_from_record(
*addr, &record,
));
Expand All @@ -975,15 +982,13 @@ impl Runtime {
let mut program_memory_events = program_memory_map
.into_iter()
.map(|(addr, (value, used))| {
MemoryInitializeFinalizeEvent::intialize(*addr, value, used)
MemoryInitializeFinalizeEvent::initialize(*addr, value, used)
})
.collect::<Vec<MemoryInitializeFinalizeEvent>>();
// Sort the program_memory_events by addr to create a canonical ordering for the
// preprocessed table, as this is part of the vkey.
program_memory_events.sort_by_key(|event| event.addr);

self.record.memory_initialize_events = memory_initialize_events;
self.record.memory_finalize_events = memory_finalize_events;
self.record.program_memory_events = program_memory_events;
}

Expand All @@ -1000,7 +1005,7 @@ pub mod tests {
utils::tests::{FIBONACCI_ELF, SSZ_WITHDRAWALS_ELF},
};

use super::{Instruction, Opcode, Program, Runtime, SyscallCode};
use super::{Instruction, Opcode, Program, Runtime};

pub fn simple_program() -> Program {
let instructions = vec![
Expand All @@ -1019,14 +1024,6 @@ pub mod tests {
Program::from(SSZ_WITHDRAWALS_ELF)
}

pub fn ecall_lwa_program() -> Program {
let instructions = vec![
Instruction::new(Opcode::ADD, 5, 0, SyscallCode::LWA as u32, false, true),
Instruction::new(Opcode::ECALL, 5, 10, 11, false, false),
];
Program::new(instructions, 0, 0)
}

#[test]
fn test_simple_program_run() {
let program = simple_program();
Expand Down
Loading

0 comments on commit fe8df5e

Please sign in to comment.