From 427fa5b55d261254c76da650c9c3faea2f1c5b4b Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:39:05 -0300 Subject: [PATCH 1/3] proving: migrate interleaving proof to new neo-fold-clean api (rev 675f2ce) Replace the old Twist/Shout proof adapter with the existing Nebula memory backend, and wire the proof crate through the new flow: - extract per-batch ark R1CS assignments from the interleaving circuit - fold them with neo-fold-clean using deterministic preprocessing - carry the folded audit through ZkTransactionProof - verify the audit from the interleaving spec/ledger path Rework circuit synthesis around Nebula's ordered RS/WS commitment model: - keep tracer and circuit memory-op order aligned - initialize dynamic ref memories before tracing - pad traces to scan and folding batch boundaries - support sparse memory tag ordering in the scan - zero gated-off write values so tracer and circuit witnesses agree This intentionally leaves some soundness work for follow-up: the Nebula Poseidon IC commitment is still disabled, and public binding for memory tables / host-call roots needs a new (maybe Nightstream-side) API. The current fold only cross-links the program-state IVC wires. Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> --- Cargo.toml | 11 +- .../starstream-interleaving-proof/Cargo.toml | 24 +- .../src/circuit.rs | 302 ++++--- .../src/folding.rs | 349 ++++++++ .../starstream-interleaving-proof/src/lib.rs | 215 ++--- .../src/memory/dummy.rs | 2 +- .../src/memory/mod.rs | 14 +- .../src/memory/nebula/gadget.rs | 38 +- .../src/memory/nebula/ic.rs | 17 +- .../src/memory/nebula/mod.rs | 16 +- .../src/memory/nebula/tracer.rs | 23 +- .../src/memory/tag.rs | 10 +- .../src/memory/twist_and_shout/mod.rs | 766 ------------------ .../starstream-interleaving-proof/src/neo.rs | 498 ------------ .../starstream-interleaving-spec/Cargo.toml | 12 +- .../starstream-interleaving-spec/src/lib.rs | 156 ++-- .../src/memory_tags.rs | 12 - .../src/transaction_effects/instance.rs | 30 +- interleaving/starstream-runtime/src/lib.rs | 18 +- .../tests/test_dex_swap_flow.rs | 13 +- 20 files changed, 767 insertions(+), 1759 deletions(-) create mode 100644 interleaving/starstream-interleaving-proof/src/folding.rs delete mode 100644 interleaving/starstream-interleaving-proof/src/memory/twist_and_shout/mod.rs delete mode 100644 interleaving/starstream-interleaving-proof/src/neo.rs diff --git a/Cargo.toml b/Cargo.toml index 8c913174..a1eabed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,13 +49,10 @@ wasmparser = "0.240.0" wasmtime = { version = "46.0", default-features = false } wit-component = "0.240.0" -neo-fold = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-math = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-ccs = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-ajtai = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-params = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-vm-trace = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } -neo-memory = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" } +neo-fold-clean = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "675f2ce9d9b05553d518c7ccb36abe2eddbad048" } +neo-math = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "675f2ce9d9b05553d518c7ccb36abe2eddbad048" } +neo-ccs = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "675f2ce9d9b05553d518c7ccb36abe2eddbad048" } +neo-params = { git = "https://github.com/LFDT-Nightstream/Nightstream.git", rev = "675f2ce9d9b05553d518c7ccb36abe2eddbad048" } [profile.dev.package] insta.opt-level = 3 diff --git a/interleaving/starstream-interleaving-proof/Cargo.toml b/interleaving/starstream-interleaving-proof/Cargo.toml index 1cd2c21f..a955457d 100644 --- a/interleaving/starstream-interleaving-proof/Cargo.toml +++ b/interleaving/starstream-interleaving-proof/Cargo.toml @@ -7,28 +7,16 @@ edition = "2024" ark-ff = { version = "0.5.0", default-features = false } ark-relations = { version = "0.5.0", features = ["std"] } ark-r1cs-std = { version = "0.5.0", default-features = false } -ark-bn254 = { version = "0.5.0", features = ["scalar_field"] } -ark-poly = "0.5.0" -ark-poly-commit = "0.5.0" tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } -neo-fold = { workspace = true } -neo-math = { workspace = true } -neo-ccs = { workspace = true } -neo-ajtai = { workspace = true } -neo-params = { workspace = true } -neo-vm-trace = { workspace = true } -neo-memory = { workspace = true } - starstream-interleaving-spec = { path = "../starstream-interleaving-spec" } -p3-goldilocks = { version = "0.4.1", default-features = false } -p3-field = "0.4.1" -p3-symmetric = "0.4.1" -p3-poseidon2 = "0.4.1" -rand_chacha = "0.9.0" -rand = "0.9" - ark-goldilocks = { path = "../../ark-goldilocks" } ark-poseidon2 = { path = "../../ark-poseidon2" } + +neo-fold-clean = { workspace = true } +neo-math = { workspace = true } +neo-ccs = { workspace = true } +neo-params = { workspace = true } +p3-field = "0.5.1" diff --git a/interleaving/starstream-interleaving-proof/src/circuit.rs b/interleaving/starstream-interleaving-proof/src/circuit.rs index 44d15a80..8134f370 100644 --- a/interleaving/starstream-interleaving-proof/src/circuit.rs +++ b/interleaving/starstream-interleaving-proof/src/circuit.rs @@ -65,13 +65,13 @@ type StepCircuitResult = Result< ( InterRoundWires, <>::Allocator as IVCMemoryAllocated>::FinishStepPayload, - Option, + // Program-state input and output wires for this sub-step. + IvcWires, + IvcWires, ), SynthesisError, >; -// OptionalF/OptionalFpVar live in optional.rs - /// common circuit variables to all the opcodes #[derive(Clone)] pub struct Wires { @@ -187,7 +187,66 @@ impl IvcWireLayout { } } +/// The program-state IVC wires. +#[derive(Clone)] +pub(crate) struct IvcWires { + id_curr: FpVar, + id_prev: OptionalFpVar, + next_ref_id: FpVar, + ref_arena_len: FpVar, + handler_stack_ptr: FpVar, + ref_building_remaining: FpVar, + ref_building_ptr: FpVar, +} + +impl IvcWires { + pub(crate) fn enforce_equal(&self, next: &Self) -> Result<(), SynthesisError> { + self.id_curr.enforce_equal(&next.id_curr)?; + self.id_prev + .encoded() + .enforce_equal(&next.id_prev.encoded())?; + self.next_ref_id.enforce_equal(&next.next_ref_id)?; + self.ref_arena_len.enforce_equal(&next.ref_arena_len)?; + self.handler_stack_ptr + .enforce_equal(&next.handler_stack_ptr)?; + self.ref_building_remaining + .enforce_equal(&next.ref_building_remaining)?; + self.ref_building_ptr + .enforce_equal(&next.ref_building_ptr)?; + Ok(()) + } + + /// Witness-local indices of these wires in `cs` (the positions the fold's + /// `semantic_state_*_var_indices` reference, before the `+ m_in` shift). + pub(crate) fn indices( + &self, + cs: &ConstraintSystemRef, + ) -> Result { + Ok(IvcWireIndices { + id_curr: fpvar_witness_index(cs, &self.id_curr)?, + id_prev: fpvar_witness_index(cs, &self.id_prev.encoded())?, + next_ref_id: fpvar_witness_index(cs, &self.next_ref_id)?, + ref_arena_len: fpvar_witness_index(cs, &self.ref_arena_len)?, + handler_stack_ptr: fpvar_witness_index(cs, &self.handler_stack_ptr)?, + ref_building_remaining: fpvar_witness_index(cs, &self.ref_building_remaining)?, + ref_building_ptr: fpvar_witness_index(cs, &self.ref_building_ptr)?, + }) + } +} + impl Wires { + fn ivc_wires(&self) -> IvcWires { + IvcWires { + id_curr: self.id_curr.clone(), + id_prev: self.id_prev.clone(), + next_ref_id: self.next_ref_id.clone(), + ref_arena_len: self.ref_arena_len.clone(), + handler_stack_ptr: self.handler_stack_ptr.clone(), + ref_building_remaining: self.ref_building_remaining.clone(), + ref_building_ptr: self.ref_building_ptr.clone(), + } + } + fn arg(&self, kind: ArgName) -> FpVar { self.opcode_args[kind.idx()].clone() } @@ -835,7 +894,6 @@ impl> StepCircuitBuilder { rm: &mut M::Allocator, cs: ConstraintSystemRef, mut irw: InterRoundWires, - compute_ivc_layout: bool, ) -> StepCircuitResult { rm.start_step(cs.clone()).unwrap(); @@ -881,12 +939,8 @@ impl> StepCircuitBuilder { let mem_step_data = rm.finish_step(i == self.ops.len() - 1)?; - // input <-> output mappings are done by modifying next_wires - let ivc_layout = if compute_ivc_layout { - Some(ivc_wires(&cs, &wires_in, &next_wires)?) - } else { - None - }; + let input_vars = wires_in.ivc_wires(); + let output_vars = next_wires.ivc_wires(); { let _guard = debug_span!(target: "gr1cs", "ref_building_mode").entered(); @@ -897,10 +951,14 @@ impl> StepCircuitBuilder { irw.update(next_wires); - Ok((irw, mem_step_data, ivc_layout)) + Ok((irw, mem_step_data, input_vars, output_vars)) } - pub fn trace_memory_ops(&mut self, params: >::Params) -> M { + pub fn trace_memory_ops( + &mut self, + params: >::Params, + batch_size: usize, + ) -> M { // initialize all the maps let mut mb = { let mut mb = M::new(params); @@ -1047,7 +1105,9 @@ impl> StepCircuitBuilder { ); } - for (pid, owner) in self.instance.ownership_in.iter().enumerate() { + // Per-pid memory must exist for coordination scripts too. + for pid in 0..self.instance.process_table.len() { + let owner = self.instance.ownership_in.get(pid).copied().flatten(); let encoded_owner = owner .map(|p| OptionalF::new(F::from(p.0 as u64)).encoded()) .unwrap_or_else(|| OptionalF::none().encoded()); @@ -1080,6 +1140,24 @@ impl> StepCircuitBuilder { // Initialize handler memory using simplified approach self.init_handler_memory(&mut mb); + self.init_ref_memory(&mut mb); + + // Pad scan-only steps before tracing so bookkeeping and synthesis align. + if let Some(missing) = mb.required_steps().checked_sub(self.ops.len()) { + tracing::debug!("padding with {missing} Nop operations for scan"); + self.ops + .extend(std::iter::repeat_n(LedgerOperation::Nop {}, missing)); + } + + // Every folded R1CS instance must have the same number of sub-steps. + let rem = self.ops.len() % batch_size; + if rem != 0 { + self.ops.extend(std::iter::repeat_n( + LedgerOperation::Nop {}, + batch_size - rem, + )); + } + // out of circuit memory operations. // this is needed to commit to the memory operations before-hand. // @@ -1102,13 +1180,11 @@ impl> StepCircuitBuilder { let config = instr.get_config(); - trace_ic(irw.id_curr.into_bigint().0[0] as usize, &mut mb, &config); - - let curr_switches = config.mem_switches_curr; - let target_switches = config.mem_switches_target; - let rom_switches = config.rom_switches; - let handler_switches = config.handler_switches; - let ref_arena_switches = config.ref_arena_switches; + let curr_switches = config.mem_switches_curr.clone(); + let target_switches = config.mem_switches_target.clone(); + let rom_switches = config.rom_switches.clone(); + let handler_switches = config.handler_switches.clone(); + let ref_arena_switches = config.ref_arena_switches.clone(); self.mem_switches .push((curr_switches.clone(), target_switches.clone())); @@ -1134,6 +1210,12 @@ impl> StepCircuitBuilder { _ => F::ZERO, }; + // Keep this memory-op order in lockstep with `from_irw`; Nebula's + // RS/WS commitment is an order-dependent hash chain. + let curr_read = + trace_program_state_reads(&mut mb, irw.id_curr.into_bigint().0[0], &curr_switches); + let curr_yield_to = curr_read.yield_to; + let handler_reads = trace_handler_stack_ops( &mut mb, &handler_switches, @@ -1146,26 +1228,13 @@ impl> StepCircuitBuilder { irw.handler_stack_counter += F::ONE; } - trace_ref_arena_ops( - &mut mb, - &mut next_ref_base, - &mut ref_building_ptr, - &mut ref_building_remaining, - &ref_arena_switches, - instr, - ); - - let curr_read = - trace_program_state_reads(&mut mb, irw.id_curr.into_bigint().0[0], &curr_switches); - let curr_yield_to = curr_read.yield_to; - let target_addr = match instr { LedgerOperation::Resume { target, .. } => Some(*target), LedgerOperation::CallEffectHandler { .. } => { Some(handler_reads.handler_stack_node_process) } - LedgerOperation::Yield { .. } => curr_read.yield_to.to_option(), - LedgerOperation::Return { .. } => curr_read.yield_to.to_option(), + LedgerOperation::Yield { .. } => curr_yield_to.to_option(), + LedgerOperation::Return { .. } => curr_yield_to.to_option(), LedgerOperation::Burn { .. } => None, LedgerOperation::NewUtxo { target: id, .. } => Some(*id), LedgerOperation::NewToken { target: id, .. } => Some(*id), @@ -1174,10 +1243,38 @@ impl> StepCircuitBuilder { LedgerOperation::Unbind { token_id } => Some(*token_id), _ => None, }; - let target_pid = target_addr.map(|t| t.into_bigint().0[0]); + let target_pid_value = target_pid.unwrap_or(0); + let target_read = - trace_program_state_reads(&mut mb, target_pid.unwrap_or(0), &target_switches); + trace_program_state_reads(&mut mb, target_pid_value, &target_switches); + + let curr_is_utxo = self.instance.is_utxo[irw.id_curr.into_bigint().0[0] as usize]; + let target_id = match instr { + LedgerOperation::CallEffectHandler { .. } => { + Some(handler_reads.handler_stack_node_process) + } + _ => None, + }; + let (curr_write, target_write) = instr.program_state_transitions( + irw.id_curr, + curr_read, + target_read, + curr_is_utxo, + target_id, + ); + + self.write_ops + .push((curr_write.clone(), target_write.clone())); + + trace_program_state_writes( + &mut mb, + irw.id_curr.into_bigint().0[0], + &curr_write, + &curr_switches, + ); + + trace_program_state_writes(&mut mb, target_pid_value, &target_write, &target_switches); mb.conditional_read( rom_switches.read_is_utxo_curr, @@ -1189,14 +1286,14 @@ impl> StepCircuitBuilder { mb.conditional_read( rom_switches.read_is_utxo_target, Address { - addr: target_pid.unwrap_or(0), + addr: target_pid_value, tag: RomMemoryTag::IsUtxo.memory_tag(), }, ); mb.conditional_read( rom_switches.read_is_token_target, Address { - addr: target_pid.unwrap_or(0), + addr: target_pid_value, tag: RomMemoryTag::IsToken.memory_tag(), }, ); @@ -1207,39 +1304,24 @@ impl> StepCircuitBuilder { tag: RomMemoryTag::MustBurn.memory_tag(), }, ); - let target_pid_value = target_pid.unwrap_or(0); + let _ = trace_program_hash_ops( &mut mb, rom_switches.read_program_hash_target, &F::from(target_pid_value), ); - let curr_is_utxo = self.instance.is_utxo[irw.id_curr.into_bigint().0[0] as usize]; - let target_id = match instr { - LedgerOperation::CallEffectHandler { .. } => { - Some(handler_reads.handler_stack_node_process) - } - _ => None, - }; - let (curr_write, target_write) = instr.program_state_transitions( - irw.id_curr, - curr_read, - target_read, - curr_is_utxo, - target_id, - ); - - self.write_ops - .push((curr_write.clone(), target_write.clone())); - - trace_program_state_writes( + trace_ref_arena_ops( &mut mb, - irw.id_curr.into_bigint().0[0], - &curr_write, - &curr_switches, + &mut next_ref_base, + &mut ref_building_ptr, + &mut ref_building_remaining, + &ref_arena_switches, + instr, ); - trace_program_state_writes(&mut mb, target_pid_value, &target_write, &target_switches); + // Must be last, matching `from_irw`. + trace_ic(irw.id_curr.into_bigint().0[0] as usize, &mut mb, &config); // update pids for next iteration match instr { @@ -1268,18 +1350,6 @@ impl> StepCircuitBuilder { mb.finish_step(); } - let current_steps = self.ops.len(); - if let Some(missing) = mb.required_steps().checked_sub(current_steps) { - tracing::debug!("padding with {missing} Nop operations for scan"); - self.ops - .extend(std::iter::repeat_n(LedgerOperation::Nop {}, missing)); - - // TODO: we probably want to do this before the main loop - for _ in 0..missing { - mb.finish_step(); - } - } - mb } @@ -1332,6 +1402,47 @@ impl> StepCircuitBuilder { } } + /// Pre-initialize dynamically-sized ref memories. + /// + /// `RefSizes`/`RefBases` are addressed by ref id. `RefArena` is zero-filled + /// for every cell the trace can touch. + fn init_ref_memory(&self, mem: &mut M) { + let mut total_arena = 0u64; + + for op in &self.ops { + if let LedgerOperation::NewRef { size, ret } = op { + let ref_id = ret.into_bigint().0[0]; + + mem.init( + Address { + addr: ref_id, + tag: RamMemoryTag::RefSizes.memory_tag(), + }, + vec![F::ZERO], + ); + mem.init( + Address { + addr: ref_id, + tag: RamMemoryTag::RefBases.memory_tag(), + }, + vec![F::ZERO], + ); + + total_arena += size.into_bigint().0[0] * REF_PUSH_BATCH_SIZE as u64; + } + } + + for addr in 0..total_arena { + mem.init( + Address { + addr, + tag: RamMemoryTag::RefArena.memory_tag(), + }, + vec![F::ZERO], + ); + } + } + #[tracing::instrument(target = "gr1cs", skip_all)] fn allocate_vars( &self, @@ -2349,35 +2460,6 @@ fn register_memory_segments>(mb: &mut M) { ); } -#[tracing::instrument(target = "gr1cs", skip_all)] -fn ivc_wires( - cs: &ConstraintSystemRef, - wires_in: &Wires, - wires_out: &Wires, -) -> Result { - let input = IvcWireIndices { - id_curr: fpvar_witness_index(cs, &wires_in.id_curr)?, - id_prev: fpvar_witness_index(cs, &wires_in.id_prev.encoded())?, - next_ref_id: fpvar_witness_index(cs, &wires_in.next_ref_id)?, - ref_arena_len: fpvar_witness_index(cs, &wires_in.ref_arena_len)?, - handler_stack_ptr: fpvar_witness_index(cs, &wires_in.handler_stack_ptr)?, - ref_building_remaining: fpvar_witness_index(cs, &wires_in.ref_building_remaining)?, - ref_building_ptr: fpvar_witness_index(cs, &wires_in.ref_building_ptr)?, - }; - - let output = IvcWireIndices { - id_curr: fpvar_witness_index(cs, &wires_out.id_curr)?, - id_prev: fpvar_witness_index(cs, &wires_out.id_prev.encoded())?, - next_ref_id: fpvar_witness_index(cs, &wires_out.next_ref_id)?, - ref_arena_len: fpvar_witness_index(cs, &wires_out.ref_arena_len)?, - handler_stack_ptr: fpvar_witness_index(cs, &wires_out.handler_stack_ptr)?, - ref_building_remaining: fpvar_witness_index(cs, &wires_out.ref_building_remaining)?, - ref_building_ptr: fpvar_witness_index(cs, &wires_out.ref_building_ptr)?, - }; - - Ok(IvcWireLayout { input, output }) -} - fn fpvar_witness_index( cs: &ConstraintSystemRef, var: &FpVar, @@ -2439,9 +2521,7 @@ impl PreWires { } fn trace_ic>(curr_pid: usize, mb: &mut M, config: &OpcodeConfig) { - if config.execution_switches.nop { - return; - } + let should_trace = !config.execution_switches.nop; let mut concat_data = [F::ZERO; 12]; @@ -2449,7 +2529,7 @@ fn trace_ic>(curr_pid: usize, mb: &mut M, config: &OpcodeConfig) let addr = (curr_pid * 4) + i; *slot = mb.conditional_read( - true, + should_trace, Address { tag: RamMemoryTag::TraceCommitments.memory_tag(), addr: addr as u64, @@ -2465,13 +2545,15 @@ fn trace_ic>(curr_pid: usize, mb: &mut M, config: &OpcodeConfig) for (i, elem) in new_commitment.iter().enumerate() { let addr = (curr_pid * 4) + i; + let value = if should_trace { *elem } else { F::ZERO }; + mb.conditional_write( - true, + should_trace, Address { addr: addr as u64, tag: RamMemoryTag::TraceCommitments.memory_tag(), }, - vec![*elem], + vec![value], ); } } diff --git a/interleaving/starstream-interleaving-proof/src/folding.rs b/interleaving/starstream-interleaving-proof/src/folding.rs new file mode 100644 index 00000000..a7c246aa --- /dev/null +++ b/interleaving/starstream-interleaving-proof/src/folding.rs @@ -0,0 +1,349 @@ +//! Adapter from the ark-based interleaving circuit to neo-fold-clean's R1CS +//! frontend. +//! +//! TODO: also cross-link the Nebula commitment state once the memory API +//! exposes the needed public values. + +use crate::F as ArkF; +use crate::circuit::{InterRoundWires, IvcWireLayout, IvcWires, StepCircuitBuilder}; +use crate::memory::IVCMemory as _; +use crate::nebula::tracer::{NebulaMemory, NebulaMemoryParams}; +use ark_ff::PrimeField as _; +use ark_relations::gr1cs::{ConstraintSystem, OptimizationGoal, SynthesisError}; +use neo_ccs::{CcsMatrix, CscMat}; +use neo_fold_clean::frontends::r1cs_f_prime::SparseR1cs; +use neo_math::F as NeoF; +use p3_field::PrimeCharacteristicRing as _; +use starstream_interleaving_spec::{InterleavingInstance, InterleavingWitness}; +use std::num::NonZeroUsize; + +pub(crate) fn to_neo(v: ArkF) -> NeoF { + NeoF::from_u64(v.into_bigint().0[0]) +} + +pub(crate) struct StepShape { + pub r1cs: SparseR1cs, + pub ivc_layout: IvcWireLayout, +} + +pub(crate) fn extract_r1cs_and_assignments( + inst: InterleavingInstance, + wit: InterleavingWitness, + batch_size: NonZeroUsize, +) -> Result<(StepShape, Vec>), SynthesisError> { + let ops = crate::make_interleaved_trace(&inst, &wit); + + let mut builder = StepCircuitBuilder::>::new(inst, ops); + // TODO(soundness): re-enable the Poseidon IC commitment. + let mb = builder.trace_memory_ops( + NebulaMemoryParams { + unsound_disable_poseidon_commitment: true, + }, + batch_size.get(), + ); + let mut mem = mb.constraints(); + + // `trace_memory_ops` padded the step list to a whole number of batches. + let num_steps = builder.ops.len(); + debug_assert_eq!(num_steps % batch_size, 0); + let num_batches = num_steps / batch_size; + let mut irw = InterRoundWires::new(builder.instance.entrypoint.0 as u64); + + let mut assignments = Vec::with_capacity(num_batches); + let mut shape: Option = None; + + for batch in 0..num_batches { + let cs = ConstraintSystem::::new_ref(); + cs.set_optimization_goal(OptimizationGoal::Constraints); + + // Fold a batch as one R1CS instance. Intra-batch continuity is enforced + // here; the folding plan cross-links only the batch boundary. + let mut first_input: Option = None; + let mut prev_output: Option = None; + + for k in 0..batch_size.get() { + let i = batch * batch_size.get() + k; + let (next_irw, (), input_vars, output_vars) = + builder.make_step_circuit(i, &mut mem, cs.clone(), irw)?; + irw = next_irw; + + // Intra-batch continuity: this sub-step's input state == previous + // sub-step's output state. + if let Some(prev) = prev_output.as_ref() { + prev.enforce_equal(&input_vars)?; + } + if first_input.is_none() { + first_input = Some(input_vars); + } + prev_output = Some(output_vars); + } + + let first_input = first_input.expect("batch has at least one sub-step"); + let last_output = prev_output.expect("batch has at least one sub-step"); + + if let Some(unsat) = cs.which_is_unsatisfied()? { + tracing::error!(batch, location = unsat, "batch CCS is unsat"); + } + assert!( + cs.is_satisfied()?, + "interleaving batch {batch} should be satisfied" + ); + + // Ark matrices use instance-then-witness column order. + let z: Vec = cs + .instance_assignment()? + .into_iter() + .chain(cs.witness_assignment()?) + .map(to_neo) + .collect(); + + // The per-batch R1CS shape and IVC-wire layout are uniform across + // batches, so capture them once from the first batch. + if batch == 0 { + cs.finalize(); + let m_in = cs.num_instance_variables(); + let m = m_in + cs.num_witness_variables(); + + // The batch's cross-linked program state spans the first sub-step's + // input wires to the last sub-step's output wires. + let ivc_layout = IvcWireLayout { + input: first_input.indices(&cs)?, + output: last_output.indices(&cs)?, + }; + + let mut all = cs.to_matrices().expect("R1CS matrices"); + let mut matrices = all.remove("R1CS").unwrap_or_else(|| { + panic!( + "no R1CS predicate; available: {:?}", + all.keys().collect::>() + ) + }); + let c = matrices.pop().unwrap(); + let b = matrices.pop().unwrap(); + let a = matrices.pop().unwrap(); + let n = a.len(); + tracing::info!( + n, + m, + m_in, + num_batches, + batch_size, + "extracted per-batch R1CS shape" + ); + + let r1cs = SparseR1cs::new( + csc_from_rows(&a, n, m), + csc_from_rows(&b, n, m), + csc_from_rows(&c, n, m), + n, + m, + m_in, + ) + .expect("SparseR1cs shape"); + + shape = Some(StepShape { r1cs, ivc_layout }); + } + + assignments.push(z); + } + + Ok((shape.expect("at least one batch"), assignments)) +} + +/// Build a sparse `CcsMatrix` (CSC) from ark's sparse row form `Vec<(coeff, col)>`. +fn csc_from_rows(rows: &[Vec<(ArkF, usize)>], n: usize, m: usize) -> CcsMatrix { + let mut triplets = Vec::new(); + for (r, row) in rows.iter().enumerate() { + for (coeff, col) in row { + triplets.push((r, *col, to_neo(*coeff))); + } + } + CcsMatrix::Csc(CscMat::from_triplets(triplets, n, m)) +} + +use neo_fold_clean::UncompressedAudit; +use neo_fold_clean::engine::ccs_native::poseidon2::POSEIDON2_GOLDILOCKS_BITS; +use neo_fold_clean::frontends::f_prime::image::{ + FPrimeImageLayout, NifsCeClaimShape, NifsPayloadShape, +}; +use neo_fold_clean::frontends::f_prime::recursive_plan::build_semantic_state_preimage_fields; +use neo_fold_clean::frontends::f_prime::recursive_plan::{ + AccumulatorPlanOptions, RecursiveStepImagePlan, StateXOutPlanOptions, + build_recursive_step_image_config, +}; +use neo_fold_clean::frontends::r1cs_f_prime::{self, R1csChainBuilder}; +use neo_fold_clean::paper::digest::digest_fields_as_digest32; +use neo_fold_clean::paper::f_prime::poseidon_trace::encode_poseidon_trace; +use neo_fold_clean::paper::f_prime::ring_action_trace::{LowNormEncoding, RingActionTraceLayout}; + +fn build_plan( + m: usize, + m_in: usize, + semantic_in: Vec, + semantic_out: Vec, + anchor: [u8; 32], + batch_size: usize, + ce_override: Option, +) -> RecursiveStepImagePlan { + let limbs = m * POSEIDON2_GOLDILOCKS_BITS + 1; + + // The CE-claim shape depends on the R1CS shape; `fold` retries with the + // compiler-reported shape if this initial guess is stale. + let ce_shape = ce_override.unwrap_or_else(|| NifsCeClaimShape { + c_data_entries: 972, + x_rows: 54, + x_active_cols: 5, + r_len: 14, + y_ring_inner_lens: vec![64; 8], + y_zcol_len: 64, + s_col_len: if batch_size <= 2 { 20 } else { 21 }, + }); + let c_data_entries = ce_shape.c_data_entries; + + let probe_plan = RecursiveStepImagePlan { + limbs, + app_private_var_widths: Vec::new(), + boundary_bits: 4 * POSEIDON2_GOLDILOCKS_BITS, + kmul_count: 0, + ring_action_pair_count: 0, + ring_action_pair_layout: RingActionTraceLayout::new( + LowNormEncoding::U64, + LowNormEncoding::U64, + LowNormEncoding::U64, + LowNormEncoding::U64, + ), + sponge_transcript_permutes: 0, + nifs_payload_shapes: vec![NifsPayloadShape::CeClaim(ce_shape)], + accumulator: Some(AccumulatorPlanOptions { + ce_claim_payload_index: 0, + c_data_entries, + child_count: 14, + unified: true, + }), + state_x_out: None, + }; + + let probe_layout = FPrimeImageLayout::new(build_recursive_step_image_config(&probe_plan)); + let boundary_start = probe_layout.boundary.offset; + let public_x_out_lane_bit_starts: [usize; 4] = + std::array::from_fn(|i| boundary_start + i * POSEIDON2_GOLDILOCKS_BITS); + + let mut plan = probe_plan; + plan.state_x_out = Some(StateXOutPlanOptions { + pc: 1, + public_x_out_lane_bit_starts, + app_public_input_var_indices: (0..m_in).collect(), + app_public_input_bit_var_indices: Vec::new(), + semantic_state_in_var_indices: semantic_in, + semantic_state_out_var_indices: semantic_out, + initial_semantic_state_digest_anchor: Some(anchor), + }); + plan +} + +fn semantic_state_digest(app_state: &[NeoF]) -> [u8; 32] { + digest_fields_as_digest32( + encode_poseidon_trace(&build_semantic_state_preimage_fields(app_state)).digest_native, + ) +} + +/// Fold the R1CS assignments and return the public shape, plan, and audit. +pub(crate) fn fold( + shape: &StepShape, + assignments: &[Vec], + anchor: [u8; 32], + batch_size: usize, +) -> (SparseR1cs, RecursiveStepImagePlan, UncompressedAudit) { + // `IvcWireLayout` indices are witness-local; the plan's semantic-state + // indices address the full assignment `z = [instance | witness]`, so shift + // by `m_in` (the instance count). + let to_z = |w: usize| shape.r1cs.m_in + w; + let in_idx: Vec = shape + .ivc_layout + .input_indices() + .into_iter() + .map(to_z) + .collect(); + let out_idx: Vec = shape + .ivc_layout + .output_indices() + .into_iter() + .map(to_z) + .collect(); + + // The instance-derived anchor assumes the first IVC field is `id_curr`; keep + // that layout in sync with `IVC_SEMANTIC_FIELD_COUNT`. + debug_assert_eq!( + in_idx.len(), + starstream_interleaving_spec::IVC_SEMANTIC_FIELD_COUNT, + "IvcWireLayout::FIELD_COUNT diverged from IVC_SEMANTIC_FIELD_COUNT" + ); + let z0 = assignments.first().expect("at least one step"); + debug_assert_eq!( + semantic_state_digest(&in_idx.iter().map(|&i| z0[i]).collect::>()), + anchor, + "prover's step-0 input state doesn't match the instance-derived anchor" + ); + + // Retry once if the compiler reports the exact CE-claim shape. + let mut ce_override: Option = None; + loop { + let plan = build_plan( + shape.r1cs.m, + shape.r1cs.m_in, + in_idx.clone(), + out_idx.clone(), + anchor, + batch_size, + ce_override.clone(), + ); + let prep = r1cs_f_prime::preprocess_sparse_seeded( + &shape.r1cs, + &plan, + starstream_interleaving_spec::FOLD_SEED, + ) + .expect("preprocess_sparse_seeded"); + + let mut chain = R1csChainBuilder::new(&prep).expect("chain builder"); + let mut fold_err = None; + for z in assignments { + if let Err(e) = chain.append_assignment(z.clone()) { + fold_err = Some(e); + break; + } + } + + match fold_err { + None => { + let audit = chain.finish_with_audit().expect("finish_with_audit"); + return (shape.r1cs.clone(), plan, audit); + } + Some(e) => match post_parent_actual_shape(&e) { + Some(actual) if ce_override.is_none() => { + tracing::info!( + ?actual, + "retuning CE-claim shape from PostParentShapeMismatch" + ); + ce_override = Some(actual); + } + _ => panic!("append_assignment: {e:?}"), + }, + } + } +} + +/// Pull the compiler's CE-claim shape out of a `PostParentShapeMismatch`. +fn post_parent_actual_shape( + err: &neo_fold_clean::frontends::r1cs_f_prime::Error, +) -> Option { + use neo_fold_clean::frontends::f_prime::compiler::FPrimeShellCompilerError; + use neo_fold_clean::frontends::r1cs_f_prime::Error; + use neo_fold_clean::frontends::r1cs_f_prime::compiler::R1csCompilerError; + + match err { + Error::Compiler(R1csCompilerError::Shell( + FPrimeShellCompilerError::PostParentShapeMismatch { actual, .. }, + )) => Some(actual.clone()), + _ => None, + } +} diff --git a/interleaving/starstream-interleaving-proof/src/lib.rs b/interleaving/starstream-interleaving-proof/src/lib.rs index 2b87caeb..801ae480 100644 --- a/interleaving/starstream-interleaving-proof/src/lib.rs +++ b/interleaving/starstream-interleaving-proof/src/lib.rs @@ -4,11 +4,11 @@ mod circuit; mod circuit_test; mod coroutine_args_gadget; mod execution_switches; +mod folding; mod handler_stack_gadget; mod ledger_operation; mod logging; mod memory; -mod neo; mod opcode_dsl; mod optional; mod program_hash_gadget; @@ -16,146 +16,76 @@ mod program_state; mod ref_arena_gadget; mod switchboard; -use crate::circuit::{InterRoundWires, IvcWireLayout}; -use crate::memory::IVCMemory; -use crate::memory::twist_and_shout::{TSMemLayouts, TSMemory}; -use crate::neo::{CHUNK_SIZE, StarstreamVm, StepCircuitNeo}; +pub use abi::commit; use abi::ledger_operation_from_wit; -use ark_relations::gr1cs::{ConstraintSystem, ConstraintSystemRef, SynthesisError}; -use circuit::StepCircuitBuilder; +use ark_relations::gr1cs::SynthesisError; +pub use ledger_operation::LedgerOperation; pub use memory::nebula; -use neo_ajtai::AjtaiSModule; -use neo_fold::pi_ccs::FoldingMode; -use neo_fold::session::{FoldingSession, preprocess_shared_bus_r1cs}; -use neo_fold::shard::StepLinkingConfig; -use neo_params::NeoParams; pub use optional::{OptionalF, OptionalFpVar}; -use rand::SeedableRng as _; -use starstream_interleaving_spec::{ - InterleavingInstance, InterleavingWitness, ProcessId, ZkTransactionProof, -}; -use std::collections::BTreeMap; -use std::sync::Arc; -use std::time::Instant; +use starstream_interleaving_spec::{InterleavingInstance, InterleavingWitness, ZkTransactionProof}; +use std::{collections::BTreeMap, num::NonZeroUsize}; pub type F = ark_goldilocks::FpGoldilocks; - pub type ProgramId = F; -pub use abi::commit; -pub use ledger_operation::LedgerOperation; +/// Default number of interleaving sub-steps folded as one R1CS instance. +/// +/// The batch size is a proving-time performance knob; verification derives its +/// preprocessing from the R1CS shape carried by the proof. +pub const DEFAULT_BATCH_SIZE: usize = 4; +/// Build the interleaving circuit, extract per-step R1CS assignments, and fold +/// them into a verifiable transaction proof. pub fn prove( inst: InterleavingInstance, wit: InterleavingWitness, +) -> Result { + prove_with_batch_size(inst, wit, DEFAULT_BATCH_SIZE.try_into().unwrap()) +} + +pub fn prove_with_batch_size( + inst: InterleavingInstance, + wit: InterleavingWitness, + batch_size: NonZeroUsize, ) -> Result { logging::setup_logger(); - let output_binding_config = inst.output_binding_config(); + let anchor = starstream_interleaving_spec::expected_initial_semantic_state_anchor(&inst); - // map all the disjoints vectors of traces (one per process) into a single - // list, which is simpler to think about for ivc. - let mut ops = make_interleaved_trace(&inst, &wit); + let t_extract = std::time::Instant::now(); + let (shape, assignments) = folding::extract_r1cs_and_assignments(inst, wit, batch_size)?; + let extract_ms = t_extract.elapsed().as_millis(); - ops.resize( - ops.len().next_multiple_of(CHUNK_SIZE), - crate::LedgerOperation::Nop {}, + let batches = assignments.len(); + tracing::info!( + batches, + batch_size, + n = shape.r1cs.n, + m = shape.r1cs.m, + extract_ms, + "folding interleaving trace with neo-fold-clean (r1cs_f_prime)" ); - let max_steps = ops.len(); - - tracing::info!("making proof, steps {}", ops.len()); - - let mut circuit_builder = StepCircuitBuilder::>::new(inst, ops); - - let mb = circuit_builder.trace_memory_ops(()); - - let circuit = Arc::new(StepCircuitNeo::new(mb.init_tables())); - - let pre = { - let now = std::time::Instant::now(); - let pre = - preprocess_shared_bus_r1cs(Arc::clone(&circuit)).expect("preprocess_shared_bus_r1cs"); - tracing::info!( - "preprocess_shared_bus_r1cs took {}ms", - now.elapsed().as_millis() - ); - - pre - }; - - let m = pre.m(); - - // params copy-pasted from nightstream tests, this needs review - let base_params = NeoParams::goldilocks_auto_r1cs_ccs(m).expect("params"); - let params = NeoParams::new( - base_params.q, - base_params.eta, - base_params.d, - base_params.kappa, - base_params.m, - 4, // b - 16, // k_rho - base_params.T, - base_params.s, - base_params.lambda, - ) - .expect("params"); - - let committer = setup_ajtai_committer(m, params.kappa as usize); - let prover = pre - .into_prover(params, committer.clone()) - .expect("into_prover (R1csCpu shared-bus config)"); - - let mut session = FoldingSession::new(FoldingMode::Optimized, params, committer); - - session.set_step_linking(StepLinkingConfig::new(neo::ivc_step_linking_pairs())); - - let (constraints, shout, twist) = mb.split(); - - prover - .execute_into_session( - &mut session, - StarstreamVm::new(circuit_builder, constraints), - twist, - shout, - max_steps, - ) - .expect("execute_into_session should succeed"); - - let t_prove = Instant::now(); - let run = session - .fold_and_prove_with_output_binding_auto_simple(prover.ccs(), &output_binding_config) - .unwrap(); - tracing::info!("proof generated in {} ms", t_prove.elapsed().as_millis()); - - let status = session - .verify_with_output_binding_collected_simple(prover.ccs(), &run, &output_binding_config) - .unwrap(); - - assert!(status, "optimized verification should pass"); - - let mcss_public = session.mcss_public(); - let steps_public = session.steps_public(); - - let prover_output = ZkTransactionProof::NeoProof { - proof: run, - session: Box::new(session), - ccs: prover.ccs().clone(), - mcss_public, - steps_public, - }; - - Ok(prover_output) -} + let t_fold = std::time::Instant::now(); + let (r1cs, plan, audit) = folding::fold(&shape, &assignments, anchor, batch_size.get()); + let fold_ms = t_fold.elapsed().as_millis(); + tracing::info!( + batches, + batch_size, + extract_ms, + fold_ms, + per_batch_ms = fold_ms / (batches.max(1) as u128), + "interleaving proof produced" + ); -fn setup_ajtai_committer(m: usize, kappa: usize) -> AjtaiSModule { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); - let pp = neo_ajtai::setup(&mut rng, neo_math::D, kappa, m).expect("Ajtai setup"); - AjtaiSModule::new(Arc::new(pp)) + Ok(ZkTransactionProof::Neo { + r1cs: Box::new(r1cs), + plan: Box::new(plan), + audit: Box::new(audit), + }) } -fn make_interleaved_trace( +pub(crate) fn make_interleaved_trace( inst: &InterleavingInstance, wit: &InterleavingWitness, ) -> Vec> { @@ -247,48 +177,3 @@ fn make_interleaved_trace( ops } - -fn ccs_step_shape() -> Result<(ConstraintSystemRef, TSMemLayouts, IvcWireLayout), SynthesisError> -{ - let _span = tracing::info_span!("dummy circuit").entered(); - - tracing::debug!("constructing nop circuit to get initial (stable) ccs shape"); - - let cs = ConstraintSystem::new_ref(); - cs.set_optimization_goal(ark_relations::gr1cs::OptimizationGoal::Constraints); - - let hash = starstream_interleaving_spec::Hash([0u64; 4], std::marker::PhantomData); - - let inst = InterleavingInstance { - host_calls_roots: vec![], - process_table: vec![hash], - is_utxo: vec![false], - is_token: vec![false], - must_burn: vec![false], - n_inputs: 0, - n_new: 0, - n_coords: 1, - ownership_in: vec![None], - ownership_out: vec![None], - entrypoint: ProcessId(0), - input_states: vec![], - }; - - let mut dummy_tx = StepCircuitBuilder::>::new(inst, vec![LedgerOperation::Nop {}]); - - let mb = dummy_tx.trace_memory_ops(()); - - let irw = InterRoundWires::new(dummy_tx.instance.entrypoint.0 as u64); - - let mut running_mem = mb.constraints(); - - let (_irw, _mem, captured_layout) = - dummy_tx.make_step_circuit(0, &mut running_mem, cs.clone(), irw, true)?; - let ivc_layout = captured_layout.expect("ivc layout requested"); - - cs.finalize(); - - let mem_spec = running_mem.ts_mem_layouts(); - - Ok((cs, mem_spec, ivc_layout)) -} diff --git a/interleaving/starstream-interleaving-proof/src/memory/dummy.rs b/interleaving/starstream-interleaving-proof/src/memory/dummy.rs index 96bc3ba7..d06b1c06 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/dummy.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/dummy.rs @@ -2,8 +2,8 @@ use super::Address; use super::IVCMemory; use super::IVCMemoryAllocated; use crate::memory::AllocatedAddress; +use crate::memory::Lanes; use crate::memory::MemType; -use crate::memory::twist_and_shout::Lanes; use ark_ff::PrimeField; use ark_r1cs_std::GR1CSVar as _; use ark_r1cs_std::alloc::AllocVar as _; diff --git a/interleaving/starstream-interleaving-proof/src/memory/mod.rs b/interleaving/starstream-interleaving-proof/src/memory/mod.rs index 7f22822e..077519e2 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/mod.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/mod.rs @@ -7,9 +7,19 @@ use std::fmt; pub mod dummy; pub mod nebula; pub mod tag; -pub mod twist_and_shout; pub use tag::MemoryTag; +/// Number of value lanes a memory uses per step. +#[derive(Debug, Clone, Copy)] +// Reserved in the interface while Nebula uses a fixed lane order. +pub struct Lanes(#[allow(dead_code)] pub usize); + +impl Default for Lanes { + fn default() -> Self { + Lanes(1) + } +} + #[derive(PartialOrd, Ord, PartialEq, Eq, Debug, Clone)] pub struct Address { pub tag: T, @@ -77,7 +87,7 @@ pub trait IVCMemory { tag: u64, size: u64, mem_type: MemType, - extra_info: twist_and_shout::Lanes, + extra_info: Lanes, debug_name: &'static str, ); diff --git a/interleaving/starstream-interleaving-proof/src/memory/nebula/gadget.rs b/interleaving/starstream-interleaving-proof/src/memory/nebula/gadget.rs index 42ff7e36..6c76c7b4 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/nebula/gadget.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/nebula/gadget.rs @@ -201,12 +201,15 @@ impl IVCMemoryAllocated for NebulaMemoryConstraints { .replace(self.scan_monotonic_last_addr_wires.take().unwrap().values()); if is_last_step { + // RS/WS is an order-dependent hash chain over active read/write ops; + // this catches drift between tracing and circuit synthesis order. assert!( self.ic_rs_ws .comm .iter() .zip(self.expected_rw_ws.comm.iter()) - .all(|(x, y)| x == y) + .all(|(x, y)| x == y), + "nebula ic_rs_ws != expected (trace/synth memory-op order or address mismatch)" ); assert!( @@ -309,12 +312,14 @@ impl IVCMemoryAllocated for NebulaMemoryConstraints { mem.1, ); - for ((index, val), expected) in vals.iter().enumerate().zip(wv.values.iter()) { - assert_eq!( - val.value().unwrap(), - expected.value().unwrap(), - "write doesn't match expectation at index {index}." - ); + if cond.value()? { + for ((index, val), expected) in vals.iter().enumerate().zip(wv.values.iter()) { + assert_eq!( + val.value().unwrap(), + expected.value().unwrap(), + "write doesn't match expectation at index {index}." + ); + } } Ok(()) @@ -397,6 +402,7 @@ impl NebulaMemoryConstraints { )?; self.step_ic_rs_ws.as_mut().unwrap().increment( + cond, address, rv, self.params.unsound_disable_poseidon_commitment, @@ -413,6 +419,7 @@ impl NebulaMemoryConstraints { )?; self.step_ic_rs_ws.as_mut().unwrap().increment( + cond, address, wv, self.params.unsound_disable_poseidon_commitment, @@ -455,9 +462,16 @@ impl NebulaMemoryConstraints { *last_addr = address.clone(); + // Tag-0 padding rows keep scan steps uniform without entering the + // IS/FS commitment or fingerprint. + let active = !address + .tag + .is_eq(&FpVar::new_constant(cs.clone(), F::from(0))?)?; + let is_entry = is_v.allocate(cs.clone(), max_segment_size)?; self.step_ic_is_fs.as_mut().unwrap().increment( + &active, &address, &is_entry, self.params.unsound_disable_poseidon_commitment, @@ -466,13 +480,14 @@ impl NebulaMemoryConstraints { let fs_entry = fs_v.allocate(cs.clone(), max_segment_size)?; self.step_ic_is_fs.as_mut().unwrap().increment( + &active, &address, &fs_entry, self.params.unsound_disable_poseidon_commitment, )?; Self::hash_avt( - &Boolean::constant(true), + &active, &mut self.fingerprint_wires.as_mut().unwrap().is, self.c0_wire.as_ref().unwrap(), self.c1_powers_cache.as_ref().unwrap(), @@ -482,7 +497,7 @@ impl NebulaMemoryConstraints { )?; Self::hash_avt( - &Boolean::constant(true), + &active, &mut self.fingerprint_wires.as_mut().unwrap().fs, self.c0_wire.as_ref().unwrap(), self.c1_powers_cache.as_ref().unwrap(), @@ -537,10 +552,13 @@ fn enforce_monotonic_commitment( ) -> Result<(), SynthesisError> { let same_segment = &address.tag.is_eq(&last_addr.tag)?; + // Tags are sparse because ROM and RAM occupy disjoint ranges. + // Within a segment, addresses must stay contiguous. let next_segment = address .tag - .is_eq(&(&last_addr.tag + FpVar::new_constant(cs.clone(), F::from(1))?))?; + .is_cmp(&last_addr.tag, std::cmp::Ordering::Greater, false)?; + // Tag 0 is reserved for the pre-scan sentinel and padding filler. let is_padding = address .tag .is_eq(&FpVar::new_constant(cs.clone(), F::from(0))?)?; diff --git a/interleaving/starstream-interleaving-proof/src/memory/nebula/ic.rs b/interleaving/starstream-interleaving-proof/src/memory/nebula/ic.rs index a7539457..dfcb7f4f 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/nebula/ic.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/nebula/ic.rs @@ -7,6 +7,7 @@ use ark_ff::AdditiveGroup as _; use ark_r1cs_std::GR1CSVar as _; use ark_r1cs_std::alloc::AllocVar as _; use ark_r1cs_std::fields::fp::FpVar; +use ark_r1cs_std::prelude::Boolean; use ark_relations::gr1cs::ConstraintSystemRef; use ark_relations::gr1cs::SynthesisError; use std::array; @@ -22,11 +23,13 @@ impl ICPlain { pub fn increment( &mut self, + cond: bool, a: &Address, vt: &MemOp, unsound_make_nop: bool, ) -> Result<(), SynthesisError> { - if !unsound_make_nop { + // Inactive ops leave the commitment unchanged, matching the circuit. + if cond && !unsound_make_nop { let hash_input = array::from_fn(|i| { if i == 0 { F::from(a.addr) @@ -85,6 +88,7 @@ impl IC { pub fn increment( &mut self, + cond: &Boolean, a: &AllocatedAddress, vt: &MemOpAllocated, unsound_make_nop: bool, @@ -115,7 +119,16 @@ impl IC { } }); - self.comm = ark_poseidon2::compress_8(&concat)?; + let new_comm = ark_poseidon2::compress_8(&concat)?; + + // Compute the hash unconditionally, then keep the old commitment for + // inactive ops. + self.comm = [ + cond.select(&new_comm[0], &self.comm[0])?, + cond.select(&new_comm[1], &self.comm[1])?, + cond.select(&new_comm[2], &self.comm[2])?, + cond.select(&new_comm[3], &self.comm[3])?, + ]; } Ok(()) diff --git a/interleaving/starstream-interleaving-proof/src/memory/nebula/mod.rs b/interleaving/starstream-interleaving-proof/src/memory/nebula/mod.rs index e036ad43..cfaa0a50 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/nebula/mod.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/nebula/mod.rs @@ -137,8 +137,8 @@ mod tests { ]; let false_write_vals = vec![ - FpVar::new_witness(cs.clone(), || Ok(F::from(0))).unwrap(), - FpVar::new_witness(cs.clone(), || Ok(F::from(0))).unwrap(), + FpVar::new_witness(cs.clone(), || Ok(F::from(999))).unwrap(), + FpVar::new_witness(cs.clone(), || Ok(F::from(888))).unwrap(), ]; constraints @@ -205,10 +205,14 @@ mod tests { .unwrap(); let write_vals = vec![ - FpVar::new_witness(cs.clone(), || Ok(F::from(if write_cond { 123 } else { 0 }))) - .unwrap(), - FpVar::new_witness(cs.clone(), || Ok(F::from(if write_cond { 456 } else { 0 }))) - .unwrap(), + FpVar::new_witness(cs.clone(), || { + Ok(F::from(if write_cond { 123 } else { 999 })) + }) + .unwrap(), + FpVar::new_witness(cs.clone(), || { + Ok(F::from(if write_cond { 456 } else { 888 })) + }) + .unwrap(), ]; constraints diff --git a/interleaving/starstream-interleaving-proof/src/memory/nebula/tracer.rs b/interleaving/starstream-interleaving-proof/src/memory/nebula/tracer.rs index 91fcfcdc..303b3dde 100644 --- a/interleaving/starstream-interleaving-proof/src/memory/nebula/tracer.rs +++ b/interleaving/starstream-interleaving-proof/src/memory/nebula/tracer.rs @@ -7,9 +7,9 @@ use super::NebulaMemoryConstraints; use super::ic::ICPlain; use crate::F; use crate::memory::IVCMemory; +use crate::memory::Lanes; use crate::memory::MemType; use crate::memory::nebula::gadget::FingerPrintPreWires; -use crate::memory::twist_and_shout::Lanes; use std::collections::BTreeMap; use std::collections::VecDeque; @@ -63,12 +63,9 @@ impl NebulaMemory { MemOp::padding() }; - // println!( - // "Tracing: incrementing ic_rs_ws with rv: {:?}, wv: {:?}", - // rv, wv - // ); self.ic_rs_ws .increment( + cond, address, &rv, self.params.unsound_disable_poseidon_commitment, @@ -76,6 +73,7 @@ impl NebulaMemory { .unwrap(); self.ic_rs_ws .increment( + cond, address, &wv, self.params.unsound_disable_poseidon_commitment, @@ -215,11 +213,22 @@ impl IVCMemory for NebulaMemory, - pub has_read: Option, - pub rv: Option, - pub wa: Option, - pub has_write: Option, - pub wv: Option, -} - -impl PartialTwistCpuBinding { - pub fn to_complete(&self) -> TwistCpuBinding { - TwistCpuBinding { - ra: self.ra.unwrap(), - has_read: self.has_read.unwrap(), - rv: self.rv.unwrap(), - wa: self.wa.unwrap(), - has_write: self.has_write.unwrap(), - wv: self.wv.unwrap(), - } - } -} - -/// Event representing a shout lookup operation -#[derive(Debug, Clone)] -pub struct ShoutEvent { - pub shout_id: u32, - pub key: u64, - pub value: u64, -} - -/// Event representing a twist memory operation -#[derive(Debug, Clone)] -pub struct TwistEvent { - pub twist_id: u32, - pub addr: u64, - pub val: u64, - pub op: TwistOpKind, - pub cond: bool, - pub lane: Option, -} - -#[derive(Clone)] -pub struct TSMemory { - pub(crate) reads: BTreeMap, VecDeque>>, - pub(crate) writes: BTreeMap, VecDeque>>, - pub(crate) init: BTreeMap, Vec>, - - pub(crate) mems: BTreeMap, - - /// Captured shout events for witness generation, organized by address - pub(crate) shout_events: BTreeMap, VecDeque>, - /// Captured twist events for witness generation, organized by address - pub(crate) twist_events: BTreeMap, VecDeque>, - - pub(crate) current_step_read_lanes: BTreeMap, - pub(crate) current_step_write_lanes: BTreeMap, -} - -/// Initial ROM tables computed by TSMemory -pub struct TSMemInitTables { - pub mems: BTreeMap, - pub rom_sizes: BTreeMap, - pub init: BTreeMap>, -} - -/// Layout/bindings computed by TSMemoryConstraints -pub struct TSMemLayouts { - pub shout_bindings: BTreeMap>, - pub twist_bindings: BTreeMap>, -} - -#[derive(Debug, Clone, Copy)] -pub struct Lanes(pub usize); - -impl Default for Lanes { - fn default() -> Self { - Self(1) - } -} - -impl IVCMemory for TSMemory { - type Allocator = TSMemoryConstraints; - type Params = (); - - fn new(_params: Self::Params) -> Self { - TSMemory { - reads: BTreeMap::default(), - writes: BTreeMap::default(), - init: BTreeMap::default(), - mems: BTreeMap::default(), - shout_events: BTreeMap::default(), - twist_events: BTreeMap::default(), - current_step_read_lanes: BTreeMap::default(), - current_step_write_lanes: BTreeMap::default(), - } - } - - // TODO: remove it like for shout? - fn register_mem_with_lanes( - &mut self, - tag: u64, - size: u64, - mem_type: MemType, - lanes: Lanes, - debug_name: &'static str, - ) { - self.mems.insert(tag, (size, lanes, mem_type, debug_name)); - } - - fn init(&mut self, address: Address, values: Vec) { - self.init.insert(address, values.clone()); - } - - fn conditional_read(&mut self, cond: bool, address: Address) -> Vec { - *self.current_step_read_lanes.entry(address.tag).or_default() += 1; - - if let Some(&(_, _, MemType::Rom, debug_name)) = self.mems.get(&address.tag) { - if cond { - let value = self.init.get(&address).unwrap().clone(); - let shout_event = ShoutEvent { - shout_id: address.tag as u32, - key: address.addr, - value: value[0].into_bigint().as_ref()[0], - }; - - if address.tag == 20 { - tracing::info!( - "traced rom read from address {} in table {} with value {:?}", - address.addr, - debug_name, - value - ); - } - - let shout_events = self.shout_events.entry(address.clone()).or_default(); - shout_events.push_back(shout_event); - value - } else { - let mem_value_size = self.mems.get(&address.tag).unwrap().0; - std::iter::repeat_n(F::from(0), mem_value_size as usize).collect() - } - } else { - let reads = self.reads.entry(address.clone()).or_default(); - if cond { - let mem_value_size = self.mems.get(&address.tag).unwrap().0 as usize; - let last = self - .writes - .get(&address) - .and_then(|writes| writes.back().cloned()) - .or_else(|| self.init.get(&address).cloned()) - .unwrap_or_else(|| vec![F::ZERO; mem_value_size]); - - reads.push_back(last.clone()); - - let twist_event = TwistEvent { - twist_id: address.tag as u32, - addr: address.addr, - val: last[0].into_bigint().as_ref()[0], - op: TwistOpKind::Read, - cond, - lane: None, - }; - - self.twist_events - .entry(address.clone()) - .or_default() - .push_back(twist_event); - - last - } else { - let mem_value_size = self.mems.get(&address.tag).unwrap().0; - std::iter::repeat_n(F::from(0), mem_value_size as usize).collect() - } - } - } - - fn conditional_write(&mut self, cond: bool, address: Address, values: Vec) { - *self - .current_step_write_lanes - .entry(address.tag) - .or_default() += 1; - - assert_eq!( - self.mems.get(&address.tag).unwrap().0 as usize, - values.len(), - "write doesn't match mem value size" - ); - if cond { - self.writes - .entry(address.clone()) - .or_default() - .push_back(values.clone()); - - let twist_event = TwistEvent { - twist_id: address.tag as u32, - addr: address.addr, - val: values[0].into_bigint().as_ref()[0], - op: TwistOpKind::Write, - cond, - lane: None, - }; - - self.twist_events - .entry(address) - .or_default() - .push_back(twist_event); - } - } - - fn finish_step(&mut self) { - let mut current_step_read_lanes = BTreeMap::new(); - let mut current_step_write_lanes = BTreeMap::new(); - - std::mem::swap( - &mut current_step_read_lanes, - &mut self.current_step_read_lanes, - ); - - std::mem::swap( - &mut current_step_write_lanes, - &mut self.current_step_write_lanes, - ); - - for (tag, reads) in current_step_read_lanes { - if let Some(writes) = current_step_write_lanes.get(&tag) { - assert_eq!( - reads, *writes, - "each step must have the same number of (conditional) reads and writes per memory" - ); - } - - if let Some(entry) = self.mems.get_mut(&tag) { - entry.1 = Lanes(reads); - } - } - - self.current_step_read_lanes.clear(); - self.current_step_write_lanes.clear(); - } - - fn required_steps(&self) -> usize { - 0 - } - - fn constraints(self) -> Self::Allocator { - TSMemoryConstraints { - cs: None, - mems: self.mems, - shout_events: self.shout_events, - twist_events: self.twist_events, - shout_bindings: BTreeMap::new(), - partial_twist_bindings: BTreeMap::new(), - step_events_shout: vec![], - step_events_twist: vec![], - is_first_step: true, - write_lanes: BTreeMap::new(), - read_lanes: BTreeMap::new(), - } - } -} - -impl TSMemory { - pub fn init_tables(&self) -> TSMemInitTables { - let mut rom_sizes = BTreeMap::new(); - let mut init = BTreeMap::>::new(); - - for (address, val) in &self.init { - if let Some((_, _, MemType::Rom, _)) = self.mems.get(&address.tag) { - *rom_sizes.entry(address.tag).or_insert(0) += 1; - } - - init.entry(address.tag) - .or_default() - .insert(address.addr, val[0]); - } - - TSMemInitTables { - mems: self.mems.clone(), - rom_sizes, - init, - } - } - - pub fn split(self) -> (TSMemoryConstraints, TracedShout, TracedTwist) { - let mems = self.mems.clone(); - - let (init, twist_events, _mems, shout_events) = - (self.init, self.twist_events, self.mems, self.shout_events); - - let traced_shout = TracedShout { init }; - let traced_twist = TracedTwist { - twist_events: twist_events.clone(), - }; - - let constraints = TSMemoryConstraints { - cs: None, - mems, - shout_events, - twist_events, - shout_bindings: BTreeMap::new(), - partial_twist_bindings: BTreeMap::new(), - step_events_shout: vec![], - step_events_twist: vec![], - is_first_step: true, - write_lanes: BTreeMap::new(), - read_lanes: BTreeMap::new(), - }; - - (constraints, traced_shout, traced_twist) - } -} - -pub struct TracedShout { - pub init: BTreeMap, Vec>, -} - -impl Shout for TracedShout { - fn lookup(&mut self, shout_id: neo_vm_trace::ShoutId, key: u64) -> u64 { - let value = self - .init - .get(&Address { - tag: shout_id.0 as u64, - addr: key, - }) - .unwrap() - .clone(); - - value[0].into_bigint().as_ref()[0] - } -} - -pub struct TracedTwist { - pub twist_events: BTreeMap, VecDeque>, -} - -impl Twist for TracedTwist { - fn load(&mut self, id: TwistId, addr: u64) -> u64 { - let address = Address { - tag: id.0 as u64, - addr, - }; - - let event = self - .twist_events - .get_mut(&address) - .unwrap() - .pop_front() - .unwrap(); - - assert_eq!(event.op, TwistOpKind::Read); - event.val - } - - fn store(&mut self, id: TwistId, addr: u64, val: u64) { - let address = Address { - tag: id.0 as u64, - addr, - }; - let event = self - .twist_events - .get_mut(&address) - .unwrap() - .pop_front() - .unwrap(); - - assert_eq!(event.op, TwistOpKind::Write); - assert_eq!(event.val, val); - } -} - -pub struct TSMemoryConstraints { - pub(crate) cs: Option>, - - pub(crate) mems: BTreeMap, - - pub(crate) shout_events: BTreeMap, VecDeque>, - pub(crate) twist_events: BTreeMap, VecDeque>, - - pub(crate) shout_bindings: BTreeMap>, - pub(crate) partial_twist_bindings: BTreeMap>, - - step_events_shout: Vec, - step_events_twist: Vec, - - is_first_step: bool, - - write_lanes: BTreeMap, - read_lanes: BTreeMap, -} - -impl TSMemoryConstraints { - pub fn ts_mem_layouts(&self) -> TSMemLayouts { - let mut twist_bindings = BTreeMap::new(); - - for (tag, partials) in &self.partial_twist_bindings { - let mut complete = Vec::new(); - for p in partials { - complete.push(p.to_complete()); - } - - twist_bindings.insert(*tag, complete); - } - - TSMemLayouts { - shout_bindings: self.shout_bindings.clone(), - twist_bindings, - } - } - - pub fn get_shout_traced_values( - &mut self, - address: &Address, - ) -> Result<(F, F, F), SynthesisError> { - let (has_lookup_val, addr_val, val_val) = { - let event = self - .shout_events - .get_mut(address) - .unwrap() - .pop_front() - .unwrap(); - - self.step_events_shout.push(event.clone()); - - (F::from(1), F::from(event.key), F::from(event.value)) - }; - - Ok((has_lookup_val, addr_val, val_val)) - } - - pub fn get_twist_traced_values( - &mut self, - address: &Address, - lane: u32, - kind: TwistOpKind, - ) -> Result<(F, F, F), SynthesisError> { - let (ra, rv) = { - let queue = self.twist_events.get_mut(address).unwrap_or_else(|| { - panic!( - "missing twist events for address {:?} kind {:?} lane {}", - address, kind, lane - ) - }); - let mut event = queue.pop_front().unwrap_or_else(|| { - panic!( - "empty twist event queue for address {:?} kind {:?} lane {}", - address, kind, lane - ) - }); - - event.lane.replace(lane); - - assert_eq!(event.op, kind); - - self.step_events_twist.push(event.clone()); - - (F::from(event.addr), F::from(event.val)) - }; - - Ok((F::one(), ra, rv)) - } -} - -impl TSMemoryConstraints { - fn get_next_read_lane(&mut self, twist_id: u64) -> u32 { - *self - .read_lanes - .entry(twist_id.try_into().unwrap()) - .and_modify(|l| *l += 1) - .or_insert(0) - } - - fn get_next_write_lane(&mut self, twist_id: u64) -> u32 { - *self - .write_lanes - .entry(twist_id.try_into().unwrap()) - .and_modify(|l| *l += 1) - .or_insert(0) - } - - fn update_partial_twist_bindings_read(&mut self, tag: u64, base_index: usize, lane: usize) { - let bindings = self.partial_twist_bindings.entry(tag).or_default(); - - while bindings.len() <= lane { - bindings.push(PartialTwistCpuBinding::default()); - } - - let b = &mut bindings[lane]; - b.has_read = Some(base_index); - b.ra = Some(base_index + 1); - b.rv = Some(base_index + 2); - } - - fn update_partial_twist_bindings_write(&mut self, tag: u64, base_index: usize, lane: usize) { - let bindings = self.partial_twist_bindings.entry(tag).or_default(); - - while bindings.len() <= lane { - bindings.push(PartialTwistCpuBinding::default()); - } - - let b = &mut bindings[lane]; - b.has_write = Some(base_index); - b.wa = Some(base_index + 1); - b.wv = Some(base_index + 2); - } - - fn conditional_read_rom( - &mut self, - cond: &Boolean, - address: &AllocatedAddress, - ) -> Result>, SynthesisError> { - let address_val = Address { - addr: address.address_value(), - tag: address.tag_value(), - }; - - let cs = self.get_cs(); - - let base_index = cs.num_witness_variables(); - - let (has_lookup_val, addr_witness_val, val_witness_val) = if cond.value()? { - self.get_shout_traced_values(&address_val)? - } else { - (F::ZERO, F::from(address.address_value()), F::ZERO) - }; - - let has_lookup = FpVar::new_witness(cs.clone(), || Ok(has_lookup_val))?; - let addr_witness = FpVar::new_witness(cs.clone(), || Ok(addr_witness_val))?; - let val_witness = FpVar::new_witness(cs.clone(), || Ok(val_witness_val))?; - - let tag = address.tag_value(); - - if let Some(&(_, _lanes, MemType::Rom, _)) = self.mems.get(&tag) - && self.is_first_step - { - let binding = ShoutCpuBinding { - has_lookup: base_index, - addr: base_index + 1, - val: base_index + 2, - }; - self.shout_bindings.entry(tag).or_default().push(binding); - } - - FpVar::from(cond.clone()).enforce_equal(&has_lookup)?; - - let addr_fp = FpVar::new_witness(self.get_cs(), || Ok(F::from(address.address_value())))?; - addr_witness.enforce_equal(&addr_fp)?; - - Ok(vec![val_witness]) - } - - fn conditional_read_ram( - &mut self, - cond: &Boolean, - address: &AllocatedAddress, - ) -> Result>, SynthesisError> { - let twist_id = address.tag_value(); - let address_val = Address { - addr: address.address_value(), - tag: twist_id, - }; - - let cs = self.get_cs(); - let base_index = cs.num_witness_variables(); - - let cond_val = cond.value()?; - - let lane = self.get_next_read_lane(twist_id); - - let (has_read_val, ra_val, rv_val) = if cond_val { - self.get_twist_traced_values(&address_val, lane, TwistOpKind::Read)? - } else { - self.step_events_twist.push(TwistEvent { - twist_id: twist_id as u32, - addr: 0, - val: 0, - op: TwistOpKind::Write, - cond: cond_val, - lane: Some(lane), - }); - - (F::ZERO, F::ZERO, F::ZERO) - }; - - let has_read = FpVar::new_witness(cs.clone(), || Ok(has_read_val))?; - let ra = FpVar::new_witness(cs.clone(), || Ok(ra_val))?; - let rv = FpVar::new_witness(cs.clone(), || Ok(rv_val))?; - - assert_eq!(cs.num_witness_variables(), base_index + 3); - - let tag = address.tag_value(); - - if let Some(&(_, _lanes, MemType::Ram, _)) = self.mems.get(&tag) - && self.is_first_step - { - self.update_partial_twist_bindings_read(tag, base_index, lane as usize); - } - - FpVar::from(cond.clone()).enforce_equal(&has_read)?; - - let addr_fp = FpVar::new_witness(self.get_cs(), || Ok(F::from(address.address_value())))?; - let addr_constraint = cond.select(&addr_fp, &FpVar::zero())?; - ra.enforce_equal(&addr_constraint)?; - - Ok(vec![rv]) - } -} - -impl IVCMemoryAllocated for TSMemoryConstraints { - type FinishStepPayload = (Vec, Vec); - - fn start_step(&mut self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - self.cs.replace(cs.clone()); - - Ok(()) - } - - fn finish_step( - &mut self, - _is_last_step: bool, - ) -> Result { - self.cs = None; - - let mut step_events_shout = vec![]; - std::mem::swap(&mut step_events_shout, &mut self.step_events_shout); - - let mut step_events_twist = vec![]; - std::mem::swap(&mut step_events_twist, &mut self.step_events_twist); - - self.is_first_step = false; - - self.read_lanes.clear(); - self.write_lanes.clear(); - - Ok((step_events_shout, step_events_twist)) - } - - fn get_cs(&self) -> ConstraintSystemRef { - self.cs.as_ref().unwrap().clone() - } - - #[tracing::instrument(target = "gr1cs", skip_all)] - fn conditional_read( - &mut self, - cond: &Boolean, - address: &AllocatedAddress, - ) -> Result>, SynthesisError> { - let _guard = tracing::debug_span!("conditional_read").entered(); - - let mem = self.mems.get(&address.tag_value()).copied().unwrap(); - - if mem.2 == MemType::Rom { - self.conditional_read_rom(cond, address) - } else { - self.conditional_read_ram(cond, address) - } - } - - #[tracing::instrument(target = "gr1cs", skip_all)] - fn conditional_write( - &mut self, - cond: &Boolean, - address: &AllocatedAddress, - vals: &[FpVar], - ) -> Result<(), SynthesisError> { - let _guard = tracing::debug_span!("conditional_write").entered(); - - let mem_tag = address.tag_value(); - let mem = self.mems.get(&mem_tag).copied().unwrap(); - - if mem.2 != MemType::Ram { - unreachable!("can't write to Rom memory"); - } - - if cond.value().unwrap() { - assert_eq!( - mem.0 as usize, - vals.len(), - "write doesn't match mem value size" - ); - } - - let twist_id = address.tag_value(); - let address_cpu = Address { - addr: address.address_value(), - tag: twist_id, - }; - - let cs = self.get_cs(); - let base_index = cs.num_witness_variables(); - - let cond_val = cond.value()?; - - let lane = self.get_next_write_lane(twist_id); - - let (has_write_val, wa_val, wv_val) = if cond_val { - self.get_twist_traced_values(&address_cpu, lane, TwistOpKind::Write)? - } else { - self.step_events_twist.push(TwistEvent { - twist_id: twist_id as u32, - addr: 0, - val: 0, - op: TwistOpKind::Write, - cond: cond_val, - lane: Some(lane), - }); - - ( - F::ZERO, - F::from(address.address_value()), - vals[0].value().unwrap_or(F::ZERO), - ) - }; - - let has_write = FpVar::new_witness(cs.clone(), || Ok(has_write_val))?; - let wa = FpVar::new_witness(cs.clone(), || Ok(wa_val))?; - let wv = FpVar::new_witness(cs.clone(), || Ok(wv_val))?; - - if self.is_first_step { - self.update_partial_twist_bindings_write(mem_tag, base_index, lane as usize); - } - - FpVar::from(cond.clone()).enforce_equal(&has_write)?; - - let addr_fp = FpVar::new_witness(self.get_cs(), || Ok(F::from(address.address_value())))?; - wa.enforce_equal(&addr_fp)?; - wv.enforce_equal(&vals[0])?; - - Ok(()) - } -} diff --git a/interleaving/starstream-interleaving-proof/src/neo.rs b/interleaving/starstream-interleaving-proof/src/neo.rs deleted file mode 100644 index be3cda1a..00000000 --- a/interleaving/starstream-interleaving-proof/src/neo.rs +++ /dev/null @@ -1,498 +0,0 @@ -use crate::{ - ccs_step_shape, - circuit::{InterRoundWires, IvcWireLayout, StepCircuitBuilder}, - memory::twist_and_shout::{TSMemInitTables, TSMemLayouts, TSMemory, TSMemoryConstraints}, -}; -use ark_ff::PrimeField; -use ark_goldilocks::FpGoldilocks; -use ark_relations::gr1cs::{ConstraintSystem, OptimizationGoal, SynthesisError}; -use neo_fold::session::{NeoCircuit, WitnessLayout}; -use neo_memory::{ShoutCpuBinding, TwistCpuBinding}; -use neo_vm_trace::{Shout, StepTrace, Twist, VmCpu}; -use p3_field::PrimeCharacteristicRing; -use std::collections::HashMap; - -// TODO: benchmark properly -pub(crate) const CHUNK_SIZE: usize = 40; -const PER_STEP_COLS: usize = 1248; -const BASE_INSTANCE_COLS: usize = 1; -const EXTRA_INSTANCE_COLS: usize = IvcWireLayout::FIELD_COUNT * 2; -const M_IN: usize = BASE_INSTANCE_COLS + EXTRA_INSTANCE_COLS; -const USED_COLS: usize = M_IN + (PER_STEP_COLS - BASE_INSTANCE_COLS) * CHUNK_SIZE; - -pub(crate) struct StepCircuitNeo { - pub(crate) matrices: Vec>>, - pub(crate) num_constraints: usize, - pub(crate) ts_mem_spec: TSMemLayouts, - pub(crate) ts_mem_init: TSMemInitTables, - pub(crate) ivc_layout: crate::circuit::IvcWireLayout, -} - -impl StepCircuitNeo { - pub fn new(ts_mem_init: TSMemInitTables) -> Self { - let (ark_cs, ts_mem_spec, ivc_layout) = ccs_step_shape().unwrap(); - - let num_constraints = ark_cs.num_constraints(); - let num_instance_variables = ark_cs.num_instance_variables(); - let num_variables = ark_cs.num_variables(); - - tracing::info!("num constraints {}", num_constraints); - tracing::info!("num instance variables {}", num_instance_variables); - tracing::info!("num variables {}", num_variables); - - assert_eq!(num_variables, PER_STEP_COLS); - - let matrices = ark_cs - .into_inner() - .unwrap() - .to_matrices() - .unwrap() - .remove("R1CS") - .unwrap(); - - Self { - matrices, - num_constraints, - ts_mem_spec, - ts_mem_init, - ivc_layout, - } - } - - fn get_mem_content_iter<'a>( - &'a self, - tag: &'a u64, - ) -> impl Iterator + 'a { - self.ts_mem_init - .init - .get(tag) - .into_iter() - .flat_map(|content| content.iter()) - .map(|(addr, val)| (*addr, ark_field_to_p3_goldilocks(val))) - } -} - -pub(crate) fn ivc_step_linking_pairs() -> Vec<(usize, usize)> { - // Per-step instance vector is [1, inputs..., outputs...]. - // Enforce prev outputs == next inputs. - let input_base = BASE_INSTANCE_COLS; - let output_base = BASE_INSTANCE_COLS + IvcWireLayout::FIELD_COUNT; - (0..IvcWireLayout::FIELD_COUNT) - .map(|i| (output_base + i, input_base + i)) - .collect() -} - -#[derive(Clone)] -pub struct CircuitLayout {} - -impl WitnessLayout for CircuitLayout { - // instance.len() - const M_IN: usize = M_IN; - - // instance.len()+witness.len() - const USED_COLS: usize = USED_COLS; - - fn new_layout() -> Self { - CircuitLayout {} - } -} - -impl NeoCircuit for StepCircuitNeo { - type Layout = CircuitLayout; - - fn chunk_size(&self) -> usize { - CHUNK_SIZE - } - - fn const_one_col(&self, _layout: &Self::Layout) -> usize { - 0 - } - - fn resources(&self, resources: &mut neo_fold::session::SharedBusResources) { - for (tag, (_dims, lanes, ty, _)) in &self.ts_mem_init.mems { - match ty { - crate::memory::MemType::Rom => { - let size = self - .ts_mem_init - .rom_sizes - .get(tag) - .copied() - // it can't be empty - .unwrap_or(1usize); - - let mut dense_content = vec![neo_math::F::ZERO; size]; - - for (addr, val) in self.get_mem_content_iter(tag) { - dense_content[addr as usize] = val; - } - - resources - .shout(*tag as u32) - .lanes(lanes.0) - .padded_binary_table(dense_content); - } - crate::memory::MemType::Ram => { - let twist_id = *tag as u32; - let k = 256usize; // TODO: hardcoded number - assert!(k > 0, "set_binary_mem_layout: k must be > 0"); - assert!( - k.is_power_of_two(), - "set_binary_mem_layout: k must be a power of two" - ); - resources - .twist(twist_id) - .layout(neo_memory::PlainMemLayout { - k, - d: k.trailing_zeros() as usize, - n_side: 2, - lanes: lanes.0, - }) - .init(self.get_mem_content_iter(tag)); - } - } - } - } - - fn define_cpu_constraints( - &self, - cs: &mut neo_fold::session::CcsBuilder, - _layout: &Self::Layout, - ) -> Result<(), String> { - let matrices = &self.matrices; - let m_in = ::M_IN; - let base_m_in = BASE_INSTANCE_COLS; - - for ((matrix_a, matrix_b), matrix_c) in matrices[0] - .iter() - .zip(&matrices[1]) - .zip(&matrices[2]) - .take(self.num_constraints) - { - let a_row = ark_matrix_to_neo(matrix_a); - let b_row = ark_matrix_to_neo(matrix_b); - let c_row = ark_matrix_to_neo(matrix_c); - - for j in 0..CHUNK_SIZE { - let map_idx = |idx: usize| { - if idx < base_m_in { - idx - } else { - // NOTE: m_in includes the per folding step IVC - // variables (inputs and outputs) - m_in + (idx - base_m_in) * CHUNK_SIZE + j - } - }; - - let a_row: Vec<_> = a_row.iter().map(|(i, v)| (map_idx(*i), *v)).collect(); - let b_row: Vec<_> = b_row.iter().map(|(i, v)| (map_idx(*i), *v)).collect(); - let c_row: Vec<_> = c_row.iter().map(|(i, v)| (map_idx(*i), *v)).collect(); - - cs.r1cs_terms(a_row, b_row, c_row); - } - } - - let one = neo_math::F::ONE; - let minus_one = -neo_math::F::ONE; - let input_base = BASE_INSTANCE_COLS; - let output_base = BASE_INSTANCE_COLS + IvcWireLayout::FIELD_COUNT; - - for (field_offset, (&input_idx, &output_idx)) in self - .ivc_layout - .input_indices() - .iter() - .zip(self.ivc_layout.output_indices().iter()) - .enumerate() - { - for j in 0..(CHUNK_SIZE - 1) { - // The chunked matrix is interleaved column-major: - // for variable x, step 0 is at `m_in + x * CHUNK_SIZE`, step 1 is at - // `m_in + x * CHUNK_SIZE + 1`, etc. So `x * CHUNK_SIZE` picks the - // contiguous block for that variable, and `j` selects the step. - // - // We enforce continuity inside a chunk: - // wire[in][s+1] == wire[out][s] - let out_col = m_in + output_idx * CHUNK_SIZE + j; - let in_col = m_in + input_idx * CHUNK_SIZE + (j + 1); - cs.r1cs_terms( - vec![(out_col, one), (in_col, minus_one)], - vec![(0, one)], - vec![], - ); - } - - let first_chunk_in_col = m_in + input_idx * CHUNK_SIZE; - let last_chunk_out_col = m_in + output_idx * CHUNK_SIZE + (CHUNK_SIZE - 1); - - // The per-folding-step public instance is: - // [1, inputs, outputs] - // We wire the first chunk input to the instance inputs... - let in_instance_col = input_base + field_offset; - // ...and the last chunk output to the instance outputs. - let out_instance_col = output_base + field_offset; - - // This means step_linking in the IVC setup should link pairs: - // (output_base + i, input_base + i) - - cs.r1cs_terms( - vec![(in_instance_col, one), (first_chunk_in_col, minus_one)], - vec![(0, one)], - vec![], - ); - cs.r1cs_terms( - vec![(out_instance_col, one), (last_chunk_out_col, minus_one)], - vec![(0, one)], - vec![], - ); - } - - Ok(()) - } - - fn build_witness_prefix( - &self, - _layout: &Self::Layout, - chunk: &[StepTrace], - ) -> Result, String> { - if chunk.len() != CHUNK_SIZE { - return Err(format!( - "chunk len {} != CHUNK_SIZE {}", - chunk.len(), - CHUNK_SIZE - )); - } - - let m_in = ::M_IN; - let base_m_in = BASE_INSTANCE_COLS; - let per_step_cols = chunk[0].regs_after.len(); - if per_step_cols != PER_STEP_COLS { - return Err(format!( - "per-step witness len {} != PER_STEP_COLS {}", - per_step_cols, PER_STEP_COLS - )); - } - - let mut witness = vec![neo_math::F::ZERO; USED_COLS]; - - witness[0] = neo_math::F::from_u64(chunk[0].regs_after[0]); - - let input_base = BASE_INSTANCE_COLS; - let output_base = BASE_INSTANCE_COLS + IvcWireLayout::FIELD_COUNT; - let last_step = chunk.len() - 1; - - for (field_idx, (&input_idx, &output_idx)) in self - .ivc_layout - .input_indices() - .iter() - .zip(self.ivc_layout.output_indices().iter()) - .enumerate() - { - let input_full_idx = base_m_in + input_idx; - let output_full_idx = base_m_in + output_idx; - witness[input_base + field_idx] = - neo_math::F::from_u64(chunk[0].regs_after[input_full_idx]); - witness[output_base + field_idx] = - neo_math::F::from_u64(chunk[last_step].regs_after[output_full_idx]); - } - - for (j, step) in chunk.iter().enumerate() { - for i in base_m_in..per_step_cols { - let idx = m_in + (i - base_m_in) * CHUNK_SIZE + j; - witness[idx] = neo_math::F::from_u64(step.regs_after[i]); - } - } - - Ok(witness) - } - - fn cpu_bindings( - &self, - _layout: &Self::Layout, - ) -> Result< - ( - HashMap>, - HashMap>, - ), - String, - > { - let m_in = ::M_IN; - let map_idx = |witness_idx: usize| m_in + witness_idx * CHUNK_SIZE; - - let mut shout_map: HashMap> = HashMap::new(); - - for (tag, layouts) in &self.ts_mem_spec.shout_bindings { - for layout in layouts { - let entry = shout_map.entry(*tag as u32).or_default(); - entry.push(ShoutCpuBinding { - has_lookup: map_idx(layout.has_lookup), - addr: Some(map_idx(layout.addr)), - val: map_idx(layout.val), - }); - } - } - - let mut twist_map: HashMap> = HashMap::new(); - - for (tag, layouts) in &self.ts_mem_spec.twist_bindings { - for layout in layouts { - let entry = twist_map.entry(*tag as u32).or_default(); - entry.push(TwistCpuBinding { - read_addr: map_idx(layout.ra), - has_read: map_idx(layout.has_read), - rv: map_idx(layout.rv), - write_addr: map_idx(layout.wa), - has_write: map_idx(layout.has_write), - wv: map_idx(layout.wv), - inc: None, - }); - } - } - - Ok((shout_map, twist_map)) - } -} - -fn ark_matrix_to_neo(sparse_row: &[(FpGoldilocks, usize)]) -> Vec<(usize, neo_math::F)> { - let mut row = vec![]; - - for (col_v, col_i) in sparse_row.iter() { - row.push((*col_i, ark_field_to_p3_goldilocks(col_v))); - } - - row -} - -pub struct StarstreamVm { - step_circuit_builder: StepCircuitBuilder>, - step_i: usize, - mem: TSMemoryConstraints, - irw: InterRoundWires, - regs: Vec, -} - -impl StarstreamVm { - pub fn new( - step_circuit_builder: StepCircuitBuilder>, - mem: TSMemoryConstraints, - ) -> Self { - let irw = InterRoundWires::new(step_circuit_builder.instance.entrypoint.0 as u64); - - Self { - step_circuit_builder, - step_i: 0, - mem, - irw, - regs: vec![0; PER_STEP_COLS], - } - } -} - -impl VmCpu for StarstreamVm { - type Error = SynthesisError; - - fn snapshot_regs(&self) -> Vec { - self.regs.clone() - } - - fn pc(&self) -> u64 { - self.step_i as u64 - } - - fn halted(&self) -> bool { - self.step_i == self.step_circuit_builder.ops.len() - } - - fn step( - &mut self, - twist: &mut T, - shout: &mut S, - ) -> Result, Self::Error> - where - T: Twist, - S: Shout, - { - let cs = ConstraintSystem::::new_ref(); - cs.set_optimization_goal(OptimizationGoal::Constraints); - - let (irw, (shout_events, twist_events), _ivc_layout) = - self.step_circuit_builder.make_step_circuit( - self.step_i, - &mut self.mem, - cs.clone(), - self.irw.clone(), - false, - )?; - - if let Some(unsat) = cs.which_is_unsatisfied().unwrap() { - tracing::error!(location = unsat, "step CCS is unsat"); - } - - assert!(cs.is_satisfied().unwrap()); - - self.irw = irw; - - self.step_i += 1; - - self.regs = cs - .instance_assignment()? - .into_iter() - .map(|input| input.into_bigint().0[0]) - .chain( - cs.witness_assignment()? - .into_iter() - .map(|wit| wit.into_bigint().0[0]), - ) - .collect(); - - for event in shout_events { - assert_eq!( - shout.lookup(neo_vm_trace::ShoutId(event.shout_id), event.key), - event.value - ); - } - - for event in twist_events { - match event.op { - neo_vm_trace::TwistOpKind::Read => { - assert_eq!( - twist.load_if_lane( - event.cond, - neo_vm_trace::TwistId(event.twist_id), - event.addr, - event.val, - event.lane.unwrap(), - ), - event.val, - ); - } - neo_vm_trace::TwistOpKind::Write => { - twist.store_if_lane( - event.cond, - neo_vm_trace::TwistId(event.twist_id), - event.addr, - event.val, - event.lane.unwrap(), - ); - } - } - } - - Ok(neo_vm_trace::StepMeta { - pc_after: self.step_i as u64, - opcode: 0, - }) - } -} - -pub fn ark_field_to_p3_goldilocks(v: &FpGoldilocks) -> p3_goldilocks::Goldilocks { - let original_u64 = v.into_bigint().0[0]; - let result = neo_math::F::from_u64(original_u64); - - // Assert that we can convert back and get the same element - let converted_back = FpGoldilocks::from(original_u64); - assert_eq!( - *v, converted_back, - "Field element conversion is not reversible" - ); - - result -} diff --git a/interleaving/starstream-interleaving-spec/Cargo.toml b/interleaving/starstream-interleaving-spec/Cargo.toml index 3344556d..4c2f1557 100644 --- a/interleaving/starstream-interleaving-spec/Cargo.toml +++ b/interleaving/starstream-interleaving-spec/Cargo.toml @@ -10,12 +10,8 @@ thiserror = "2.0.17" ark-ff = { version = "0.5.0", default-features = false } ark-goldilocks = { path = "../../ark-goldilocks" } -# TODO: move to workspace deps -neo-fold = { workspace = true } -neo-math = { workspace = true } -neo-ccs = { workspace = true } -neo-ajtai = { workspace = true } -neo-memory = { workspace = true } - -p3-field = "0.4.1" +p3-field = "0.5.1" ark-poseidon2 = { path = "../../ark-poseidon2" } + +neo-fold-clean = { workspace = true } +neo-math = { workspace = true } diff --git a/interleaving/starstream-interleaving-spec/src/lib.rs b/interleaving/starstream-interleaving-spec/src/lib.rs index a3909bd7..0ef6e2fc 100644 --- a/interleaving/starstream-interleaving-spec/src/lib.rs +++ b/interleaving/starstream-interleaving-spec/src/lib.rs @@ -13,8 +13,6 @@ pub use crate::{ transaction_effects::ProcessId, }; use imbl::{HashMap, HashSet}; -use neo_ajtai::Commitment; -use p3_field::PrimeCharacteristicRing; use std::{hash::Hasher, marker::PhantomData}; pub use transaction_effects::{ FunctionId, InterfaceId, @@ -56,10 +54,6 @@ impl Value { } } -fn encode_hash_to_fields(hash: Hash) -> [neo_math::F; 4] { - hash.0.map(neo_math::F::from_u64) -} - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Ref(pub u64); @@ -92,22 +86,40 @@ impl CoroutineState { } } -// pub struct ZkTransactionProof {} - pub enum ZkTransactionProof { - NeoProof { - // does the verifier need this? - session: Box>, - proof: neo_fold::shard::ShardProof, - mcss_public: Vec>, - steps_public: Vec>, - // TODO: this shouldn't be here I think, the ccs should be known somehow by - // the verifier - ccs: neo_ccs::CcsStructure, + /// Folded interleaving proof. The verifier derives preprocessing from the + /// public shape and [`FOLD_SEED`]. + Neo { + r1cs: Box, + plan: Box, + audit: Box, }, Dummy, } +/// Seed for deterministic folding preprocessing. +pub const FOLD_SEED: u64 = 0x5742_0001; + +/// Must match the proof crate's `IvcWireLayout::FIELD_COUNT`. +pub const IVC_SEMANTIC_FIELD_COUNT: usize = 7; + +/// Initial program-state digest derived from the public instance. +pub fn expected_initial_semantic_state_anchor(inst: &InterleavingInstance) -> [u8; 32] { + use neo_fold_clean::frontends::f_prime::recursive_plan::build_semantic_state_preimage_fields; + use neo_fold_clean::paper::digest::digest_fields_as_digest32; + use neo_fold_clean::paper::f_prime::poseidon_trace::encode_poseidon_trace; + use neo_math::F as NeoF; + use p3_field::PrimeCharacteristicRing as _; + + let mut app_state = vec![NeoF::from_u64(0); IVC_SEMANTIC_FIELD_COUNT]; + // input_indices()[0] is id_curr; the rest of the initial state is zero. + app_state[0] = NeoF::from_u64(inst.entrypoint.0 as u64); + + digest_fields_as_digest32( + encode_poseidon_trace(&build_semantic_state_preimage_fields(&app_state)).digest_native, + ) +} + impl ZkTransactionProof { pub fn verify( &self, @@ -115,97 +127,25 @@ impl ZkTransactionProof { wit: &InterleavingWitness, ) -> Result<(), VerificationError> { match self { - ZkTransactionProof::NeoProof { - session, - proof, - mcss_public, - steps_public, - ccs, - } => { - let output_binding_config = inst.output_binding_config(); - - let ok = session - .verify_with_output_binding_simple( - ccs, - mcss_public, - proof, - &output_binding_config, - ) - .expect("verify should run"); - - assert!(ok, "optimized verification should pass"); - - // dbg!(&self.steps_public[0].lut_insts[0].table); - - // NOTE: the indices in steps_public match the memory initializations - // ordered by MemoryTag in the circuit - let mut expected_fields = Vec::with_capacity(inst.process_table.len() * 4); - for hash in &inst.process_table { - let hash_fields = encode_hash_to_fields(*hash); - expected_fields.extend(hash_fields.iter().copied()); + ZkTransactionProof::Neo { r1cs, plan, audit } => { + let expected_anchor = expected_initial_semantic_state_anchor(inst); + if plan + .state_x_out + .as_ref() + .and_then(|s| s.initial_semantic_state_digest_anchor) + != Some(expected_anchor) + { + return Err(VerificationError::FoldVerification( + "initial-state digest anchor doesn't match the instance".into(), + )); } - // TODO: review if this is correct, I think all ROM's need to be - // of the same size, so we have some extra padding. - // - // we may need to check the length or something as a new check, - // or maybe try to just use a sparse definition? - let process_table = &steps_public[0].lut_insts - [RomMemoryTag::ProcessTable.lut_index()] - .table[0..expected_fields.len()]; - assert!( - expected_fields - .iter() - .zip(process_table.iter()) - .all(|(expected, found)| *expected == *found), - "program hash table mismatch" - ); - - assert!( - inst.must_burn - .iter() - .zip( - steps_public[0].lut_insts[RomMemoryTag::MustBurn.lut_index()] - .table - .iter(), - ) - .all(|(expected, found)| { - neo_math::F::from_u64(if *expected { 1 } else { 0 }) == *found - }), - "must burn table mismatch" - ); - - assert!( - inst.is_utxo - .iter() - .zip( - steps_public[0].lut_insts[RomMemoryTag::IsUtxo.lut_index()] - .table - .iter(), - ) - .all(|(expected, found)| { - neo_math::F::from_u64(if *expected { 1 } else { 0 }) == *found - }), - "is_utxo table mismatch" - ); - - assert!( - inst.is_token - .iter() - .zip( - steps_public[0].lut_insts[RomMemoryTag::IsToken.lut_index()] - .table - .iter(), - ) - .all(|(expected, found)| { - neo_math::F::from_u64(if *expected { 1 } else { 0 }) == *found - }), - "is_token table mismatch" - ); - - // TODO: check interfaces? but I think this can be private - // dbg!(&self.steps_public[0].lut_insts[4].table); - - // dbg!(&steps_public[0].mcs_inst.x); + + let prep = neo_fold_clean::frontends::r1cs_f_prime::preprocess_sparse_seeded( + r1cs, plan, FOLD_SEED, + ) + .map_err(|e| VerificationError::FoldVerification(format!("preprocess: {e:?}")))?; + neo_fold_clean::verify_uncompressed_audit(&prep.prep, audit) + .map_err(|e| VerificationError::FoldVerification(format!("{e:?}")))?; } ZkTransactionProof::Dummy => {} } @@ -269,6 +209,8 @@ pub enum VerificationError { OwnerHasNoStableIdentity, #[error("Interleaving proof error: {0}")] InterleavingProofError(#[from] mocked_verifier::InterleavingError), + #[error("Interleaving fold verification failed: {0}")] + FoldVerification(String), #[error("Transaction input not found")] InputNotFound, #[error("Invalid token storage shape: expected {expected}, got {actual}")] diff --git a/interleaving/starstream-interleaving-spec/src/memory_tags.rs b/interleaving/starstream-interleaving-spec/src/memory_tags.rs index 7124d79b..acac8f2d 100644 --- a/interleaving/starstream-interleaving-spec/src/memory_tags.rs +++ b/interleaving/starstream-interleaving-spec/src/memory_tags.rs @@ -8,12 +8,6 @@ pub enum RomMemoryTag { IsToken = 4, } -impl RomMemoryTag { - pub const fn lut_index(self) -> usize { - self as usize - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u64)] pub enum RamMemoryTag { @@ -38,9 +32,3 @@ pub enum RamMemoryTag { MustEnter = 18, MustExit = 19, } - -impl RamMemoryTag { - pub const fn mem_index(self) -> usize { - self as usize - } -} diff --git a/interleaving/starstream-interleaving-spec/src/transaction_effects/instance.rs b/interleaving/starstream-interleaving-spec/src/transaction_effects/instance.rs index b72d6779..eac20bf8 100644 --- a/interleaving/starstream-interleaving-spec/src/transaction_effects/instance.rs +++ b/interleaving/starstream-interleaving-spec/src/transaction_effects/instance.rs @@ -1,10 +1,5 @@ -use ark_ff::PrimeField as _; -use neo_fold::output_binding::OutputBindingConfig; -use neo_memory::ProgramIO; -use p3_field::PrimeCharacteristicRing; - use crate::{ - CoroutineState, Hash, RamMemoryTag, WasmModule, mocked_verifier::InterleavingError, + CoroutineState, Hash, WasmModule, mocked_verifier::InterleavingError, mocked_verifier::LedgerEffectsCommitment, transaction_effects::ProcessId, }; @@ -96,27 +91,4 @@ impl InterleavingInstance { Ok(()) } - - pub fn output_binding_config(&self) -> OutputBindingConfig { - let mut program_io = ProgramIO::new(); - - let mut addr = 0; - for comm in self.host_calls_roots.iter() { - for v in comm.0 { - program_io = - program_io.with_output(addr, neo_math::F::from_u64(v.into_bigint().0[0])); - addr += 1; - } - } - - let num_bits = 8; - // currently the twist tables have a size of 256, so 2**8 == 256 - // - // need to figure out if that can be generalized, or if we need a bound or not - - // TraceCommitments RAM index in the sorted twist_id list (see proof MemoryTag ordering). - // - OutputBindingConfig::new(num_bits, program_io) - .with_mem_idx(RamMemoryTag::TraceCommitments.mem_index()) - } } diff --git a/interleaving/starstream-runtime/src/lib.rs b/interleaving/starstream-runtime/src/lib.rs index 585d9a89..57f7622b 100644 --- a/interleaving/starstream-runtime/src/lib.rs +++ b/interleaving/starstream-runtime/src/lib.rs @@ -9,6 +9,7 @@ use starstream_interleaving_spec::{ }; use starstream_interleaving_spec::{REF_GET_WIDTH, REF_PUSH_WIDTH, REF_WRITE_WIDTH}; use std::collections::{HashMap, HashSet}; +use std::num::NonZeroUsize; use wasmtime::component::{ Component, Instance as ComponentInstance, Linker as ComponentLinker, Val as ComponentVal, }; @@ -928,10 +929,23 @@ impl UnprovenTransaction { } pub fn prove(self) -> Result { + self.prove_with_batch_size( + NonZeroUsize::new(starstream_interleaving_proof::DEFAULT_BATCH_SIZE).unwrap(), + ) + } + + pub fn prove_with_batch_size( + self, + batch_size: NonZeroUsize, + ) -> Result { let (instance, state, witness) = self.execute()?; - let proof = starstream_interleaving_proof::prove(instance.clone(), witness.clone()) - .map_err(|e| Error::RuntimeError(e.to_string()))?; + let proof = starstream_interleaving_proof::prove_with_batch_size( + instance.clone(), + witness.clone(), + batch_size, + ) + .map_err(|e| Error::RuntimeError(e.to_string()))?; trace_mermaid::emit_trace_mermaid(&instance, &state); diff --git a/interleaving/starstream-runtime/tests/test_dex_swap_flow.rs b/interleaving/starstream-runtime/tests/test_dex_swap_flow.rs index 2bd8a438..6586abdc 100644 --- a/interleaving/starstream-runtime/tests/test_dex_swap_flow.rs +++ b/interleaving/starstream-runtime/tests/test_dex_swap_flow.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroUsize; + use starstream_interleaving_spec::{CoroutineState, Ledger, UtxoId, Value}; use starstream_runtime::{ UnprovenTransaction, poseidon_program_hash, register_mermaid_default_decoder, @@ -571,7 +573,12 @@ fn test_dex_swap_flow() { entrypoint: 3, }; - let proven_init = tx_init.prove().unwrap(); + // The dex traces are large, so use a bigger interleaving batch size to + // amortize the per-fold recursion overhead (a pure performance knob — it + // doesn't affect verification). + let proven_init = tx_init + .prove_with_batch_size(NonZeroUsize::new(8).unwrap()) + .unwrap(); let mut ledger = Ledger::new(); ledger = ledger.apply_transaction(&proven_init).unwrap(); @@ -608,7 +615,9 @@ fn test_dex_swap_flow() { entrypoint: 3, }; - let proven_swap = tx_swap.prove().unwrap(); + let proven_swap = tx_swap + .prove_with_batch_size(NonZeroUsize::new(8).unwrap()) + .unwrap(); ledger = ledger.apply_transaction(&proven_swap).unwrap(); assert_eq!(ledger.utxos.len(), 3); From 46ab2ca71f7807cdd45855149ec951ea3aef6470 Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:39:24 -0300 Subject: [PATCH 2/3] chore: update Cargo.lock Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> --- Cargo.lock | 498 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 384 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb5ad934..e47687ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -167,7 +178,7 @@ dependencies = [ "fnv", "hashbrown 0.15.5", "merlin", - "num-bigint", + "num-bigint 0.4.6", "sha2", ] @@ -196,7 +207,7 @@ dependencies = [ "fnv", "hashbrown 0.15.5", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "rayon", @@ -217,7 +228,7 @@ dependencies = [ "digest", "educe", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.6", "num-traits", "paste", "rayon", @@ -240,7 +251,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "num-traits", "proc-macro2", "quote", @@ -321,7 +332,7 @@ dependencies = [ "ark-std", "educe", "itertools 0.14.0", - "num-bigint", + "num-bigint 0.4.6", "num-integer", "num-traits", "tracing", @@ -350,7 +361,7 @@ dependencies = [ "ark-serialize-derive", "ark-std", "digest", - "num-bigint", + "num-bigint 0.4.6", "rayon", "serde_with", ] @@ -587,6 +598,30 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bellpepper" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae286c2cb403324ab644c7cc68dceb25fe52ca9429908a726d7ed272c1edf7b" +dependencies = [ + "bellpepper-core", + "byteorder", + "ff", +] + +[[package]] +name = "bellpepper-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8abb418570756396d722841b19edfec21d4e89e1cf8990610663040ecb1aea" +dependencies = [ + "blake2s_simd", + "byteorder", + "ff", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "bincode" version = "1.3.3" @@ -614,6 +649,18 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -624,17 +671,14 @@ dependencies = [ ] [[package]] -name = "blake3" -version = "1.8.3" +name = "blake2s_simd" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" dependencies = [ "arrayref", "arrayvec 0.7.6", - "cc", - "cfg-if", "constant_time_eq", - "cpufeatures", ] [[package]] @@ -1598,6 +1642,34 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -1610,6 +1682,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fluent-uri" version = "0.1.4" @@ -1637,6 +1719,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1767,8 +1855,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1778,11 +1868,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1828,6 +1916,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "half" version = "1.8.3" @@ -1845,6 +1944,48 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2curves" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82f3e7809d6118c2afbde9667b3ba5c7621723df3729cc2157fac39121ecb62" +dependencies = [ + "digest", + "ff", + "group", + "halo2derive", + "hex", + "lazy_static", + "libm", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "pairing", + "paste", + "plonky2_maybe_rayon", + "rand_core 0.6.4", + "rayon", + "serde", + "serde_arrays", + "sha2", + "static_assertions", + "subtle", +] + +[[package]] +name = "halo2derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d74794c1b24716c5abf5d6bfd98ee3c2d346bc67da374ba21d80adb38c336c" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1903,6 +2044,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "httparse" @@ -2149,6 +2293,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "leb128fmt" @@ -2303,6 +2450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -2319,7 +2467,7 @@ dependencies = [ [[package]] name = "neo-ajtai" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ "neo-ccs", "neo-math", @@ -2331,15 +2479,13 @@ dependencies = [ "rand_chacha 0.9.0", "rayon", "serde", - "subtle", "thiserror 2.0.17", - "tracing", ] [[package]] name = "neo-ccs" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ "neo-math", "neo-params", @@ -2350,6 +2496,7 @@ dependencies = [ "p3-poseidon2", "p3-symmetric", "rand 0.9.2", + "rand_chacha 0.10.0", "rand_chacha 0.9.0", "rayon", "serde", @@ -2358,36 +2505,33 @@ dependencies = [ ] [[package]] -name = "neo-fold" +name = "neo-fold-clean" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ - "getrandom 0.3.4", - "js-sys", + "bellpepper-core", + "ff", "neo-ajtai", "neo-ccs", "neo-math", - "neo-memory", "neo-params", "neo-reductions", "neo-transcript", - "neo-vm-trace", "p3-field", "p3-goldilocks", - "p3-matrix", - "rand_chacha 0.9.0", + "rand_chacha 0.10.0", "rayon", "serde", - "serde_json", + "spartan2", "thiserror 2.0.17", - "tracing", ] [[package]] name = "neo-math" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ + "neo-params", "p3-field", "p3-goldilocks", "p3-matrix", @@ -2397,29 +2541,10 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "neo-memory" -version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" -dependencies = [ - "neo-ajtai", - "neo-ccs", - "neo-math", - "neo-params", - "neo-reductions", - "neo-transcript", - "neo-vm-trace", - "p3-field", - "p3-goldilocks", - "p3-matrix", - "serde", - "thiserror 2.0.17", -] - [[package]] name = "neo-params" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ "serde", "thiserror 2.0.17", @@ -2428,10 +2553,9 @@ dependencies = [ [[package]] name = "neo-reductions" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ "bincode", - "blake3", "neo-ajtai", "neo-ccs", "neo-math", @@ -2444,34 +2568,25 @@ dependencies = [ "p3-poseidon2", "p3-symmetric", "rand 0.9.2", + "rand_chacha 0.10.0", "rand_chacha 0.9.0", "rayon", + "serde", "thiserror 2.0.17", ] [[package]] name = "neo-transcript" version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" dependencies = [ "neo-ccs", "neo-math", - "once_cell", "p3-field", "p3-goldilocks", "p3-symmetric", "rand 0.9.2", "rand_chacha 0.9.0", - "subtle", -] - -[[package]] -name = "neo-vm-trace" -version = "0.1.0" -source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb#8b32cc8fa7cbb3e28fff34a6f6d0160ee7bc61fb" -dependencies = [ - "serde", - "thiserror 2.0.17", ] [[package]] @@ -2483,6 +2598,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2491,6 +2617,8 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.5", + "serde", ] [[package]] @@ -2565,9 +2693,9 @@ checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "p3-challenger" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e42ba74a49c08c6e99f74cd9b343bfa31aa5721fea55079b18e3fd65f1dcbc" +checksum = "8972ccd1d5dc90e46cdb1f2ab4ee2bae49b3917e5e98aa533f0c2b779c010445" dependencies = [ "p3-field", "p3-maybe-rayon", @@ -2579,144 +2707,166 @@ dependencies = [ [[package]] name = "p3-dft" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63fa5eb1bd12a240089e72ae3fe10350944d9c166d00a3bfd2a1794db65cf5c" +checksum = "17771aca44632f9cc11f2718d7ea7ec06794946c4190ef3a985bfc893f14c18a" dependencies = [ "itertools 0.14.0", "p3-field", "p3-matrix", "p3-maybe-rayon", "p3-util", - "spin", + "spin 0.10.0", "tracing", ] [[package]] name = "p3-field" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ebfdb6ef992ae64e9e8f449ac46516ffa584f11afbdf9ee244288c2a633cdf4" +checksum = "6f3eb24d0591fd4d282d89cbe4e4efba5571c699375006f80b2cbf53ce83461c" dependencies = [ "itertools 0.14.0", - "num-bigint", + "num-bigint 0.4.6", "p3-maybe-rayon", "p3-util", "paste", - "rand 0.9.2", + "rand 0.10.2", "serde", "tracing", ] [[package]] name = "p3-goldilocks" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64716244b5612622d4e78a4f48b74f6d3bb7b4085b7b6b25364b1dfca7198c66" +checksum = "5751c6591a0d2397d726620c2c29a7436ec6c5e19d2ed74ca5d078d4fbb18eb5" dependencies = [ - "num-bigint", + "num-bigint 0.4.6", "p3-challenger", "p3-dft", "p3-field", "p3-mds", + "p3-poseidon1", "p3-poseidon2", "p3-symmetric", "p3-util", "paste", - "rand 0.9.2", + "rand 0.10.2", "serde", ] [[package]] name = "p3-matrix" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5542f96504dae8100c91398fb1e3f5ec669eb9c73d9e0b018a93b5fe32bad230" +checksum = "ea9c94c0714944e7b8a9a62e6340b1e3e1d3f8ecfd3e35c08798360200e73eff" dependencies = [ "itertools 0.14.0", "p3-field", "p3-maybe-rayon", "p3-util", - "rand 0.9.2", + "rand 0.10.2", "serde", "tracing", - "transpose", ] [[package]] name = "p3-maybe-rayon" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e5669ca75645f99cd001e9d0289a4eeff2bc2cd9dc3c6c3aaf22643966e83df" +checksum = "eebc233a34b1ab0273f35b4052fa2eeb3114b22ba4575bd7da00716e878ffb77" [[package]] name = "p3-mds" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038763af23df9da653065867fd85b38626079031576c86fd537097e5be6a0da0" +checksum = "6b5441fa8116246ec9e6c835f15273cb27777ca572960ec87476b67fef13e01e" dependencies = [ "p3-dft", "p3-field", "p3-symmetric", "p3-util", - "rand 0.9.2", + "rand 0.10.2", ] [[package]] name = "p3-monty-31" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a981d60da3d8cbf8561014e2c186068578405fd69098fa75b43d4afb364a47" +checksum = "8724f330ea6d19dd4f2436aa0f88b5fcbf88f0f55ca7fccd3fea8b736dbcddad" dependencies = [ "itertools 0.14.0", - "num-bigint", + "num-bigint 0.4.6", "p3-dft", "p3-field", "p3-matrix", "p3-maybe-rayon", "p3-mds", + "p3-poseidon1", "p3-poseidon2", "p3-symmetric", "p3-util", "paste", - "rand 0.9.2", + "rand 0.10.2", "serde", - "spin", + "spin 0.10.0", "tracing", - "transpose", +] + +[[package]] +name = "p3-poseidon1" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e2a562fea210baae390a32f9ecf0dd8724ae3f4352d1c8e413077b6f00a162" +dependencies = [ + "p3-field", + "p3-symmetric", + "rand 0.10.2", ] [[package]] name = "p3-poseidon2" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903b73e4f9a7781a18561c74dc169cf03333497b57a8dd02aaeb130c0f386599" +checksum = "06394851c161d17e4aa4ad2aad5557d32f14cadd1dc838f965d8e1821a63b8c5" dependencies = [ "p3-field", "p3-mds", "p3-symmetric", "p3-util", - "rand 0.9.2", + "rand 0.10.2", ] [[package]] name = "p3-symmetric" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd788f04e86dd5c35dd87cad29eefdb6371d2fd5f7664451382eeacae3c3ed0" +checksum = "9ac1a276d421f8ef3361bb7d8c39a02c93c6b3f10eeaa559cc4c50222f9a5b82" dependencies = [ "itertools 0.14.0", "p3-field", + "p3-util", "serde", ] [[package]] name = "p3-util" -version = "0.4.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "663b16021930bc600ecada915c6c3965730a3b9d6a6c23434ccf70bfc29d6881" +checksum = "d08a58162a4c264269ef454f0b28dcda89939490eecacb2b2cf5b00f719b80f6" dependencies = [ "serde", + "transpose", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", ] [[package]] @@ -2799,6 +2949,15 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plonky2_maybe_rayon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1e554181dc95243b8d9948ae7bae5759c7fb2502fed28f671f95ef38079406" +dependencies = [ + "rayon", +] + [[package]] name = "plotters" version = "0.3.7" @@ -2979,6 +3138,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -3000,6 +3165,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3020,6 +3194,16 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_chacha" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" +dependencies = [ + "ppv-lite86", + "rand_core 0.10.1", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -3038,6 +3222,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xoshiro" version = "0.7.0" @@ -3285,6 +3475,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -3377,6 +3576,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3401,6 +3610,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "similar" version = "2.7.0" @@ -3432,6 +3647,53 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spartan2" +version = "0.4.0" +source = "git+https://github.com/LFDT-Nightstream/Nightstream.git?rev=675f2ce9d9b05553d518c7ccb36abe2eddbad048#675f2ce9d9b05553d518c7ccb36abe2eddbad048" +dependencies = [ + "bellpepper", + "bellpepper-core", + "bincode", + "bitvec", + "byteorder", + "digest", + "ff", + "flate2", + "generic-array", + "getrandom 0.2.17", + "group", + "halo2curves", + "itertools 0.14.0", + "js-sys", + "neo-params", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "once_cell", + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "p3-symmetric", + "rand 0.9.2", + "rand_chacha 0.10.0", + "rand_chacha 0.9.0", + "rand_core 0.6.4", + "rayon", + "serde", + "sha3", + "subtle", + "thiserror 2.0.17", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spin" version = "0.10.0" @@ -3518,27 +3780,16 @@ dependencies = [ name = "starstream-interleaving-proof" version = "0.1.0" dependencies = [ - "ark-bn254", "ark-ff", "ark-goldilocks", - "ark-poly", - "ark-poly-commit", "ark-poseidon2", "ark-r1cs-std", "ark-relations", - "neo-ajtai", "neo-ccs", - "neo-fold", + "neo-fold-clean", "neo-math", - "neo-memory", "neo-params", - "neo-vm-trace", "p3-field", - "p3-goldilocks", - "p3-poseidon2", - "p3-symmetric", - "rand 0.9.2", - "rand_chacha 0.9.0", "starstream-interleaving-spec", "tracing", "tracing-subscriber", @@ -3553,11 +3804,8 @@ dependencies = [ "ark-poseidon2", "hex", "imbl", - "neo-ajtai", - "neo-ccs", - "neo-fold", + "neo-fold-clean", "neo-math", - "neo-memory", "p3-field", "thiserror 2.0.17", ] @@ -3674,6 +3922,12 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "str_indices" version = "0.4.4" @@ -3747,6 +4001,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.13.5" @@ -4054,6 +4314,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", + "time", "tracing", "tracing-core", "tracing-log", @@ -5420,6 +5681,15 @@ dependencies = [ "wasmparser 0.251.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.39" From 0b373564b5dff4fb1494f978a9c1470cf7eb56bd Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:21:53 -0300 Subject: [PATCH 3/3] cleanup imports in folding.rs (cosmetic) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> --- .../src/folding.rs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/interleaving/starstream-interleaving-proof/src/folding.rs b/interleaving/starstream-interleaving-proof/src/folding.rs index a7c246aa..ab494341 100644 --- a/interleaving/starstream-interleaving-proof/src/folding.rs +++ b/interleaving/starstream-interleaving-proof/src/folding.rs @@ -11,7 +11,21 @@ use crate::nebula::tracer::{NebulaMemory, NebulaMemoryParams}; use ark_ff::PrimeField as _; use ark_relations::gr1cs::{ConstraintSystem, OptimizationGoal, SynthesisError}; use neo_ccs::{CcsMatrix, CscMat}; +use neo_fold_clean::UncompressedAudit; +use neo_fold_clean::engine::ccs_native::poseidon2::POSEIDON2_GOLDILOCKS_BITS; +use neo_fold_clean::frontends::f_prime::image::{ + FPrimeImageLayout, NifsCeClaimShape, NifsPayloadShape, +}; +use neo_fold_clean::frontends::f_prime::recursive_plan::build_semantic_state_preimage_fields; +use neo_fold_clean::frontends::f_prime::recursive_plan::{ + AccumulatorPlanOptions, RecursiveStepImagePlan, StateXOutPlanOptions, + build_recursive_step_image_config, +}; use neo_fold_clean::frontends::r1cs_f_prime::SparseR1cs; +use neo_fold_clean::frontends::r1cs_f_prime::{self, R1csChainBuilder}; +use neo_fold_clean::paper::digest::digest_fields_as_digest32; +use neo_fold_clean::paper::f_prime::poseidon_trace::encode_poseidon_trace; +use neo_fold_clean::paper::f_prime::ring_action_trace::{LowNormEncoding, RingActionTraceLayout}; use neo_math::F as NeoF; use p3_field::PrimeCharacteristicRing as _; use starstream_interleaving_spec::{InterleavingInstance, InterleavingWitness}; @@ -161,21 +175,6 @@ fn csc_from_rows(rows: &[Vec<(ArkF, usize)>], n: usize, m: usize) -> CcsMatrix