From 4f3bd7b9ca699dd57ed8d2fe6480b9b7f9626b0c Mon Sep 17 00:00:00 2001 From: John Guibas Date: Sun, 2 Jun 2024 18:53:51 -0700 Subject: [PATCH 1/8] chore: fixes (#821) Co-authored-by: Ratan Kaliani Co-authored-by: Kevin Jue Co-authored-by: Eugene Rabinovich --- core/Cargo.toml | 1 + core/src/air/builder.rs | 8 + core/src/alu/add_sub/mod.rs | 23 +- core/src/alu/bitwise/mod.rs | 20 ++ core/src/alu/divrem/mod.rs | 204 +++++++++++-- core/src/alu/divrem/utils.rs | 48 --- core/src/alu/lt/mod.rs | 19 ++ core/src/alu/mod.rs | 25 ++ core/src/alu/mul/mod.rs | 27 +- core/src/alu/sll/mod.rs | 18 ++ core/src/alu/sr/mod.rs | 25 ++ core/src/bytes/trace.rs | 2 +- core/src/cpu/air/branch.rs | 37 ++- core/src/cpu/air/ecall.rs | 13 +- core/src/cpu/air/memory.rs | 75 ++++- core/src/cpu/air/mod.rs | 48 ++- core/src/cpu/air/register.rs | 9 + core/src/cpu/columns/auipc.rs | 4 +- core/src/cpu/columns/branch.rs | 13 +- core/src/cpu/columns/ecall.rs | 3 + core/src/cpu/columns/jump.rs | 12 +- core/src/cpu/columns/memory.rs | 9 +- core/src/cpu/columns/mod.rs | 4 + core/src/cpu/event.rs | 11 + core/src/cpu/trace.rs | 265 +++++++++++++---- core/src/lookup/interaction.rs | 1 - core/src/memory/global.rs | 170 ++++++++++- core/src/memory/mod.rs | 2 +- core/src/memory/program.rs | 34 +-- core/src/operations/baby_bear_range.rs | 88 ++++++ core/src/operations/baby_bear_word.rs | 94 ++++++ core/src/operations/field/field_op.rs | 1 - core/src/operations/field/field_sqrt.rs | 22 ++ core/src/operations/mod.rs | 4 + core/src/operations/or.rs | 2 - core/src/runtime/mod.rs | 74 +++-- core/src/runtime/record.rs | 104 +++++-- core/src/runtime/syscall.rs | 22 +- core/src/stark/air.rs | 7 +- core/src/stark/chip.rs | 2 - core/src/stark/machine.rs | 8 + .../precompiles/blake3/compress/air.rs | 235 --------------- .../precompiles/blake3/compress/columns.rs | 55 ---- .../precompiles/blake3/compress/execute.rs | 76 ----- .../syscall/precompiles/blake3/compress/g.rs | 277 ------------------ .../precompiles/blake3/compress/mod.rs | 179 ----------- .../precompiles/blake3/compress/trace.rs | 131 --------- core/src/syscall/precompiles/blake3/mod.rs | 3 - .../src/syscall/precompiles/edwards/ed_add.rs | 158 +++++----- .../precompiles/edwards/ed_decompress.rs | 34 ++- core/src/syscall/precompiles/keccak256/air.rs | 8 + .../syscall/precompiles/keccak256/columns.rs | 1 + .../syscall/precompiles/keccak256/execute.rs | 2 + core/src/syscall/precompiles/keccak256/mod.rs | 1 + .../syscall/precompiles/keccak256/trace.rs | 13 +- core/src/syscall/precompiles/mod.rs | 8 +- .../precompiles/sha256/compress/air.rs | 69 +++-- .../precompiles/sha256/compress/columns.rs | 3 + .../precompiles/sha256/compress/execute.rs | 2 + .../precompiles/sha256/compress/mod.rs | 1 + .../precompiles/sha256/compress/trace.rs | 40 ++- .../syscall/precompiles/sha256/extend/air.rs | 23 +- .../precompiles/sha256/extend/columns.rs | 4 +- .../precompiles/sha256/extend/execute.rs | 2 + .../precompiles/sha256/extend/flags.rs | 9 +- .../syscall/precompiles/sha256/extend/mod.rs | 1 + .../precompiles/sha256/extend/trace.rs | 17 +- core/src/syscall/precompiles/uint256/air.rs | 28 +- .../weierstrass/weierstrass_add.rs | 171 ++++++----- .../weierstrass/weierstrass_decompress.rs | 140 +++++---- .../weierstrass/weierstrass_double.rs | 176 ++++++----- core/src/syscall/verify.rs | 13 +- prover/src/build.rs | 3 - prover/src/verify.rs | 55 +++- recursion/compiler/src/ir/bits.rs | 9 + recursion/core/src/air/builder.rs | 4 + recursion/core/src/cpu/air/branch.rs | 22 +- recursion/core/src/cpu/air/jump.rs | 16 +- recursion/core/src/cpu/air/memory.rs | 4 +- recursion/core/src/multi/mod.rs | 5 +- recursion/core/src/poseidon2/columns.rs | 4 + recursion/core/src/poseidon2/external.rs | 122 ++++++-- recursion/core/src/poseidon2/trace.rs | 18 ++ recursion/groth16/constraints.json | 1 - recursion/groth16/lib/libbabybear.a | Bin 14863472 -> 0 bytes recursion/groth16/main | Bin 20741074 -> 0 bytes recursion/groth16/witness.json | 1 - recursion/program/src/machine/compress.rs | 2 + recursion/program/src/machine/core.rs | 10 + recursion/program/src/machine/deferred.rs | 5 +- recursion/program/src/machine/root.rs | 11 +- recursion/program/src/stark.rs | 77 +++-- zkvm/precompiles/src/uint256_div.rs | 1 - 93 files changed, 2133 insertions(+), 1675 deletions(-) create mode 100644 core/src/operations/baby_bear_range.rs create mode 100644 core/src/operations/baby_bear_word.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/air.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/columns.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/execute.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/g.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/mod.rs delete mode 100644 core/src/syscall/precompiles/blake3/compress/trace.rs delete mode 100644 core/src/syscall/precompiles/blake3/mod.rs delete mode 100644 recursion/groth16/constraints.json delete mode 100644 recursion/groth16/lib/libbabybear.a delete mode 100755 recursion/groth16/main delete mode 100644 recursion/groth16/witness.json diff --git a/core/Cargo.toml b/core/Cargo.toml index df02550f1..548fcde6c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,7 @@ web-time = "1.1.0" rayon-scan = "0.1.1" thiserror = "1.0.60" num-bigint = { version = "0.4.3", default-features = false } +rand = "0.8.5" [dev-dependencies] tiny-keccak = { version = "2.0.2", features = ["keccak"] } diff --git a/core/src/air/builder.rs b/core/src/air/builder.rs index eba567d92..d957f43cd 100644 --- a/core/src/air/builder.rs +++ b/core/src/air/builder.rs @@ -308,6 +308,7 @@ pub trait AluAirBuilder: BaseAirBuilder { c: Word>, shard: impl Into, channel: impl Into, + nonce: impl Into, multiplicity: impl Into, ) { let values = once(opcode.into()) @@ -316,6 +317,7 @@ pub trait AluAirBuilder: BaseAirBuilder { .chain(c.0.into_iter().map(Into::into)) .chain(once(shard.into())) .chain(once(channel.into())) + .chain(once(nonce.into())) .collect(); self.send(AirInteraction::new( @@ -335,6 +337,7 @@ pub trait AluAirBuilder: BaseAirBuilder { c: Word>, shard: impl Into, channel: impl Into, + nonce: impl Into, multiplicity: impl Into, ) { let values = once(opcode.into()) @@ -343,6 +346,7 @@ pub trait AluAirBuilder: BaseAirBuilder { .chain(c.0.into_iter().map(Into::into)) .chain(once(shard.into())) .chain(once(channel.into())) + .chain(once(nonce.into())) .collect(); self.receive(AirInteraction::new( @@ -359,6 +363,7 @@ pub trait AluAirBuilder: BaseAirBuilder { shard: impl Into + Clone, channel: impl Into + Clone, clk: impl Into + Clone, + nonce: impl Into + Clone, syscall_id: impl Into + Clone, arg1: impl Into + Clone, arg2: impl Into + Clone, @@ -369,6 +374,7 @@ pub trait AluAirBuilder: BaseAirBuilder { shard.clone().into(), channel.clone().into(), clk.clone().into(), + nonce.clone().into(), syscall_id.clone().into(), arg1.clone().into(), arg2.clone().into(), @@ -385,6 +391,7 @@ pub trait AluAirBuilder: BaseAirBuilder { shard: impl Into + Clone, channel: impl Into + Clone, clk: impl Into + Clone, + nonce: impl Into + Clone, syscall_id: impl Into + Clone, arg1: impl Into + Clone, arg2: impl Into + Clone, @@ -395,6 +402,7 @@ pub trait AluAirBuilder: BaseAirBuilder { shard.clone().into(), channel.clone().into(), clk.clone().into(), + nonce.clone().into(), syscall_id.clone().into(), arg1.clone().into(), arg2.clone().into(), diff --git a/core/src/alu/add_sub/mod.rs b/core/src/alu/add_sub/mod.rs index 2321427c5..3179d4d77 100644 --- a/core/src/alu/add_sub/mod.rs +++ b/core/src/alu/add_sub/mod.rs @@ -1,7 +1,8 @@ use core::borrow::{Borrow, BorrowMut}; use core::mem::size_of; -use p3_air::{Air, BaseAir}; +use p3_air::{Air, AirBuilder, BaseAir}; +use p3_field::AbstractField; use p3_field::PrimeField; use p3_matrix::dense::RowMajorMatrix; use p3_matrix::Matrix; @@ -38,6 +39,9 @@ pub struct AddSubCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// Instance of `AddOperation` to handle addition logic in `AddSubChip`'s ALU operations. /// It's result will be `a` for the add operation and `b` for the sub operation. pub add_operation: AddOperation, @@ -129,6 +133,13 @@ impl MachineAir for AddSubChip { // Pad the trace to a power of two. pad_to_power_of_two::(&mut trace.values); + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut AddSubCols = + trace.values[i * NUM_ADD_SUB_COLS..(i + 1) * NUM_ADD_SUB_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -151,6 +162,14 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &AddSubCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &AddSubCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); // Evaluate the addition operation. AddOperation::::eval( @@ -172,6 +191,7 @@ where local.operand_2, local.shard, local.channel, + local.nonce, local.is_add, ); @@ -183,6 +203,7 @@ where local.operand_2, local.shard, local.channel, + local.nonce, local.is_sub, ); diff --git a/core/src/alu/bitwise/mod.rs b/core/src/alu/bitwise/mod.rs index 3e7227b70..81163b11e 100644 --- a/core/src/alu/bitwise/mod.rs +++ b/core/src/alu/bitwise/mod.rs @@ -1,7 +1,9 @@ use core::borrow::{Borrow, BorrowMut}; use core::mem::size_of; +use p3_air::AirBuilder; use p3_air::{Air, BaseAir}; +use p3_field::AbstractField; use p3_field::PrimeField; use p3_matrix::dense::RowMajorMatrix; use p3_matrix::Matrix; @@ -31,6 +33,9 @@ pub struct BitwiseCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// The output operand. pub a: Word, @@ -111,6 +116,12 @@ impl MachineAir for BitwiseChip { // Pad the trace to a power of two. pad_to_power_of_two::(&mut trace.values); + for i in 0..trace.height() { + let cols: &mut BitwiseCols = + trace.values[i * NUM_BITWISE_COLS..(i + 1) * NUM_BITWISE_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -133,6 +144,14 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &BitwiseCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &BitwiseCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); // Get the opcode for the operation. let opcode = local.is_xor * ByteOpcode::XOR.as_field::() @@ -166,6 +185,7 @@ where local.c, local.shard, local.channel, + local.nonce, local.is_xor + local.is_or + local.is_and, ); diff --git a/core/src/alu/divrem/mod.rs b/core/src/alu/divrem/mod.rs index b54206469..84198b16b 100644 --- a/core/src/alu/divrem/mod.rs +++ b/core/src/alu/divrem/mod.rs @@ -64,6 +64,7 @@ mod utils; use core::borrow::{Borrow, BorrowMut}; use core::mem::size_of; +use std::collections::HashMap; use p3_air::{Air, AirBuilder, BaseAir}; use p3_field::AbstractField; @@ -72,11 +73,10 @@ use p3_matrix::dense::RowMajorMatrix; use p3_matrix::Matrix; use sp1_derive::AlignedBorrow; -use self::utils::eval_abs_value; use crate::air::MachineAir; use crate::air::{SP1AirBuilder, Word}; use crate::alu::divrem::utils::{get_msb, get_quotient_and_remainder, is_signed_operation}; -use crate::alu::AluEvent; +use crate::alu::{create_alu_lookups, AluEvent}; use crate::bytes::event::ByteRecord; use crate::bytes::{ByteLookupEvent, ByteOpcode}; use crate::disassembler::WORD_SIZE; @@ -107,6 +107,9 @@ pub struct DivRemCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// The output operand. pub a: Word, @@ -184,6 +187,23 @@ pub struct DivRemCols { /// Flag to indicate whether `c` is negative. pub c_neg: T, + /// The lower nonce of the operation. + pub lower_nonce: T, + + /// The upper nonce of the operation. + pub upper_nonce: T, + + /// The absolute nonce of the operation. + pub abs_nonce: T, + + /// Selector to determine whether an ALU Event is sent for absolute value computation of `c`. + pub abs_c_alu_event: T, + pub abs_c_alu_event_nonce: T, + + /// Selector to determine whether an ALU Event is sent for absolute value computation of `rem`. + pub abs_rem_alu_event: T, + pub abs_rem_alu_event_nonce: T, + /// Selector to know whether this row is enabled. pub is_real: T, @@ -259,6 +279,24 @@ impl MachineAir for DivRemChip { cols.max_abs_c_or_1 = Word::from(u32::max(1, event.c)); } + // Set the `alu_event` flags. + cols.abs_c_alu_event = cols.c_neg * cols.is_real; + cols.abs_c_alu_event_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[4]) + .copied() + .unwrap_or_default(), + ); + cols.abs_rem_alu_event = cols.rem_neg * cols.is_real; + cols.abs_rem_alu_event_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[5]) + .copied() + .unwrap_or_default(), + ); + // Insert the MSB lookup events. { let words = [event.b, event.c, remainder]; @@ -281,7 +319,7 @@ impl MachineAir for DivRemChip { // Calculate the modified multiplicity { - cols.remainder_check_multiplicity = cols.is_real * cols.is_c_0.result; + cols.remainder_check_multiplicity = cols.is_real * (F::one() - cols.is_c_0.result); } // Calculate c * quotient + remainder. @@ -321,6 +359,40 @@ impl MachineAir for DivRemChip { // mul and LT upon which div depends. This ordering is critical as mul and LT // require all the mul and LT events be added before we can call generate_trace. { + // Insert the absolute value computation events. + { + let mut add_events: Vec = vec![]; + if cols.abs_c_alu_event == F::one() { + add_events.push(AluEvent { + lookup_id: event.sub_lookups[4], + shard: event.shard, + channel: event.channel, + clk: event.clk, + opcode: Opcode::ADD, + a: 0, + b: event.c, + c: (event.c as i32).abs() as u32, + sub_lookups: create_alu_lookups(), + }) + } + if cols.abs_rem_alu_event == F::one() { + add_events.push(AluEvent { + lookup_id: event.sub_lookups[5], + shard: event.shard, + channel: event.channel, + clk: event.clk, + opcode: Opcode::ADD, + a: 0, + b: remainder, + c: (remainder as i32).abs() as u32, + sub_lookups: create_alu_lookups(), + }) + } + let mut alu_events = HashMap::new(); + alu_events.insert(Opcode::ADD, add_events); + output.add_alu_events(alu_events); + } + let mut lower_word = 0; for i in 0..WORD_SIZE { lower_word += (c_times_quotient[i] as u32) << (i * BYTE_SIZE); @@ -332,6 +404,7 @@ impl MachineAir for DivRemChip { } let lower_multiplication = AluEvent { + lookup_id: event.sub_lookups[0], shard: event.shard, channel: event.channel, clk: event.clk, @@ -339,10 +412,19 @@ impl MachineAir for DivRemChip { a: lower_word, c: event.c, b: quotient, + sub_lookups: create_alu_lookups(), }; + cols.lower_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[0]) + .copied() + .unwrap_or_default(), + ); output.add_mul_event(lower_multiplication); let upper_multiplication = AluEvent { + lookup_id: event.sub_lookups[1], shard: event.shard, channel: event.channel, clk: event.clk, @@ -356,22 +438,45 @@ impl MachineAir for DivRemChip { a: upper_word, c: event.c, b: quotient, + sub_lookups: create_alu_lookups(), }; - + cols.upper_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[1]) + .copied() + .unwrap_or_default(), + ); output.add_mul_event(upper_multiplication); - let lt_event = if is_signed_operation(event.opcode) { + cols.abs_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[2]) + .copied() + .unwrap_or_default(), + ); AluEvent { + lookup_id: event.sub_lookups[2], shard: event.shard, channel: event.channel, - opcode: Opcode::SLT, + opcode: Opcode::SLTU, a: 1, b: (remainder as i32).abs() as u32, c: u32::max(1, (event.c as i32).abs() as u32), clk: event.clk, + sub_lookups: create_alu_lookups(), } } else { + cols.abs_nonce = F::from_canonical_u32( + input + .nonce_lookup + .get(&event.sub_lookups[3]) + .copied() + .unwrap_or_default(), + ); AluEvent { + lookup_id: event.sub_lookups[3], shard: event.shard, channel: event.channel, opcode: Opcode::SLTU, @@ -379,8 +484,10 @@ impl MachineAir for DivRemChip { b: remainder, c: u32::max(1, event.c), clk: event.clk, + sub_lookups: create_alu_lookups(), } }; + if cols.remainder_check_multiplicity == F::one() { output.add_lt_event(lt_event); } @@ -430,6 +537,13 @@ impl MachineAir for DivRemChip { trace.values[i] = padded_row_template[i % NUM_DIVREM_COLS]; } + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut DivRemCols = + trace.values[i * NUM_DIVREM_COLS..(i + 1) * NUM_DIVREM_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -452,10 +566,18 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &DivRemCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &DivRemCols = (*next).borrow(); let base = AB::F::from_canonical_u32(1 << 8); let one: AB::Expr = AB::F::one().into(); let zero: AB::Expr = AB::F::zero().into(); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + // Calculate whether b, remainder, and c are negative. { // Negative if and only if op code is signed & MSB = 1. @@ -490,6 +612,7 @@ where local.c, local.shard, local.channel, + local.lower_nonce, local.is_real, ); @@ -515,6 +638,7 @@ where local.c, local.shard, local.channel, + local.upper_nonce, local.is_real, ); } @@ -659,18 +783,37 @@ where // Range check remainder. (i.e., |remainder| < |c| when not is_c_0) { - eval_abs_value( - builder, - local.remainder.borrow(), - local.abs_remainder.borrow(), - local.rem_neg.borrow(), + // For each of `c` and `rem`, assert that the absolute value is equal to the original value, + // if the original value is non-negative or the minimum i32. + for i in 0..WORD_SIZE { + builder + .when_not(local.c_neg) + .assert_eq(local.c[i], local.abs_c[i]); + builder + .when_not(local.rem_neg) + .assert_eq(local.remainder[i], local.abs_remainder[i]); + } + // In the case that `c` or `rem` is negative, instead check that their sum is zero by + // sending an AddEvent. + builder.send_alu( + AB::Expr::from_canonical_u32(Opcode::ADD as u32), + Word([zero.clone(), zero.clone(), zero.clone(), zero.clone()]), + local.c, + local.abs_c, + local.shard, + local.channel, + local.abs_c_alu_event_nonce, + local.abs_c_alu_event, ); - - eval_abs_value( - builder, - local.c.borrow(), - local.abs_c.borrow(), - local.c_neg.borrow(), + builder.send_alu( + AB::Expr::from_canonical_u32(Opcode::ADD as u32), + Word([zero.clone(), zero.clone(), zero.clone(), zero.clone()]), + local.remainder, + local.abs_remainder, + local.shard, + local.channel, + local.abs_rem_alu_event_nonce, + local.abs_rem_alu_event, ); // max(abs(c), 1) = abs(c) * (1 - is_c_0) + 1 * is_c_0 @@ -691,29 +834,31 @@ where builder.assert_eq(local.max_abs_c_or_1[i], max_abs_c_or_1[i].clone()); } - let opcode = { - let is_signed = local.is_div + local.is_rem; - let is_unsigned = local.is_divu + local.is_remu; - let slt = AB::Expr::from_canonical_u32(Opcode::SLT as u32); - let sltu = AB::Expr::from_canonical_u32(Opcode::SLTU as u32); - is_signed * slt + is_unsigned * sltu - }; - - // Check that the event multiplicity column is computed correctly. + // Handle cases: + // - If is_real == 0 then remainder_check_multiplicity == 0 is forced. + // - If is_real == 1 then is_c_0_result must be the expected one, so + // remainder_check_multiplicity = (1 - is_c_0_result) * is_real. builder.assert_eq( + (AB::Expr::one() - local.is_c_0.result) * local.is_real, local.remainder_check_multiplicity, - local.is_c_0.result * local.is_real, ); + // the cleaner idea is simply remainder_check_multiplicity == (1 - is_c_0_result) * is_real + + // Check that the absolute value selector columns are computed correctly. + builder.assert_eq(local.abs_c_alu_event, local.c_neg * local.is_real); + builder.assert_eq(local.abs_rem_alu_event, local.rem_neg * local.is_real); + // Dispatch abs(remainder) < max(abs(c), 1), this is equivalent to abs(remainder) < // abs(c) if not division by 0. builder.send_alu( - opcode, + AB::Expr::from_canonical_u32(Opcode::SLTU as u32), Word([one.clone(), zero.clone(), zero.clone(), zero.clone()]), local.abs_remainder, local.max_abs_c_or_1, local.shard, local.channel, + local.abs_nonce, local.remainder_check_multiplicity, ); } @@ -783,6 +928,8 @@ where local.rem_neg, local.c_neg, local.is_real, + local.abs_c_alu_event, + local.abs_rem_alu_event, ]; for flag in bool_flags.iter() { @@ -817,6 +964,7 @@ where local.c, local.shard, local.channel, + local.nonce, local.is_real, ); } diff --git a/core/src/alu/divrem/utils.rs b/core/src/alu/divrem/utils.rs index f3a7b7070..d71c35aad 100644 --- a/core/src/alu/divrem/utils.rs +++ b/core/src/alu/divrem/utils.rs @@ -1,7 +1,3 @@ -use p3_air::AirBuilder; -use p3_field::AbstractField; - -use crate::air::{SP1AirBuilder, Word, WORD_SIZE}; use crate::runtime::Opcode; /// Returns `true` if the given `opcode` is a signed operation. @@ -32,47 +28,3 @@ pub fn get_quotient_and_remainder(b: u32, c: u32, opcode: Opcode) -> (u32, u32) pub const fn get_msb(a: u32) -> u8 { ((a >> 31) & 1) as u8 } - -/// Verifies that `abs_value = abs(value)` using `is_negative` as a flag. -/// -/// `abs(value) + value = 0` if `value` is negative. `abs(value) = value` otherwise. -/// -/// In two's complement arithmetic, the negation involves flipping its bits and adding 1. Therefore, -/// for a negative number, `abs(value) + value` equals 0. This is because `abs(value)` is the two's -/// complement (negation) of `value`. For a positive number, `abs(value)` is the same as `value`. -/// -/// The function iterates over each limb of the `value` and `abs_value`, checking the following -/// conditions: -/// -/// 1. If `value` is non-negative, it checks that each limb in `value` and `abs_value` is identical. -/// 2. If `value` is negative, it checks that the sum of each corresponding limb in `value` and -/// `abs_value` equals the expected sum for a two's complement representation. The least -/// significant limb (first limb) should add up to `0xff + 1` (to account for the +1 in two's -/// complement negation), and other limbs should add up to `0xff` (as the rest of the limbs just -/// have their bits flipped). -pub fn eval_abs_value( - builder: &mut AB, - value: &Word, - abs_value: &Word, - is_negative: &AB::Var, -) where - AB: SP1AirBuilder, -{ - for i in 0..WORD_SIZE { - let exp_sum_if_negative = AB::Expr::from_canonical_u32({ - if i == 0 { - 0xff + 1 - } else { - 0xff - } - }); - - builder - .when(*is_negative) - .assert_eq(value[i] + abs_value[i], exp_sum_if_negative.clone()); - - builder - .when_not(*is_negative) - .assert_eq(value[i], abs_value[i]); - } -} diff --git a/core/src/alu/lt/mod.rs b/core/src/alu/lt/mod.rs index 91b504181..54d5768c2 100644 --- a/core/src/alu/lt/mod.rs +++ b/core/src/alu/lt/mod.rs @@ -34,6 +34,9 @@ pub struct LtCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// If the opcode is SLT. pub is_slt: T, @@ -220,6 +223,13 @@ impl MachineAir for LtChip { // Pad the trace to a power of two. pad_to_power_of_two::(&mut trace.values); + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut LtCols = + trace.values[i * NUM_LT_COLS..(i + 1) * NUM_LT_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -242,6 +252,14 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &LtCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &LtCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); let is_real = local.is_slt + local.is_sltu; @@ -431,6 +449,7 @@ where local.c, local.shard, local.channel, + local.nonce, is_real, ); } diff --git a/core/src/alu/mod.rs b/core/src/alu/mod.rs index c667c612c..a67d1ff90 100644 --- a/core/src/alu/mod.rs +++ b/core/src/alu/mod.rs @@ -11,6 +11,7 @@ pub use bitwise::*; pub use divrem::*; pub use lt::*; pub use mul::*; +use rand::Rng; pub use sll::*; pub use sr::*; @@ -21,6 +22,9 @@ use crate::runtime::Opcode; /// A standard format for describing ALU operations that need to be proven. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct AluEvent { + /// The lookup id of the event. + pub lookup_id: usize, + /// The shard number, used for byte lookup table. pub shard: u32, @@ -41,12 +45,15 @@ pub struct AluEvent { // The second input operand. pub c: u32, + + pub sub_lookups: [usize; 6], } impl AluEvent { /// Creates a new `AluEvent`. pub fn new(shard: u32, channel: u32, clk: u32, opcode: Opcode, a: u32, b: u32, c: u32) -> Self { Self { + lookup_id: 0, shard, channel, clk, @@ -54,6 +61,24 @@ impl AluEvent { a, b, c, + sub_lookups: create_alu_lookups(), } } } + +pub fn create_alu_lookup_id() -> usize { + let mut rng = rand::thread_rng(); + rng.gen() +} + +pub fn create_alu_lookups() -> [usize; 6] { + let mut rng = rand::thread_rng(); + [ + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + ] +} diff --git a/core/src/alu/mul/mod.rs b/core/src/alu/mul/mod.rs index c30a59c4f..1351e78c3 100644 --- a/core/src/alu/mul/mod.rs +++ b/core/src/alu/mul/mod.rs @@ -79,6 +79,9 @@ pub struct MulCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// The output operand. pub a: Word, @@ -270,6 +273,13 @@ impl MachineAir for MulChip { // Pad the trace to a power of two. pad_to_power_of_two::(&mut trace.values); + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut MulCols = + trace.values[i * NUM_MUL_COLS..(i + 1) * NUM_MUL_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -292,12 +302,20 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &MulCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &MulCols = (*next).borrow(); let base = AB::F::from_canonical_u32(1 << 8); let zero: AB::Expr = AB::F::zero().into(); let one: AB::Expr = AB::F::one().into(); let byte_mask = AB::F::from_canonical_u8(BYTE_MASK); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + // Calculate the MSBs. let (b_msb, c_msb) = { let msb_pairs = [ @@ -412,14 +430,6 @@ where .when(local.c_sign_extend) .assert_eq(local.c_msb, one.clone()); - // If the opcode doesn't allow sign extension for an operand, we must not extend their sign. - builder - .when(local.is_mul + local.is_mulhu) - .assert_zero(local.b_sign_extend + local.c_sign_extend); - builder - .when(local.is_mul + local.is_mulhsu + local.is_mulhsu) - .assert_zero(local.c_sign_extend); - // Calculate the opcode. let opcode = { // Exactly one of the op codes must be on. @@ -455,6 +465,7 @@ where local.c, local.shard, local.channel, + local.nonce, local.is_real, ); } diff --git a/core/src/alu/sll/mod.rs b/core/src/alu/sll/mod.rs index d87ee780d..b5a711542 100644 --- a/core/src/alu/sll/mod.rs +++ b/core/src/alu/sll/mod.rs @@ -67,6 +67,9 @@ pub struct ShiftLeftCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// The output operand. pub a: Word, @@ -199,6 +202,12 @@ impl MachineAir for ShiftLeft { trace.values[i] = padded_row_template[i % NUM_SHIFT_LEFT_COLS]; } + for i in 0..trace.height() { + let cols: &mut ShiftLeftCols = + trace.values[i * NUM_SHIFT_LEFT_COLS..(i + 1) * NUM_SHIFT_LEFT_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -221,11 +230,19 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &ShiftLeftCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &ShiftLeftCols = (*next).borrow(); let zero: AB::Expr = AB::F::zero().into(); let one: AB::Expr = AB::F::one().into(); let base: AB::Expr = AB::F::from_canonical_u32(1 << BYTE_SIZE).into(); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + // We first "bit shift" and next we "byte shift". Then we compare the results with a. // Finally, we perform some misc checks. @@ -354,6 +371,7 @@ where local.c, local.shard, local.channel, + local.nonce, local.is_real, ); } diff --git a/core/src/alu/sr/mod.rs b/core/src/alu/sr/mod.rs index 8f9ea721e..bd7a91d52 100644 --- a/core/src/alu/sr/mod.rs +++ b/core/src/alu/sr/mod.rs @@ -85,6 +85,9 @@ pub struct ShiftRightCols { /// The channel number, used for byte lookup table. pub channel: T, + /// The nonce of the operation. + pub nonce: T, + /// The output operand. pub a: Word, @@ -283,6 +286,13 @@ impl MachineAir for ShiftRightChip { trace.values[i] = padded_row_template[i % NUM_SHIFT_RIGHT_COLS]; } + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut ShiftRightCols = + trace.values[i * NUM_SHIFT_RIGHT_COLS..(i + 1) * NUM_SHIFT_RIGHT_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + trace } @@ -305,9 +315,17 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &ShiftRightCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &ShiftRightCols = (*next).borrow(); let zero: AB::Expr = AB::F::zero().into(); let one: AB::Expr = AB::F::one().into(); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + // Check that the MSB of most_significant_byte matches local.b_msb using lookup. { let byte = local.b[WORD_SIZE - 1]; @@ -464,6 +482,9 @@ where for shift_by_n_bit in local.shift_by_n_bits.iter() { builder.assert_bool(*shift_by_n_bit); } + for bit in local.c_least_sig_byte.iter() { + builder.assert_bool(*bit); + } } // Range check bytes. @@ -485,6 +506,9 @@ where builder.assert_bool(local.is_sra); builder.assert_bool(local.is_real); + // Check that is_real is the sum of the two operation flags. + builder.assert_eq(local.is_srl + local.is_sra, local.is_real); + // Receive the arguments. builder.receive_alu( local.is_srl * AB::F::from_canonical_u32(Opcode::SRL as u32) @@ -494,6 +518,7 @@ where local.c, local.shard, local.channel, + local.nonce, local.is_real, ); } diff --git a/core/src/bytes/trace.rs b/core/src/bytes/trace.rs index 39f2b72ff..d6ba3921b 100644 --- a/core/src/bytes/trace.rs +++ b/core/src/bytes/trace.rs @@ -28,7 +28,7 @@ impl MachineAir for ByteChip { } fn generate_preprocessed_trace(&self, _program: &Self::Program) -> Option> { - // TODO: We should be able to make this a constant. Also, trace / map should be separate. + // OPT: We should be able to make this a constant. Also, trace / map should be separate. // Since we only need the trace and not the map, we can just pass 0 as the shard. let (trace, _) = Self::trace_and_map(0); diff --git a/core/src/cpu/air/branch.rs b/core/src/cpu/air/branch.rs index fad654de3..60bba4175 100644 --- a/core/src/cpu/air/branch.rs +++ b/core/src/cpu/air/branch.rs @@ -3,6 +3,7 @@ use p3_field::AbstractField; use crate::air::{BaseAirBuilder, SP1AirBuilder, Word, WordAirBuilder}; use crate::cpu::columns::{CpuCols, OpcodeSelectorCols}; +use crate::operations::BabyBearWordRangeChecker; use crate::{cpu::CpuChip, runtime::Opcode}; impl CpuChip { @@ -57,6 +58,20 @@ impl CpuChip { .when(local.branching) .assert_eq(branch_cols.next_pc.reduce::(), local.next_pc); + // Range check branch_cols.pc and branch_cols.next_pc. + BabyBearWordRangeChecker::::range_check( + builder, + branch_cols.pc, + branch_cols.pc_range_checker, + is_branch_instruction.clone(), + ); + BabyBearWordRangeChecker::::range_check( + builder, + branch_cols.next_pc, + branch_cols.next_pc_range_checker, + is_branch_instruction.clone(), + ); + // When we are branching, calculate branch_cols.next_pc <==> branch_cols.pc + c. builder.send_alu( Opcode::ADD.as_field::(), @@ -65,6 +80,7 @@ impl CpuChip { local.op_c_val(), local.shard, local.channel, + branch_cols.next_pc_nonce, local.branching, ); @@ -83,15 +99,21 @@ impl CpuChip { .when(local.is_real) .when(local.not_branching) .assert_eq(local.pc + AB::Expr::from_canonical_u8(4), local.next_pc); - } - // Evaluate branching value constraints. - { - // Assert that local.is_branching is a bit. + // Assert that either we are branching or not branching when the instruction is a branch. + builder + .when(is_branch_instruction.clone()) + .assert_one(local.branching + local.not_branching); builder .when(is_branch_instruction.clone()) .assert_bool(local.branching); + builder + .when(is_branch_instruction.clone()) + .assert_bool(local.not_branching); + } + // Evaluate branching value constraints. + { // When the opcode is BEQ and we are branching, assert that a_eq_b is true. builder .when(local.selectors.is_beq * local.branching) @@ -146,6 +168,11 @@ impl CpuChip { .when(is_branch_instruction.clone() * branch_cols.a_eq_b) .assert_word_eq(local.op_a_val(), local.op_b_val()); + // To prevent this ALU send to be arbitrarily large when is_branch_instruction is false. + builder + .when_not(is_branch_instruction.clone()) + .assert_zero(local.branching); + // Calculate a_lt_b <==> a < b (using appropriate signedness). let use_signed_comparison = local.selectors.is_blt + local.selectors.is_bge; builder.send_alu( @@ -157,6 +184,7 @@ impl CpuChip { local.op_b_val(), local.shard, local.channel, + branch_cols.a_lt_b_nonce, is_branch_instruction.clone(), ); @@ -169,6 +197,7 @@ impl CpuChip { local.op_a_val(), local.shard, local.channel, + branch_cols.a_gt_b_nonce, is_branch_instruction.clone(), ); } diff --git a/core/src/cpu/air/ecall.rs b/core/src/cpu/air/ecall.rs index 506b2c7b7..870513b83 100644 --- a/core/src/cpu/air/ecall.rs +++ b/core/src/cpu/air/ecall.rs @@ -35,14 +35,19 @@ impl CpuChip { let syscall_id = syscall_code[0]; let send_to_table = syscall_code[1]; - // When is_ecall_instruction == true AND sent_to_table == true, ecall_mul_send_to_table should be true. - builder - .when(is_ecall_instruction.clone()) - .assert_eq(send_to_table, local.ecall_mul_send_to_table); + // Handle cases: + // - is_ecall_instruction = 1 => ecall_mul_send_to_table == send_to_table + // - is_ecall_instruction = 0 => ecall_mul_send_to_table == 0 + builder.assert_eq( + local.ecall_mul_send_to_table, + send_to_table * is_ecall_instruction.clone(), + ); + builder.send_syscall( local.shard, local.channel, local.clk, + ecall_cols.syscall_nonce, syscall_id, local.op_b_val().reduce::(), local.op_c_val().reduce::(), diff --git a/core/src/cpu/air/memory.rs b/core/src/cpu/air/memory.rs index 6ac1a07c1..707a50ff9 100644 --- a/core/src/cpu/air/memory.rs +++ b/core/src/cpu/air/memory.rs @@ -5,6 +5,7 @@ use crate::air::{BaseAirBuilder, SP1AirBuilder, Word, WordAirBuilder}; use crate::cpu::columns::{CpuCols, MemoryColumns, OpcodeSelectorCols}; use crate::cpu::CpuChip; use crate::memory::MemoryCols; +use crate::operations::BabyBearWordRangeChecker; use crate::runtime::{MemoryAccessPosition, Opcode}; impl CpuChip { @@ -66,6 +67,15 @@ impl CpuChip { local.op_c_val(), local.shard, local.channel, + memory_columns.addr_word_nonce, + is_memory_instruction.clone(), + ); + + // Range check the addr_word to be a valid babybear word. + BabyBearWordRangeChecker::::range_check( + builder, + memory_columns.addr_word, + memory_columns.addr_word_range_checker, is_memory_instruction.clone(), ); @@ -88,6 +98,35 @@ impl CpuChip { memory_columns.addr_word.reduce::(), ); + // Verify that the least significant byte of addr_word - addr_offset is divisible by 4. + let offset = [ + memory_columns.offset_is_one, + memory_columns.offset_is_two, + memory_columns.offset_is_three, + ] + .iter() + .enumerate() + .fold(AB::Expr::zero(), |acc, (index, &value)| { + acc + AB::Expr::from_canonical_usize(index + 1) * value + }); + let mut recomposed_byte = AB::Expr::zero(); + memory_columns + .aa_least_sig_byte_decomp + .iter() + .enumerate() + .for_each(|(i, value)| { + builder + .when(is_memory_instruction.clone()) + .assert_bool(*value); + + recomposed_byte = + recomposed_byte.clone() + AB::Expr::from_canonical_usize(1 << (i + 2)) * *value; + }); + + builder + .when(is_memory_instruction.clone()) + .assert_eq(memory_columns.addr_word[0] - offset, recomposed_byte); + // For operations that require reading from memory (not registers), we need to read the // value into the memory columns. builder.eval_memory_access( @@ -98,6 +137,14 @@ impl CpuChip { &memory_columns.memory_access, is_memory_instruction.clone(), ); + + // On memory load instructions, make sure that the memory value is not changed. + builder + .when(self.is_load_instruction::(&local.selectors)) + .assert_word_eq( + *memory_columns.memory_access.value(), + *memory_columns.memory_access.prev_value(), + ); } /// Evaluates constraints related to loading from memory. @@ -121,12 +168,11 @@ impl CpuChip { // Assert that if `is_lb` and `is_lh` are both true, then the most significant byte // matches the value of `local.mem_value_is_neg`. - builder - .when(local.selectors.is_lb + local.selectors.is_lh) - .assert_eq( - local.mem_value_is_neg, - memory_columns.most_sig_byte_decomp[7], - ); + builder.assert_eq( + local.mem_value_is_neg, + (local.selectors.is_lb + local.selectors.is_lh) + * memory_columns.most_sig_byte_decomp[7], + ); // When the memory value is negative, use the SUB opcode to compute the signed value of // the memory value and verify that the op_a value is correct. @@ -143,6 +189,7 @@ impl CpuChip { signed_value, local.shard, local.channel, + local.unsigned_mem_val_nonce, local.mem_value_is_neg, ); @@ -195,6 +242,11 @@ impl CpuChip { .when(local.selectors.is_sh) .assert_zero(memory_columns.offset_is_one + memory_columns.offset_is_three); + // When the instruction is SW, ensure that the offset is 0. + builder + .when(local.selectors.is_sw) + .assert_one(offset_is_zero.clone()); + // Compute the expected stored value for a SH instruction. let a_is_lower_half = offset_is_zero; let a_is_upper_half = memory_columns.offset_is_two; @@ -247,6 +299,12 @@ impl CpuChip { builder .when(local.selectors.is_lh + local.selectors.is_lhu) .assert_zero(memory_columns.offset_is_one + memory_columns.offset_is_three); + + // When the instruction is LW, ensure that the offset is zero. + builder + .when(local.selectors.is_lw) + .assert_one(offset_is_zero.clone()); + let use_lower_half = offset_is_zero; let use_upper_half = memory_columns.offset_is_two; let half_value = Word([ @@ -273,9 +331,12 @@ impl CpuChip { local: &CpuCols, unsigned_mem_val: &Word, ) { + let is_mem = self.is_memory_instruction::(&local.selectors); let mut recomposed_byte = AB::Expr::zero(); for i in 0..8 { - builder.assert_bool(memory_columns.most_sig_byte_decomp[i]); + builder + .when(is_mem.clone()) + .assert_bool(memory_columns.most_sig_byte_decomp[i]); recomposed_byte += memory_columns.most_sig_byte_decomp[i] * AB::Expr::from_canonical_u8(1 << i); } diff --git a/core/src/cpu/air/mod.rs b/core/src/cpu/air/mod.rs index 11a985bb5..653c809c9 100644 --- a/core/src/cpu/air/mod.rs +++ b/core/src/cpu/air/mod.rs @@ -22,6 +22,7 @@ use crate::bytes::ByteOpcode; use crate::cpu::columns::OpcodeSelectorCols; use crate::cpu::columns::{CpuCols, NUM_CPU_COLS}; use crate::cpu::CpuChip; +use crate::operations::BabyBearWordRangeChecker; use crate::runtime::Opcode; use super::columns::eval_channel_selectors; @@ -84,6 +85,7 @@ where local.op_c_val(), local.shard, local.channel, + local.nonce, is_alu_instruction, ); @@ -175,6 +177,26 @@ impl CpuChip { .when(is_jump_instruction.clone()) .assert_eq(jump_columns.next_pc.reduce::(), local.next_pc); + // Range check op_a, pc, and next_pc. + BabyBearWordRangeChecker::::range_check( + builder, + local.op_a_val(), + jump_columns.op_a_range_checker, + is_jump_instruction.clone(), + ); + BabyBearWordRangeChecker::::range_check( + builder, + jump_columns.pc, + jump_columns.pc_range_checker, + local.selectors.is_jal.into(), + ); + BabyBearWordRangeChecker::::range_check( + builder, + jump_columns.next_pc, + jump_columns.next_pc_range_checker, + is_jump_instruction.clone(), + ); + // Verify that the new pc is calculated correctly for JAL instructions. builder.send_alu( AB::Expr::from_canonical_u32(Opcode::ADD as u32), @@ -183,6 +205,7 @@ impl CpuChip { local.op_b_val(), local.shard, local.channel, + jump_columns.jal_nonce, local.selectors.is_jal, ); @@ -194,6 +217,7 @@ impl CpuChip { local.op_c_val(), local.shard, local.channel, + jump_columns.jalr_nonce, local.selectors.is_jalr, ); } @@ -208,6 +232,14 @@ impl CpuChip { .when(local.selectors.is_auipc) .assert_eq(auipc_columns.pc.reduce::(), local.pc); + // Range check the pc. + BabyBearWordRangeChecker::::range_check( + builder, + auipc_columns.pc, + auipc_columns.pc_range_checker, + local.selectors.is_auipc.into(), + ); + // Verify that op_a == pc + op_b. builder.send_alu( AB::Expr::from_canonical_u32(Opcode::ADD as u32), @@ -216,6 +248,7 @@ impl CpuChip { local.op_b_val(), local.shard, local.channel, + auipc_columns.auipc_nonce, local.selectors.is_auipc, ); } @@ -288,17 +321,16 @@ impl CpuChip { next: &CpuCols, is_branch_instruction: AB::Expr, ) { - // Verify that if is_sequential_instr is true, assert that local.is_real is true. - // This is needed for the following constraint, which is already degree 3. - builder - .when(local.is_sequential_instr) - .assert_one(local.is_real); - // When is_sequential_instr is true, assert that instruction is not branch, jump, or halt. // Note that the condition `when(local_is_real)` is implied from the previous constraint. let is_halt = self.get_is_halt_syscall::(builder, local); - builder.when(local.is_sequential_instr).assert_zero( - is_branch_instruction + local.selectors.is_jal + local.selectors.is_jalr + is_halt, + builder.when(local.is_real).assert_eq( + local.is_sequential_instr, + AB::Expr::one() + - (is_branch_instruction + + local.selectors.is_jal + + local.selectors.is_jalr + + is_halt), ); // Verify that the pc increments by 4 for all instructions except branch, jump and halt instructions. diff --git a/core/src/cpu/air/register.rs b/core/src/cpu/air/register.rs index e0b989c2b..23b6551d1 100644 --- a/core/src/cpu/air/register.rs +++ b/core/src/cpu/air/register.rs @@ -57,6 +57,15 @@ impl CpuChip { local.is_real, ); + // Always range check the word value in `op_a`, as JUMP instructions may witness + // an invalid word and write it to memory. + builder.slice_range_check_u8( + &local.op_a_access.access.value.0, + local.shard, + local.channel, + local.is_real, + ); + // If we are performing a branch or a store, then the value of `a` is the previous value. builder .when(is_branch_instruction.clone() + self.is_store_instruction::(&local.selectors)) diff --git a/core/src/cpu/columns/auipc.rs b/core/src/cpu/columns/auipc.rs index a6eb410e7..fa6871c21 100644 --- a/core/src/cpu/columns/auipc.rs +++ b/core/src/cpu/columns/auipc.rs @@ -1,7 +1,7 @@ use sp1_derive::AlignedBorrow; use std::mem::size_of; -use crate::air::Word; +use crate::{air::Word, operations::BabyBearWordRangeChecker}; pub const NUM_AUIPC_COLS: usize = size_of::>(); @@ -10,4 +10,6 @@ pub const NUM_AUIPC_COLS: usize = size_of::>(); pub struct AuipcCols { /// The current program counter. pub pc: Word, + pub pc_range_checker: BabyBearWordRangeChecker, + pub auipc_nonce: T, } diff --git a/core/src/cpu/columns/branch.rs b/core/src/cpu/columns/branch.rs index 06a77ad30..c6298ef0f 100644 --- a/core/src/cpu/columns/branch.rs +++ b/core/src/cpu/columns/branch.rs @@ -1,7 +1,7 @@ use sp1_derive::AlignedBorrow; use std::mem::size_of; -use crate::air::Word; +use crate::{air::Word, operations::BabyBearWordRangeChecker}; pub const NUM_BRANCH_COLS: usize = size_of::>(); @@ -11,9 +11,11 @@ pub const NUM_BRANCH_COLS: usize = size_of::>(); pub struct BranchCols { /// The current program counter. pub pc: Word, + pub pc_range_checker: BabyBearWordRangeChecker, /// The next program counter. pub next_pc: Word, + pub next_pc_range_checker: BabyBearWordRangeChecker, /// Whether a equals b. pub a_eq_b: T, @@ -23,4 +25,13 @@ pub struct BranchCols { /// Whether a is less than b. pub a_lt_b: T, + + /// The nonce of the operation to compute `a_lt_b`. + pub a_lt_b_nonce: T, + + /// The nonce of the operation to compute `a_gt_b`. + pub a_gt_b_nonce: T, + + /// The nonce of the operation to compute `next_pc`. + pub next_pc_nonce: T, } diff --git a/core/src/cpu/columns/ecall.rs b/core/src/cpu/columns/ecall.rs index 927b70614..5d91622c3 100644 --- a/core/src/cpu/columns/ecall.rs +++ b/core/src/cpu/columns/ecall.rs @@ -26,4 +26,7 @@ pub struct EcallCols { /// Field to store the word index passed into the COMMIT ecall. index_bitmap[word index] should /// be set to 1 and everything else set to 0. pub index_bitmap: [T; PV_DIGEST_NUM_WORDS], + + /// The nonce of the syscall operation. + pub syscall_nonce: T, } diff --git a/core/src/cpu/columns/jump.rs b/core/src/cpu/columns/jump.rs index ca94f3eca..0e1b5701f 100644 --- a/core/src/cpu/columns/jump.rs +++ b/core/src/cpu/columns/jump.rs @@ -1,7 +1,7 @@ use sp1_derive::AlignedBorrow; use std::mem::size_of; -use crate::air::Word; +use crate::{air::Word, operations::BabyBearWordRangeChecker}; pub const NUM_JUMP_COLS: usize = size_of::>(); @@ -10,7 +10,15 @@ pub const NUM_JUMP_COLS: usize = size_of::>(); pub struct JumpCols { /// The current program counter. pub pc: Word, + pub pc_range_checker: BabyBearWordRangeChecker, - /// THe next program counter. + /// The next program counter. pub next_pc: Word, + pub next_pc_range_checker: BabyBearWordRangeChecker, + + // A range checker for `op_a` which may contain `pc + 4`. + pub op_a_range_checker: BabyBearWordRangeChecker, + + pub jal_nonce: T, + pub jalr_nonce: T, } diff --git a/core/src/cpu/columns/memory.rs b/core/src/cpu/columns/memory.rs index fc54de34c..baab9e1fc 100644 --- a/core/src/cpu/columns/memory.rs +++ b/core/src/cpu/columns/memory.rs @@ -1,7 +1,7 @@ use sp1_derive::AlignedBorrow; use std::mem::size_of; -use crate::{air::Word, memory::MemoryReadWriteCols}; +use crate::{air::Word, memory::MemoryReadWriteCols, operations::BabyBearWordRangeChecker}; pub const NUM_MEMORY_COLUMNS: usize = size_of::>(); @@ -17,7 +17,11 @@ pub struct MemoryColumns { // addr_offset = addr_word % 4 // Note that this all needs to be verified in the AIR pub addr_word: Word, + pub addr_word_range_checker: BabyBearWordRangeChecker, + pub addr_aligned: T, + /// The LE bit decomp of the least significant byte of address aligned. + pub aa_least_sig_byte_decomp: [T; 6], pub addr_offset: T, pub memory_access: MemoryReadWriteCols, @@ -28,4 +32,7 @@ pub struct MemoryColumns { // LE bit decomposition for the most significant byte of memory value. This is used to determine // the sign for that value (used for LB and LH). pub most_sig_byte_decomp: [T; 8], + + pub addr_word_nonce: T, + pub unsigned_mem_val_nonce: T, } diff --git a/core/src/cpu/columns/mod.rs b/core/src/cpu/columns/mod.rs index d81bd806f..968c58362 100644 --- a/core/src/cpu/columns/mod.rs +++ b/core/src/cpu/columns/mod.rs @@ -40,6 +40,8 @@ pub struct CpuCols { /// The channel value, used for byte lookup multiplicity. pub channel: T, + pub nonce: T, + /// The clock cycle value. This should be within 24 bits. pub clk: T, /// The least significant 16 bit limb of clk. @@ -97,6 +99,8 @@ pub struct CpuCols { /// memory opcodes (i.e. LB, LH, LW, LBU, and LHU). pub unsigned_mem_val: Word, + pub unsigned_mem_val_nonce: T, + /// The result of selectors.is_ecall * the send_to_table column for the ECALL opcode. pub ecall_mul_send_to_table: T, diff --git a/core/src/cpu/event.rs b/core/src/cpu/event.rs index 2170d91d5..cdd38f476 100644 --- a/core/src/cpu/event.rs +++ b/core/src/cpu/event.rs @@ -51,4 +51,15 @@ pub struct CpuEvent { /// Exit code called with halt. pub exit_code: u32, + + pub alu_lookup_id: usize, + pub syscall_lookup_id: usize, + pub memory_add_lookup_id: usize, + pub memory_sub_lookup_id: usize, + pub branch_gt_lookup_id: usize, + pub branch_lt_lookup_id: usize, + pub branch_add_lookup_id: usize, + pub jump_jal_lookup_id: usize, + pub jump_jalr_lookup_id: usize, + pub auipc_lookup_id: usize, } diff --git a/core/src/cpu/trace.rs b/core/src/cpu/trace.rs index b65c4e43c..893faa385 100644 --- a/core/src/cpu/trace.rs +++ b/core/src/cpu/trace.rs @@ -1,3 +1,4 @@ +use std::array; use std::borrow::BorrowMut; use std::collections::HashMap; @@ -11,6 +12,8 @@ use tracing::instrument; use super::columns::{CPU_COL_MAP, NUM_CPU_COLS}; use super::{CpuChip, CpuEvent}; use crate::air::MachineAir; +use crate::air::Word; +use crate::alu::create_alu_lookups; use crate::alu::{self, AluEvent}; use crate::bytes::event::ByteRecord; use crate::bytes::{ByteLookupEvent, ByteOpcode}; @@ -42,7 +45,7 @@ impl MachineAir for CpuChip { let mut rows_with_events = input .cpu_events .par_iter() - .map(|op: &CpuEvent| self.event_to_row::(*op)) + .map(|op: &CpuEvent| self.event_to_row::(*op, &input.nonce_lookup)) .collect::>(); // No need to sort by the shard, since the cpu events are already partitioned by that. @@ -91,7 +94,7 @@ impl MachineAir for CpuChip { let mut alu = HashMap::new(); let mut blu: Vec<_> = Vec::default(); ops.iter().for_each(|op| { - let (_, alu_events, blu_events) = self.event_to_row::(*op); + let (_, alu_events, blu_events) = self.event_to_row::(*op, &HashMap::new()); alu_events.into_iter().for_each(|(key, value)| { alu.entry(key).or_insert(Vec::default()).extend(value); }); @@ -124,6 +127,7 @@ impl CpuChip { fn event_to_row( &self, event: CpuEvent, + nonce_lookup: &HashMap, ) -> ( [F; NUM_CPU_COLS], HashMap>, @@ -138,6 +142,14 @@ impl CpuChip { // Populate shard and clk columns. self.populate_shard_clk(cols, event, &mut new_blu_events); + // Populate the nonce. + cols.nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.alu_lookup_id) + .copied() + .unwrap_or_default(), + ); + // Populate basic fields. cols.pc = F::from_canonical_u32(event.pc); cols.next_pc = F::from_canonical_u32(event.next_pc); @@ -150,17 +162,45 @@ impl CpuChip { // Populate memory accesses for a, b, and c. if let Some(record) = event.a_record { cols.op_a_access - .populate(event.channel, record, &mut new_blu_events) + .populate(event.channel, record, &mut new_blu_events); } if let Some(MemoryRecordEnum::Read(record)) = event.b_record { cols.op_b_access - .populate(event.channel, record, &mut new_blu_events) + .populate(event.channel, record, &mut new_blu_events); } if let Some(MemoryRecordEnum::Read(record)) = event.c_record { cols.op_c_access - .populate(event.channel, record, &mut new_blu_events) + .populate(event.channel, record, &mut new_blu_events); } + // Populate range checks for a. + let a_bytes = cols + .op_a_access + .access + .value + .0 + .iter() + .map(|x| x.as_canonical_u32()) + .collect::>(); + new_blu_events.push(ByteLookupEvent { + shard: event.shard, + channel: event.channel, + opcode: ByteOpcode::U8Range, + a1: 0, + a2: 0, + b: a_bytes[0], + c: a_bytes[1], + }); + new_blu_events.push(ByteLookupEvent { + shard: event.shard, + channel: event.channel, + opcode: ByteOpcode::U8Range, + a1: 0, + a2: 0, + b: a_bytes[2], + c: a_bytes[3], + }); + // Populate memory accesses for reading from memory. assert_eq!(event.memory_record.is_some(), event.memory.is_some()); let memory_columns = cols.opcode_specific_columns.memory_mut(); @@ -171,19 +211,23 @@ impl CpuChip { } // Populate memory, branch, jump, and auipc specific fields. - self.populate_memory(cols, event, &mut new_alu_events, &mut new_blu_events); - self.populate_branch(cols, event, &mut new_alu_events); - self.populate_jump(cols, event, &mut new_alu_events); - self.populate_auipc(cols, event, &mut new_alu_events); - let is_halt = self.populate_ecall(cols, event); - - if !event.instruction.is_branch_instruction() - && !event.instruction.is_jump_instruction() - && !event.instruction.is_ecall_instruction() - && !is_halt - { - cols.is_sequential_instr = F::one(); - } + self.populate_memory( + cols, + event, + &mut new_alu_events, + &mut new_blu_events, + nonce_lookup, + ); + self.populate_branch(cols, event, &mut new_alu_events, nonce_lookup); + self.populate_jump(cols, event, &mut new_alu_events, nonce_lookup); + self.populate_auipc(cols, event, &mut new_alu_events, nonce_lookup); + let is_halt = self.populate_ecall(cols, event, nonce_lookup); + + cols.is_sequential_instr = F::from_bool( + !event.instruction.is_branch_instruction() + && !event.instruction.is_jump_instruction() + && !is_halt, + ); // Assert that the instruction is not a no-op. cols.is_real = F::one(); @@ -243,6 +287,7 @@ impl CpuChip { event: CpuEvent, new_alu_events: &mut HashMap>, new_blu_events: &mut Vec, + nonce_lookup: &HashMap, ) { if !matches!( event.instruction.opcode, @@ -261,12 +306,20 @@ impl CpuChip { // Populate addr_word and addr_aligned columns. let memory_columns = cols.opcode_specific_columns.memory_mut(); let memory_addr = event.b.wrapping_add(event.c); + let aligned_addr = memory_addr - memory_addr % WORD_SIZE as u32; memory_columns.addr_word = memory_addr.into(); - memory_columns.addr_aligned = - F::from_canonical_u32(memory_addr - memory_addr % WORD_SIZE as u32); + memory_columns.addr_word_range_checker.populate(memory_addr); + memory_columns.addr_aligned = F::from_canonical_u32(aligned_addr); + + // Populate the aa_least_sig_byte_decomp columns. + assert!(aligned_addr % 4 == 0); + let aligned_addr_ls_byte = (aligned_addr & 0x000000FF) as u8; + let bits: [bool; 8] = array::from_fn(|i| aligned_addr_ls_byte & (1 << i) != 0); + memory_columns.aa_least_sig_byte_decomp = array::from_fn(|i| F::from_bool(bits[i + 2])); // Add event to ALU check to check that addr == b + c let add_event = AluEvent { + lookup_id: event.memory_add_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -274,11 +327,18 @@ impl CpuChip { a: memory_addr, b: event.b, c: event.c, + sub_lookups: create_alu_lookups(), }; new_alu_events .entry(Opcode::ADD) .and_modify(|op_new_events| op_new_events.push(add_event)) .or_insert(vec![add_event]); + memory_columns.addr_word_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.memory_add_lookup_id) + .copied() + .unwrap_or_default(), + ); // Populate memory offsets. let addr_offset = (memory_addr % WORD_SIZE as u32) as u8; @@ -332,6 +392,7 @@ impl CpuChip { if memory_columns.most_sig_byte_decomp[7] == F::one() { cols.mem_value_is_neg = F::one(); let sub_event = AluEvent { + lookup_id: event.memory_sub_lookup_id, channel: event.channel, shard: event.shard, clk: event.clk, @@ -339,7 +400,14 @@ impl CpuChip { a: event.a, b: cols.unsigned_mem_val.to_u32(), c: sign_value, + sub_lookups: create_alu_lookups(), }; + cols.unsigned_mem_val_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.memory_sub_lookup_id) + .copied() + .unwrap_or_default(), + ); new_alu_events .entry(Opcode::SUB) @@ -370,6 +438,7 @@ impl CpuChip { cols: &mut CpuCols, event: CpuEvent, alu_events: &mut HashMap>, + nonce_lookup: &HashMap, ) { if event.instruction.is_branch_instruction() { let branch_columns = cols.opcode_specific_columns.branch_mut(); @@ -395,8 +464,10 @@ impl CpuChip { } else { Opcode::SLTU }; + // Add the ALU events for the comparisons let lt_comp_event = AluEvent { + lookup_id: event.branch_lt_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -404,7 +475,14 @@ impl CpuChip { a: a_lt_b as u32, b: event.a, c: event.b, + sub_lookups: create_alu_lookups(), }; + branch_columns.a_lt_b_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.branch_lt_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(alu_op_code) @@ -412,6 +490,7 @@ impl CpuChip { .or_insert(vec![lt_comp_event]); let gt_comp_event = AluEvent { + lookup_id: event.branch_gt_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -419,7 +498,14 @@ impl CpuChip { a: a_gt_b as u32, b: event.b, c: event.a, + sub_lookups: create_alu_lookups(), }; + branch_columns.a_gt_b_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.branch_gt_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(alu_op_code) @@ -438,14 +524,17 @@ impl CpuChip { _ => unreachable!(), }; - if branching { - let next_pc = event.pc.wrapping_add(event.c); + let next_pc = event.pc.wrapping_add(event.c); + branch_columns.pc = Word::from(event.pc); + branch_columns.next_pc = Word::from(next_pc); + branch_columns.pc_range_checker.populate(event.pc); + branch_columns.next_pc_range_checker.populate(next_pc); + if branching { cols.branching = F::one(); - branch_columns.pc = event.pc.into(); - branch_columns.next_pc = next_pc.into(); let add_event = AluEvent { + lookup_id: event.branch_add_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -453,7 +542,14 @@ impl CpuChip { a: next_pc, b: event.pc, c: event.c, + sub_lookups: create_alu_lookups(), }; + branch_columns.next_pc_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.branch_add_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(Opcode::ADD) @@ -471,6 +567,7 @@ impl CpuChip { cols: &mut CpuCols, event: CpuEvent, alu_events: &mut HashMap>, + nonce_lookup: &HashMap, ) { if event.instruction.is_jump_instruction() { let jump_columns = cols.opcode_specific_columns.jump_mut(); @@ -478,10 +575,14 @@ impl CpuChip { match event.instruction.opcode { Opcode::JAL => { let next_pc = event.pc.wrapping_add(event.b); - jump_columns.pc = event.pc.into(); - jump_columns.next_pc = next_pc.into(); + jump_columns.op_a_range_checker.populate(event.a); + jump_columns.pc = Word::from(event.pc); + jump_columns.pc_range_checker.populate(event.pc); + jump_columns.next_pc = Word::from(next_pc); + jump_columns.next_pc_range_checker.populate(next_pc); let add_event = AluEvent { + lookup_id: event.jump_jal_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -489,7 +590,14 @@ impl CpuChip { a: next_pc, b: event.pc, c: event.b, + sub_lookups: create_alu_lookups(), }; + jump_columns.jal_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.jump_jal_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(Opcode::ADD) @@ -498,9 +606,12 @@ impl CpuChip { } Opcode::JALR => { let next_pc = event.b.wrapping_add(event.c); - jump_columns.next_pc = next_pc.into(); + jump_columns.op_a_range_checker.populate(event.a); + jump_columns.next_pc = Word::from(next_pc); + jump_columns.next_pc_range_checker.populate(next_pc); let add_event = AluEvent { + lookup_id: event.jump_jalr_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -508,7 +619,14 @@ impl CpuChip { a: next_pc, b: event.b, c: event.c, + sub_lookups: create_alu_lookups(), }; + jump_columns.jalr_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.jump_jalr_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(Opcode::ADD) @@ -526,13 +644,16 @@ impl CpuChip { cols: &mut CpuCols, event: CpuEvent, alu_events: &mut HashMap>, + nonce_lookup: &HashMap, ) { if matches!(event.instruction.opcode, Opcode::AUIPC) { let auipc_columns = cols.opcode_specific_columns.auipc_mut(); - auipc_columns.pc = event.pc.into(); + auipc_columns.pc = Word::from(event.pc); + auipc_columns.pc_range_checker.populate(event.pc); let add_event = AluEvent { + lookup_id: event.auipc_lookup_id, shard: event.shard, channel: event.channel, clk: event.clk, @@ -540,7 +661,14 @@ impl CpuChip { a: event.a, b: event.pc, c: event.b, + sub_lookups: create_alu_lookups(), }; + auipc_columns.auipc_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.auipc_lookup_id) + .copied() + .unwrap_or_default(), + ); alu_events .entry(Opcode::ADD) @@ -550,7 +678,12 @@ impl CpuChip { } /// Populate columns related to ECALL. - fn populate_ecall(&self, cols: &mut CpuCols, _: CpuEvent) -> bool { + fn populate_ecall( + &self, + cols: &mut CpuCols, + event: CpuEvent, + nonce_lookup: &HashMap, + ) -> bool { let mut is_halt = false; if cols.selectors.is_ecall == F::one() { @@ -604,6 +737,14 @@ impl CpuChip { ecall_cols.index_bitmap[digest_idx] = F::one(); } + // Write the syscall nonce. + ecall_cols.syscall_nonce = F::from_canonical_u32( + nonce_lookup + .get(&event.syscall_lookup_id) + .copied() + .unwrap_or_default(), + ); + is_halt = syscall_id == F::from_canonical_u32(SyscallCode::HALT.syscall_id()); } @@ -640,41 +781,41 @@ mod tests { use super::*; - use crate::runtime::{tests::simple_program, Instruction, Runtime}; + use crate::runtime::{tests::simple_program, Runtime}; use crate::utils::{run_test, setup_logger, SP1CoreOpts}; - #[test] - fn generate_trace() { - let mut shard = ExecutionRecord::default(); - shard.cpu_events = vec![CpuEvent { - shard: 1, - channel: 0, - clk: 6, - pc: 1, - next_pc: 5, - instruction: Instruction { - opcode: Opcode::ADD, - op_a: 0, - op_b: 1, - op_c: 2, - imm_b: false, - imm_c: false, - }, - a: 1, - a_record: None, - b: 2, - b_record: None, - c: 3, - c_record: None, - memory: None, - memory_record: None, - exit_code: 0, - }]; - let chip = CpuChip::default(); - let trace: RowMajorMatrix = - chip.generate_trace(&shard, &mut ExecutionRecord::default()); - println!("{:?}", trace.values); - } + // #[test] + // fn generate_trace() { + // let mut shard = ExecutionRecord::default(); + // shard.cpu_events = vec![CpuEvent { + // shard: 1, + // channel: 0, + // clk: 6, + // pc: 1, + // next_pc: 5, + // instruction: Instruction { + // opcode: Opcode::ADD, + // op_a: 0, + // op_b: 1, + // op_c: 2, + // imm_b: false, + // imm_c: false, + // }, + // a: 1, + // a_record: None, + // b: 2, + // b_record: None, + // c: 3, + // c_record: None, + // memory: None, + // memory_record: None, + // exit_code: 0, + // }]; + // let chip = CpuChip::default(); + // let trace: RowMajorMatrix = + // chip.generate_trace(&shard, &mut ExecutionRecord::default()); + // println!("{:?}", trace.values); + // } #[test] fn generate_trace_simple_program() { diff --git a/core/src/lookup/interaction.rs b/core/src/lookup/interaction.rs index 74b7a9fc0..1c20938cc 100644 --- a/core/src/lookup/interaction.rs +++ b/core/src/lookup/interaction.rs @@ -74,7 +74,6 @@ impl Interaction { } } -// TODO: add debug for VirtualPairCol so that we can derive Debug for Interaction. impl Debug for Interaction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Interaction") diff --git a/core/src/memory/global.rs b/core/src/memory/global.rs index 3786dd4ca..a6cf49d02 100644 --- a/core/src/memory/global.rs +++ b/core/src/memory/global.rs @@ -1,5 +1,6 @@ use core::borrow::{Borrow, BorrowMut}; use core::mem::size_of; +use std::array; use p3_air::BaseAir; use p3_air::{Air, AirBuilder}; @@ -10,8 +11,9 @@ use p3_matrix::Matrix; use sp1_derive::AlignedBorrow; use super::MemoryInitializeFinalizeEvent; -use crate::air::{AirInteraction, SP1AirBuilder, Word}; -use crate::air::{MachineAir, WordAirBuilder}; +use crate::air::MachineAir; +use crate::air::{AirInteraction, BaseAirBuilder, SP1AirBuilder}; +use crate::operations::BabyBearBitDecomposition; use crate::runtime::{ExecutionRecord, Program}; use crate::utils::pad_to_power_of_two; @@ -62,7 +64,7 @@ impl MachineAir for MemoryChip { MemoryChipType::Finalize => input.memory_finalize_events.clone(), }; memory_events.sort_by_key(|event| event.addr); - let rows: Vec<[F; 8]> = (0..memory_events.len()) // TODO: change this back to par_iter + let rows: Vec<[F; NUM_MEMORY_INIT_COLS]> = (0..memory_events.len()) // OPT: change this to par_iter .map(|i| { let MemoryInitializeFinalizeEvent { addr, @@ -71,14 +73,37 @@ impl MachineAir for MemoryChip { timestamp, used, } = memory_events[i]; + let mut row = [F::zero(); NUM_MEMORY_INIT_COLS]; let cols: &mut MemoryInitCols = row.as_mut_slice().borrow_mut(); cols.addr = F::from_canonical_u32(addr); + cols.addr_bits.populate(addr); cols.shard = F::from_canonical_u32(shard); cols.timestamp = F::from_canonical_u32(timestamp); - cols.value = value.into(); + cols.value = array::from_fn(|i| F::from_canonical_u32((value >> i) & 1)); cols.is_real = F::from_canonical_u32(used); + if i != memory_events.len() - 1 { + let next_addr = memory_events[i + 1].addr; + assert_ne!(next_addr, addr); + + cols.addr_bits.populate(addr); + + cols.seen_diff_bits[0] = F::zero(); + for j in 0..32 { + let rev_j = 32 - j - 1; + let next_bit = ((next_addr >> rev_j) & 1) == 1; + let local_bit = ((addr >> rev_j) & 1) == 1; + cols.match_bits[j] = + F::from_bool((local_bit && next_bit) || (!local_bit && !next_bit)); + cols.seen_diff_bits[j + 1] = cols.seen_diff_bits[j] + + (F::one() - cols.seen_diff_bits[j]) * (F::one() - cols.match_bits[j]); + cols.not_match_and_not_seen_diff_bits[j] = + (F::one() - cols.match_bits[j]) * (F::one() - cols.seen_diff_bits[j]); + } + assert_eq!(cols.seen_diff_bits[cols.seen_diff_bits.len() - 1], F::one()); + } + row }) .collect::>(); @@ -101,7 +126,7 @@ impl MachineAir for MemoryChip { } } -#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] +#[derive(AlignedBorrow, Debug, Clone, Copy)] #[repr(C)] pub struct MemoryInitCols { /// The shard number of the memory access. @@ -113,8 +138,20 @@ pub struct MemoryInitCols { /// The address of the memory access. pub addr: T, + /// A bit decomposition of `addr`. + pub addr_bits: BabyBearBitDecomposition, + + // Whether the i'th bit matches the next addr's bit. + pub match_bits: [T; 32], + + // Whether we've seen a different bit in the comparison. + pub seen_diff_bits: [T; 33], + + // Whether the i'th bit doesn't match the next addr's bit and we haven't seen a diff bitn yet. + pub not_match_and_not_seen_diff_bits: [T; 32], + /// The value of the memory access. - pub value: Word, + pub value: [T; 32], /// Whether the memory access is a real access. pub is_real: T, @@ -130,10 +167,29 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &MemoryInitCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &MemoryInitCols = (*next).borrow(); + + builder.assert_bool(local.is_real); + for i in 0..32 { + builder.assert_bool(local.value[i]); + } + + let mut byte1 = AB::Expr::zero(); + let mut byte2 = AB::Expr::zero(); + let mut byte3 = AB::Expr::zero(); + let mut byte4 = AB::Expr::zero(); + for i in 0..8 { + byte1 += local.value[i].into() * AB::F::from_canonical_u8(1 << i); + byte2 += local.value[i + 8].into() * AB::F::from_canonical_u8(1 << i); + byte3 += local.value[i + 16].into() * AB::F::from_canonical_u8(1 << i); + byte4 += local.value[i + 24].into() * AB::F::from_canonical_u8(1 << i); + } + let value = [byte1, byte2, byte3, byte4]; if self.kind == MemoryChipType::Initialize { let mut values = vec![AB::Expr::zero(), AB::Expr::zero(), local.addr.into()]; - values.extend(local.value.map(Into::into)); + values.extend(value.map(Into::into)); builder.receive(AirInteraction::new( values, local.is_real.into(), @@ -145,7 +201,7 @@ where local.timestamp.into(), local.addr.into(), ]; - values.extend(local.value.map(Into::into)); + values.extend(value); builder.send(AirInteraction::new( values, local.is_real.into(), @@ -153,16 +209,106 @@ where )); } + // We want to assert addr < addr'. Assume seen_diff_0 = 0. + // + // match_i = (addr_i & addr'_i) || (!addr_i & !addr'_i) + // => + // match_i == addr_i * addr_i + (1 - addr_i) * (1 - addr'_i) + // + // when !match_i and !seen_diff_i, then enforce (addr_i == 0) and (addr'_i == 1). + // if seen_diff_i: + // seen_diff_{i+1} = 1 + // else: + // seen_diff_{i+1} = !match_i + // => + // builder.when(!match_i * !seen_diff_i).assert_zero(addr_i) + // builder.when(!match_i * !seen_diff_i).assert_one(addr'_i) + // seen_diff_bit_{i+1} == seen_diff_i + (1-seen_diff_i) * (1 - match_i) + // + // at the end of the algorithm, assert that we've seen a diff bit. + // => + // seen_diff_bit_{last} == 1 + + // Assert that we start with assuming that we haven't seen a diff bit. + builder.assert_zero(local.seen_diff_bits[0]); + + for i in 0..local.addr_bits.bits.len() { + // Compute the i'th msb bit's index. + let rev_i = local.addr_bits.bits.len() - i - 1; + + // Compute whether the i'th msb bit matches. + let match_i = local.addr_bits.bits[rev_i] * next.addr_bits.bits[rev_i] + + (AB::Expr::one() - local.addr_bits.bits[rev_i]) + * (AB::Expr::one() - next.addr_bits.bits[rev_i]); + builder + .when_transition() + .when(next.is_real) + .assert_eq(match_i.clone(), local.match_bits[i]); + + // Compute whether it's not a match and we haven't seen a diff bit. + let not_match_and_not_seen_diff_i = (AB::Expr::one() - local.match_bits[i]) + * (AB::Expr::one() - local.seen_diff_bits[i]); + builder.when_transition().when(next.is_real).assert_eq( + local.not_match_and_not_seen_diff_bits[i], + not_match_and_not_seen_diff_i, + ); + + // If the i'th msb bit doesn't match and it's the first time we've seen a diff bit, + // then enforce that the next bit is one and the current bit is zero. + builder + .when_transition() + .when(local.not_match_and_not_seen_diff_bits[i]) + .when(next.is_real) + .assert_zero(local.addr_bits.bits[rev_i]); + builder + .when_transition() + .when(local.not_match_and_not_seen_diff_bits[i]) + .when(next.is_real) + .assert_one(next.addr_bits.bits[rev_i]); + + // Update the seen diff bits. + builder.when_transition().assert_eq( + local.seen_diff_bits[i + 1], + local.seen_diff_bits[i] + local.not_match_and_not_seen_diff_bits[i], + ); + } + + // Assert that on rows where the next row is real, we've seen a diff bit. + builder + .when_transition() + .when(next.is_real) + .assert_one(local.seen_diff_bits[local.addr_bits.bits.len()]); + + // Canonically decompose the address into bits so we can do comparisons. + BabyBearBitDecomposition::::range_check( + builder, + local.addr, + local.addr_bits, + local.is_real.into(), + ); + + // Assert that the real rows are all padded to the top. + builder + .when_transition() + .when_not(local.is_real) + .assert_zero(next.is_real); + + if self.kind == MemoryChipType::Initialize { + builder + .when(local.is_real) + .assert_eq(local.timestamp, AB::F::one()); + } + // Register %x0 should always be 0. See 2.6 Load and Store Instruction on // P.18 of the RISC-V spec. To ensure that, we expect that the first row of the Initialize // and Finalize global memory chip is for register %x0 (i.e. addr = 0x0), and that those rows // have a value of 0. Additionally, in the CPU air, we ensure that whenever op_a is set to // %x0, its value is 0. - // - // TODO: Add a similar check for MemoryChipType::Initialize. - if self.kind == MemoryChipType::Finalize { + if self.kind == MemoryChipType::Initialize || self.kind == MemoryChipType::Finalize { builder.when_first_row().assert_zero(local.addr); - builder.when_first_row().assert_word_zero(local.value); + for i in 0..32 { + builder.when_first_row().assert_zero(local.value[i]); + } } } } diff --git a/core/src/memory/mod.rs b/core/src/memory/mod.rs index 7acdee1fb..4246db14c 100644 --- a/core/src/memory/mod.rs +++ b/core/src/memory/mod.rs @@ -27,7 +27,7 @@ impl MemoryInitializeFinalizeEvent { addr, value, shard: 0, - timestamp: 0, + timestamp: 1, used: if used { 1 } else { 0 }, } } diff --git a/core/src/memory/program.rs b/core/src/memory/program.rs index 3d922c4ae..64eeb25a2 100644 --- a/core/src/memory/program.rs +++ b/core/src/memory/program.rs @@ -1,6 +1,6 @@ use core::borrow::{Borrow, BorrowMut}; use core::mem::size_of; -use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, BaseAir, PairBuilder}; +use p3_air::{Air, AirBuilderWithPublicValues, BaseAir, PairBuilder}; use p3_field::AbstractField; use p3_field::PrimeField; use p3_matrix::dense::RowMajorMatrix; @@ -10,7 +10,6 @@ use sp1_derive::AlignedBorrow; use crate::air::{AirInteraction, PublicValues, SP1AirBuilder}; use crate::air::{MachineAir, Word}; -use crate::operations::IsZeroOperation; use crate::runtime::{ExecutionRecord, Program}; use crate::utils::pad_to_power_of_two; @@ -31,10 +30,10 @@ pub struct MemoryProgramPreprocessedCols { #[derive(AlignedBorrow, Clone, Copy, Default)] #[repr(C)] pub struct MemoryProgramMultCols { - /// The multiplicity of the event, must be 1 in the first shard and 0 otherwise. + /// The multiplicity of the event. + /// + /// This column is technically redundant with `is_real`, but it's included for clarity. pub multiplicity: T, - /// Columns to see if current shard is 1. - pub is_first_shard: IsZeroOperation, } /// Chip that initializes memory that is provided from the program. The table is preprocessed and @@ -120,8 +119,6 @@ impl MachineAir for MemoryProgramChip { let mut row = [F::zero(); NUM_MEMORY_PROGRAM_MULT_COLS]; let cols: &mut MemoryProgramMultCols = row.as_mut_slice().borrow_mut(); cols.multiplicity = mult; - IsZeroOperation::populate(&mut cols.is_first_shard, input.index - 1); - row }) .collect::>(); @@ -138,8 +135,8 @@ impl MachineAir for MemoryProgramChip { trace } - fn included(&self, _: &Self::Record) -> bool { - true + fn included(&self, record: &Self::Record) -> bool { + record.index == 1 } } @@ -171,24 +168,15 @@ where .map(|elm| (*elm).into()) .collect::>(), ); - IsZeroOperation::::eval( - builder, - public_values.shard - AB::Expr::one(), - mult_local.is_first_shard, - prep_local.is_real.into(), - ); - let is_first_shard = mult_local.is_first_shard.result; // Multiplicity must be either 0 or 1. builder.assert_bool(mult_local.multiplicity); + // If first shard and preprocessed is real, multiplicity must be one. - builder - .when(is_first_shard * prep_local.is_real) - .assert_one(mult_local.multiplicity); - // If not first shard or preprocessed is not real, multiplicity must be zero. - builder - .when((AB::Expr::one() - is_first_shard) + (AB::Expr::one() - prep_local.is_real)) - .assert_zero(mult_local.multiplicity); + builder.assert_eq(mult_local.multiplicity, prep_local.is_real.into()); + + // The shard this chip is contained in must be one. + builder.assert_one(public_values.shard); let mut values = vec![AB::Expr::zero(), AB::Expr::zero(), prep_local.addr.into()]; values.extend(prep_local.value.map(Into::into)); diff --git a/core/src/operations/baby_bear_range.rs b/core/src/operations/baby_bear_range.rs new file mode 100644 index 000000000..7e1ad0ef4 --- /dev/null +++ b/core/src/operations/baby_bear_range.rs @@ -0,0 +1,88 @@ +use std::array; + +use p3_air::AirBuilder; +use p3_field::{AbstractField, Field}; +use sp1_derive::AlignedBorrow; + +use crate::stark::SP1AirBuilder; + +#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] +#[repr(C)] +pub struct BabyBearBitDecomposition { + /// The bit decoposition of the`value`. + pub bits: [T; 32], + + /// The product of the the bits 3 to 5 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_5: T, + + /// The product of the the bits 3 to 6 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_6: T, + + /// The product of the the bits 3 to 7 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_7: T, +} + +impl BabyBearBitDecomposition { + pub fn populate(&mut self, value: u32) { + self.bits = array::from_fn(|i| F::from_canonical_u32((value >> i) & 1)); + let most_sig_byte_decomp = &self.bits[24..32]; + self.and_most_sig_byte_decomp_3_to_5 = most_sig_byte_decomp[3] * most_sig_byte_decomp[4]; + self.and_most_sig_byte_decomp_3_to_6 = + self.and_most_sig_byte_decomp_3_to_5 * most_sig_byte_decomp[5]; + self.and_most_sig_byte_decomp_3_to_7 = + self.and_most_sig_byte_decomp_3_to_6 * most_sig_byte_decomp[6]; + } + + pub fn range_check( + builder: &mut AB, + value: AB::Var, + cols: BabyBearBitDecomposition, + is_real: AB::Expr, + ) { + let mut reconstructed_value = AB::Expr::zero(); + for (i, bit) in cols.bits.iter().enumerate() { + builder.when(is_real.clone()).assert_bool(*bit); + reconstructed_value += AB::Expr::from_wrapped_u32(1 << i) * *bit; + } + + // Assert that bits2num(bits) == value. + builder + .when(is_real.clone()) + .assert_eq(reconstructed_value, value); + + // Range check that value is less than baby bear modulus. To do this, it is sufficient + // to just do comparisons for the most significant byte. BabyBear's modulus is (in big endian binary) + // 01111000_00000000_00000000_00000001. So we need to check the following conditions: + // 1) if most_sig_byte > 01111000, then fail. + // 2) if most_sig_byte == 01111000, then value's lower sig bytes must all be 0. + // 3) if most_sig_byte < 01111000, then pass. + let most_sig_byte_decomp = &cols.bits[24..32]; + builder + .when(is_real.clone()) + .assert_zero(most_sig_byte_decomp[7]); + + // Compute the product of the "top bits". + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_5, + most_sig_byte_decomp[3] * most_sig_byte_decomp[4], + ); + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_6, + cols.and_most_sig_byte_decomp_3_to_5 * most_sig_byte_decomp[5], + ); + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_7, + cols.and_most_sig_byte_decomp_3_to_6 * most_sig_byte_decomp[6], + ); + + // If the top bits are all 0, then the lower bits must all be 0. + let mut lower_bits_sum: AB::Expr = AB::Expr::zero(); + for bit in cols.bits[0..27].iter() { + lower_bits_sum = lower_bits_sum + *bit; + } + builder + .when(is_real) + .when(cols.and_most_sig_byte_decomp_3_to_7) + .assert_zero(lower_bits_sum); + } +} diff --git a/core/src/operations/baby_bear_word.rs b/core/src/operations/baby_bear_word.rs new file mode 100644 index 000000000..2e773b3e6 --- /dev/null +++ b/core/src/operations/baby_bear_word.rs @@ -0,0 +1,94 @@ +use std::array; + +use p3_air::AirBuilder; +use p3_field::{AbstractField, Field}; +use sp1_derive::AlignedBorrow; + +use crate::{air::Word, stark::SP1AirBuilder}; + +/// A set of columns needed to compute the add of two words. +#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] +#[repr(C)] +pub struct BabyBearWordRangeChecker { + /// Most sig byte LE bit decomposition. + pub most_sig_byte_decomp: [T; 8], + + /// The product of the the bits 3 to 5 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_5: T, + + /// The product of the the bits 3 to 6 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_6: T, + + /// The product of the the bits 3 to 7 in `most_sig_byte_decomp`. + pub and_most_sig_byte_decomp_3_to_7: T, +} + +impl BabyBearWordRangeChecker { + pub fn populate(&mut self, value: u32) { + self.most_sig_byte_decomp = array::from_fn(|i| F::from_bool(value & (1 << (i + 24)) != 0)); + self.and_most_sig_byte_decomp_3_to_5 = + self.most_sig_byte_decomp[3] * self.most_sig_byte_decomp[4]; + self.and_most_sig_byte_decomp_3_to_6 = + self.and_most_sig_byte_decomp_3_to_5 * self.most_sig_byte_decomp[5]; + self.and_most_sig_byte_decomp_3_to_7 = + self.and_most_sig_byte_decomp_3_to_6 * self.most_sig_byte_decomp[6]; + } + + pub fn range_check( + builder: &mut AB, + value: Word, + cols: BabyBearWordRangeChecker, + is_real: AB::Expr, + ) { + let mut recomposed_byte = AB::Expr::zero(); + cols.most_sig_byte_decomp + .iter() + .enumerate() + .for_each(|(i, value)| { + builder.when(is_real.clone()).assert_bool(*value); + recomposed_byte = + recomposed_byte.clone() + AB::Expr::from_canonical_usize(1 << i) * *value; + }); + + builder + .when(is_real.clone()) + .assert_eq(recomposed_byte, value[3]); + + // Range check that value is less than baby bear modulus. To do this, it is sufficient + // to just do comparisons for the most significant byte. BabyBear's modulus is (in big endian binary) + // 01111000_00000000_00000000_00000001. So we need to check the following conditions: + // 1) if most_sig_byte > 01111000, then fail. + // 2) if most_sig_byte == 01111000, then value's lower sig bytes must all be 0. + // 3) if most_sig_byte < 01111000, then pass. + builder + .when(is_real.clone()) + .assert_zero(cols.most_sig_byte_decomp[7]); + + // Compute the product of the "top bits". + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_5, + cols.most_sig_byte_decomp[3] * cols.most_sig_byte_decomp[4], + ); + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_6, + cols.and_most_sig_byte_decomp_3_to_5 * cols.most_sig_byte_decomp[5], + ); + builder.when(is_real.clone()).assert_eq( + cols.and_most_sig_byte_decomp_3_to_7, + cols.and_most_sig_byte_decomp_3_to_6 * cols.most_sig_byte_decomp[6], + ); + + let bottom_bits: AB::Expr = cols.most_sig_byte_decomp[0..3] + .iter() + .map(|bit| (*bit).into()) + .sum(); + builder + .when(is_real.clone()) + .when(cols.and_most_sig_byte_decomp_3_to_7) + .assert_zero(bottom_bits); + builder + .when(is_real) + .when(cols.and_most_sig_byte_decomp_3_to_7) + .assert_zero(value[0] + value[1] + value[2]); + } +} diff --git a/core/src/operations/field/field_op.rs b/core/src/operations/field/field_op.rs index ae04e2b9b..995142c2f 100644 --- a/core/src/operations/field/field_op.rs +++ b/core/src/operations/field/field_op.rs @@ -445,7 +445,6 @@ mod tests { let mut challenger = config.challenger(); - // TODO: test with other fields let chip: FieldOpChip = FieldOpChip::new(*op); let shard = ExecutionRecord::default(); let trace: RowMajorMatrix = diff --git a/core/src/operations/field/field_sqrt.rs b/core/src/operations/field/field_sqrt.rs index c0401a1d4..e16de147b 100644 --- a/core/src/operations/field/field_sqrt.rs +++ b/core/src/operations/field/field_sqrt.rs @@ -83,6 +83,20 @@ impl FieldSqrtCols { }; record.add_byte_lookup_event(and_event); + // Add the byte range check for `sqrt`. + record.add_u8_range_checks( + shard, + channel, + self.multiplication + .result + .0 + .as_slice() + .iter() + .map(|x| x.as_canonical_u32() as u8) + .collect::>() + .as_slice(), + ); + sqrt } } @@ -129,6 +143,14 @@ where is_real.clone(), ); + // Range check that `sqrt` limbs are bytes. + builder.slice_range_check_u8( + sqrt.0.as_slice(), + shard.clone(), + channel.clone(), + is_real.clone(), + ); + // Assert that the square root is the positive one, i.e., with least significant bit 0. // This is done by computing LSB = least_significant_byte & 1. builder.assert_bool(self.lsb); diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 242c9100b..e3fbcc78b 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -8,6 +8,8 @@ mod add; mod add4; mod add5; mod and; +mod baby_bear_range; +mod baby_bear_word; pub mod field; mod fixed_rotate_right; mod fixed_shift_right; @@ -22,6 +24,8 @@ pub use add::*; pub use add4::*; pub use add5::*; pub use and::*; +pub use baby_bear_range::*; +pub use baby_bear_word::*; pub use fixed_rotate_right::*; pub use fixed_shift_right::*; pub use is_equal_word::*; diff --git a/core/src/operations/or.rs b/core/src/operations/or.rs index 8cb3f0019..b30821532 100644 --- a/core/src/operations/or.rs +++ b/core/src/operations/or.rs @@ -10,8 +10,6 @@ use crate::disassembler::WORD_SIZE; use crate::runtime::ExecutionRecord; /// A set of columns needed to compute the or of two words. -/// -/// TODO: This is currently not in use, and thus not tested thoroughly yet. #[derive(AlignedBorrow, Default, Debug, Clone, Copy)] #[repr(C)] pub struct OrOperation { diff --git a/core/src/runtime/mod.rs b/core/src/runtime/mod.rs index 003d35e8c..3eb8c4ead 100644 --- a/core/src/runtime/mod.rs +++ b/core/src/runtime/mod.rs @@ -30,6 +30,8 @@ use std::sync::Arc; use thiserror::Error; +use crate::alu::create_alu_lookup_id; +use crate::alu::create_alu_lookups; use crate::bytes::NUM_BYTE_LOOKUP_CHANNELS; use crate::memory::MemoryInitializeFinalizeEvent; use crate::utils::SP1CoreOpts; @@ -445,6 +447,8 @@ impl Runtime { memory_store_value: Option, record: MemoryAccessRecord, exit_code: u32, + lookup_id: usize, + syscall_lookup_id: usize, ) { let cpu_event = CpuEvent { shard, @@ -462,14 +466,25 @@ impl Runtime { memory: memory_store_value, memory_record: record.memory, exit_code, + alu_lookup_id: lookup_id, + syscall_lookup_id, + memory_add_lookup_id: create_alu_lookup_id(), + memory_sub_lookup_id: create_alu_lookup_id(), + branch_lt_lookup_id: create_alu_lookup_id(), + branch_gt_lookup_id: create_alu_lookup_id(), + branch_add_lookup_id: create_alu_lookup_id(), + jump_jal_lookup_id: create_alu_lookup_id(), + jump_jalr_lookup_id: create_alu_lookup_id(), + auipc_lookup_id: create_alu_lookup_id(), }; self.record.cpu_events.push(cpu_event); } /// Emit an ALU event. - fn emit_alu(&mut self, clk: u32, opcode: Opcode, a: u32, b: u32, c: u32) { + fn emit_alu(&mut self, clk: u32, opcode: Opcode, a: u32, b: u32, c: u32, lookup_id: usize) { let event = AluEvent { + lookup_id, shard: self.shard(), clk, channel: self.channel(), @@ -477,6 +492,7 @@ impl Runtime { a, b, c, + sub_lookups: create_alu_lookups(), }; match opcode { Opcode::ADD => { @@ -530,10 +546,18 @@ impl Runtime { } /// Set the destination register with the result and emit an ALU event. - fn alu_rw(&mut self, instruction: Instruction, rd: Register, a: u32, b: u32, c: u32) { + fn alu_rw( + &mut self, + instruction: Instruction, + rd: Register, + a: u32, + b: u32, + c: u32, + lookup_id: usize, + ) { self.rw(rd, a); if self.emit_events { - self.emit_alu(self.state.clk, instruction.opcode, a, b, c); + self.emit_alu(self.state.clk, instruction.opcode, a, b, c, lookup_id); } } @@ -586,6 +610,9 @@ impl Runtime { let mut memory_store_value: Option = None; self.memory_accesses = MemoryAccessRecord::default(); + let lookup_id = create_alu_lookup_id(); + let syscall_lookup_id = create_alu_lookup_id(); + if self.should_report && !self.unconstrained { self.report .instruction_counts @@ -599,52 +626,52 @@ impl Runtime { Opcode::ADD => { (rd, b, c) = self.alu_rr(instruction); a = b.wrapping_add(c); - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SUB => { (rd, b, c) = self.alu_rr(instruction); a = b.wrapping_sub(c); - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::XOR => { (rd, b, c) = self.alu_rr(instruction); a = b ^ c; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::OR => { (rd, b, c) = self.alu_rr(instruction); a = b | c; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::AND => { (rd, b, c) = self.alu_rr(instruction); a = b & c; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SLL => { (rd, b, c) = self.alu_rr(instruction); a = b.wrapping_shl(c); - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SRL => { (rd, b, c) = self.alu_rr(instruction); a = b.wrapping_shr(c); - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SRA => { (rd, b, c) = self.alu_rr(instruction); a = (b as i32).wrapping_shr(c) as u32; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SLT => { (rd, b, c) = self.alu_rr(instruction); a = if (b as i32) < (c as i32) { 1 } else { 0 }; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::SLTU => { (rd, b, c) = self.alu_rr(instruction); a = if b < c { 1 } else { 0 }; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } // Load instructions. @@ -818,6 +845,7 @@ impl Runtime { let syscall_impl = self.get_syscall(syscall).cloned(); let mut precompile_rt = SyscallContext::new(self); + precompile_rt.syscall_lookup_id = syscall_lookup_id; let (precompile_next_pc, precompile_cycles, returned_exit_code) = if let Some(syscall_impl) = syscall_impl { // Executing a syscall optionally returns a value to write to the t0 register. @@ -862,22 +890,22 @@ impl Runtime { Opcode::MUL => { (rd, b, c) = self.alu_rr(instruction); a = b.wrapping_mul(c); - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::MULH => { (rd, b, c) = self.alu_rr(instruction); a = (((b as i32) as i64).wrapping_mul((c as i32) as i64) >> 32) as u32; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::MULHU => { (rd, b, c) = self.alu_rr(instruction); a = ((b as u64).wrapping_mul(c as u64) >> 32) as u32; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::MULHSU => { (rd, b, c) = self.alu_rr(instruction); a = (((b as i32) as i64).wrapping_mul(c as i64) >> 32) as u32; - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::DIV => { (rd, b, c) = self.alu_rr(instruction); @@ -886,7 +914,7 @@ impl Runtime { } else { a = (b as i32).wrapping_div(c as i32) as u32; } - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::DIVU => { (rd, b, c) = self.alu_rr(instruction); @@ -895,7 +923,7 @@ impl Runtime { } else { a = b.wrapping_div(c); } - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::REM => { (rd, b, c) = self.alu_rr(instruction); @@ -904,7 +932,7 @@ impl Runtime { } else { a = (b as i32).wrapping_rem(c as i32) as u32; } - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } Opcode::REMU => { (rd, b, c) = self.alu_rr(instruction); @@ -913,7 +941,7 @@ impl Runtime { } else { a = b.wrapping_rem(c); } - self.alu_rw(instruction, rd, a, b, c); + self.alu_rw(instruction, rd, a, b, c, lookup_id); } // See https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#instruction-aliases @@ -950,6 +978,8 @@ impl Runtime { memory_store_value, self.memory_accesses, exit_code, + lookup_id, + syscall_lookup_id, ); }; Ok(()) @@ -1111,7 +1141,7 @@ impl Runtime { None => &MemoryRecord { value: 0, shard: 0, - timestamp: 0, + timestamp: 1, }, }; memory_finalize_events.push(MemoryInitializeFinalizeEvent::finalize_from_record( diff --git a/core/src/runtime/record.rs b/core/src/runtime/record.rs index 67a2464f9..124e38e22 100644 --- a/core/src/runtime/record.rs +++ b/core/src/runtime/record.rs @@ -17,7 +17,6 @@ use crate::cpu::CpuEvent; use crate::runtime::MemoryInitializeFinalizeEvent; use crate::runtime::MemoryRecordEnum; use crate::stark::MachineRecord; -use crate::syscall::precompiles::blake3::Blake3CompressInnerEvent; use crate::syscall::precompiles::edwards::EdDecompressEvent; use crate::syscall::precompiles::keccak256::KeccakPermuteEvent; use crate::syscall::precompiles::sha256::{ShaCompressEvent, ShaExtendEvent}; @@ -87,8 +86,6 @@ pub struct ExecutionRecord { pub k256_decompress_events: Vec, - pub blake3_compress_inner_events: Vec, - pub bls12381_add_events: Vec, pub bls12381_double_events: Vec, @@ -103,6 +100,8 @@ pub struct ExecutionRecord { /// The public values. pub public_values: PublicValues, + + pub nonce_lookup: HashMap, } pub struct ShardingConfig { @@ -220,10 +219,6 @@ impl MachineRecord for ExecutionRecord { "k256_decompress_events".to_string(), self.k256_decompress_events.len(), ); - stats.insert( - "blake3_compress_inner_events".to_string(), - self.blake3_compress_inner_events.len(), - ); stats.insert( "bls12381_add_events".to_string(), self.bls12381_add_events.len(), @@ -272,8 +267,6 @@ impl MachineRecord for ExecutionRecord { .append(&mut other.bn254_double_events); self.k256_decompress_events .append(&mut other.k256_decompress_events); - self.blake3_compress_inner_events - .append(&mut other.blake3_compress_inner_events); self.bls12381_add_events .append(&mut other.bls12381_add_events); self.bls12381_double_events @@ -356,22 +349,15 @@ impl MachineRecord for ExecutionRecord { } } - // Shard all the other events according to the configuration. - // Shard the ADD events. for (add_chunk, shard) in take(&mut self.add_events) .chunks_mut(config.add_len) .zip(shards.iter_mut()) { shard.add_events.extend_from_slice(add_chunk); - } - - // Shard the MUL events. - for (mul_chunk, shard) in take(&mut self.mul_events) - .chunks_mut(config.mul_len) - .zip(shards.iter_mut()) - { - shard.mul_events.extend_from_slice(mul_chunk); + for (i, event) in add_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the SUB events. @@ -380,6 +366,21 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.sub_events.extend_from_slice(sub_chunk); + for (i, event) in sub_chunk.iter().enumerate() { + self.nonce_lookup + .insert(event.lookup_id, shard.add_events.len() as u32 + i as u32); + } + } + + // Shard the MUL events. + for (mul_chunk, shard) in take(&mut self.mul_events) + .chunks_mut(config.mul_len) + .zip(shards.iter_mut()) + { + shard.mul_events.extend_from_slice(mul_chunk); + for (i, event) in mul_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the bitwise events. @@ -388,6 +389,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.bitwise_events.extend_from_slice(bitwise_chunk); + for (i, event) in bitwise_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the shift left events. @@ -396,6 +400,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.shift_left_events.extend_from_slice(shift_left_chunk); + for (i, event) in shift_left_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the shift right events. @@ -406,6 +413,9 @@ impl MachineRecord for ExecutionRecord { shard .shift_right_events .extend_from_slice(shift_right_chunk); + for (i, event) in shift_right_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the divrem events. @@ -414,6 +424,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.divrem_events.extend_from_slice(divrem_chunk); + for (i, event) in divrem_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Shard the LT events. @@ -422,6 +435,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.lt_events.extend_from_slice(lt_chunk); + for (i, event) in lt_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Keccak-256 permute events. @@ -430,6 +446,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.keccak_permute_events.extend_from_slice(keccak_chunk); + for (i, event) in keccak_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, (i * 24) as u32); + } } // secp256k1 curve add events. @@ -440,6 +459,9 @@ impl MachineRecord for ExecutionRecord { shard .secp256k1_add_events .extend_from_slice(secp256k1_add_chunk); + for (i, event) in secp256k1_add_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // secp256k1 curve double events. @@ -450,6 +472,9 @@ impl MachineRecord for ExecutionRecord { shard .secp256k1_double_events .extend_from_slice(secp256k1_double_chunk); + for (i, event) in secp256k1_double_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // bn254 curve add events. @@ -458,6 +483,9 @@ impl MachineRecord for ExecutionRecord { .zip(shards.iter_mut()) { shard.bn254_add_events.extend_from_slice(bn254_add_chunk); + for (i, event) in bn254_add_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // bn254 curve double events. @@ -468,6 +496,9 @@ impl MachineRecord for ExecutionRecord { shard .bn254_double_events .extend_from_slice(bn254_double_chunk); + for (i, event) in bn254_double_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // BLS12-381 curve add events. @@ -478,6 +509,9 @@ impl MachineRecord for ExecutionRecord { shard .bls12381_add_events .extend_from_slice(bls12381_add_chunk); + for (i, event) in bls12381_add_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // BLS12-381 curve double events. @@ -488,6 +522,9 @@ impl MachineRecord for ExecutionRecord { shard .bls12381_double_events .extend_from_slice(bls12381_double_chunk); + for (i, event) in bls12381_double_chunk.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } } // Put the precompile events in the first shard. @@ -495,27 +532,45 @@ impl MachineRecord for ExecutionRecord { // SHA-256 extend events. first.sha_extend_events = std::mem::take(&mut self.sha_extend_events); + for (i, event) in first.sha_extend_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, (i * 48) as u32); + } // SHA-256 compress events. first.sha_compress_events = std::mem::take(&mut self.sha_compress_events); + for (i, event) in first.sha_compress_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, (i * 24) as u32); + } // Edwards curve add events. first.ed_add_events = std::mem::take(&mut self.ed_add_events); + for (i, event) in first.ed_add_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } // Edwards curve decompress events. first.ed_decompress_events = std::mem::take(&mut self.ed_decompress_events); + for (i, event) in first.ed_decompress_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } // K256 curve decompress events. first.k256_decompress_events = std::mem::take(&mut self.k256_decompress_events); - - // Blake3 compress events . - first.blake3_compress_inner_events = std::mem::take(&mut self.blake3_compress_inner_events); + for (i, event) in first.k256_decompress_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } // Uint256 mul arithmetic events. first.uint256_mul_events = std::mem::take(&mut self.uint256_mul_events); + for (i, event) in first.uint256_mul_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } // Bls12-381 decompress events . first.bls12381_decompress_events = std::mem::take(&mut self.bls12381_decompress_events); + for (i, event) in first.bls12381_decompress_events.iter().enumerate() { + self.nonce_lookup.insert(event.lookup_id, i as u32); + } // Put the memory records in the last shard. let last_shard = shards.last_mut().unwrap(); @@ -527,6 +582,11 @@ impl MachineRecord for ExecutionRecord { .memory_finalize_events .extend_from_slice(&self.memory_finalize_events); + // Copy the nonce lookup to all shards. + for shard in shards.iter_mut() { + shard.nonce_lookup.clone_from(&self.nonce_lookup); + } + shards } diff --git a/core/src/runtime/syscall.rs b/core/src/runtime/syscall.rs index c320c7e28..7cb1cb338 100644 --- a/core/src/runtime/syscall.rs +++ b/core/src/runtime/syscall.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use strum_macros::EnumIter; use crate::runtime::{Register, Runtime}; -use crate::stark::Blake3CompressInnerChip; use crate::syscall::precompiles::edwards::EdAddAssignChip; use crate::syscall::precompiles::edwards::EdDecompressChip; use crate::syscall::precompiles::keccak256::KeccakPermuteChip; @@ -68,9 +67,6 @@ pub enum SyscallCode { /// Executes the `SECP256K1_DECOMPRESS` precompile. SECP256K1_DECOMPRESS = 0x00_00_01_0C, - /// Executes the `BLAKE3_COMPRESS_INNER` precompile. - BLAKE3_COMPRESS_INNER = 0x00_38_01_0D, - /// Executes the `BN254_ADD` precompile. BN254_ADD = 0x00_01_01_0E, @@ -121,7 +117,6 @@ impl SyscallCode { 0x00_01_01_0A => SyscallCode::SECP256K1_ADD, 0x00_00_01_0B => SyscallCode::SECP256K1_DOUBLE, 0x00_00_01_0C => SyscallCode::SECP256K1_DECOMPRESS, - 0x00_38_01_0D => SyscallCode::BLAKE3_COMPRESS_INNER, 0x00_01_01_0E => SyscallCode::BN254_ADD, 0x00_00_01_0F => SyscallCode::BN254_DOUBLE, 0x00_01_01_1E => SyscallCode::BLS12381_ADD, @@ -180,6 +175,7 @@ pub struct SyscallContext<'a> { /// This is the exit_code used for the HALT syscall pub(crate) exit_code: u32, pub(crate) rt: &'a mut Runtime, + pub syscall_lookup_id: usize, } impl<'a> SyscallContext<'a> { @@ -192,6 +188,7 @@ impl<'a> SyscallContext<'a> { next_pc: runtime.state.pc.wrapping_add(4), exit_code: 0, rt: runtime, + syscall_lookup_id: 0, } } @@ -304,10 +301,6 @@ pub fn default_syscall_map() -> HashMap> { SyscallCode::BN254_DOUBLE, Arc::new(WeierstrassDoubleAssignChip::::new()), ); - syscall_map.insert( - SyscallCode::BLAKE3_COMPRESS_INNER, - Arc::new(Blake3CompressInnerChip::new()), - ); syscall_map.insert( SyscallCode::BLS12381_ADD, Arc::new(WeierstrassAddAssignChip::::new()), @@ -316,10 +309,6 @@ pub fn default_syscall_map() -> HashMap> { SyscallCode::BLS12381_DOUBLE, Arc::new(WeierstrassDoubleAssignChip::::new()), ); - syscall_map.insert( - SyscallCode::BLAKE3_COMPRESS_INNER, - Arc::new(Blake3CompressInnerChip::new()), - ); syscall_map.insert(SyscallCode::UINT256_MUL, Arc::new(Uint256MulChip::new())); syscall_map.insert( SyscallCode::ENTER_UNCONSTRAINED, @@ -359,10 +348,6 @@ mod tests { fn test_syscalls_in_default_map() { let default_syscall_map = default_syscall_map(); for code in SyscallCode::iter() { - if code == SyscallCode::BLAKE3_COMPRESS_INNER { - // Blake3 is currently disabled. - continue; - } default_syscall_map.get(&code).unwrap(); } } @@ -412,9 +397,6 @@ mod tests { SyscallCode::SECP256K1_DOUBLE => { assert_eq!(code as u32, sp1_zkvm::syscalls::SECP256K1_DOUBLE) } - SyscallCode::BLAKE3_COMPRESS_INNER => { - assert_eq!(code as u32, sp1_zkvm::syscalls::BLAKE3_COMPRESS_INNER) - } SyscallCode::BLS12381_ADD => { assert_eq!(code as u32, sp1_zkvm::syscalls::BLS12381_ADD) } diff --git a/core/src/stark/air.rs b/core/src/stark/air.rs index dc181b18d..558ccec5a 100644 --- a/core/src/stark/air.rs +++ b/core/src/stark/air.rs @@ -21,7 +21,6 @@ pub(crate) mod riscv_chips { pub use crate::cpu::CpuChip; pub use crate::memory::MemoryChip; pub use crate::program::ProgramChip; - pub use crate::syscall::precompiles::blake3::Blake3CompressInnerChip; pub use crate::syscall::precompiles::edwards::EdAddAssignChip; pub use crate::syscall::precompiles::edwards::EdDecompressChip; pub use crate::syscall::precompiles::keccak256::KeccakPermuteChip; @@ -88,8 +87,6 @@ pub enum RiscvAir { Secp256k1Double(WeierstrassDoubleAssignChip>), /// A precompile for the Keccak permutation. KeccakP(KeccakPermuteChip), - /// A precompile for the Blake3 compression function. (Disabled by default.) - Blake3Compress(Blake3CompressInnerChip), /// A precompile for addition on the Elliptic curve bn254. Bn254Add(WeierstrassAddAssignChip>), /// A precompile for doubling a point on the Elliptic curve bn254. @@ -152,12 +149,12 @@ impl RiscvAir { chips.push(RiscvAir::Uint256Mul(uint256_mul)); let bls12381_decompress = WeierstrassDecompressChip::>::new(); chips.push(RiscvAir::Bls12381Decompress(bls12381_decompress)); + let div_rem = DivRemChip::default(); + chips.push(RiscvAir::DivRem(div_rem)); let add = AddSubChip::default(); chips.push(RiscvAir::Add(add)); let bitwise = BitwiseChip::default(); chips.push(RiscvAir::Bitwise(bitwise)); - let div_rem = DivRemChip::default(); - chips.push(RiscvAir::DivRem(div_rem)); let mul = MulChip::default(); chips.push(RiscvAir::Mul(mul)); let shift_right = ShiftRightChip::default(); diff --git a/core/src/stark/chip.rs b/core/src/stark/chip.rs index 4a3164643..c3e8abdc0 100644 --- a/core/src/stark/chip.rs +++ b/core/src/stark/chip.rs @@ -61,12 +61,10 @@ where where A: MachineAir + Air> + Air>, { - // Todo: correct values let mut builder = InteractionBuilder::new(air.preprocessed_width(), air.width()); air.eval(&mut builder); let (sends, receives) = builder.interactions(); - // TODO: enable different numbers of public values. let mut max_constraint_degree = get_max_constraint_degree(&air, air.preprocessed_width(), PROOF_MAX_NUM_PVS); diff --git a/core/src/stark/machine.rs b/core/src/stark/machine.rs index 672226030..9f61460a0 100644 --- a/core/src/stark/machine.rs +++ b/core/src/stark/machine.rs @@ -473,6 +473,8 @@ pub enum MachineVerificationError { DebugInteractionsFailed, EmptyProof, InvalidPublicValues(&'static str), + TooManyShards, + InvalidChipOccurence(String), } impl Debug for MachineVerificationError { @@ -499,6 +501,12 @@ impl Debug for MachineVerificationError { MachineVerificationError::InvalidPublicValues(s) => { write!(f, "Invalid public values: {}", s) } + MachineVerificationError::TooManyShards => { + write!(f, "Too many shards") + } + MachineVerificationError::InvalidChipOccurence(s) => { + write!(f, "Invalid chip occurence: {}", s) + } } } } diff --git a/core/src/syscall/precompiles/blake3/compress/air.rs b/core/src/syscall/precompiles/blake3/compress/air.rs deleted file mode 100644 index a5876866e..000000000 --- a/core/src/syscall/precompiles/blake3/compress/air.rs +++ /dev/null @@ -1,235 +0,0 @@ -use core::borrow::Borrow; - -use p3_air::{Air, AirBuilder, BaseAir}; -use p3_field::AbstractField; -use p3_matrix::Matrix; - -use super::columns::{Blake3CompressInnerCols, NUM_BLAKE3_COMPRESS_INNER_COLS}; -use super::g::GOperation; -use super::{ - Blake3CompressInnerChip, G_INDEX, MSG_SCHEDULE, NUM_MSG_WORDS_PER_CALL, - NUM_STATE_WORDS_PER_CALL, OPERATION_COUNT, ROUND_COUNT, -}; -use crate::air::{BaseAirBuilder, SP1AirBuilder, WORD_SIZE}; -use crate::runtime::SyscallCode; - -impl BaseAir for Blake3CompressInnerChip { - fn width(&self) -> usize { - NUM_BLAKE3_COMPRESS_INNER_COLS - } -} - -impl Air for Blake3CompressInnerChip -where - AB: SP1AirBuilder, -{ - fn eval(&self, builder: &mut AB) { - let main = builder.main(); - let (local, next) = (main.row_slice(0), main.row_slice(1)); - let local: &Blake3CompressInnerCols = (*local).borrow(); - let next: &Blake3CompressInnerCols = (*next).borrow(); - - self.constrain_control_flow_flags(builder, local, next); - - self.constrain_memory(builder, local); - - self.constrain_g_operation(builder, local); - - // TODO: constraint ecall_receive column. - // TODO: constraint clk column to increment by 1 within same invocation of syscall. - builder.receive_syscall( - local.shard, - local.channel, - local.clk, - AB::F::from_canonical_u32(SyscallCode::BLAKE3_COMPRESS_INNER.syscall_id()), - local.state_ptr, - local.message_ptr, - local.ecall_receive, - ); - } -} - -impl Blake3CompressInnerChip { - /// Constrains the given index is correct for the given selector. The `selector` is an - /// `n`-dimensional boolean array whose `i`-th element is true if and only if the index is `i`. - fn constrain_index_selector( - &self, - builder: &mut AB, - selector: &[AB::Var], - index: AB::Var, - is_real: AB::Var, - ) { - let mut acc: AB::Expr = AB::F::zero().into(); - for i in 0..selector.len() { - acc += selector[i].into(); - builder.assert_bool(selector[i]) - } - builder - .when(is_real) - .assert_eq(acc, AB::F::from_canonical_usize(1)); - for i in 0..selector.len() { - builder - .when(selector[i]) - .assert_eq(index, AB::F::from_canonical_usize(i)); - } - } - - /// Constrains the control flow flags such as the operation index and the round index. - fn constrain_control_flow_flags( - &self, - builder: &mut AB, - local: &Blake3CompressInnerCols, - next: &Blake3CompressInnerCols, - ) { - // If this is the i-th operation, then the next row should be the (i+1)-th operation. - for i in 0..OPERATION_COUNT { - builder.when_transition().when(next.is_real).assert_eq( - local.is_operation_index_n[i], - next.is_operation_index_n[(i + 1) % OPERATION_COUNT], - ); - } - - // If this is the last operation, the round index should be incremented. Otherwise, the - // round index should remain the same. - for i in 0..OPERATION_COUNT { - if i + 1 < OPERATION_COUNT { - builder - .when_transition() - .when(local.is_operation_index_n[i]) - .assert_eq(local.round_index, next.round_index); - } else { - builder - .when_transition() - .when(local.is_operation_index_n[i]) - .when_not(local.is_round_index_n[ROUND_COUNT - 1]) - .assert_eq( - local.round_index + AB::F::from_canonical_u16(1), - next.round_index, - ); - - builder - .when_transition() - .when(local.is_operation_index_n[i]) - .when(local.is_round_index_n[ROUND_COUNT - 1]) - .assert_zero(next.round_index); - } - } - } - - /// Constrain the memory access for the state and the message. - fn constrain_memory( - &self, - builder: &mut AB, - local: &Blake3CompressInnerCols, - ) { - // Calculate the 4 indices to read from the state. This corresponds to a, b, c, and d. - for i in 0..NUM_STATE_WORDS_PER_CALL { - let index_to_read = { - self.constrain_index_selector( - builder, - &local.is_operation_index_n, - local.operation_index, - local.is_real, - ); - - let mut acc = AB::Expr::from_canonical_usize(0); - for operation in 0..OPERATION_COUNT { - acc += AB::Expr::from_canonical_usize(G_INDEX[operation][i]) - * local.is_operation_index_n[operation]; - } - acc - }; - builder.assert_eq(local.state_index[i], index_to_read); - } - - // Read & write the state. - for i in 0..NUM_STATE_WORDS_PER_CALL { - builder.eval_memory_access( - local.shard, - local.channel, - local.clk, - local.state_ptr + local.state_index[i] * AB::F::from_canonical_usize(WORD_SIZE), - &local.state_reads_writes[i], - local.is_real, - ); - } - - // Calculate the indices to read from the message. - for i in 0..NUM_MSG_WORDS_PER_CALL { - let index_to_read = { - self.constrain_index_selector( - builder, - &local.is_round_index_n, - local.round_index, - local.is_real, - ); - - let mut acc = AB::Expr::from_canonical_usize(0); - - for round in 0..ROUND_COUNT { - for operation in 0..OPERATION_COUNT { - acc += - AB::Expr::from_canonical_usize(MSG_SCHEDULE[round][2 * operation + i]) - * local.is_operation_index_n[operation] - * local.is_round_index_n[round]; - } - } - acc - }; - builder.assert_eq(local.msg_schedule[i], index_to_read); - } - - // Read the message. - for i in 0..NUM_MSG_WORDS_PER_CALL { - builder.eval_memory_access( - local.shard, - local.channel, - local.clk, - local.message_ptr + local.msg_schedule[i] * AB::F::from_canonical_usize(WORD_SIZE), - &local.message_reads[i], - local.is_real, - ); - } - } - - /// Constrains the input and the output of the `g` operation. - fn constrain_g_operation( - &self, - builder: &mut AB, - local: &Blake3CompressInnerCols, - ) { - builder.assert_bool(local.is_real); - - // Call g and write the result to the state. - { - let input = [ - local.state_reads_writes[0].prev_value, - local.state_reads_writes[1].prev_value, - local.state_reads_writes[2].prev_value, - local.state_reads_writes[3].prev_value, - local.message_reads[0].access.value, - local.message_reads[1].access.value, - ]; - - // Call the g function. - GOperation::::eval( - builder, - input, - local.g, - local.shard, - local.channel, - local.is_real, - ); - - // Finally, the results of the g function should be written to the memory. - for i in 0..NUM_STATE_WORDS_PER_CALL { - for j in 0..WORD_SIZE { - builder.when(local.is_real).assert_eq( - local.state_reads_writes[i].access.value[j], - local.g.result[i][j], - ); - } - } - } - } -} diff --git a/core/src/syscall/precompiles/blake3/compress/columns.rs b/core/src/syscall/precompiles/blake3/compress/columns.rs deleted file mode 100644 index bf7bbe4e1..000000000 --- a/core/src/syscall/precompiles/blake3/compress/columns.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::mem::size_of; - -use sp1_derive::AlignedBorrow; - -use crate::memory::MemoryReadCols; -use crate::memory::MemoryReadWriteCols; - -use super::g::GOperation; -use super::NUM_MSG_WORDS_PER_CALL; -use super::NUM_STATE_WORDS_PER_CALL; -use super::OPERATION_COUNT; -use super::ROUND_COUNT; - -pub const NUM_BLAKE3_COMPRESS_INNER_COLS: usize = size_of::>(); - -#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] -#[repr(C)] -pub struct Blake3CompressInnerCols { - pub shard: T, - pub channel: T, - pub clk: T, - pub ecall_receive: T, - - /// The pointer to the state. - pub state_ptr: T, - - /// The pointer to the message. - pub message_ptr: T, - - /// Reads and writes a part of the state. - pub state_reads_writes: [MemoryReadWriteCols; NUM_STATE_WORDS_PER_CALL], - - /// Reads a part of the message. - pub message_reads: [MemoryReadCols; NUM_MSG_WORDS_PER_CALL], - - /// Indicates which call of `g` is being performed. - pub operation_index: T, - pub is_operation_index_n: [T; OPERATION_COUNT], - - /// Indicates which call of `round` is being performed. - pub round_index: T, - pub is_round_index_n: [T; ROUND_COUNT], - - /// The indices to pass to `g`. - pub state_index: [T; NUM_STATE_WORDS_PER_CALL], - - /// The two values from `MSG_SCHEDULE` to pass to `g`. - pub msg_schedule: [T; NUM_MSG_WORDS_PER_CALL], - - /// The `g` operation to perform. - pub g: GOperation, - - /// Indicates if the current call is real or not. - pub is_real: T, -} diff --git a/core/src/syscall/precompiles/blake3/compress/execute.rs b/core/src/syscall/precompiles/blake3/compress/execute.rs deleted file mode 100644 index 35298b041..000000000 --- a/core/src/syscall/precompiles/blake3/compress/execute.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::runtime::Syscall; -use crate::runtime::{MemoryReadRecord, MemoryWriteRecord}; -use crate::syscall::precompiles::blake3::{ - g_func, Blake3CompressInnerChip, Blake3CompressInnerEvent, G_INDEX, MSG_SCHEDULE, - NUM_MSG_WORDS_PER_CALL, NUM_STATE_WORDS_PER_CALL, OPERATION_COUNT, ROUND_COUNT, -}; -use crate::syscall::precompiles::SyscallContext; - -impl Syscall for Blake3CompressInnerChip { - fn num_extra_cycles(&self) -> u32 { - (ROUND_COUNT * OPERATION_COUNT) as u32 - } - - fn execute(&self, rt: &mut SyscallContext, arg1: u32, arg2: u32) -> Option { - let state_ptr = arg1; - let message_ptr = arg2; - - let start_clk = rt.clk; - let mut message_reads = - [[[MemoryReadRecord::default(); NUM_MSG_WORDS_PER_CALL]; OPERATION_COUNT]; ROUND_COUNT]; - let mut state_writes = [[[MemoryWriteRecord::default(); NUM_STATE_WORDS_PER_CALL]; - OPERATION_COUNT]; ROUND_COUNT]; - - for round in 0..ROUND_COUNT { - for operation in 0..OPERATION_COUNT { - let state_index = G_INDEX[operation]; - let message_index: [usize; NUM_MSG_WORDS_PER_CALL] = [ - MSG_SCHEDULE[round][2 * operation], - MSG_SCHEDULE[round][2 * operation + 1], - ]; - - let mut input = vec![]; - // Read the input to g. - { - for index in state_index.iter() { - input.push(rt.word_unsafe(state_ptr + (*index as u32) * 4)); - } - for i in 0..NUM_MSG_WORDS_PER_CALL { - let (record, value) = rt.mr(message_ptr + (message_index[i] as u32) * 4); - message_reads[round][operation][i] = record; - input.push(value); - } - } - - // Call g. - let results = g_func(input.try_into().unwrap()); - - // Write the state. - for i in 0..NUM_STATE_WORDS_PER_CALL { - state_writes[round][operation][i] = - rt.mw(state_ptr + (state_index[i] as u32) * 4, results[i]); - } - - // Increment the clock for the next call of g. - rt.clk += 1; - } - } - - let shard = rt.current_shard(); - let channel = rt.current_channel(); - - rt.record_mut() - .blake3_compress_inner_events - .push(Blake3CompressInnerEvent { - shard, - channel, - clk: start_clk, - state_ptr, - message_reads, - state_writes, - message_ptr, - }); - - None - } -} diff --git a/core/src/syscall/precompiles/blake3/compress/g.rs b/core/src/syscall/precompiles/blake3/compress/g.rs deleted file mode 100644 index 06e8c3034..000000000 --- a/core/src/syscall/precompiles/blake3/compress/g.rs +++ /dev/null @@ -1,277 +0,0 @@ -use p3_field::Field; -use sp1_derive::AlignedBorrow; - -use crate::air::SP1AirBuilder; -use crate::air::Word; -use crate::air::WORD_SIZE; -use crate::operations::AddOperation; -use crate::operations::FixedRotateRightOperation; -use crate::operations::XorOperation; -use crate::runtime::ExecutionRecord; - -use super::g_func; -/// A set of columns needed to compute the `g` of the input state. -/// ``` ignore -/// fn g(state: &mut BlockWords, a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) { -/// state[a] = state[a].wrapping_add(state[b]).wrapping_add(x); -/// state[d] = (state[d] ^ state[a]).rotate_right(16); -/// state[c] = state[c].wrapping_add(state[d]); -/// state[b] = (state[b] ^ state[c]).rotate_right(12); -/// state[a] = state[a].wrapping_add(state[b]).wrapping_add(y); -/// state[d] = (state[d] ^ state[a]).rotate_right(8); -/// state[c] = state[c].wrapping_add(state[d]); -/// state[b] = (state[b] ^ state[c]).rotate_right(7); -/// } -/// ``` -#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] -#[repr(C)] -pub struct GOperation { - pub a_plus_b: AddOperation, - pub a_plus_b_plus_x: AddOperation, - pub d_xor_a: XorOperation, - // Rotate right by 16 bits by just shifting bytes. - pub c_plus_d: AddOperation, - pub b_xor_c: XorOperation, - pub b_xor_c_rotate_right_12: FixedRotateRightOperation, - pub a_plus_b_2: AddOperation, - pub a_plus_b_2_add_y: AddOperation, - // Rotate right by 8 bits by just shifting bytes. - pub d_xor_a_2: XorOperation, - pub c_plus_d_2: AddOperation, - pub b_xor_c_2: XorOperation, - pub b_xor_c_2_rotate_right_7: FixedRotateRightOperation, - /// `state[a]`, `state[b]`, `state[c]`, `state[d]` after all the steps. - pub result: [Word; 4], -} - -impl GOperation { - pub fn populate( - &mut self, - record: &mut ExecutionRecord, - shard: u32, - channel: u32, - input: [u32; 6], - ) -> [u32; 4] { - let mut a = input[0]; - let mut b = input[1]; - let mut c = input[2]; - let mut d = input[3]; - let x = input[4]; - let y = input[5]; - - // First 4 steps. - { - // a = a + b + x. - a = self.a_plus_b.populate(record, shard, channel, a, b); - a = self.a_plus_b_plus_x.populate(record, shard, channel, a, x); - - // d = (d ^ a).rotate_right(16). - d = self.d_xor_a.populate(record, shard, channel, d, a); - d = d.rotate_right(16); - - // c = c + d. - c = self.c_plus_d.populate(record, shard, channel, c, d); - - // b = (b ^ c).rotate_right(12). - b = self.b_xor_c.populate(record, shard, channel, b, c); - b = self - .b_xor_c_rotate_right_12 - .populate(record, shard, channel, b, 12); - } - - // Second 4 steps. - { - // a = a + b + y. - a = self.a_plus_b_2.populate(record, shard, channel, a, b); - a = self.a_plus_b_2_add_y.populate(record, shard, channel, a, y); - - // d = (d ^ a).rotate_right(8). - d = self.d_xor_a_2.populate(record, shard, channel, d, a); - d = d.rotate_right(8); - - // c = c + d. - c = self.c_plus_d_2.populate(record, shard, channel, c, d); - - // b = (b ^ c).rotate_right(7). - b = self.b_xor_c_2.populate(record, shard, channel, b, c); - b = self - .b_xor_c_2_rotate_right_7 - .populate(record, shard, channel, b, 7); - } - - let result = [a, b, c, d]; - assert_eq!(result, g_func(input)); - self.result = result.map(Word::from); - result - } - - pub fn eval( - builder: &mut AB, - input: [Word; 6], - cols: GOperation, - shard: AB::Var, - channel: impl Into + Clone, - is_real: AB::Var, - ) { - builder.assert_bool(is_real); - let mut a = input[0]; - let mut b = input[1]; - let mut c = input[2]; - let mut d = input[3]; - let x = input[4]; - let y = input[5]; - - // First 4 steps. - { - // a = a + b + x. - AddOperation::::eval( - builder, - a, - b, - cols.a_plus_b, - shard, - channel.clone(), - is_real.into(), - ); - a = cols.a_plus_b.value; - AddOperation::::eval( - builder, - a, - x, - cols.a_plus_b_plus_x, - shard, - channel.clone(), - is_real.into(), - ); - a = cols.a_plus_b_plus_x.value; - - // d = (d ^ a).rotate_right(16). - XorOperation::::eval( - builder, - d, - a, - cols.d_xor_a, - shard, - channel.clone(), - is_real, - ); - d = cols.d_xor_a.value; - // Rotate right by 16 bits. - d = Word([d[2], d[3], d[0], d[1]]); - - // c = c + d. - AddOperation::::eval( - builder, - c, - d, - cols.c_plus_d, - shard, - channel.clone(), - is_real.into(), - ); - c = cols.c_plus_d.value; - - // b = (b ^ c).rotate_right(12). - XorOperation::::eval( - builder, - b, - c, - cols.b_xor_c, - shard, - channel.clone(), - is_real, - ); - b = cols.b_xor_c.value; - FixedRotateRightOperation::::eval( - builder, - b, - 12, - cols.b_xor_c_rotate_right_12, - shard, - channel.clone(), - is_real, - ); - b = cols.b_xor_c_rotate_right_12.value; - } - - // Second 4 steps. - { - // a = a + b + y. - AddOperation::::eval( - builder, - a, - b, - cols.a_plus_b_2, - shard, - channel.clone(), - is_real.into(), - ); - a = cols.a_plus_b_2.value; - AddOperation::::eval( - builder, - a, - y, - cols.a_plus_b_2_add_y, - shard, - channel.clone(), - is_real.into(), - ); - a = cols.a_plus_b_2_add_y.value; - - // d = (d ^ a).rotate_right(8). - XorOperation::::eval( - builder, - d, - a, - cols.d_xor_a_2, - shard, - channel.clone(), - is_real, - ); - d = cols.d_xor_a_2.value; - // Rotate right by 8 bits. - d = Word([d[1], d[2], d[3], d[0]]); - - // c = c + d. - AddOperation::::eval( - builder, - c, - d, - cols.c_plus_d_2, - shard, - channel.clone(), - is_real.into(), - ); - c = cols.c_plus_d_2.value; - - // b = (b ^ c).rotate_right(7). - XorOperation::::eval( - builder, - b, - c, - cols.b_xor_c_2, - shard, - channel.clone(), - is_real, - ); - b = cols.b_xor_c_2.value; - FixedRotateRightOperation::::eval( - builder, - b, - 7, - cols.b_xor_c_2_rotate_right_7, - shard, - channel.clone(), - is_real, - ); - b = cols.b_xor_c_2_rotate_right_7.value; - } - - let results = [a, b, c, d]; - for i in 0..4 { - for j in 0..WORD_SIZE { - builder.assert_eq(cols.result[i][j], results[i][j]); - } - } - } -} diff --git a/core/src/syscall/precompiles/blake3/compress/mod.rs b/core/src/syscall/precompiles/blake3/compress/mod.rs deleted file mode 100644 index a89b9bcc3..000000000 --- a/core/src/syscall/precompiles/blake3/compress/mod.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! This module contains the implementation of the `blake3_compress_inner` precompile based on the -//! implementation of the `blake3` hash function in BLAKE3. -//! -//! Pseudo-code. -//! -//! state = [0u32; 16] -//! message = [0u32; 16] -//! -//! for round in 0..7 { -//! for operation in 0..8 { -//! // * Pick 4 indices a, b, c, d for the state, based on the operation index. -//! // * Pick 2 indices x, y for the message, based on both the round and the operation index. -//! // -//! // g takes those 6 values, and updates the 4 state values, at indices a, b, c, d. -//! // -//! // Each call of g becomes one row in the trace. -//! g(&mut state[a], &mut state[b], &mut state[c], &mut state[d], message[x], message[y]); -//! } -//! } -//! -//! Note that this precompile is only the blake3 compress inner function. The Blake3 compress -//! function has a series of 8 XOR operations after the compress inner function. -mod air; -mod columns; -mod execute; -mod g; -mod trace; -use crate::runtime::{MemoryReadRecord, MemoryWriteRecord}; - -use serde::{Deserialize, Serialize}; - -/// The number of `Word`s in the message of the compress inner operation. -pub(crate) const MSG_SIZE: usize = 16; - -/// The number of times we call `round` in the compress inner operation. -pub(crate) const ROUND_COUNT: usize = 7; - -/// The number of times we call `g` in the compress inner operation. -pub(crate) const OPERATION_COUNT: usize = 8; - -/// The number of `Word`s in the state that we pass to `g`. -pub(crate) const NUM_STATE_WORDS_PER_CALL: usize = 4; - -/// The number of `Word`s in the message that we pass to `g`. -pub(crate) const NUM_MSG_WORDS_PER_CALL: usize = 2; - -/// The number of `Word`s in the input of `g`. -pub(crate) const G_INPUT_SIZE: usize = NUM_MSG_WORDS_PER_CALL + NUM_STATE_WORDS_PER_CALL; - -/// 2-dimensional array specifying which message values `g` should access. Values at `(i, 2 * j)` -/// and `(i, 2 * j + 1)` are the indices of the message values that `g` should access in the `j`-th -/// call of the `i`-th round. -pub(crate) const MSG_SCHEDULE: [[usize; MSG_SIZE]; ROUND_COUNT] = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8], - [3, 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1], - [10, 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6], - [12, 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4], - [9, 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7], - [11, 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13], -]; - -/// The `i`-th row of `G_INDEX` is the indices used for the `i`-th call to `g`. -pub(crate) const G_INDEX: [[usize; NUM_STATE_WORDS_PER_CALL]; OPERATION_COUNT] = [ - [0, 4, 8, 12], - [1, 5, 9, 13], - [2, 6, 10, 14], - [3, 7, 11, 15], - [0, 5, 10, 15], - [1, 6, 11, 12], - [2, 7, 8, 13], - [3, 4, 9, 14], -]; - -pub(crate) const fn g_func(input: [u32; 6]) -> [u32; 4] { - let mut a = input[0]; - let mut b = input[1]; - let mut c = input[2]; - let mut d = input[3]; - let x = input[4]; - let y = input[5]; - a = a.wrapping_add(b).wrapping_add(x); - d = (d ^ a).rotate_right(16); - c = c.wrapping_add(d); - b = (b ^ c).rotate_right(12); - a = a.wrapping_add(b).wrapping_add(y); - d = (d ^ a).rotate_right(8); - c = c.wrapping_add(d); - b = (b ^ c).rotate_right(7); - [a, b, c, d] -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Blake3CompressInnerEvent { - pub clk: u32, - pub shard: u32, - pub channel: u32, - pub state_ptr: u32, - pub message_ptr: u32, - pub message_reads: [[[MemoryReadRecord; NUM_MSG_WORDS_PER_CALL]; OPERATION_COUNT]; ROUND_COUNT], - pub state_writes: - [[[MemoryWriteRecord; NUM_STATE_WORDS_PER_CALL]; OPERATION_COUNT]; ROUND_COUNT], -} - -pub struct Blake3CompressInnerChip {} - -impl Blake3CompressInnerChip { - pub const fn new() -> Self { - Self {} - } -} - -#[cfg(test)] -pub mod compress_tests { - use crate::runtime::Instruction; - use crate::runtime::Opcode; - use crate::runtime::Register; - use crate::runtime::SyscallCode; - use crate::Program; - - use super::MSG_SIZE; - - /// The number of `Word`s in the state of the compress inner operation. - const STATE_SIZE: usize = 16; - - pub fn blake3_compress_internal_program() -> Program { - let state_ptr = 100; - let msg_ptr = 500; - let mut instructions = vec![]; - - for i in 0..STATE_SIZE { - // Store 1000 + i in memory for the i-th word of the state. 1000 + i is an arbitrary - // number that is easy to spot while debugging. - instructions.extend(vec![ - Instruction::new(Opcode::ADD, 29, 0, 1000 + i as u32, false, true), - Instruction::new(Opcode::ADD, 30, 0, state_ptr + i as u32 * 4, false, true), - Instruction::new(Opcode::SW, 29, 30, 0, false, true), - ]); - } - for i in 0..MSG_SIZE { - // Store 2000 + i in memory for the i-th word of the message. 2000 + i is an arbitrary - // number that is easy to spot while debugging. - instructions.extend(vec![ - Instruction::new(Opcode::ADD, 29, 0, 2000 + i as u32, false, true), - Instruction::new(Opcode::ADD, 30, 0, msg_ptr + i as u32 * 4, false, true), - Instruction::new(Opcode::SW, 29, 30, 0, false, true), - ]); - } - instructions.extend(vec![ - Instruction::new( - Opcode::ADD, - 5, - 0, - SyscallCode::BLAKE3_COMPRESS_INNER as u32, - false, - true, - ), - Instruction::new(Opcode::ADD, Register::X10 as u32, 0, state_ptr, false, true), - Instruction::new(Opcode::ADD, Register::X11 as u32, 0, msg_ptr, false, true), - Instruction::new(Opcode::ECALL, 5, 10, 11, false, false), - ]); - Program::new(instructions, 0, 0) - } - - // Tests disabled because syscall is not enabled in default runtime/chip configs. - // #[test] - // fn prove_babybear() { - // setup_logger(); - // let program = blake3_compress_internal_program(); - // run_test(program).unwrap(); - // } - - // #[test] - // fn test_blake3_compress_inner_elf() { - // setup_logger(); - // let program = Program::from(BLAKE3_COMPRESS_ELF); - // run_test(program).unwrap(); - // } -} diff --git a/core/src/syscall/precompiles/blake3/compress/trace.rs b/core/src/syscall/precompiles/blake3/compress/trace.rs deleted file mode 100644 index 14994cb03..000000000 --- a/core/src/syscall/precompiles/blake3/compress/trace.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::borrow::BorrowMut; - -use p3_field::PrimeField32; -use p3_matrix::dense::RowMajorMatrix; - -use super::columns::Blake3CompressInnerCols; -use super::{ - G_INDEX, G_INPUT_SIZE, MSG_SCHEDULE, NUM_MSG_WORDS_PER_CALL, NUM_STATE_WORDS_PER_CALL, - OPERATION_COUNT, -}; -use crate::air::MachineAir; -use crate::bytes::event::ByteRecord; -use crate::runtime::ExecutionRecord; -use crate::runtime::MemoryRecordEnum; -use crate::runtime::Program; -use crate::syscall::precompiles::blake3::compress::columns::NUM_BLAKE3_COMPRESS_INNER_COLS; -use crate::syscall::precompiles::blake3::{Blake3CompressInnerChip, ROUND_COUNT}; -use crate::utils::pad_rows; - -impl MachineAir for Blake3CompressInnerChip { - type Record = ExecutionRecord; - type Program = Program; - - fn name(&self) -> String { - "Blake3CompressInner".to_string() - } - - fn generate_trace( - &self, - input: &ExecutionRecord, - output: &mut ExecutionRecord, - ) -> RowMajorMatrix { - let mut rows = Vec::new(); - - let mut new_byte_lookup_events = Vec::new(); - - for i in 0..input.blake3_compress_inner_events.len() { - let event = input.blake3_compress_inner_events[i].clone(); - let shard = event.shard; - let channel = event.channel; - let mut clk = event.clk; - for round in 0..ROUND_COUNT { - for operation in 0..OPERATION_COUNT { - let mut row = [F::zero(); NUM_BLAKE3_COMPRESS_INNER_COLS]; - let cols: &mut Blake3CompressInnerCols = row.as_mut_slice().borrow_mut(); - - // Assign basic values to the columns. - { - cols.shard = F::from_canonical_u32(event.shard); - cols.channel = F::from_canonical_u32(event.channel); - cols.clk = F::from_canonical_u32(clk); - - cols.round_index = F::from_canonical_u32(round as u32); - cols.is_round_index_n[round] = F::one(); - - cols.operation_index = F::from_canonical_u32(operation as u32); - cols.is_operation_index_n[operation] = F::one(); - - for i in 0..NUM_STATE_WORDS_PER_CALL { - cols.state_index[i] = F::from_canonical_usize(G_INDEX[operation][i]); - } - - for i in 0..NUM_MSG_WORDS_PER_CALL { - cols.msg_schedule[i] = - F::from_canonical_usize(MSG_SCHEDULE[round][2 * operation + i]); - } - - if round == 0 && operation == 0 { - cols.ecall_receive = F::one(); - } - } - - // Memory columns. - { - cols.message_ptr = F::from_canonical_u32(event.message_ptr); - for i in 0..NUM_MSG_WORDS_PER_CALL { - cols.message_reads[i].populate( - channel, - event.message_reads[round][operation][i], - &mut new_byte_lookup_events, - ); - } - - cols.state_ptr = F::from_canonical_u32(event.state_ptr); - for i in 0..NUM_STATE_WORDS_PER_CALL { - cols.state_reads_writes[i].populate( - channel, - MemoryRecordEnum::Write(event.state_writes[round][operation][i]), - &mut new_byte_lookup_events, - ); - } - } - - // Apply the `g` operation. - { - let input: [u32; G_INPUT_SIZE] = [ - event.state_writes[round][operation][0].prev_value, - event.state_writes[round][operation][1].prev_value, - event.state_writes[round][operation][2].prev_value, - event.state_writes[round][operation][3].prev_value, - event.message_reads[round][operation][0].value, - event.message_reads[round][operation][1].value, - ]; - - cols.g.populate(output, shard, channel, input); - } - - clk += 1; - - cols.is_real = F::one(); - - rows.push(row); - } - } - } - - output.add_byte_lookup_events(new_byte_lookup_events); - - pad_rows(&mut rows, || [F::zero(); NUM_BLAKE3_COMPRESS_INNER_COLS]); - - // Convert the trace to a row major matrix. - RowMajorMatrix::new( - rows.into_iter().flatten().collect::>(), - NUM_BLAKE3_COMPRESS_INNER_COLS, - ) - } - - fn included(&self, shard: &Self::Record) -> bool { - !shard.blake3_compress_inner_events.is_empty() - } -} diff --git a/core/src/syscall/precompiles/blake3/mod.rs b/core/src/syscall/precompiles/blake3/mod.rs deleted file mode 100644 index 8b286ad17..000000000 --- a/core/src/syscall/precompiles/blake3/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod compress; - -pub use compress::*; diff --git a/core/src/syscall/precompiles/edwards/ed_add.rs b/core/src/syscall/precompiles/edwards/ed_add.rs index f4e15423a..44d29edb8 100644 --- a/core/src/syscall/precompiles/edwards/ed_add.rs +++ b/core/src/syscall/precompiles/edwards/ed_add.rs @@ -6,6 +6,7 @@ use std::marker::PhantomData; use num::BigUint; use num::Zero; +use p3_air::AirBuilder; use p3_air::{Air, BaseAir}; use p3_field::AbstractField; use p3_field::PrimeField32; @@ -54,6 +55,7 @@ pub struct EdAddAssignCols { pub shard: T, pub channel: T, pub clk: T, + pub nonce: T, pub p_ptr: T, pub q_ptr: T, pub p_access: [MemoryWriteCols; WORDS_CURVE_POINT], @@ -238,10 +240,19 @@ impl MachineAir for Ed }); // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), NUM_ED_ADD_COLS, - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut EdAddAssignCols = + trace.values[i * NUM_ED_ADD_COLS..(i + 1) * NUM_ED_ADD_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -261,141 +272,150 @@ where { fn eval(&self, builder: &mut AB) { let main = builder.main(); - let row = main.row_slice(0); - let row: &EdAddAssignCols = (*row).borrow(); + let local = main.row_slice(0); + let local: &EdAddAssignCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &EdAddAssignCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); - let x1 = limbs_from_prev_access(&row.p_access[0..8]); - let x2 = limbs_from_prev_access(&row.q_access[0..8]); - let y1 = limbs_from_prev_access(&row.p_access[8..16]); - let y2 = limbs_from_prev_access(&row.q_access[8..16]); + let x1 = limbs_from_prev_access(&local.p_access[0..8]); + let x2 = limbs_from_prev_access(&local.q_access[0..8]); + let y1 = limbs_from_prev_access(&local.p_access[8..16]); + let y2 = limbs_from_prev_access(&local.q_access[8..16]); // x3_numerator = x1 * y2 + x2 * y1. - row.x3_numerator.eval( + local.x3_numerator.eval( builder, &[x1, x2], &[y2, y1], - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // y3_numerator = y1 * y2 + x1 * x2. - row.y3_numerator.eval( + local.y3_numerator.eval( builder, &[y1, x1], &[y2, x2], - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // f = x1 * x2 * y1 * y2. - row.x1_mul_y1.eval( + local.x1_mul_y1.eval( builder, &x1, &y1, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.x2_mul_y2.eval( + local.x2_mul_y2.eval( builder, &x2, &y2, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - let x1_mul_y1 = row.x1_mul_y1.result; - let x2_mul_y2 = row.x2_mul_y2.result; - row.f.eval( + let x1_mul_y1 = local.x1_mul_y1.result; + let x2_mul_y2 = local.x2_mul_y2.result; + local.f.eval( builder, &x1_mul_y1, &x2_mul_y2, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // d * f. - let f = row.f.result; + let f = local.f.result; let d_biguint = E::d_biguint(); let d_const = E::BaseField::to_limbs_field::(&d_biguint); - row.d_mul_f.eval( + local.d_mul_f.eval( builder, &f, &d_const, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - let d_mul_f = row.d_mul_f.result; + let d_mul_f = local.d_mul_f.result; // x3 = x3_numerator / (1 + d * f). - row.x3_ins.eval( + local.x3_ins.eval( builder, - &row.x3_numerator.result, + &local.x3_numerator.result, &d_mul_f, true, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // y3 = y3_numerator / (1 - d * f). - row.y3_ins.eval( + local.y3_ins.eval( builder, - &row.y3_numerator.result, + &local.y3_numerator.result, &d_mul_f, false, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // Constraint self.p_access.value = [self.x3_ins.result, self.y3_ins.result] // This is to ensure that p_access is updated with the new value. - let p_access_vec = value_as_limbs(&row.p_access); + let p_access_vec = value_as_limbs(&local.p_access); builder - .when(row.is_real) - .assert_all_eq(row.x3_ins.result, p_access_vec[0..NUM_LIMBS].to_vec()); - builder.when(row.is_real).assert_all_eq( - row.y3_ins.result, + .when(local.is_real) + .assert_all_eq(local.x3_ins.result, p_access_vec[0..NUM_LIMBS].to_vec()); + builder.when(local.is_real).assert_all_eq( + local.y3_ins.result, p_access_vec[NUM_LIMBS..NUM_LIMBS * 2].to_vec(), ); builder.eval_memory_access_slice( - row.shard, - row.channel, - row.clk.into(), - row.q_ptr, - &row.q_access, - row.is_real, + local.shard, + local.channel, + local.clk.into(), + local.q_ptr, + &local.q_access, + local.is_real, ); builder.eval_memory_access_slice( - row.shard, - row.channel, - row.clk + AB::F::from_canonical_u32(1), - row.p_ptr, - &row.p_access, - row.is_real, + local.shard, + local.channel, + local.clk + AB::F::from_canonical_u32(1), + local.p_ptr, + &local.p_access, + local.is_real, ); builder.receive_syscall( - row.shard, - row.channel, - row.clk, + local.shard, + local.channel, + local.clk, + local.nonce, AB::F::from_canonical_u32(SyscallCode::ED_ADD.syscall_id()), - row.p_ptr, - row.q_ptr, - row.is_real, + local.p_ptr, + local.q_ptr, + local.is_real, ); } } diff --git a/core/src/syscall/precompiles/edwards/ed_decompress.rs b/core/src/syscall/precompiles/edwards/ed_decompress.rs index be62467c0..34599f955 100644 --- a/core/src/syscall/precompiles/edwards/ed_decompress.rs +++ b/core/src/syscall/precompiles/edwards/ed_decompress.rs @@ -53,6 +53,7 @@ use super::{WordsFieldElement, WORDS_FIELD_ELEMENT}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EdDecompressEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, @@ -78,6 +79,7 @@ pub struct EdDecompressCols { pub shard: T, pub channel: T, pub clk: T, + pub nonce: T, pub ptr: T, pub sign: T, pub x_access: GenericArray, WordsFieldElement>, @@ -276,6 +278,7 @@ impl EdDecompressCols { self.shard, self.channel, self.clk, + AB::F::zero(), AB::F::from_canonical_u32(SyscallCode::ED_DECOMPRESS.syscall_id()), self.ptr, self.sign, @@ -326,11 +329,13 @@ impl Syscall for EdDecompressChip { let x_memory_records_vec = rt.mw_slice(slice_ptr, &decompressed_x_words); let x_memory_records: [MemoryWriteRecord; 8] = x_memory_records_vec.try_into().unwrap(); + let lookup_id = rt.syscall_lookup_id; let shard = rt.current_shard(); let channel = rt.current_channel(); rt.record_mut() .ed_decompress_events .push(EdDecompressEvent { + lookup_id, shard, channel, clk: start_clk, @@ -390,10 +395,20 @@ impl MachineAir for EdDecompressChip>(), NUM_ED_DECOMPRESS_COLS, - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut EdDecompressCols = trace.values + [i * NUM_ED_DECOMPRESS_COLS..(i + 1) * NUM_ED_DECOMPRESS_COLS] + .borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -413,9 +428,18 @@ where { fn eval(&self, builder: &mut AB) { let main = builder.main(); - let row = main.row_slice(0); - let row: &EdDecompressCols = (*row).borrow(); - row.eval::(builder); + let local = main.row_slice(0); + let local: &EdDecompressCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &EdDecompressCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + + local.eval::(builder); } } diff --git a/core/src/syscall/precompiles/keccak256/air.rs b/core/src/syscall/precompiles/keccak256/air.rs index 9e67c1249..9288f8088 100644 --- a/core/src/syscall/precompiles/keccak256/air.rs +++ b/core/src/syscall/precompiles/keccak256/air.rs @@ -32,6 +32,12 @@ where let local: &KeccakMemCols = (*local).borrow(); let next: &KeccakMemCols = (*next).borrow(); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + let first_step = local.keccak.step_flags[0]; let final_step = local.keccak.step_flags[NUM_ROUNDS - 1]; let not_final_step = AB::Expr::one() - final_step; @@ -68,6 +74,7 @@ where local.shard, local.channel, local.clk, + local.nonce, AB::F::from_canonical_u32(SyscallCode::KECCAK_PERMUTE.syscall_id()), local.state_addr, AB::Expr::zero(), @@ -79,6 +86,7 @@ where let mut transition_not_final_builder = transition_builder.when(not_final_step); transition_not_final_builder.assert_eq(local.shard, next.shard); transition_not_final_builder.assert_eq(local.clk, next.clk); + transition_not_final_builder.assert_eq(local.channel, next.channel); transition_not_final_builder.assert_eq(local.state_addr, next.state_addr); transition_not_final_builder.assert_eq(local.is_real, next.is_real); diff --git a/core/src/syscall/precompiles/keccak256/columns.rs b/core/src/syscall/precompiles/keccak256/columns.rs index a3e2dd304..ad3aa5f09 100644 --- a/core/src/syscall/precompiles/keccak256/columns.rs +++ b/core/src/syscall/precompiles/keccak256/columns.rs @@ -20,6 +20,7 @@ pub(crate) struct KeccakMemCols { pub shard: T, pub channel: T, pub clk: T, + pub nonce: T, pub state_addr: T, /// Memory columns for the state. diff --git a/core/src/syscall/precompiles/keccak256/execute.rs b/core/src/syscall/precompiles/keccak256/execute.rs index d6c306c45..eecc747be 100644 --- a/core/src/syscall/precompiles/keccak256/execute.rs +++ b/core/src/syscall/precompiles/keccak256/execute.rs @@ -99,9 +99,11 @@ impl Syscall for KeccakPermuteChip { // Push the Keccak permute event. let shard = rt.current_shard(); let channel = rt.current_channel(); + let lookup_id = rt.syscall_lookup_id; rt.record_mut() .keccak_permute_events .push(KeccakPermuteEvent { + lookup_id, shard, channel, clk: start_clk, diff --git a/core/src/syscall/precompiles/keccak256/mod.rs b/core/src/syscall/precompiles/keccak256/mod.rs index 4110707a8..2b95b8b40 100644 --- a/core/src/syscall/precompiles/keccak256/mod.rs +++ b/core/src/syscall/precompiles/keccak256/mod.rs @@ -15,6 +15,7 @@ const STATE_NUM_WORDS: usize = STATE_SIZE * 2; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KeccakPermuteEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, diff --git a/core/src/syscall/precompiles/keccak256/trace.rs b/core/src/syscall/precompiles/keccak256/trace.rs index 01b07fb74..89b85f0bc 100644 --- a/core/src/syscall/precompiles/keccak256/trace.rs +++ b/core/src/syscall/precompiles/keccak256/trace.rs @@ -147,10 +147,19 @@ impl MachineAir for KeccakPermuteChip { } // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), NUM_KECCAK_MEM_COLS, - ) + ); + + // Write the nonce to the trace. + for i in 0..trace.height() { + let cols: &mut KeccakMemCols = + trace.values[i * NUM_KECCAK_MEM_COLS..(i + 1) * NUM_KECCAK_MEM_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { diff --git a/core/src/syscall/precompiles/mod.rs b/core/src/syscall/precompiles/mod.rs index 7b107e2d5..bc08c6856 100644 --- a/core/src/syscall/precompiles/mod.rs +++ b/core/src/syscall/precompiles/mod.rs @@ -1,4 +1,3 @@ -pub mod blake3; pub mod edwards; pub mod keccak256; pub mod sha256; @@ -20,6 +19,7 @@ use serde::{Deserialize, Serialize}; /// Elliptic curve add event. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ECAddEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, @@ -67,7 +67,9 @@ pub fn create_ec_add_event( let p_memory_records = rt.mw_slice(p_ptr, &result_words); + println!("ec-add lookup id {:?}", rt.syscall_lookup_id); ECAddEvent { + lookup_id: rt.syscall_lookup_id, shard: rt.current_shard(), channel: rt.current_channel(), clk: start_clk, @@ -83,6 +85,7 @@ pub fn create_ec_add_event( /// Elliptic curve double event. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ECDoubleEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, @@ -119,6 +122,7 @@ pub fn create_ec_double_event( let p_memory_records = rt.mw_slice(p_ptr, &result_words); ECDoubleEvent { + lookup_id: rt.syscall_lookup_id, shard: rt.current_shard(), channel: rt.current_channel(), clk: start_clk, @@ -131,6 +135,7 @@ pub fn create_ec_double_event( /// Elliptic curve point decompress event. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ECDecompressEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, @@ -176,6 +181,7 @@ pub fn create_ec_decompress_event( let y_memory_records = rt.mw_slice(slice_ptr, &y_words); ECDecompressEvent { + lookup_id: rt.syscall_lookup_id, shard: rt.current_shard(), channel: rt.current_channel(), clk: start_clk, diff --git a/core/src/syscall/precompiles/sha256/compress/air.rs b/core/src/syscall/precompiles/sha256/compress/air.rs index 2f4bd5000..7a28a456b 100644 --- a/core/src/syscall/precompiles/sha256/compress/air.rs +++ b/core/src/syscall/precompiles/sha256/compress/air.rs @@ -30,6 +30,12 @@ where let local: &ShaCompressCols = (*local).borrow(); let next: &ShaCompressCols = (*next).borrow(); + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + self.eval_control_flow_flags(builder, local, next); self.eval_memory(builder, local); @@ -46,6 +52,7 @@ where local.shard, local.channel, local.clk, + local.nonce, AB::F::from_canonical_u32(SyscallCode::SHA_COMPRESS.syscall_id()), local.w_ptr, local.h_ptr, @@ -71,19 +78,15 @@ impl ShaCompressChip { for i in 0..8 { octet_sum += local.octet[i].into(); } - builder.when(local.is_real).assert_one(octet_sum); + builder.assert_one(octet_sum); // Verify that the first row's octet value is correct. - builder - .when_first_row() - .when(local.is_real) - .assert_one(local.octet[0]); + builder.when_first_row().assert_one(local.octet[0]); // Verify correct transition for octet column. for i in 0..8 { builder .when_transition() - .when(next.is_real) .when(local.octet[i]) .assert_one(next.octet[(i + 1) % 8]) } @@ -98,19 +101,15 @@ impl ShaCompressChip { for i in 0..10 { octet_num_sum += local.octet_num[i].into(); } - builder.when(local.is_real).assert_one(octet_num_sum); + builder.assert_one(octet_num_sum); // The first row should have octet_num[0] = 1 if it's real. - builder - .when_first_row() - .when(local.is_real) - .assert_one(local.octet_num[0]); + builder.when_first_row().assert_one(local.octet_num[0]); // If current row is not last of an octet and next row is real, octet_num should be the same. for i in 0..10 { builder .when_transition() - .when(next.is_real) .when_not(local.octet[7]) .assert_eq(local.octet_num[i], next.octet_num[i]); } @@ -119,7 +118,6 @@ impl ShaCompressChip { for i in 0..10 { builder .when_transition() - .when(next.is_real) .when(local.octet[7]) .assert_eq(local.octet_num[i], next.octet_num[(i + 1) % 10]); } @@ -146,19 +144,26 @@ impl ShaCompressChip { .assert_word_eq(*var, *local.mem.value()); } + // Assert that the is_initialize flag is correct. + builder.assert_eq(local.is_initialize, local.octet_num[0] * local.is_real); + // Assert that the is_compression flag is correct. builder.assert_eq( local.is_compression, - local.octet_num[1] + (local.octet_num[1] + local.octet_num[2] + local.octet_num[3] + local.octet_num[4] + local.octet_num[5] + local.octet_num[6] + local.octet_num[7] - + local.octet_num[8], + + local.octet_num[8]) + * local.is_real, ); + // Assert that the is_finalize flag is correct. + builder.assert_eq(local.is_finalize, local.octet_num[9] * local.is_real); + builder.assert_eq( local.is_last_row.into(), local.octet[7] * local.octet_num[9], @@ -175,6 +180,10 @@ impl ShaCompressChip { .when(local.is_real) .when_not(local.is_last_row) .assert_eq(local.clk, next.clk); + builder + .when_transition() + .when_not(local.is_last_row) + .assert_eq(local.channel, next.channel); builder .when_transition() .when(local.is_real) @@ -186,6 +195,9 @@ impl ShaCompressChip { .when_not(local.is_last_row) .assert_eq(local.h_ptr, next.h_ptr); + // Assert that is_real is a bool. + builder.assert_bool(local.is_real); + // If this row is real and not the last cycle, then next row should also be real. builder .when_transition() @@ -193,6 +205,12 @@ impl ShaCompressChip { .when_not(local.is_last_row) .assert_one(next.is_real); + // Once the is_real flag is changed to false, it should not be changed back. + builder + .when_transition() + .when_not(local.is_real) + .assert_zero(next.is_real); + // Assert that the table ends in nonreal columns. Since each compress ecall is 80 cycles and // the table is padded to a power of 2, the last row of the table should always be padding. builder.when_last_row().assert_zero(local.is_real); @@ -200,15 +218,13 @@ impl ShaCompressChip { /// Constrains that memory address is correct and that memory is correctly written/read. fn eval_memory(&self, builder: &mut AB, local: &ShaCompressCols) { - let is_initialize = local.octet_num[0]; - let is_finalize = local.octet_num[9]; builder.eval_memory_access( local.shard, local.channel, - local.clk + is_finalize, + local.clk + local.is_finalize, local.mem_addr, &local.mem, - is_initialize + local.is_compression + is_finalize, + local.is_initialize + local.is_compression + local.is_finalize, ); // Calculate the current cycle_num. @@ -224,7 +240,7 @@ impl ShaCompressChip { } // Verify correct mem address for initialize phase - builder.when(is_initialize).assert_eq( + builder.when(local.is_initialize).assert_eq( local.mem_addr, local.h_ptr + cycle_step.clone() * AB::Expr::from_canonical_u32(4), ); @@ -239,7 +255,7 @@ impl ShaCompressChip { ); // Verify correct mem address for finalize phase - builder.when(is_finalize).assert_eq( + builder.when(local.is_finalize).assert_eq( local.mem_addr, local.h_ptr + cycle_step.clone() * AB::Expr::from_canonical_u32(4), ); @@ -251,11 +267,11 @@ impl ShaCompressChip { ]; for (i, var) in vars.iter().enumerate() { builder - .when(is_initialize) + .when(local.is_initialize) .when(local.octet[i]) .assert_word_eq(*var, *local.mem.prev_value()); builder - .when(is_initialize) + .when(local.is_initialize) .when(local.octet[i]) .assert_word_eq(*var, *local.mem.value()); } @@ -267,7 +283,7 @@ impl ShaCompressChip { // In the finalize phase, verify that the correct value is written to memory. builder - .when(is_finalize) + .when(local.is_finalize) .assert_word_eq(*local.mem.value(), local.finalize_add.value); } @@ -579,7 +595,6 @@ impl ShaCompressChip { builder: &mut AB, local: &ShaCompressCols, ) { - let is_finalize = local.octet_num[9]; // In the finalize phase, need to execute h[0] + a, h[1] + b, ..., h[7] + h, for each of the // phase's 8 rows. // We can get the needed operand (a,b,c,...,h) by doing an inner product between octet and @@ -596,7 +611,7 @@ impl ShaCompressChip { } builder - .when(is_finalize) + .when(local.is_finalize) .assert_word_eq(filtered_operand, local.finalized_operand.map(|x| x.into())); // finalize_add.result = h[i] + finalized_operand @@ -607,7 +622,7 @@ impl ShaCompressChip { local.finalize_add, local.shard, local.channel, - is_finalize.into(), + local.is_finalize.into(), ); // Memory write is constrained in constrain_memory. diff --git a/core/src/syscall/precompiles/sha256/compress/columns.rs b/core/src/syscall/precompiles/sha256/compress/columns.rs index 94a200aed..0fd7a7fbf 100644 --- a/core/src/syscall/precompiles/sha256/compress/columns.rs +++ b/core/src/syscall/precompiles/sha256/compress/columns.rs @@ -26,6 +26,7 @@ pub struct ShaCompressCols { /// Inputs. pub shard: T, pub channel: T, + pub nonce: T, pub clk: T, pub w_ptr: T, pub h_ptr: T, @@ -102,7 +103,9 @@ pub struct ShaCompressCols { pub finalized_operand: Word, pub finalize_add: AddOperation, + pub is_initialize: T, pub is_compression: T, + pub is_finalize: T, pub is_last_row: T, pub is_real: T, diff --git a/core/src/syscall/precompiles/sha256/compress/execute.rs b/core/src/syscall/precompiles/sha256/compress/execute.rs index a019abbd4..5ed33dd2b 100644 --- a/core/src/syscall/precompiles/sha256/compress/execute.rs +++ b/core/src/syscall/precompiles/sha256/compress/execute.rs @@ -76,9 +76,11 @@ impl Syscall for ShaCompressChip { } // Push the SHA extend event. + let lookup_id = rt.syscall_lookup_id; let shard = rt.current_shard(); let channel = rt.current_channel(); rt.record_mut().sha_compress_events.push(ShaCompressEvent { + lookup_id, shard, channel, clk: start_clk, diff --git a/core/src/syscall/precompiles/sha256/compress/mod.rs b/core/src/syscall/precompiles/sha256/compress/mod.rs index fd6c50f0f..47401a25b 100644 --- a/core/src/syscall/precompiles/sha256/compress/mod.rs +++ b/core/src/syscall/precompiles/sha256/compress/mod.rs @@ -20,6 +20,7 @@ pub const SHA_COMPRESS_K: [u32; 64] = [ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShaCompressEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, diff --git a/core/src/syscall/precompiles/sha256/compress/trace.rs b/core/src/syscall/precompiles/sha256/compress/trace.rs index bd0b8f817..1b373a6b2 100644 --- a/core/src/syscall/precompiles/sha256/compress/trace.rs +++ b/core/src/syscall/precompiles/sha256/compress/trace.rs @@ -2,6 +2,7 @@ use std::borrow::BorrowMut; use p3_field::PrimeField32; use p3_matrix::dense::RowMajorMatrix; +use p3_matrix::Matrix; use super::{ columns::{ShaCompressCols, NUM_SHA_COMPRESS_COLS}, @@ -53,6 +54,7 @@ impl MachineAir for ShaCompressChip { cols.octet[j] = F::one(); cols.octet_num[octet_num_idx] = F::one(); + cols.is_initialize = F::one(); cols.mem.populate_read( channel, @@ -207,6 +209,7 @@ impl MachineAir for ShaCompressChip { cols.octet[j] = F::one(); cols.octet_num[octet_num_idx] = F::one(); + cols.is_finalize = F::one(); cols.finalize_add .populate(output, shard, channel, og_h[j], event.h[j]); @@ -249,13 +252,46 @@ impl MachineAir for ShaCompressChip { output.add_byte_lookup_events(new_byte_lookup_events); + let num_real_rows = rows.len(); + pad_rows(&mut rows, || [F::zero(); NUM_SHA_COMPRESS_COLS]); + // Set the octet_num and octect columns for the padded rows. + let mut octet_num = 0; + let mut octet = 0; + for row in rows[num_real_rows..].iter_mut() { + let cols: &mut ShaCompressCols = row.as_mut_slice().borrow_mut(); + cols.octet_num[octet_num] = F::one(); + cols.octet[octet] = F::one(); + + // If in the compression phase, set the k value. + if octet_num != 0 && octet_num != 9 { + let compression_idx = octet_num - 1; + let k_idx = compression_idx * 8 + octet; + cols.k = Word::from(SHA_COMPRESS_K[k_idx]); + } + + octet = (octet + 1) % 8; + if octet == 0 { + octet_num = (octet_num + 1) % 10; + } + } + // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), NUM_SHA_COMPRESS_COLS, - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut ShaCompressCols = trace.values + [i * NUM_SHA_COMPRESS_COLS..(i + 1) * NUM_SHA_COMPRESS_COLS] + .borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { diff --git a/core/src/syscall/precompiles/sha256/extend/air.rs b/core/src/syscall/precompiles/sha256/extend/air.rs index 9da604804..69f38c355 100644 --- a/core/src/syscall/precompiles/sha256/extend/air.rs +++ b/core/src/syscall/precompiles/sha256/extend/air.rs @@ -27,6 +27,13 @@ where let (local, next) = (main.row_slice(0), main.row_slice(1)); let local: &ShaExtendCols = (*local).borrow(); let next: &ShaExtendCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); + let i_start = AB::F::from_canonical_u32(16); let nb_bytes_in_word = AB::F::from_canonical_u32(4); @@ -42,6 +49,10 @@ where .when_transition() .when_not(local.cycle_16_end.result * local.cycle_48[2]) .assert_eq(local.clk, next.clk); + builder + .when_transition() + .when_not(local.cycle_16_end.result * local.cycle_48[2]) + .assert_eq(local.channel, next.channel); builder .when_transition() .when_not(local.cycle_16_end.result * local.cycle_48[2]) @@ -214,22 +225,28 @@ where local.is_real, ); + builder.assert_word_eq(*local.w_i.value(), local.s2.value); + // Receive syscall event in first row of 48-cycle. builder.receive_syscall( local.shard, local.channel, local.clk, + local.nonce, AB::F::from_canonical_u32(SyscallCode::SHA_EXTEND.syscall_id()), local.w_ptr, AB::Expr::zero(), local.cycle_48_start, ); - // If this row is real and not the last cycle, then next row should also be real. + // Assert that is_real is a bool. + builder.assert_bool(local.is_real); + + // Ensure that all rows in a 48 row cycle has the same `is_real` values. builder .when_transition() - .when(local.is_real - local.cycle_48_end) - .assert_one(next.is_real); + .when_not(local.cycle_48_end) + .assert_eq(local.is_real, next.is_real); // Assert that the table ends in nonreal columns. Since each extend ecall is 48 cycles and // the table is padded to a power of 2, the last row of the table should always be padding. diff --git a/core/src/syscall/precompiles/sha256/extend/columns.rs b/core/src/syscall/precompiles/sha256/extend/columns.rs index 5eb99e1f4..0855b4413 100644 --- a/core/src/syscall/precompiles/sha256/extend/columns.rs +++ b/core/src/syscall/precompiles/sha256/extend/columns.rs @@ -18,6 +18,7 @@ pub struct ShaExtendCols { /// Inputs. pub shard: T, pub channel: T, + pub nonce: T, pub clk: T, pub w_ptr: T, @@ -36,8 +37,9 @@ pub struct ShaExtendCols { /// Flags for when in the first, second, or third 16-row cycle. pub cycle_48: [T; 3], - /// Whether the current row is the first of a 48-row cycle. + /// Whether the current row is the first of a 48-row cycle and is real. pub cycle_48_start: T, + /// Whether the current row is the end of a 48-row cycle and is real. pub cycle_48_end: T, /// Inputs to `s0`. diff --git a/core/src/syscall/precompiles/sha256/extend/execute.rs b/core/src/syscall/precompiles/sha256/extend/execute.rs index bd163c26c..d9b1a70e0 100644 --- a/core/src/syscall/precompiles/sha256/extend/execute.rs +++ b/core/src/syscall/precompiles/sha256/extend/execute.rs @@ -60,9 +60,11 @@ impl Syscall for ShaExtendChip { } // Push the SHA extend event. + let lookup_id = rt.syscall_lookup_id; let shard = rt.current_shard(); let channel = rt.current_channel(); rt.record_mut().sha_extend_events.push(ShaExtendEvent { + lookup_id, shard, channel, clk: clk_init, diff --git a/core/src/syscall/precompiles/sha256/extend/flags.rs b/core/src/syscall/precompiles/sha256/extend/flags.rs index 2f97dc92f..a06f117e3 100644 --- a/core/src/syscall/precompiles/sha256/extend/flags.rs +++ b/core/src/syscall/precompiles/sha256/extend/flags.rs @@ -7,6 +7,7 @@ use p3_field::PrimeField32; use p3_field::TwoAdicField; use p3_matrix::Matrix; +use crate::air::BaseAirBuilder; use crate::air::SP1AirBuilder; use crate::operations::IsZeroOperation; @@ -70,7 +71,7 @@ impl ShaExtendChip { builder, local.cycle_16 - AB::Expr::from(g), local.cycle_16_start, - local.is_real.into(), + one.clone(), ); // Constrain `cycle_16_end.result` to be `cycle_16 - 1 == 0`. Intuitively g^16 is 1. @@ -78,7 +79,7 @@ impl ShaExtendChip { builder, local.cycle_16 - AB::Expr::one(), local.cycle_16_end, - local.is_real.into(), + one.clone(), ); // Constrain `cycle_48` to be [1, 0, 0] in the first row. @@ -123,10 +124,10 @@ impl ShaExtendChip { .when(local.cycle_16_end.result * local.cycle_48[2]) .assert_eq(next.i, AB::F::from_canonical_u32(16)); - // When it's not the end of a 16-cycle, the next `i` must be the current plus one. + // When it's not the end of a 48-cycle, the next `i` must be the current plus one. builder .when_transition() - .when(one.clone() - local.cycle_16_end.result) + .when_not(local.cycle_16_end.result * local.cycle_48[2]) .assert_eq(local.i + one.clone(), next.i); } } diff --git a/core/src/syscall/precompiles/sha256/extend/mod.rs b/core/src/syscall/precompiles/sha256/extend/mod.rs index 4caff508b..7868cabd8 100644 --- a/core/src/syscall/precompiles/sha256/extend/mod.rs +++ b/core/src/syscall/precompiles/sha256/extend/mod.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShaExtendEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, diff --git a/core/src/syscall/precompiles/sha256/extend/trace.rs b/core/src/syscall/precompiles/sha256/extend/trace.rs index 2a976ef0d..2dcf88226 100644 --- a/core/src/syscall/precompiles/sha256/extend/trace.rs +++ b/core/src/syscall/precompiles/sha256/extend/trace.rs @@ -1,7 +1,7 @@ -use std::borrow::BorrowMut; - use p3_field::PrimeField32; use p3_matrix::dense::RowMajorMatrix; +use p3_matrix::Matrix; +use std::borrow::BorrowMut; use crate::{ air::MachineAir, @@ -156,10 +156,19 @@ impl MachineAir for ShaExtendChip { } // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), NUM_SHA_EXTEND_COLS, - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut ShaExtendCols = + trace.values[i * NUM_SHA_EXTEND_COLS..(i + 1) * NUM_SHA_EXTEND_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { diff --git a/core/src/syscall/precompiles/uint256/air.rs b/core/src/syscall/precompiles/uint256/air.rs index 498ac78c6..dd8cce29e 100644 --- a/core/src/syscall/precompiles/uint256/air.rs +++ b/core/src/syscall/precompiles/uint256/air.rs @@ -17,6 +17,7 @@ use crate::utils::{ use generic_array::GenericArray; use num::Zero; use num::{BigUint, One}; +use p3_air::AirBuilder; use p3_air::{Air, BaseAir}; use p3_field::AbstractField; use p3_field::PrimeField32; @@ -33,6 +34,7 @@ const NUM_COLS: usize = size_of::>(); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Uint256MulEvent { + pub lookup_id: usize, pub shard: u32, pub channel: u32, pub clk: u32, @@ -71,6 +73,9 @@ pub struct Uint256MulCols { /// The clock cycle of the syscall. pub clk: T, + /// The none of the operation. + pub nonce: T, + /// The pointer to the first input. pub x_ptr: T, @@ -201,7 +206,17 @@ impl MachineAir for Uint256MulChip { }); // Convert the trace to a row major matrix. - RowMajorMatrix::new(rows.into_iter().flatten().collect::>(), NUM_COLS) + let mut trace = + RowMajorMatrix::new(rows.into_iter().flatten().collect::>(), NUM_COLS); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut Uint256MulCols = + trace.values[i * NUM_COLS..(i + 1) * NUM_COLS].borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -257,10 +272,12 @@ impl Syscall for Uint256MulChip { // Write the result to x and keep track of the memory records. let x_memory_records = rt.mw_slice(x_ptr, &result); + let lookup_id = rt.syscall_lookup_id; let shard = rt.current_shard(); let channel = rt.current_channel(); let clk = rt.clk; rt.record_mut().uint256_mul_events.push(Uint256MulEvent { + lookup_id, shard, channel, clk, @@ -293,6 +310,14 @@ where let main = builder.main(); let local = main.row_slice(0); let local: &Uint256MulCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &Uint256MulCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); // We are computing (x * y) % modulus. The value of x is stored in the "prev_value" of // the x_memory, since we write to it later. @@ -368,6 +393,7 @@ where local.shard, local.channel, local.clk, + local.nonce, AB::F::from_canonical_u32(SyscallCode::UINT256_MUL.syscall_id()), local.x_ptr, local.y_ptr, diff --git a/core/src/syscall/precompiles/weierstrass/weierstrass_add.rs b/core/src/syscall/precompiles/weierstrass/weierstrass_add.rs index adbab629f..2eef29c6c 100644 --- a/core/src/syscall/precompiles/weierstrass/weierstrass_add.rs +++ b/core/src/syscall/precompiles/weierstrass/weierstrass_add.rs @@ -52,6 +52,7 @@ pub struct WeierstrassAddAssignCols { pub is_real: T, pub shard: T, pub channel: T, + pub nonce: T, pub clk: T, pub p_ptr: T, pub q_ptr: T, @@ -302,10 +303,21 @@ impl MachineAir }); // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), num_weierstrass_add_cols::(), - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut WeierstrassAddAssignCols = trace.values[i + * num_weierstrass_add_cols::() + ..(i + 1) * num_weierstrass_add_cols::()] + .borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -331,117 +343,125 @@ where { fn eval(&self, builder: &mut AB) { let main = builder.main(); - let row = main.row_slice(0); - let row: &WeierstrassAddAssignCols = (*row).borrow(); + let local = main.row_slice(0); + let local: &WeierstrassAddAssignCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &WeierstrassAddAssignCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); let num_words_field_element = ::Limbs::USIZE / 4; - let p_x = limbs_from_prev_access(&row.p_access[0..num_words_field_element]); - let p_y = limbs_from_prev_access(&row.p_access[num_words_field_element..]); + let p_x = limbs_from_prev_access(&local.p_access[0..num_words_field_element]); + let p_y = limbs_from_prev_access(&local.p_access[num_words_field_element..]); - let q_x = limbs_from_prev_access(&row.q_access[0..num_words_field_element]); - let q_y = limbs_from_prev_access(&row.q_access[num_words_field_element..]); + let q_x = limbs_from_prev_access(&local.q_access[0..num_words_field_element]); + let q_y = limbs_from_prev_access(&local.q_access[num_words_field_element..]); // slope = (q.y - p.y) / (q.x - p.x). let slope = { - row.slope_numerator.eval( + local.slope_numerator.eval( builder, &q_y, &p_y, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope_denominator.eval( + local.slope_denominator.eval( builder, &q_x, &p_x, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope.eval( + local.slope.eval( builder, - &row.slope_numerator.result, - &row.slope_denominator.result, + &local.slope_numerator.result, + &local.slope_denominator.result, FieldOperation::Div, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - &row.slope.result + &local.slope.result }; // x = slope * slope - self.x - other.x. let x = { - row.slope_squared.eval( + local.slope_squared.eval( builder, slope, slope, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.p_x_plus_q_x.eval( + local.p_x_plus_q_x.eval( builder, &p_x, &q_x, FieldOperation::Add, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.x3_ins.eval( + local.x3_ins.eval( builder, - &row.slope_squared.result, - &row.p_x_plus_q_x.result, + &local.slope_squared.result, + &local.p_x_plus_q_x.result, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - &row.x3_ins.result + &local.x3_ins.result }; // y = slope * (p.x - x_3n) - q.y. { - row.p_x_minus_x.eval( + local.p_x_minus_x.eval( builder, &p_x, x, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope_times_p_x_minus_x.eval( + local.slope_times_p_x_minus_x.eval( builder, slope, - &row.p_x_minus_x.result, + &local.p_x_minus_x.result, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.y3_ins.eval( + local.y3_ins.eval( builder, - &row.slope_times_p_x_minus_x.result, + &local.slope_times_p_x_minus_x.result, &p_y, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); } @@ -449,29 +469,29 @@ where // ensure that p_access is updated with the new value. for i in 0..E::BaseField::NB_LIMBS { builder - .when(row.is_real) - .assert_eq(row.x3_ins.result[i], row.p_access[i / 4].value()[i % 4]); - builder.when(row.is_real).assert_eq( - row.y3_ins.result[i], - row.p_access[num_words_field_element + i / 4].value()[i % 4], + .when(local.is_real) + .assert_eq(local.x3_ins.result[i], local.p_access[i / 4].value()[i % 4]); + builder.when(local.is_real).assert_eq( + local.y3_ins.result[i], + local.p_access[num_words_field_element + i / 4].value()[i % 4], ); } builder.eval_memory_access_slice( - row.shard, - row.channel, - row.clk.into(), - row.q_ptr, - &row.q_access, - row.is_real, + local.shard, + local.channel, + local.clk.into(), + local.q_ptr, + &local.q_access, + local.is_real, ); builder.eval_memory_access_slice( - row.shard, - row.channel, - row.clk + AB::F::from_canonical_u32(1), // We read p at +1 since p, q could be the same. - row.p_ptr, - &row.p_access, - row.is_real, + local.shard, + local.channel, + local.clk + AB::F::from_canonical_u32(1), // We read p at +1 since p, q could be the same. + local.p_ptr, + &local.p_access, + local.is_real, ); // Fetch the syscall id for the curve type. @@ -487,13 +507,14 @@ where }; builder.receive_syscall( - row.shard, - row.channel, - row.clk, + local.shard, + local.channel, + local.clk, + local.nonce, syscall_id_felt, - row.p_ptr, - row.q_ptr, - row.is_real, + local.p_ptr, + local.q_ptr, + local.is_real, ); } } diff --git a/core/src/syscall/precompiles/weierstrass/weierstrass_decompress.rs b/core/src/syscall/precompiles/weierstrass/weierstrass_decompress.rs index bd38edea8..62958e86c 100644 --- a/core/src/syscall/precompiles/weierstrass/weierstrass_decompress.rs +++ b/core/src/syscall/precompiles/weierstrass/weierstrass_decompress.rs @@ -54,6 +54,7 @@ pub struct WeierstrassDecompressCols { pub shard: T, pub channel: T, pub clk: T, + pub nonce: T, pub ptr: T, pub is_odd: T, pub x_access: GenericArray, P::WordsFieldElement>, @@ -222,10 +223,21 @@ impl MachineAir row }); - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), num_weierstrass_decompress_cols::(), - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut WeierstrassDecompressCols = trace.values[i + * num_weierstrass_decompress_cols::() + ..(i + 1) * num_weierstrass_decompress_cols::()] + .borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -250,99 +262,108 @@ where { fn eval(&self, builder: &mut AB) { let main = builder.main(); - let row = main.row_slice(0); - let row: &WeierstrassDecompressCols = (*row).borrow(); + let local = main.row_slice(0); + let local: &WeierstrassDecompressCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &WeierstrassDecompressCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); let num_limbs = ::Limbs::USIZE; let num_words_field_element = num_limbs / 4; - builder.assert_bool(row.is_odd); + builder.assert_bool(local.is_odd); let x: Limbs::Limbs> = - limbs_from_prev_access(&row.x_access); - row.range_x - .eval(builder, &x, row.shard, row.channel, row.is_real); - row.x_2.eval( + limbs_from_prev_access(&local.x_access); + local + .range_x + .eval(builder, &x, local.shard, local.channel, local.is_real); + local.x_2.eval( builder, &x, &x, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.x_3.eval( + local.x_3.eval( builder, - &row.x_2.result, + &local.x_2.result, &x, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); let b = E::b_int(); let b_const = E::BaseField::to_limbs_field::(&b); - row.x_3_plus_b.eval( + local.x_3_plus_b.eval( builder, - &row.x_3.result, + &local.x_3.result, &b_const, FieldOperation::Add, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.neg_y.eval( + local.neg_y.eval( builder, &[AB::Expr::zero()].iter(), - &row.y.multiplication.result, + &local.y.multiplication.result, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); // Interpret the lowest bit of Y as whether it is odd or not. - let y_is_odd = row.y.lsb; + let y_is_odd = local.y.lsb; - row.y.eval( + local.y.eval( builder, - &row.x_3_plus_b.result, - row.y.lsb, - row.shard, - row.channel, - row.is_real, + &local.x_3_plus_b.result, + local.y.lsb, + local.shard, + local.channel, + local.is_real, ); let y_limbs: Limbs::Limbs> = - limbs_from_access(&row.y_access); + limbs_from_access(&local.y_access); builder - .when(row.is_real) - .when_ne(y_is_odd, AB::Expr::one() - row.is_odd) - .assert_all_eq(row.y.multiplication.result, y_limbs); + .when(local.is_real) + .when_ne(y_is_odd, AB::Expr::one() - local.is_odd) + .assert_all_eq(local.y.multiplication.result, y_limbs); builder - .when(row.is_real) - .when_ne(y_is_odd, row.is_odd) - .assert_all_eq(row.neg_y.result, y_limbs); + .when(local.is_real) + .when_ne(y_is_odd, local.is_odd) + .assert_all_eq(local.neg_y.result, y_limbs); for i in 0..num_words_field_element { builder.eval_memory_access( - row.shard, - row.channel, - row.clk, - row.ptr.into() + AB::F::from_canonical_u32((i as u32) * 4 + num_limbs as u32), - &row.x_access[i], - row.is_real, + local.shard, + local.channel, + local.clk, + local.ptr.into() + AB::F::from_canonical_u32((i as u32) * 4 + num_limbs as u32), + &local.x_access[i], + local.is_real, ); } for i in 0..num_words_field_element { builder.eval_memory_access( - row.shard, - row.channel, - row.clk, - row.ptr.into() + AB::F::from_canonical_u32((i as u32) * 4), - &row.y_access[i], - row.is_real, + local.shard, + local.channel, + local.clk, + local.ptr.into() + AB::F::from_canonical_u32((i as u32) * 4), + &local.y_access[i], + local.is_real, ); } @@ -357,13 +378,14 @@ where }; builder.receive_syscall( - row.shard, - row.channel, - row.clk, + local.shard, + local.channel, + local.clk, + local.nonce, syscall_id, - row.ptr, - row.is_odd, - row.is_real, + local.ptr, + local.is_odd, + local.is_real, ); } } diff --git a/core/src/syscall/precompiles/weierstrass/weierstrass_double.rs b/core/src/syscall/precompiles/weierstrass/weierstrass_double.rs index 50bb0a433..9221d680f 100644 --- a/core/src/syscall/precompiles/weierstrass/weierstrass_double.rs +++ b/core/src/syscall/precompiles/weierstrass/weierstrass_double.rs @@ -53,6 +53,7 @@ pub struct WeierstrassDoubleAssignCols { pub is_real: T, pub shard: T, pub channel: T, + pub nonce: T, pub clk: T, pub p_ptr: T, pub p_access: GenericArray, P::WordsCurvePoint>, @@ -317,10 +318,21 @@ impl MachineAir }); // Convert the trace to a row major matrix. - RowMajorMatrix::new( + let mut trace = RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), num_weierstrass_double_cols::(), - ) + ); + + // Write the nonces to the trace. + for i in 0..trace.height() { + let cols: &mut WeierstrassDoubleAssignCols = trace.values[i + * num_weierstrass_double_cols::() + ..(i + 1) * num_weierstrass_double_cols::()] + .borrow_mut(); + cols.nonce = F::from_canonical_usize(i); + } + + trace } fn included(&self, shard: &Self::Record) -> bool { @@ -346,136 +358,143 @@ where { fn eval(&self, builder: &mut AB) { let main = builder.main(); - let row = main.row_slice(0); - let row: &WeierstrassDoubleAssignCols = (*row).borrow(); + let local = main.row_slice(0); + let local: &WeierstrassDoubleAssignCols = (*local).borrow(); + let next = main.row_slice(1); + let next: &WeierstrassDoubleAssignCols = (*next).borrow(); + + // Constrain the incrementing nonce. + builder.when_first_row().assert_zero(local.nonce); + builder + .when_transition() + .assert_eq(local.nonce + AB::Expr::one(), next.nonce); let num_words_field_element = E::BaseField::NB_LIMBS / 4; - let p_x = limbs_from_prev_access(&row.p_access[0..num_words_field_element]); - let p_y = limbs_from_prev_access(&row.p_access[num_words_field_element..]); + let p_x = limbs_from_prev_access(&local.p_access[0..num_words_field_element]); + let p_y = limbs_from_prev_access(&local.p_access[num_words_field_element..]); - // a in the Weierstrass form: y^2 = x^3 + a * x + b. - // TODO: U32 can't be hardcoded here? + // `a` in the Weierstrass form: y^2 = x^3 + a * x + b. let a = E::BaseField::to_limbs_field::(&E::a_int()); // slope = slope_numerator / slope_denominator. let slope = { // slope_numerator = a + (p.x * p.x) * 3. { - row.p_x_squared.eval( + local.p_x_squared.eval( builder, &p_x, &p_x, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.p_x_squared_times_3.eval( + local.p_x_squared_times_3.eval( builder, - &row.p_x_squared.result, + &local.p_x_squared.result, &E::BaseField::to_limbs_field::(&BigUint::from(3u32)), FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope_numerator.eval( + local.slope_numerator.eval( builder, &a, - &row.p_x_squared_times_3.result, + &local.p_x_squared_times_3.result, FieldOperation::Add, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); }; // slope_denominator = 2 * y. - row.slope_denominator.eval( + local.slope_denominator.eval( builder, &E::BaseField::to_limbs_field::(&BigUint::from(2u32)), &p_y, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope.eval( + local.slope.eval( builder, - &row.slope_numerator.result, - &row.slope_denominator.result, + &local.slope_numerator.result, + &local.slope_denominator.result, FieldOperation::Div, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - &row.slope.result + &local.slope.result }; // x = slope * slope - (p.x + p.x). let x = { - row.slope_squared.eval( + local.slope_squared.eval( builder, slope, slope, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.p_x_plus_p_x.eval( + local.p_x_plus_p_x.eval( builder, &p_x, &p_x, FieldOperation::Add, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.x3_ins.eval( + local.x3_ins.eval( builder, - &row.slope_squared.result, - &row.p_x_plus_p_x.result, + &local.slope_squared.result, + &local.p_x_plus_p_x.result, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - &row.x3_ins.result + &local.x3_ins.result }; // y = slope * (p.x - x) - p.y. { - row.p_x_minus_x.eval( + local.p_x_minus_x.eval( builder, &p_x, x, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.slope_times_p_x_minus_x.eval( + local.slope_times_p_x_minus_x.eval( builder, slope, - &row.p_x_minus_x.result, + &local.p_x_minus_x.result, FieldOperation::Mul, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); - row.y3_ins.eval( + local.y3_ins.eval( builder, - &row.slope_times_p_x_minus_x.result, + &local.slope_times_p_x_minus_x.result, &p_y, FieldOperation::Sub, - row.shard, - row.channel, - row.is_real, + local.shard, + local.channel, + local.is_real, ); } @@ -483,21 +502,21 @@ where // ensure that p_access is updated with the new value. for i in 0..E::BaseField::NB_LIMBS { builder - .when(row.is_real) - .assert_eq(row.x3_ins.result[i], row.p_access[i / 4].value()[i % 4]); - builder.when(row.is_real).assert_eq( - row.y3_ins.result[i], - row.p_access[num_words_field_element + i / 4].value()[i % 4], + .when(local.is_real) + .assert_eq(local.x3_ins.result[i], local.p_access[i / 4].value()[i % 4]); + builder.when(local.is_real).assert_eq( + local.y3_ins.result[i], + local.p_access[num_words_field_element + i / 4].value()[i % 4], ); } builder.eval_memory_access_slice( - row.shard, - row.channel, - row.clk.into(), - row.p_ptr, - &row.p_access, - row.is_real, + local.shard, + local.channel, + local.clk.into(), + local.p_ptr, + &local.p_access, + local.is_real, ); // Fetch the syscall id for the curve type. @@ -513,13 +532,14 @@ where }; builder.receive_syscall( - row.shard, - row.channel, - row.clk, + local.shard, + local.channel, + local.clk, + local.nonce, syscall_id_felt, - row.p_ptr, + local.p_ptr, AB::Expr::zero(), - row.is_real, + local.is_real, ); } } diff --git a/core/src/syscall/verify.rs b/core/src/syscall/verify.rs index e40639aeb..11b043010 100644 --- a/core/src/syscall/verify.rs +++ b/core/src/syscall/verify.rs @@ -1,6 +1,6 @@ use crate::{ runtime::{Syscall, SyscallContext}, - stark::{RiscvAir, StarkGenericConfig}, + stark::StarkGenericConfig, utils::BabyBearPoseidon2Inner, }; @@ -38,17 +38,6 @@ impl Syscall for SyscallVerifySP1Proof { let config = BabyBearPoseidon2Inner::new(); let mut challenger = config.challenger(); - // TODO: need to use RecursionAir here - let machine = RiscvAir::machine(config); - - // TODO: Need to import PublicValues from recursion. - // Assert the commit in vkey from runtime inputs matches the one from syscall. - // Assert that the public values digest from runtime inputs matches the one from syscall. - - // TODO: Verify proof - // machine - // .verify(proof_vk, proof, &mut challenger) - // .expect("proof verification failed"); None } diff --git a/prover/src/build.rs b/prover/src/build.rs index 129ec454a..24f152d49 100644 --- a/prover/src/build.rs +++ b/prover/src/build.rs @@ -37,9 +37,6 @@ pub fn try_install_plonk_bn254_artifacts() -> PathBuf { } /// Tries to build the PLONK artifacts inside the development directory. -/// -/// TODO: Maybe add some additional logic here to handle rebuilding the artifacts if they are -/// already built. pub fn try_build_plonk_bn254_artifacts_dev( template_vk: &StarkVerifyingKey, template_proof: &ShardProof, diff --git a/prover/src/verify.rs b/prover/src/verify.rs index fedd467cf..f829f4d6a 100644 --- a/prover/src/verify.rs +++ b/prover/src/verify.rs @@ -4,6 +4,7 @@ use anyhow::Result; use num_bigint::BigUint; use p3_baby_bear::BabyBear; use p3_field::{AbstractField, PrimeField}; +use sp1_core::air::MachineAir; use sp1_core::{ air::PublicValues, io::SP1PublicValues, @@ -45,7 +46,7 @@ impl SP1Prover { self.core_machine .verify(&vk.vk, &machine_proof, &mut challenger)?; - // Verify shard transitions + // Verify shard transitions. for (i, shard_proof) in proof.0.iter().enumerate() { let public_values = PublicValues::from_vec(shard_proof.public_values.clone()); // Verify shard transitions @@ -100,6 +101,58 @@ impl SP1Prover { } } + // Verify that the number of shards is not too large. + if proof.0.len() > 1 << 16 { + return Err(MachineVerificationError::TooManyShards); + } + + // Verify that the `MemoryInit` and `MemoryFinalize` chips are the last chips in the proof. + for (i, shard_proof) in proof.0.iter().enumerate() { + let chips = self + .core_machine + .shard_chips_ordered(&shard_proof.chip_ordering) + .collect::>(); + let program_memory_init_count = chips + .clone() + .into_iter() + .filter(|chip| chip.name() == "MemoryProgram") + .count(); + let memory_init_count = chips + .clone() + .into_iter() + .filter(|chip| chip.name() == "MemoryInit") + .count(); + let memory_final_count = chips + .into_iter() + .filter(|chip| chip.name() == "MemoryFinalize") + .count(); + + // Assert that the `MemoryProgram` chip only exists in the first shard. + if i == 0 && program_memory_init_count != 1 { + return Err(MachineVerificationError::InvalidChipOccurence( + "memory should exist in the first chip".to_string(), + )); + } + if i != 0 && program_memory_init_count > 0 { + return Err(MachineVerificationError::InvalidChipOccurence( + "memory program should not exist in the first chip".to_string(), + )); + } + + // Assert that the `MemoryInit` and `MemoryFinalize` chips only exist in the last shard. + if i != proof.0.len() - 1 && (memory_final_count > 0 || memory_init_count > 0) { + return Err(MachineVerificationError::InvalidChipOccurence( + "memory init and finalize should not eixst anywhere but the last chip" + .to_string(), + )); + } + if i == proof.0.len() - 1 && (memory_init_count != 1 || memory_final_count != 1) { + return Err(MachineVerificationError::InvalidChipOccurence( + "memory init and finalize should exist the last chip".to_string(), + )); + } + } + Ok(()) } diff --git a/recursion/compiler/src/ir/bits.rs b/recursion/compiler/src/ir/bits.rs index f69c8cee1..396fb92be 100644 --- a/recursion/compiler/src/ir/bits.rs +++ b/recursion/compiler/src/ir/bits.rs @@ -26,6 +26,15 @@ impl Builder { output } + /// Range checks a variable to a certain number of bits. + pub fn range_check_v(&mut self, num: Var, num_bits: usize) { + let bits = self.num2bits_v(num); + self.range(num_bits, bits.len()).for_each(|i, builder| { + let bit = builder.get(&bits, i); + builder.assert_var_eq(bit, C::N::zero()); + }); + } + /// Converts a variable to bits inside a circuit. pub fn num2bits_v_circuit(&mut self, num: Var, bits: usize) -> Vec> { let mut output = Vec::new(); diff --git a/recursion/core/src/air/builder.rs b/recursion/core/src/air/builder.rs index 6bcf20d40..ab6e6c101 100644 --- a/recursion/core/src/air/builder.rs +++ b/recursion/core/src/air/builder.rs @@ -30,6 +30,8 @@ pub trait RecursionMemoryAirBuilder: RecursionInteractionAirBuilder { is_real: impl Into, ) { let is_real: Self::Expr = is_real.into(); + self.assert_bool(is_real.clone()); + let timestamp: Self::Expr = timestamp.into(); let mem_access = memory_access.access(); @@ -66,6 +68,8 @@ pub trait RecursionMemoryAirBuilder: RecursionInteractionAirBuilder { is_real: impl Into, ) { let is_real: Self::Expr = is_real.into(); + self.assert_bool(is_real.clone()); + let timestamp: Self::Expr = timestamp.into(); let mem_access = memory_access.access(); diff --git a/recursion/core/src/cpu/air/branch.rs b/recursion/core/src/cpu/air/branch.rs index 91bebfa65..105dd771d 100644 --- a/recursion/core/src/cpu/air/branch.rs +++ b/recursion/core/src/cpu/air/branch.rs @@ -3,7 +3,9 @@ use p3_field::{AbstractField, Field}; use sp1_core::air::{BinomialExtension, ExtensionAirBuilder}; use crate::{ - air::{BinomialExtensionUtils, IsExtZeroOperation, SP1RecursionAirBuilder}, + air::{ + BinomialExtensionUtils, Block, BlockBuilder, IsExtZeroOperation, SP1RecursionAirBuilder, + }, cpu::{CpuChip, CpuCols}, memory::MemoryCols, }; @@ -22,18 +24,24 @@ impl CpuChip { let is_branch_instruction = self.is_branch_instruction::(local); let one = AB::Expr::one(); - // If the instruction is a BNEINC, verify that the a value is incremented by one. - builder - .when(local.is_real) - .when(local.selectors.is_bneinc) - .assert_eq(local.a.value()[0], local.a.prev_value()[0] + one.clone()); - // Convert operand values from Block to BinomialExtension. Note that it gets the // previous value of the `a` and `b` operands, since BNENIC will modify `a`. + let a_prev_ext: BinomialExtension = + BinomialExtensionUtils::from_block(local.a.prev_value().map(|x| x.into())); let a_ext: BinomialExtension = BinomialExtensionUtils::from_block(local.a.value().map(|x| x.into())); let b_ext: BinomialExtension = BinomialExtensionUtils::from_block(local.b.value().map(|x| x.into())); + let one_ext: BinomialExtension = + BinomialExtensionUtils::from_block(Block::from(one.clone())); + + let expected_a_ext = a_prev_ext + one_ext; + + // If the instruction is a BNEINC, verify that the a value is incremented by one. + builder + .when(local.is_real) + .when(local.selectors.is_bneinc) + .assert_block_eq(a_ext.as_block(), expected_a_ext.as_block()); let comparison_diff = a_ext - b_ext; diff --git a/recursion/core/src/cpu/air/jump.rs b/recursion/core/src/cpu/air/jump.rs index bf86a70cc..dd5e9b8bb 100644 --- a/recursion/core/src/cpu/air/jump.rs +++ b/recursion/core/src/cpu/air/jump.rs @@ -2,7 +2,7 @@ use p3_air::AirBuilder; use p3_field::{AbstractField, Field}; use crate::{ - air::SP1RecursionAirBuilder, + air::{Block, BlockBuilder, SP1RecursionAirBuilder}, cpu::{CpuChip, CpuCols}, memory::MemoryCols, runtime::STACK_SIZE, @@ -21,19 +21,29 @@ impl CpuChip { ) where AB: SP1RecursionAirBuilder, { + let is_jump_instr = self.is_jump_instruction::(local); + // Verify the next row's fp. builder .when_first_row() .assert_eq(local.fp, F::from_canonical_usize(STACK_SIZE)); - let not_jump_instruction = AB::Expr::one() - self.is_jump_instruction::(local); + let not_jump_instruction = AB::Expr::one() - is_jump_instr.clone(); let expected_next_fp = local.selectors.is_jal * (local.fp + local.c.value()[0]) - + local.selectors.is_jalr * local.a.value()[0] + + local.selectors.is_jalr * local.c.value()[0] + not_jump_instruction * local.fp; builder .when_transition() .when(next.is_real) .assert_eq(next.fp, expected_next_fp); + // Verify the a operand values. + let expected_a_val = local.selectors.is_jal * local.pc + + local.selectors.is_jalr * (local.pc + AB::Expr::one()); + let expected_a_val_block = Block::from(expected_a_val); + builder + .when(is_jump_instr) + .assert_block_eq(*local.a.value(), expected_a_val_block); + // Add to the `next_pc` expression. *next_pc += local.selectors.is_jal * (local.pc + local.b.value()[0]); *next_pc += local.selectors.is_jalr * local.b.value()[0]; diff --git a/recursion/core/src/cpu/air/memory.rs b/recursion/core/src/cpu/air/memory.rs index c0a3a2b63..d1b024130 100644 --- a/recursion/core/src/cpu/air/memory.rs +++ b/recursion/core/src/cpu/air/memory.rs @@ -30,7 +30,7 @@ impl CpuChip { local.clk + AB::F::from_canonical_u32(MemoryAccessPosition::Memory as u32), memory_cols.memory_addr, &memory_cols.memory, - is_memory_instr, + is_memory_instr.clone(), ); // Constraints on the memory column depending on load or store. @@ -41,7 +41,7 @@ impl CpuChip { ); // When there is a store, we ensure that we are writing the value of the a operand to the memory. builder - .when(local.selectors.is_store) + .when(is_memory_instr) .assert_block_eq(*local.a.value(), *memory_cols.memory.value()); } } diff --git a/recursion/core/src/multi/mod.rs b/recursion/core/src/multi/mod.rs index 23173f7de..10a9c3db0 100644 --- a/recursion/core/src/multi/mod.rs +++ b/recursion/core/src/multi/mod.rs @@ -190,8 +190,7 @@ where local.poseidon2_receive_table, ); sub_builder.assert_eq( - local.is_poseidon2 - * Poseidon2Chip::do_memory_access::(poseidon2_columns), + local.is_poseidon2 * Poseidon2Chip::do_memory_access::(poseidon2_columns), local.poseidon2_memory_access, ); @@ -201,7 +200,7 @@ where local.poseidon2(), next.poseidon2(), local.poseidon2_receive_table, - local.poseidon2_memory_access.into(), + local.poseidon2_memory_access, ); } } diff --git a/recursion/core/src/poseidon2/columns.rs b/recursion/core/src/poseidon2/columns.rs index 12fa73047..fa12a655f 100644 --- a/recursion/core/src/poseidon2/columns.rs +++ b/recursion/core/src/poseidon2/columns.rs @@ -11,7 +11,10 @@ pub struct Poseidon2Cols { pub left_input: T, pub right_input: T, pub rounds: [T; 24], // 1 round for memory input; 1 round for initialize; 8 rounds for external; 13 rounds for internal; 1 round for memory output + pub do_receive: T, + pub do_memory: T, pub round_specific_cols: RoundSpecificCols, + pub is_real: T, } #[derive(AlignedBorrow, Clone, Copy)] @@ -45,6 +48,7 @@ impl RoundSpecificCols { pub struct ComputationCols { pub input: [T; WIDTH], pub add_rc: [T; WIDTH], + pub sbox_deg_3: [T; WIDTH], pub sbox_deg_7: [T; WIDTH], pub output: [T; WIDTH], } diff --git a/recursion/core/src/poseidon2/external.rs b/recursion/core/src/poseidon2/external.rs index c871bd873..d340ba2b4 100644 --- a/recursion/core/src/poseidon2/external.rs +++ b/recursion/core/src/poseidon2/external.rs @@ -6,7 +6,6 @@ use p3_field::AbstractField; use p3_matrix::Matrix; use sp1_core::air::{BaseAirBuilder, ExtensionAirBuilder, SP1AirBuilder}; use sp1_primitives::RC_16_30_U32; -use std::ops::Add; use crate::air::{RecursionInteractionAirBuilder, RecursionMemoryAirBuilder}; use crate::memory::MemoryCols; @@ -40,7 +39,7 @@ impl Poseidon2Chip { local: &Poseidon2Cols, next: &Poseidon2Cols, receive_table: AB::Var, - memory_access: AB::Expr, + memory_access: AB::Var, ) { const NUM_ROUNDS_F: usize = 8; const NUM_ROUNDS_P: usize = 13; @@ -66,6 +65,10 @@ impl Poseidon2Chip { .sum::(); let is_memory_write = local.rounds[local.rounds.len() - 1]; + self.eval_control_flow_and_inputs(builder, local, next); + + self.eval_syscall(builder, local, receive_table); + self.eval_mem( builder, local, @@ -84,16 +87,71 @@ impl Poseidon2Chip { is_internal_layer.clone(), NUM_ROUNDS_F + NUM_ROUNDS_P + 1, ); + } - self.eval_syscall(builder, local, receive_table); - - // Range check all flags. - for i in 0..local.rounds.len() { + fn eval_control_flow_and_inputs( + &self, + builder: &mut AB, + local: &Poseidon2Cols, + next: &Poseidon2Cols, + ) { + let num_total_rounds = local.rounds.len(); + for i in 0..num_total_rounds { + // Verify that the round flags are correct. builder.assert_bool(local.rounds[i]); + + // Assert that the next round is correct. + builder + .when_transition() + .assert_eq(local.rounds[i], next.rounds[(i + 1) % num_total_rounds]); + + if i != num_total_rounds - 1 { + builder + .when_transition() + .when(local.rounds[i]) + .assert_eq(local.clk, next.clk); + builder + .when_transition() + .when(local.rounds[i]) + .assert_eq(local.dst_input, next.dst_input); + builder + .when_transition() + .when(local.rounds[i]) + .assert_eq(local.left_input, next.left_input); + builder + .when_transition() + .when(local.rounds[i]) + .assert_eq(local.right_input, next.right_input); + } } - builder.assert_bool( - is_memory_read + is_initial + is_external_layer + is_internal_layer + is_memory_write, + + // Ensure that at most one of the round flags is set. + let round_acc = local + .rounds + .iter() + .fold(AB::Expr::zero(), |acc, round_flag| acc + *round_flag); + builder.assert_bool(round_acc); + + // Verify the do_memory flag. + builder.assert_eq( + local.do_memory, + local.is_real * (local.rounds[0] + local.rounds[23]), ); + + // Verify the do_receive flag. + builder.assert_eq(local.do_receive, local.is_real * local.rounds[0]); + + // Verify the first row starts at round 0. + builder.when_first_row().assert_one(local.rounds[0]); + // The round count is not a power of 2, so the last row should not be real. + builder.when_last_row().assert_zero(local.is_real); + + // Verify that all is_real flags within a round are equal. + let is_last_round = local.rounds[23]; + builder + .when_transition() + .when_not(is_last_round) + .assert_eq(local.is_real, next.is_real); } fn eval_mem( @@ -103,20 +161,23 @@ impl Poseidon2Chip { next: &Poseidon2Cols, is_memory_read: AB::Var, is_memory_write: AB::Var, - memory_access: AB::Expr, + memory_access: AB::Var, ) { let memory_access_cols = local.round_specific_cols.memory_access(); builder + .when(local.is_real) .when(is_memory_read) .assert_eq(local.left_input, memory_access_cols.addr_first_half); builder + .when(local.is_real) .when(is_memory_read) .assert_eq(local.right_input, memory_access_cols.addr_second_half); builder + .when(local.is_real) .when(is_memory_write) .assert_eq(local.dst_input, memory_access_cols.addr_first_half); - builder.when(is_memory_write).assert_eq( + builder.when(local.is_real).when(is_memory_write).assert_eq( local.dst_input + AB::F::from_canonical_usize(WIDTH / 2), memory_access_cols.addr_second_half, ); @@ -131,7 +192,11 @@ impl Poseidon2Chip { local.clk + AB::Expr::one() * is_memory_write, addr, &memory_access_cols.mem_access[i], - memory_access.clone(), + memory_access, + ); + builder.when(local.is_real).when(is_memory_read).assert_eq( + *memory_access_cols.mem_access[i].value(), + *memory_access_cols.mem_access[i].prev_value(), ); } @@ -139,10 +204,14 @@ impl Poseidon2Chip { // computation round. let next_computation_col = next.round_specific_cols.computation(); for i in 0..WIDTH { - builder.when_transition().when(is_memory_read).assert_eq( - *memory_access_cols.mem_access[i].value(), - next_computation_col.input[i], - ); + builder + .when_transition() + .when(local.is_real) + .when(is_memory_read) + .assert_eq( + *memory_access_cols.mem_access[i].value(), + next_computation_col.input[i], + ); } } @@ -184,6 +253,7 @@ impl Poseidon2Chip { } } builder + .when(local.is_real) .when(is_initial.clone() + is_external_layer.clone() + is_internal_layer.clone()) .assert_eq(result, computation_cols.add_rc[i]); } @@ -196,8 +266,15 @@ impl Poseidon2Chip { let sbox_deg_3 = computation_cols.add_rc[i] * computation_cols.add_rc[i] * computation_cols.add_rc[i]; - let sbox_deg_7 = sbox_deg_3.clone() * sbox_deg_3.clone() * computation_cols.add_rc[i]; builder + .when(local.is_real) + .when(is_initial.clone() + is_external_layer.clone() + is_internal_layer.clone()) + .assert_eq(computation_cols.sbox_deg_3[i], sbox_deg_3); + let sbox_deg_7 = computation_cols.sbox_deg_3[i] + * computation_cols.sbox_deg_3[i] + * computation_cols.add_rc[i]; + builder + .when(local.is_real) .when(is_initial.clone() + is_external_layer.clone() + is_internal_layer.clone()) .assert_eq(sbox_deg_7, computation_cols.sbox_deg_7[i]); } @@ -253,6 +330,7 @@ impl Poseidon2Chip { for i in 0..WIDTH { state[i] += sums[i % 4].clone(); builder + .when(local.is_real) .when(is_external_layer.clone() + is_initial.clone()) .assert_eq(state[i].clone(), computation_cols.output[i]); } @@ -264,6 +342,7 @@ impl Poseidon2Chip { let mut state: [AB::Expr; WIDTH] = sbox_result.clone(); internal_linear_layer(&mut state); builder + .when(local.is_real) .when(is_internal_layer.clone()) .assert_all_eq(state.clone(), computation_cols.output); } @@ -281,6 +360,7 @@ impl Poseidon2Chip { builder .when_transition() + .when(local.is_real) .when(is_initial.clone() + is_external_layer.clone() + is_internal_layer.clone()) .assert_eq(computation_cols.output[i], next_round_value); } @@ -307,13 +387,11 @@ impl Poseidon2Chip { } pub const fn do_receive_table(local: &Poseidon2Cols) -> T { - local.rounds[0] + local.do_receive } - pub fn do_memory_access, Output>( - local: &Poseidon2Cols, - ) -> Output { - local.rounds[0] + local.rounds[23] + pub fn do_memory_access(local: &Poseidon2Cols) -> T { + local.do_memory } } @@ -333,7 +411,7 @@ where local, next, Self::do_receive_table::(local), - Self::do_memory_access::(local), + Self::do_memory_access::(local), ); } } diff --git a/recursion/core/src/poseidon2/trace.rs b/recursion/core/src/poseidon2/trace.rs index cc6a41d94..2d5639edd 100644 --- a/recursion/core/src/poseidon2/trace.rs +++ b/recursion/core/src/poseidon2/trace.rs @@ -49,7 +49,9 @@ impl MachineAir for Poseidon2Chip { for r in 0..rounds { let mut row = [F::zero(); NUM_POSEIDON2_COLS]; let cols: &mut Poseidon2Cols = row.as_mut_slice().borrow_mut(); + cols.is_real = F::one(); + let is_receive = r == 0; let is_memory_read = r == 0; let is_initial_layer = r == 1; let is_external_layer = @@ -78,6 +80,10 @@ impl MachineAir for Poseidon2Chip { cols.right_input = poseidon2_event.right; cols.rounds[r] = F::one(); + if is_receive { + cols.do_receive = F::one(); + } + if is_memory_read || is_memory_write { let memory_access_cols = cols.round_specific_cols.memory_access_mut(); @@ -97,6 +103,7 @@ impl MachineAir for Poseidon2Chip { .populate(&poseidon2_event.result_records[i]); } } + cols.do_memory = F::one(); } else { let computation_cols = cols.round_specific_cols.computation_mut(); @@ -131,6 +138,7 @@ impl MachineAir for Poseidon2Chip { let sbox_deg_3 = computation_cols.add_rc[j] * computation_cols.add_rc[j] * computation_cols.add_rc[j]; + computation_cols.sbox_deg_3[j] = sbox_deg_3; computation_cols.sbox_deg_7[j] = sbox_deg_3 * sbox_deg_3 * computation_cols.add_rc[j]; } @@ -163,6 +171,8 @@ impl MachineAir for Poseidon2Chip { } } + let num_real_rows = rows.len(); + // Pad the trace to a power of two. pad_rows_fixed( &mut rows, @@ -170,6 +180,14 @@ impl MachineAir for Poseidon2Chip { self.fixed_log2_rows, ); + let mut round_num = 0; + for row in rows[num_real_rows..].iter_mut() { + let cols: &mut Poseidon2Cols = row.as_mut_slice().borrow_mut(); + cols.rounds[round_num] = F::one(); + + round_num = (round_num + 1) % rounds; + } + // Convert the trace to a row major matrix. RowMajorMatrix::new( rows.into_iter().flatten().collect::>(), diff --git a/recursion/groth16/constraints.json b/recursion/groth16/constraints.json deleted file mode 100644 index 28fc1fdac..000000000 --- a/recursion/groth16/constraints.json +++ /dev/null @@ -1 +0,0 @@ -[{"opcode":"ImmV","args":[["var0"],["100"]]},{"opcode":"Num2BitsV","args":[["var1","var2","var3","var4","var5","var6","var7","var8","var9","var10","var11","var12","var13","var14","var15","var16","var17","var18","var19","var20","var21","var22","var23","var24","var25","var26","var27","var28","var29","var30","var31","var32"],["var0"],["32"]]},{"opcode":"ImmV","args":[["backend0"],["0"]]},{"opcode":"AssertEqV","args":[["var1"],["backend0"]]},{"opcode":"ImmV","args":[["backend1"],["0"]]},{"opcode":"AssertEqV","args":[["var2"],["backend1"]]},{"opcode":"ImmV","args":[["backend2"],["1"]]},{"opcode":"AssertEqV","args":[["var3"],["backend2"]]},{"opcode":"ImmV","args":[["backend3"],["0"]]},{"opcode":"AssertEqV","args":[["var4"],["backend3"]]},{"opcode":"ImmV","args":[["backend4"],["0"]]},{"opcode":"AssertEqV","args":[["var5"],["backend4"]]},{"opcode":"ImmV","args":[["backend5"],["1"]]},{"opcode":"AssertEqV","args":[["var6"],["backend5"]]},{"opcode":"ImmV","args":[["backend6"],["1"]]},{"opcode":"AssertEqV","args":[["var7"],["backend6"]]},{"opcode":"ImmV","args":[["backend7"],["0"]]},{"opcode":"AssertEqV","args":[["var8"],["backend7"]]},{"opcode":"ImmV","args":[["backend8"],["0"]]},{"opcode":"AssertEqV","args":[["var9"],["backend8"]]},{"opcode":"ImmV","args":[["backend9"],["0"]]},{"opcode":"AssertEqV","args":[["var10"],["backend9"]]},{"opcode":"ImmV","args":[["backend10"],["0"]]},{"opcode":"AssertEqV","args":[["var11"],["backend10"]]},{"opcode":"ImmV","args":[["backend11"],["0"]]},{"opcode":"AssertEqV","args":[["var12"],["backend11"]]},{"opcode":"ImmV","args":[["backend12"],["0"]]},{"opcode":"AssertEqV","args":[["var13"],["backend12"]]},{"opcode":"ImmV","args":[["backend13"],["0"]]},{"opcode":"AssertEqV","args":[["var14"],["backend13"]]},{"opcode":"ImmV","args":[["backend14"],["0"]]},{"opcode":"AssertEqV","args":[["var15"],["backend14"]]},{"opcode":"ImmV","args":[["backend15"],["0"]]},{"opcode":"AssertEqV","args":[["var16"],["backend15"]]},{"opcode":"ImmV","args":[["backend16"],["0"]]},{"opcode":"AssertEqV","args":[["var17"],["backend16"]]},{"opcode":"ImmV","args":[["backend17"],["0"]]},{"opcode":"AssertEqV","args":[["var18"],["backend17"]]},{"opcode":"ImmV","args":[["backend18"],["0"]]},{"opcode":"AssertEqV","args":[["var19"],["backend18"]]},{"opcode":"ImmV","args":[["backend19"],["0"]]},{"opcode":"AssertEqV","args":[["var20"],["backend19"]]},{"opcode":"ImmV","args":[["backend20"],["0"]]},{"opcode":"AssertEqV","args":[["var21"],["backend20"]]},{"opcode":"ImmV","args":[["backend21"],["0"]]},{"opcode":"AssertEqV","args":[["var22"],["backend21"]]},{"opcode":"ImmV","args":[["backend22"],["0"]]},{"opcode":"AssertEqV","args":[["var23"],["backend22"]]},{"opcode":"ImmV","args":[["backend23"],["0"]]},{"opcode":"AssertEqV","args":[["var24"],["backend23"]]},{"opcode":"ImmV","args":[["backend24"],["0"]]},{"opcode":"AssertEqV","args":[["var25"],["backend24"]]},{"opcode":"ImmV","args":[["backend25"],["0"]]},{"opcode":"AssertEqV","args":[["var26"],["backend25"]]},{"opcode":"ImmV","args":[["backend26"],["0"]]},{"opcode":"AssertEqV","args":[["var27"],["backend26"]]},{"opcode":"ImmV","args":[["backend27"],["0"]]},{"opcode":"AssertEqV","args":[["var28"],["backend27"]]},{"opcode":"ImmV","args":[["backend28"],["0"]]},{"opcode":"AssertEqV","args":[["var29"],["backend28"]]},{"opcode":"ImmV","args":[["backend29"],["0"]]},{"opcode":"AssertEqV","args":[["var30"],["backend29"]]},{"opcode":"ImmV","args":[["backend30"],["0"]]},{"opcode":"AssertEqV","args":[["var31"],["backend30"]]},{"opcode":"ImmV","args":[["backend31"],["0"]]},{"opcode":"AssertEqV","args":[["var32"],["backend31"]]}] \ No newline at end of file diff --git a/recursion/groth16/lib/libbabybear.a b/recursion/groth16/lib/libbabybear.a deleted file mode 100644 index e047c94965470d8d4e42512d6264f637eb3ceaa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14863472 zcmdR%2b>f|_Q%_diaBRg%(`|CW@=t zPR}z31XM(Y1&o;W%-*U0U-kR-?CkDrU|GZ8kMQMHPj^>!S697y_3G8;yM=YpxII?d z(%9KB`!xTjH~+Jb{JZq;K7CBb3IbRCXv|8vT*b&k57>L)AWhTe9E88{U+scrwAI1$ zKU+rIl-z}k$jRgx*CK)y!qg@UW=-%Gk{i2NX^ zH!Q1-T2uHLx#LikX4dhqkej8}o>US)w9Yuab1{+HHRPq~g+0Ep*M~)|_lCP61^^xBVAG`dIRA@(1$ut>yTG+X#2sRycuto!oFcNxz(&L+-V` zq@PSaNv^nqr0-9jOTGwBK4dv2;$n|!T<73F1$rURky^5Scx;snyRpj!z z$P>x8$U_ZDf1W(h)U-waTuwWce1QBP+0T;WcaR&|BA*XlHfuR;2D!Q;@^JEeat67E zE5{EbZy`S-bx)2@BL7OhPj2ta@hb9e@>_C`K#reGE+U8RBIys1eRdW3kK}*I@w-X- zOmf%VMZT5XbPtg)B6rzSDBi`2yK{Upc-%c{ceRx$B z#}6QnC2uFcBzN0iem9xCk6derq#sQ#eSpZgx2me-CNC47op{UDKtkXMplkoLiHypp_{ ze1=@}5IO!k@_O=P(jP6yPbR068yqU>e&P)13q#lWU6S-;3>yW(@kxwSSC+iB5{s9?{6?rb18z=HYGO85$-{h7@h@UO@gEoZ+sZjXzen#&N3g0F#J4xgrCkvN4MR-3s{#23soF;sQJnM9k_dG-R zC3*9iBInK$u5-5VdGh3QMBevY;i~5eUm>smqsUXv7Y@BZ*!M!=_vDO=M85G)!ecHL zj<`g)-K9e9GT|KZ&dWu<^9tcze-=J`rSPe%gfCw${NOLbO{WUSl6R9okiWl1j$cgL z*NXfy8C@sx_hiNOB0oe9xGzRa+$Zu`~Clko0}Yo5{8RDd{JZpODcb&;vK^rF}~t^{B|R$mn01rtRKKyM%m_{F>bS zF*$xFx$VD2zL5MMS$JI1XORP+5czMU@ubK{kk^s#lG{Bc$E(P{lK&x{r{(x$@?miF zm|j}NGs2_E_sQI|l0N)7;g#gaWca+K&m^4}M4n7OO#TSYIKG#5-gIG~7ln6|@00h= zko4nc3J1;-zD1t$lE_=VEIgC^gdF*br2mcVGh5`-!P@D)wD-wjuZnyJxyl@oE6K;n z&0mxBlgOvYjGJgO5c%hJ2SC{)wdj zlXO27`3`dX&qThFe4pI$bA0a~t*9*`Z~TwQ@fX4y$wlN2UrKtOyqug#uK1N4A3z>Q zK13eywH&{dT=5%`$B|Ey+kGqPXOSP0qra2%e~?>!FY>wMcVx|fCH);T_dk(e1NW+0 zN!#KF;j!c+rWSlDCm-_LB6;WD^;#DCr-N^(%?|Kl0R-MP9YH@KUnRDk9%Ren9TDs-)MF_mE$c z!D@2+81fNt(GM$YKaqQ`F7ol@qvTR+NP2(rWb$!xxi#hZzU0~DbaKtLx1R_LvrOK!Ba$cK?vlCP5M zZzIPKCC?}SO@2+f+sf~*CVOosavgau`3qfo7};W^i#;!N!^t6v%t@0 z_txf;6_&_<1jl^WTU*-}UO{fdsmUC zlMTCxyw>i*2gp_H_i{zSvF#! z;p7?QL*z%~1_#RThLOjRw~(ujkmHAt7m{`>xl56DU_(9}*RM6Nwvj_*&NL_S1*LGD;3zZ*+lOU@@Z znIOl1NB)_dOKwsv$8+R0*Tr<<@hM_B61eF$|O1dJMv2M zC34lta(pkcj=Y|Hm0W3x{BC#hNb&~qHFA}sCWn-M-|pX{szsdE^lH()D>&Z9B4bGP1hmaSNFOVyr zBgc0q>&aWlh2%!(%I|(pUQ5m(mp)I9?@Bh3*O0TwW&bF@+l_1>Zy;xrz0PO8$jRh2 z5yq0{4-2Tt zenbv_Leg&`SAJ6D0y&*@pOW-zN$qKo|3E%UZt;wypGAI6j(S$o? zl0J=G{soc8ku%8Mrc3%2TCm$s@eNEDjAzvqh*CqWj@*DEt zHzfT&a_zYy*ORkIYo4TEM1DbzY?AbQ$+hQ;+(^zQy#3<_Pep}?@$v4S??@0PIa^-hLt|sS^{oa%G8^{$Gi9CgzPY!!u((fZT`at9p z$al#7K9uyol6^iB`E>F>Dk&h9*O9A!E%FrdeR9+{lKwck^S2^jPOkQy$VZbOl81aR z=}(Zm|5xN&$@Tsx@;T(UWZ?%%e~BFUqsWht!A~MjBiGTEPVIu{kl&M)OG)~xxY9<{E>-;j;#iToe3W_^*rB&Td3@(<+E8;blRdE7=KFTJtwByyQe zL_V2Zc~g-uA~)PjDCxJ7+wVk~{4cp@g`}TO z&LI2kEa`tD7m=PW>1UF&$!!ctKY@Ij3`|MCk(^6zXi55L@=~&iT*H>*N0T3thdYw~ zJh{6o^4(+~Pvk$6KasV*q`yOs3`Bm6GF<)iA0hH9xNzi+lmO@*yIhM1DseIa<=+ zC&NQUevKS)n8?qOd;d=4zmxXiBHu>#{k_Onk?a0JwA;$-hx08LwOZp|?)7PJ&tx+W$K-Pj|-Z?|NiCjpoKS9z*kY|%mkxNy} z@jb{X=y>`D`yOA``68Twj{MjP!a*ohD zSGebS!ae>dyo7v}tT|uOx4b}j1o=FvUnuE!lg33NSChAqpOV&}{TyBt0f?BflhfyH}2%N=_%&zfaQh{}TBK@`7&ueE9n=KpOeF%lk~rmt3EIC1o8!PrxzssT=HY` zfa#KcJGsh>JfECS?mR=%&nG`8N6eJ;d&sqCiQGueCcT#={W9`9^01dB{b6$BS42LR zoKNh%e^Y{Sn?TihdGjdF8LWb@-<1npWNVek*AQak-NSj=~t23T#+O4Npib+ zl71k09{D`kt4WR@LS9V1O!l5H#|M!okPnkzk@^Dp-FWgQaz45KLODKyJcoRV-0)2~ z9+K0@Pf7PJIer}ZB)RI_l729GHTf>N?mKdP2ze6uAi43oa{MH65jpBTNq>yg7m54; zx!3z5KSPfGK;-wyqdpY*PV!xHu_jGM`BL5b|QOiQMK> zIbKWNOnyP`{+S#axdOddtvLB356`H%eWUUHK!L_UrDlpOt~r2mWD{wtCH zME*!tel6)Qlk0vX@@VoZ@>Oz;Z{_$<@*MJ6ve$QVd>`^O@(FUe@8$SF@?`Q~0MfP4=(hnoAB|j#OW#ssAcSSi~MNT6>AS+gq;|=6L$X+W; z`T%kwc?;P@uGw3DcPM!s`7yc2Dsucx@)dH;RV95mc>y__+-Nmkm%NGmiX5=I9KVoU zNa|}y`tjs+a+5VBy^4I8Ty-r;&y)9)tE?^Q1@cjHlXWEhAo5)DNm5%^j_*cJChsHv zL%Qq9?@l4_Cf_FaTwjh~L4HaO+(6Q=BEKRJ+ECK(Ay?i=+!&w&D5YhvblLCH;1C)$K&CA!m@r_L6=f`6YSq4wC*Lx#5l? zA44{gd+j9Y*OIGLh&+*;L+-k>q+d-grHdSsGfBsg^vlT~$k>$hXUGam1GG`X`U@}=ZbzR2Urm&jcMNxzOK$48PEkS~J0wtHGz<9EW5l)Q>uNN!ar$H$X*k>8NJA0fw2CZ7R^ zANicN<#^%oJ1+4-6>|ZZvCxL#`3);U(Z8Glr{R`TPU~S*&+WO#SgQsg- zfZtY5*LDK;J#D(?gZ@L)wY|a7Z%o&Qf-{zSQ5y|T-u6Z9WN^-)7qxT2;S*le{-Sv4 zi`t!_|LBX_L*TfNU)25$PF-(?HWNI@<-1n%L zv~$4)m%pT42@ZecCGAOY&KoajpM#(M@RGLC(a6UpFKgR^=MR2abHJ(fFKhi(`c*G$ z2ZFu+{j!z=Yd?QkI|973&nsFZxc&jJXs3g#Pku$aP{}vEqFn_(I_nkf7Eu4+E7~id zw()FjKDe(rTl-9<|8BOn;W4=0l-b%g;G@^h)@<;>>9e&xz`Z`5tsMZazuv3bXmHNJ zSG5RSed4QH6}a!zSG8loK2N@?odFL2;8pDc@Upe%XnzJ5Y(Gc45q$8FIoc=Soz-)+ z4UWb0JAaP01Gw!Yb2JA$;gdPqAaL~BuW7@;JNI}^I}EI?dQFSKX_vgFoeM6y_ciSb zaPqv@v>U))YrL-A1^VXe+TTGv_qz5JIQ8t;wVB|eYhKsh0vAkwUHb_1zk6N#8mwLO z4Q;98@cVvmXe)!`4tqm$!8@nCq4fi6r@f&KSMrQEv_ru~-@l=a1^3-&|Gc#J^=l7=4n5IPj5U=Tk{0mx9>b{d+^&G=4pF^ldXB$2=KFA=4lByuKzsk z7$py$r(FQn9ym|C9$Ykfp7sDZX3RWox{}Av)7}Ed)y&hr1{X}6r>%G*u7BJ-tq-{1 z)Onf%jyZRpwl6s6PxG|jfy1wyr`0JL!5Pj3$K5(lyBhrL?s?ih;N*wqY0rbJKZfgr zQ=gfqeG6_obDp-!NqGKq=4o4l`!1NL?GDa)5BUejd^%4X3oiP4o^}Fw!jJQ`OTpEb zZ_;i9FI%Na`xn?}ohI#d@WG9nw9mnP`!;FIpNw{AhbC;TC-iU9 z#(=%{YtkCQ^ABv&&I7kSv`M=Gd~i&Y_7J$&xF+pI@adW+Z4o$TQj_)*_}Oty+S;d} zT&Fc@+kx77O!$nZPLC5d(CRnRyh^dd96v?0_?M}N%O#Ii<-3I;9j3KX=A||-!y5*fTMqE(k=iO zEI(hn1)RLnXi2auD{iMZQ0Y1pB?9G8-aV-^EDlOde`~dKydhg z`Pv`AMf=Uy8o|jU=4)qx+F|pxzkp|i^R)-TK9%#e7r{Gg=WB0+{^a@Eci^MP&)3#G z9r-zZzP3F%h>XD*H_g{-z!UD7uN?(ykIdIj1#4fJubmH0du_hCt^R*k*@&C-% z?gXd)FkgEJ?6cwm?QwARbrxvT)$z?2Xmh|B`U34uuy(fv+DG6S`z+AD22U7?ARuR; zJZCJ>RsyFzu|V5^+zwoS{sPS>hk%RTTc8~bp0Vyitp=RhXQ4I)ysToOb~1RvfQ8z5 z;Jybh)Gh~Sj9aK(4=y-np>_vYd-_7{L2%m(7HW@ycm8>y_B?q0^$WFERr;L^wKu^t z9$u(@432(sq4qua+02F73TL8xa~EoBgX7*`sBH=M`f8zOf|vcYQ0oWQuJxui1e~_n zo7y4ZoE_fO@}TCwsnvp0N4}|@K%S>K@lEY=#gpFDt_R1Q_ons$SbOQ4+DqVqYu?m8 z2B+QnrndT7$j^gsYFmNh9(z;U1QUvA!V}R#nwREu~yi zS65S)8y8l`RY_e($8z=KY8tEJTy0og+4>?!C3Q7PTs%8YTHdL;MsAHuR8GiM*5?|l z>l?y+RZ={ou6V{_Lk-8v^&K**Z>^PdQ86K|uc$a6u0J4bsH>dZ9|i1>^uCRz z-Z!jIf#26pnJ^*2@u;GrHmRG?*bp{U)>Kzi3~v_HmHRe2`Bs-#PmFhJzP6RePjLeu zC&YdG?t?S&$1vpDNe(wKym3*Ok91t-)0svbMge*VFYa#>9o5t&6%~~Y z2{fiK4r}obs7({XTIfz;tIvSTmO*!l^i1e<{5>=Z-S&Og_9D~s3r-=(YRhjclSUzQ12YN|!;hjcMu6>9 zZ~JX!(vAE=>{w2cL{Z{+zs=WOFwoRbd+@VOWCe=f)=0Qo&Jk!!QgiJIV*4iI$>}sbD{6QJkNtD6Xx0dch1W z-L#xg=SOI(Z{4Y{s*Dm_eb|PpzU2cN8`M)Bm444=Sg(dQx%5YPCPq*w_)%!&3rXG! z^NxHd-MEnFdyyB1`PkRuS)h z?Tx=xwrwRMw`OF=v0Ly9UKBWn!OyyfZ{j3=5*BPD$rl1APWVVXz08QITpeuF^cE&G zpd?AYajYte1ra6jJdYw*k^yGdnzZB;m>i4|)J@637J>dKLD5{@vGMo{zUR3?kX0#PIRa}-wBz-SRx(rJnP1h|wp*jIp;u2VaHyo+~PPq61ei$H^O(sk>%AFm3jY zzlprLN#xpARPar!;D?2%v^h$jmyjlAQ+=Fy@l z>-5|tA9;Dp)6tmb3mzL5X$Ctx*ELbi>_UJK*^dK*&$El?S|N&y=GqA&J#iPSyuNP4 z1J%eWAKxv|{;D;2MdM@C)hMxp_48u^s1iS1-hch$GJ~o*S95kvC&6 zumdYHsiSx8+*l8NH};aq!2eFTSkEdB!$ej1g6W1yq%YR{jiVqyTkV+^te5C_7pG1Z z96Tf^a@<1XTaKCOp+>PWj0@-->Y%Dj&Y8KmvH>R8Iv7}!s)~vcD%IS(vSB!rO1nxl zjp?abgTQuaWMh78T}@-{-i5T!m%#vf9m9!g>XU|CRh+=^+7OL1^jvO0zac|L4jwfS z-;S!kw(c517<&aX|J%%`eZas`{g7E0GTjjEs~h;X>DpNX<`+B938@_!N(7Wr44HBj z6{=w^>ZJ8aU5sS)XYT|8^iQR3s*a~b)ogW<5jB$z2#=_#6B~7NLyd>4E>*k?ZkJ1Y zhl4BYD>1MteQ6~(&W_@AXwS7t1Y`bKl+BGiJ?}xwvGY#Bb`z;=PSISOV+Abo&_Vbo)m|z0jj86)JKg( z@sfJejB2VHCsa?WtWR=r4JvzGP23nYw5p5|G`qYD%XKkISvc@^24*hE|N&T&+Gw>oj&nRHzXn2;123lt&Q@^+Br_>u}!#K=q z#5qj5gjI%K2r&gC|2FqerWC7(VT*25*&vk+7tTXHwn86vdD}`7#~fHuRW)%!1^Q#C zw4U#Jfp58X;JQ{iW?kGnT4R$`zqAWx7zcRi1=E3UU(#qsF3P+=voW5ZJBm#S$b?*aokCs9nmk*Hbtn_6&3ZESE{HOiN8y~ zh7KyLr56k+CzcuJEkAKNYvIUXqSa*wS7U_N_PSA3O*KC70Sc-VPMV-ztfPl^;`r#g zd=syt#fQ-9+U2txO~B9ujk0>D1CLY}Hv=$i=tkxF4?LM89HW&qsF?r zBpo@KrUU&s)}aWPQDS6c-_pv&)9Mnumza%Z5<7V(DHL?miH+E%k*LK}N>7s-8^m4= zO*x;>>t1X)R3O{_+Nw?_q0Zz(&xJy-M$DG(huIjmO`g2wVuVf-S+<_o&}~iMDp$?Y z{P?&-uCAs=wU|!idiaohCox4q?i1WxIwtN9#i zKQvF}^QIqSk`z7<7Wx+{r7zlIQBz2@riu!=SE_aPlX;1v{?oQ!PJfo3s9HSUw7x-Q z(|27pJaq9zU?lEkMw1q=2DZwoDk#>8VKl$vdc)Ye_`>TAuPzx|S&wcK11hNo>b<)L zzEL%Jgi@FHv-#4+y?9UuLtZ6@Q7Xd*=JC;LM26|W;FA=mLQ3mmI~n$e3>&cjz`b*O z4<0$Z->3om4jeHO7qk;Av5-Z4=05bFOwY0{&-M++2+)*Um|`@Xfb$uh-zgZ8W!T5y zCYOX5`(YBt!{{PNg1l>3c?>wp)#CCwFMmB728blD$AO7H(#gixi>teq(U#R)^9rG7 zsf;Ftgqml2Ph@fLNNqW>p|ZJo-H4LdP5jJ+W2Xi~rD%+5ry>upfyEK(C#o_HyOvV8 zEh}-H2)3CZvqE*}QVvD}2@D?4CQT=CTYT!!;ZY09EAjPHs+E&eO=W%RAC*$H`Ap51 zU(`*)h-Ews4U>j|Bk*AqP4lUbS4G9d1Q$9uiP|c7*_Wk^k|Wa3tqvYrN{1==gNj!0 zgFI}YN#uK0qU%v7?@!t>RXA7g$-<=#PS2MP!*n-^^B5ZyplX?3S4-!>e%WgHRr)2v zpipeiT0LGoQswdi-xU+%_{2od-^op#^j+HOl^brqy7XO+>N}uszqI|0YbI5Xh5bnx z?i0_&Ak=`d3iFcrY*MMK=ljqmG2w<0ew?y0$NSUJ*R*8@ySQprVwpgq7Ej^3VA4mo&!bL^KFc& zai3J@vrWU+)rXnaoqS@$=xv9X41oVflt3?cDh#S@WgFTqUV=K?l3cll%kFD+T86rdyCdOfGUrl={2bS1>eGSHdG~8(=mmcIZw5t&Xm=N;>5~ZXqfYu z7*rihhdO|1Qt^S!NEskt?uwvFsAeO?NHZ`zFN_MXe;6HSrL+t_8V23PSjEC@jh78J zTdKl7e175V(hvp*^c$TUGVtJmLvTy9Ck9OURv{@knElA60>l8^QGeZf8W#26M!%<~ z>l_1vIe7;5;$J91Cji$Y9oI^rpR=!aom7Kp*|ujTs;Jn%vN~@2Ij0(O>x`NS#_KF-#ex09ay{4Z3^<3$ z%jYb9hc{|l5)8GPr;(N6Z)8KNxB1F0k&}U#?YOpM+nBb3B|bDVi%Z9q4F=KrBz=gY zYqs+Y+Z4_YObeq@D~zLjKGInwIGNL7;o5syT`8givpyympg@En=9Bp0`yIc#<|4|i z7q6h67rS9-8CX$J$m7|{TMWw8%I3P?stYK6pDmwF^C?+(q4Yb$RI`3rlZ}=Wb2Ldl zG#vE)rtV}z){e~w@Vn244hGM8*v$izZBMInT9^;=egc!UuIq4e!T6Z1p(tlwT{tO+ zfrSx_2q$rDQPf9$%wA5)!DNrDI&Ks?Mgfx|1~jFJmX^i5h-aAa%S9_+aCJKyN-j}EOoeLm)+9(w$G4Z{re5c4IVmp6yB+CVj?|)uLGQ1;UQF3S7>>D zOY5EEqB=lXz_`Z<{E%9INB3`ee%=ZT0qnd4 z?ytVTX6vJGqi>DO$aIrJ-j2)KU0Rgi964avaM+0k3_ED3dVrXLD|pcQVmLOziKFue zXsQ83W*uuD0FATqPT*n`V8WINFQCqIVhuQG#E5}IN99J2>NjejmxF6%LuCUdwe}x) zsLB}Jbxg-wWu(m{pKPBwKp5eipzOy2@14j=YIZ9dn^3dn8&diT6SsS7s?CeZGd!=?Vxl-A+ zJrDkhHvCQO2wvoIr*`S$1so_>sWsDr1{~v=`7xZKe50)Swe;B)uMm_P5#aI`#js6V zp#j@*=D^VY7t(wc8Qs8)q)Ow%q!Jq@EW$7ZyPZDB=Budrc1-n(C!%@@m}fVF$P3Yn zboNkT+f;^QWTE6k!4y5JQr?gC0Ggh`zN~eD>KpU*QC(#oPf~{yPVwLAtaDCHhoV$5 z#%!1!hNBm%c<9))Ccv!-Yr-%c0PC&|2LMmEoig@aLvQCMCMUkK+av`&avU8hP848r z*zKX)SQt%IHP)V=_WMNw3^Ex%4ijomdy|6hpyq(U-u(5sI#zjm|_FYx&Gd@6>*I*gYtxv2-#~ zJyEZ@&oD8$6u_;q;9!0cowJmz?1-ZDej}5HebVlSSM6|2(b|bz`4lFwf%;aPx{$2R@6tZWF7OJF!MR49fUzP&(UsE^$X-ob%3aKZW5uZ_Mz#y0UXpi z?T>!;oVilz)CUm-P@7;F4GPL1C7ZDL^&Xnn?BC|V_5r^IRJ8zZMnT6PYdXge!MV*y zFmB!VvbD8~m&lw`mgE|oBWU+Aw^SF!&rR>wfN&Q1 z#)hQ6`TarlhuI@k9-@JcF={!Lwaw{1Joy^19s;fp&@mBZAcTH_nH=civ@&&ae_#0_ zPN;2AGj_fOlZl530St37CC+}V+yyN0$6O4*w-(-k1H|i0b)#vQp7~y9W|r=G7zVMA zmHoQzdT@Wm=h^N9GzZ6G^K67JgrOVUU_Gok@!``^aI*QtGR1aBqz$}Pl_-_ocQ6^N z25)fJ4jjAeuq%CEJgxan+j!OJM#E5tW35vREz)AZTzY2dOUCd&F$;R^LcH4u6+5Yux}j%1TKb$kGw)y}M&3cjRXbSj)*YN4 z*0%eh;NFTci57fy@A;Y<tti8KQ}&52b~?2zti(E)I$c9Y14K?113^6 z?qbiOexG`uxZ-bQ+GYHW(#6U2bai7qDhn&)(YD3;Y`US%WgNLoOV6It>tJ*OJ8@o3 zlEZ~6VsVSp7C!y>>iu-C72`0dD(H|C)r+EzTV8_sP}7K%Kb}16w&w=vxkF$yDmgg_ z9Zb>{Fr{tA2#`P{WN`?OKq0N2f7Fz<`h#YM<8)UI&!P0{SX_wljh?CHM&RmL_~*gz z2LosfGhIJz28vf-%#|GlQaK9gw776tX`PL(xu>ST!b%=E%7s{L*H~RS84Fb!VzmNh zWCQ#=i{5Og4p`c0#Hi75jS2C{GcP8iz3g#bK6B(jty0TiMy4z^UzB}f@~EH{HR}on z)^WR7Qi2sGSiS4gH15fQW`qgK!<#D&V^%V-7Bwne=U-8gE?F6=0s}O!TY)oR|5W+H zt(@e@Myz8(D;oz9##@PtwU$t@vpb1NK`xovb+V5~Swpb0T?HU1xp(<;sReQA`_5W6 z7@8Z^qBkr?h5H4pQ8pGI+7=rQPfRs@QOc#UrNd$ePR5;>YB0BL<>7#pNBjf~E7%oz zrLhXi6%y|ci`AB(oIxC!p^N1tULkUdY~-X>Wd{PN;X1Xf=kei zu6(Q8wW{HE)UR~y65f(u!tdx3$|2B@rYN2w3nt|oPxZiTU1p=1?<>g@!n*Ow3wtCS zeDR64Nlse&n3Zpu)84s7m!muIjf*U-b1`+ye6=$h+ib6GG3{ll8No=ciG^>4YT$LK zLU51^5D`Q-^JT}J?M-h5r89b%&%*>U+(AvuA{3_&S`7lyc`9e1GS>N--CK3yF|@h> zW?~N(WBglmI;B_Q)>9Bkq-Mf|nrgo)JPOf5YbG1+5d5ou&E#S~W9XIDB@1~mhOs@} ziYy%?GOWC6zFylE!nV4qX{ak=d9W;5MgN<&=B+LpI9MH*V5t|h4L^xFLsC?K9YpU* zCK~~w`XrN!9_^)iFXj)i49rr?i(M;kb5@{u0aq3V7&;<{Vfw3HmPP5YZz04}fT$^0 zpA0|!RIBXdq&%F%FbU|xEQe@>c8S6tq|V^uq4rpKeR%lrCQGo^EyDU1jNywx6c*pn z(n8N-!C%#|Z3q20J*T_jv5AoiJRjhah0&maDj+59&V^t;$NWpda^UU+XQggm2nHw^ zybbi0aD+gVHD0Lmr6bg_m2SmVD7u^X@&zngg--zr*!2Z1Z7+pEKgP`%v%>ulT{UZl z-JM*)V+Iageh?uSQq1)oG!6&%a3*1geFp?~q$#57KEg$i5@89{*6U#P2aM2TGZR3});gdinWHW;xy zxXn2jDtT~2f&~VDmyErZ^sex_cicRz_NI%8X9Orz2V`wM??$6_m~u2_;4S<&!|=0&-0VcDC?QrXO?7Wo%> zD6novh)2*|`;?PPW1Xswta#DtEO8x^VRDW(`bxGX$_yy;(c;Pgx8yO{k^zl1&67%meDJS{0g&XXROT zuOj}eSt@xsU9FElYkvMeR7=E?vwe6L8Zkm;V#Rh>s%0Ag5DuaeCe=EsvR3^;C0B6@ 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

j%d7fK0Ux5fA|5 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