diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ae931f72..a7a140b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,15 @@ - Removed `TraceSegmentId::index()` and replaced segment indexing with `TraceShape`/`FullTraceShape` across ACE codegen, MIR-to-AIR pass, and constraints (#442). - Allow computed indices (#444). - Fix regressions on MIR and list_comprehensions (#449). +- Add Plonky3 codegen backend (#461). - Fixed a vector unrolling issue in nested match evaluations (#491). - Fix evaluator argument vector slice expansion (#495). -- Fixed MIR's constant propagation to fold 0^0 to 1 (#509) +- Support importing hierarchical modules (#507). +- In Plonky3 codegen, use AirScriptAir and AirScriptBuilder traits, and generate aux constraints (#508). +- Fixed MIR's constant propagation to fold 0^0 to 1 (#509). - Support importing hierarchical modules (#507, #513, #514). +- In Plonky3 codegen, use MidenAir and MidenAirBuilder from 0xMiden's Plonky3 fork instead of AirScriptAir and AirScriptBuilder (#515). +- In Plonky3 codegen, use prove/verify workflow for tests (#523). - Fix MIR inlining loop on deeply nested calls (#524). ## 0.4.0 (2025-06-20) diff --git a/Cargo.toml b/Cargo.toml index c1bc061b8..8683cb805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ members = [ "air", "codegen/winterfell", "codegen/ace", + "codegen/plonky3", + "constraints", ] resolver = "2" diff --git a/air-script/Cargo.toml b/air-script/Cargo.toml index 8d5d5e5ed..3f7b4d03b 100644 --- a/air-script/Cargo.toml +++ b/air-script/Cargo.toml @@ -18,6 +18,7 @@ path = "src/main.rs" [dependencies] air-codegen-winter = { package = "air-codegen-winter", path = "../codegen/winterfell", version = "0.5" } +air-codegen-plonky3 = { package = "air-codegen-plonky3", path = "../codegen/plonky3", version = "0.5" } air-ir = { package = "air-ir", path = "../air", version = "0.5" } air-parser = { package = "air-parser", path = "../parser", version = "0.5" } air-pass = { package = "air-pass", path = "../pass", version = "0.5" } @@ -27,11 +28,59 @@ log = { version = "0.4", default-features = false } miden-diagnostics = { workspace = true } mir = { package = "air-mir", path = "../mir", version = "0.5" } +# Upstream Plonky3 dependencies +p3-field = { version = "0.4.2", default-features = false } +p3-matrix = { version = "0.4.2", default-features = false } +# Internal p3-miden crates +#p3-miden-air = { version = "0.4.0", default-features = false } +# Internal p3-miden crates +p3-miden-air = { package = "p3-miden-air", path = "../../p3-miden/p3-miden-air", default-features = false } + +# MassaLabs fork +miden-processor = { package = "miden-processor", git="https://github.com/massalabs/miden-vm", rev = "bc553af69a2543a0789830e8508b019694528181", default-features = false } +miden-air = { package = "miden-air", git="https://github.com/massalabs/miden-vm", rev = "bc553af69a2543a0789830e8508b019694528181", default-features = false } + [dev-dependencies] expect-test = "1.4" +pretty_assertions = "1.4" + +# Upstream Plonky3 dependencies +p3-air = { version = "0.4.2", default-features = false } +p3-baby-bear = { version = "0.4.2", default-features = false } +p3-challenger = { version = "0.4.2", default-features = false } +p3-circle = { version = "0.4.2", default-features = false } +p3-commit = { version = "0.4.2", default-features = false } +p3-dft = { version = "0.4.2", default-features = false } +p3-field-testing = { version = "0.4.2", default-features = false } +p3-fri = { version = "0.4.2", default-features = false } +p3-goldilocks = { version = "0.4.2", default-features = false } +p3-interpolation = { version = "0.4.2", default-features = false } +p3-keccak = { version = "0.4.2", default-features = false } +p3-matrix = { version = "0.4.2", default-features = false } +p3-maybe-rayon = { version = "0.4.2", default-features = false } +p3-mds = { version = "0.4.2", default-features = false } +p3-merkle-tree = { version = "0.4.2", default-features = false } +p3-mersenne-31 = { version = "0.4.2", default-features = false } +p3-poseidon = { version = "0.4.2", default-features = false } +p3-poseidon2 = { version = "0.4.2", default-features = false } +p3-sha256 = { version = "0.4.2", default-features = false } +p3-symmetric = { version = "0.4.2", default-features = false } +p3-uni-stark = { version = "0.4.2", default-features = false } +p3-util = { version = "0.4.2", default-features = false } +# Internal p3-miden crates +#p3-miden-fri = { package = "p3-miden-fri", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false } +#p3-miden-prover = { package = "p3-miden-prover", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false } +#p3-miden-uni-stark = { package = "p3-miden-uni-stark", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false } +# Internal p3-miden crates +p3-miden-fri = { package = "p3-miden-fri", path = "../../p3-miden/p3-miden-fri", default-features = false } +p3-miden-prover = { package = "p3-miden-prover", path = "../../p3-miden/p3-miden-prover", default-features = false } +p3-miden-uni-stark = { package = "p3-miden-uni-stark", path = "../../p3-miden/p3-miden-uni-stark", default-features = false } + winter-air = { package = "winter-air", version = "0.12", default-features = false } winter-math = { package = "winter-math", version = "0.12", default-features = false } winter-utils = { package = "winter-utils", version = "0.12", default-features = false } winter-prover = { package = "winter-prover", version = "0.12", default-features = false } winter-verifier = { package = "winter-verifier", version = "0.12", default-features = false } winterfell = { package = "winterfell", version = "0.12", default-features = false } +rand = { version = "0.9", features = ["small_rng"] } +rand_chacha = "0.9" diff --git a/air-script/src/cli/transpile.rs b/air-script/src/cli/transpile.rs index 2ecea6545..3da78b676 100644 --- a/air-script/src/cli/transpile.rs +++ b/air-script/src/cli/transpile.rs @@ -9,11 +9,13 @@ use miden_diagnostics::{ #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum Target { Winterfell, + Plonky3, } impl Target { pub fn extension(&self) -> &'static str { match self { Self::Winterfell => "rs", + Self::Plonky3 => "rs", } } } @@ -55,6 +57,7 @@ impl Transpile { let target = self.target.unwrap_or(Target::Winterfell); let backend: Box> = match target { Target::Winterfell => Box::new(air_codegen_winter::CodeGenerator), + Target::Plonky3 => Box::new(air_codegen_plonky3::CodeGenerator), }; // write transpiled output to the output path @@ -62,6 +65,13 @@ impl Transpile { Some(path) => path.clone(), None => { let mut path = input_path.clone(); + if target == Target::Plonky3 { + path.set_file_name(format!( + "{}_plonky3", + path.file_stem().unwrap().display() + )); + path.set_extension("air"); + } path.set_extension(target.extension()); path }, diff --git a/air-script/src/lib.rs b/air-script/src/lib.rs index 5d477c7d0..d75247b94 100644 --- a/air-script/src/lib.rs +++ b/air-script/src/lib.rs @@ -1,3 +1,11 @@ pub use air_codegen_winter::CodeGenerator as WinterfellCodeGenerator; pub use air_ir::{Air, CompileError, compile}; pub use air_parser::{parse, parse_file, transforms}; + +/// Miden VM auxiliary trace generator +pub mod miden_vm_aux_trace_generator; + +#[cfg(test)] +pub mod test_utils; +#[cfg(test)] +mod tests; diff --git a/air-script/src/miden_vm_aux_trace_generator.rs b/air-script/src/miden_vm_aux_trace_generator.rs new file mode 100644 index 000000000..e233a970a --- /dev/null +++ b/air-script/src/miden_vm_aux_trace_generator.rs @@ -0,0 +1,117 @@ +use std::collections::BTreeMap; + +use miden_air::{Felt, FieldElement, trace::main_trace::MainTrace}; +use miden_processor::{ + ColMatrix, Kernel, PrecompileTranscriptState, QuadExtension, + chiplets::{AceHints, AuxTraceBuilder as ChipletsAuxTraceBuilder}, + decoder::AuxTraceBuilder as DecoderAuxTraceBuilder, + range::AuxTraceBuilder as RangeAuxTraceBuilder, + stack::AuxTraceBuilder as StackAuxTraceBuilder, +}; +use p3_field::{ExtensionField, Field, PrimeField64}; +use p3_matrix::Matrix; +use p3_miden_air::RowMajorMatrix; + +pub enum MidenModule { + Chiplets, + Decoder, + Stack, + Range, +} + +impl MidenModule { + fn build_aux_columns>( + &self, + main_trace: &MainTrace, + rand_elements: &[EF], + ) -> Vec> { + match self { + MidenModule::Chiplets => { + let kernel = Kernel::new(&[]).unwrap(); + let ace_hints = AceHints::new(0, vec![]); + let final_transcript_state = PrecompileTranscriptState::default(); + let aux_trace_builder = + ChipletsAuxTraceBuilder::new(kernel, ace_hints, final_transcript_state); + aux_trace_builder.build_aux_columns(main_trace, rand_elements).to_vec() + }, + MidenModule::Decoder => { + let aux_trace_builder = DecoderAuxTraceBuilder {}; + aux_trace_builder.build_aux_columns(main_trace, rand_elements) + }, + MidenModule::Stack => { + let aux_trace_builder = StackAuxTraceBuilder {}; + aux_trace_builder.build_aux_columns(main_trace, rand_elements) + }, + MidenModule::Range => { + let lookup_values = vec![]; + let cycle_lookups = BTreeMap::new(); + let values_start = 0; + let aux_trace_builder = + RangeAuxTraceBuilder::new(lookup_values, cycle_lookups, values_start); + aux_trace_builder.build_aux_columns(main_trace, rand_elements) + }, + } + } +} + +/// Builds the Miden VM auxiliary trace using the provided main trace and challenges. +pub fn build_aux_trace_with_miden_vm( + main: &RowMajorMatrix, + challenges: &[EF], + module: MidenModule, +) -> RowMajorMatrix +where + F: Field + PrimeField64, + EF: ExtensionField, +{ + // Convert main trace to Miden format: transposed to column-major and use `BaseElement` instead + // of `F`` + let main_transposed = main.transpose(); + let mut felt_columns_vec = Vec::new(); + for row in main_transposed.rows() { + let col_felt = row.map(|x| Felt::new(x.as_canonical_u64())).collect::>(); + felt_columns_vec.push(col_felt); + } + let col_matrix = ColMatrix::new(felt_columns_vec); + let last_program_row = main.height().into(); + let main_trace = MainTrace::new(col_matrix, last_program_row); + + // Convert challenges to Miden format + let mut rand_elements = Vec::new(); + for r in challenges { + let coeffs: Vec = r + .as_basis_coefficients_slice() + .iter() + .map(|x| Felt::new(x.as_canonical_u64())) + .collect(); + let r_fe: &[QuadExtension] = FieldElement::slice_from_base_elements(&coeffs); + rand_elements.push(r_fe[0]); + } + + // Build aux trace using Miden VM AuxTraceBuilder + let aux_trace_miden = module.build_aux_columns(&main_trace, &rand_elements); + let aux_width = aux_trace_miden.len(); + + // Convert aux trace back to RowMajorMatrix + let num_rows = main.height(); + let trace_length = num_rows * aux_width; + let long_trace = EF::zero_vec(trace_length); + let mut aux_trace = RowMajorMatrix::new(long_trace, aux_width); + + for j in 0..aux_width { + let col = aux_trace_miden.get(j).unwrap(); + for i in 0..num_rows { + let value_felt = col[i]; + let coeffs_f: Vec = value_felt + .to_base_elements() + .iter() + .map(|x| F::from_canonical_checked(x.as_int()).unwrap()) + .collect(); + let value_ef = EF::from_basis_coefficients_iter(coeffs_f.iter().cloned()).unwrap(); + aux_trace.row_mut(i)[j] = value_ef; + } + } + + let aux_trace_f = aux_trace.flatten_to_base(); + aux_trace_f +} diff --git a/air-script/src/test_utils/air_tester_macros.rs b/air-script/src/test_utils/air_tester_macros.rs new file mode 100644 index 000000000..663e53e7f --- /dev/null +++ b/air-script/src/test_utils/air_tester_macros.rs @@ -0,0 +1,96 @@ +// Helper macros for Winterfell test generation + +/// Generates a Winterfell AIR test function with the standard boilerplate +/// +/// # Arguments +/// * `test_name` - The identifier for the test function (e.g., `test_binary_air`) +/// * `air_name` - The identifier for the AIR struct (e.g., `BinaryAir`) +/// * `tester_name` - The identifier for the `AirTester` struct (e.g., `BinaryAirTester`) +/// * `trace_length` - The length of the trace for the test (e.g., `32` or `1024`) +#[macro_export] +macro_rules! generate_air_winterfell_test { + ($test_name:ident, $air_name:path, $tester_name:ident, $trace_length:expr) => { + #[test] + fn $test_name() { + use winter_math::fields::f64::BaseElement as Felt; + let air_tester = Box::new($tester_name {}); + let length = $trace_length; + + let main_trace = air_tester.build_main_trace(length); + let aux_trace = air_tester.build_aux_trace(length); + let pub_inputs = air_tester.public_inputs(); + let trace_info = air_tester.build_trace_info(length); + let options = air_tester.build_proof_options(); + + let air = <$air_name>::new(trace_info, pub_inputs, options); + main_trace.validate::<$air_name, Felt>(&air, aux_trace.as_ref()); + } + }; +} + +// Helper macros for Plonky3 test generation + +/// Generates a Plonky3 AIR test function with the standard boilerplate +/// +/// # Arguments +/// * `test_name` - The identifier for the test function (e.g., `test_binary_air`) +/// * `air_name` - The identifier for the AIR struct (e.g., `BinaryAir`) +#[macro_export] +macro_rules! generate_air_plonky3_test_with_airscript_traits { + ($test_name:ident, $air_name:ident) => { + #[test] + fn $test_name() { + type Val = p3_goldilocks::Goldilocks; + type Challenge = p3_field::extension::BinomialExtensionField; + type ByteHash = p3_sha256::Sha256; + type FieldHash = p3_symmetric::SerializingHasher; + type MyCompress = p3_symmetric::CompressionFunctionFromHasher; + type ValMmcs = p3_merkle_tree::MerkleTreeMmcs; + type ChallengeMmcs = p3_commit::ExtensionMmcs; + type Challenger = p3_challenger::SerializingChallenger64< + Val, + p3_challenger::HashChallenger, + >; + type Dft = p3_dft::Radix2DitParallel; + type Pcs = p3_miden_fri::TwoAdicFriPcs; + type MyConfig = p3_miden_prover::StarkConfig; + + let byte_hash = ByteHash {}; + let field_hash = FieldHash::new(p3_sha256::Sha256); + let compress = MyCompress::new(byte_hash); + let val_mmcs = ValMmcs::new(field_hash, compress); + let challenge_mmcs = ChallengeMmcs::new(val_mmcs.clone()); + let challenger = Challenger::from_hasher(vec![], byte_hash); + let dft = Dft::default(); + let mut fri_params = p3_miden_fri::create_recursive_miden_fri_params(challenge_mmcs); + let pcs = Pcs::new(dft, val_mmcs, fri_params); + let config = MyConfig::new(pcs, challenger); + + // Generate public inputs and convert them to Goldilocks field elements + let inputs = generate_inputs(); + let inputs_goldilocks = crate::test_utils::pub_inputs_conversion_utils::convert_pub_inputs_to_goldilocks(&inputs); + + // Generate variable-length public inputs as a `Vec>>`. + // The outer `Vec` represents multiple tables (one for each bus in the AIR) + // The middle `Vec` represents the rows of each table + // The innermost `Vec` represents the tuple values contained by the bus, that will be combined with randomness into a single field element. + let var_len_pub_inputs = generate_var_len_pub_inputs(); + // Convert variable-length public inputs to Goldilocks field elements + let var_len_pub_inputs_goldilocks_vec = crate::test_utils::pub_inputs_conversion_utils::convert_var_len_pub_inputs_to_goldilocks(var_len_pub_inputs); + let var_len_pub_inputs_goldilocks_vec_slice = crate::test_utils::pub_inputs_conversion_utils::convert_inner_vec_to_slice(&var_len_pub_inputs_goldilocks_vec); + let var_len_pub_inputs_goldilocks = crate::test_utils::pub_inputs_conversion_utils::convert_mid_vec_to_slice(&var_len_pub_inputs_goldilocks_vec_slice); + + let trace = generate_trace_rows::(inputs); + + let proof = p3_miden_prover::prove(&config, &$air_name {}, &trace, &inputs_goldilocks); + p3_miden_prover::verify( + &config, + &$air_name {}, + &proof, + &inputs_goldilocks, + &var_len_pub_inputs_goldilocks, + ) + .expect("Verification failed"); + } + }; +} diff --git a/air-script/tests/codegen/helpers.rs b/air-script/src/test_utils/codegen.rs similarity index 92% rename from air-script/tests/codegen/helpers.rs rename to air-script/src/test_utils/codegen.rs index 3fc9c8aa6..9e68ca753 100644 --- a/air-script/tests/codegen/helpers.rs +++ b/air-script/src/test_utils/codegen.rs @@ -1,13 +1,15 @@ use std::sync::Arc; use air_ir::{CodeGenerator, CompileError}; -use air_script::compile; use miden_diagnostics::{ CodeMap, DefaultEmitter, DiagnosticsHandler, term::termcolor::ColorChoice, }; +use crate::compile; + pub enum Target { Winterfell, + Plonky3, } pub struct Test { @@ -30,6 +32,7 @@ impl Test { let backend: Box> = match target { Target::Winterfell => Box::new(air_codegen_winter::CodeGenerator), + Target::Plonky3 => Box::new(air_codegen_plonky3::CodeGenerator), }; // generate Rust code targeting Winterfell diff --git a/air-script/src/test_utils/cross_backend_comparison.rs b/air-script/src/test_utils/cross_backend_comparison.rs new file mode 100644 index 000000000..677ca2849 --- /dev/null +++ b/air-script/src/test_utils/cross_backend_comparison.rs @@ -0,0 +1,986 @@ +//! Cross-backend constraint evaluation comparison utilities. +//! +//! This module provides infrastructure for comparing constraint evaluations between +//! Winterfell and Plonky3 backends. It verifies that both backends produce equivalent +//! constraint evaluation results for the same trace data. +//! +//! # Design +//! +//! The comparison works by: +//! 1. Building the same trace data for both backends +//! 2. Evaluating all constraints at each row using both backends +//! 3. Comparing the canonical u64 representations of the results +//! +//! For Plonky3, we use a `ConstraintCapturingBuilder` that implements `MidenAirBuilder` +//! and captures all values passed to `assert_zero` calls. +//! +//! For Winterfell, we directly call `evaluate_transition` and manually evaluate boundary +//! constraints to produce comparable results. + +use std::{ + collections::hash_map::DefaultHasher, + fmt, + hash::{Hash, Hasher}, +}; + +use p3_field::{Field, PrimeCharacteristicRing, PrimeField64}; +use p3_goldilocks::Goldilocks; +use p3_matrix::{Matrix, dense::RowMajorMatrix}; +use p3_miden_air::{MidenAir, MidenAirBuilder}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use winter_air::{ + Air, BatchingMethod, EvaluationFrame, FieldExtension, ProofOptions as WinterProofOptions, + TraceInfo, +}; +use winter_math::{FieldElement, ToElements, fields::f64::BaseElement as WinterfellFelt}; +use winter_utils::Serializable; + +/// The Goldilocks field modulus: 2^64 - 2^32 + 1 +const GOLDILOCKS_MODULUS: u64 = 0xFFFF_FFFF_0000_0001; + +/// Generates a deterministic seed from a test name and iteration number. +/// +/// This ensures different tests get different random traces even with the same iteration, +/// making test results reproducible while avoiding trace collisions across tests. +pub fn generate_test_seed(test_name: &str, iteration: u64) -> u64 { + let mut hasher = DefaultHasher::new(); + test_name.hash(&mut hasher); + iteration.hash(&mut hasher); + hasher.finish() +} + +/// Trait for converting field elements to their canonical u64 representation. +/// +/// This is used for comparing field elements across different backends that may +/// use different internal representations (e.g., Montgomery form vs raw). +pub trait ToCanonicalU64 { + fn to_canonical_u64(&self) -> u64; +} + +impl ToCanonicalU64 for WinterfellFelt { + fn to_canonical_u64(&self) -> u64 { + self.as_int() + } +} + +impl ToCanonicalU64 for Goldilocks { + fn to_canonical_u64(&self) -> u64 { + self.as_canonical_u64() + } +} + +/// Represents a single constraint evaluation mismatch between backends. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ConstraintMismatch { + pub row: usize, + pub constraint_index: usize, + pub winterfell_value: u64, + pub plonky3_value: u64, +} + +impl fmt::Display for ConstraintMismatch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Row {}, Constraint {}: Winterfell={}, Plonky3={}", + self.row, self.constraint_index, self.winterfell_value, self.plonky3_value + ) + } +} + +/// Result of comparing constraint evaluations between backends. +#[derive(Debug)] +pub struct ComparisonResult { + pub mismatches: Vec, + pub total_rows: usize, + pub total_constraints_checked: usize, +} + +impl ComparisonResult { + pub fn is_ok(&self) -> bool { + self.mismatches.is_empty() + } + + /// Formats the comparison result for display. + pub fn format_report(&self) -> String { + if self.is_ok() { + return format!( + "All constraints match! Checked {} constraints across {} rows.", + self.total_constraints_checked, self.total_rows + ); + } + + let mut report = String::new(); + report.push_str("Constraint evaluation mismatches found!\n\n"); + + for mismatch in &self.mismatches { + report.push_str(&format!("{}\n", mismatch)); + } + + report.push_str(&format!( + "\nSummary: {} mismatches found across {} rows ({} total constraints checked)", + self.mismatches.len(), + self.total_rows, + self.total_constraints_checked + )); + + report + } +} + +/// Evaluates Winterfell transition constraints at a specific row. +/// +/// Returns a vector of constraint evaluation values as canonical u64. +/// +/// Note: This does NOT apply the `is_transition` multiplier. Use +/// `evaluate_winterfell_transition_at_row_with_selector` if you need +/// the results to be comparable with Plonky3's `when_transition()` behavior. +pub fn evaluate_winterfell_transition_at_row( + air: &A, + trace: &[Vec], + row: usize, +) -> Vec +where + A: Air, +{ + let trace_width = trace.len(); + let trace_length = trace[0].len(); + + // Build current and next row data + let current: Vec = (0..trace_width).map(|col| trace[col][row]).collect(); + + let next_row = (row + 1) % trace_length; + let next: Vec = (0..trace_width).map(|col| trace[col][next_row]).collect(); + + // Create evaluation frame + let frame = EvaluationFrame::from_rows(current, next); + + // Allocate result buffer based on number of transition constraints + let num_constraints = air.context().num_transition_constraints(); + let mut result = vec![WinterfellFelt::ZERO; num_constraints]; + + // Evaluate transition constraints (empty periodic values for simple AIRs) + let periodic_values: Vec = vec![]; + air.evaluate_transition(&frame, &periodic_values, &mut result); + + // Convert to canonical u64 + result.iter().map(|e| e.to_canonical_u64()).collect() +} + +/// Evaluates Winterfell transition constraints at a specific row, +/// with each result multiplied by `is_transition`. +/// +/// This makes the results comparable with Plonky3's `when_transition()` behavior, +/// where constraints are multiplied by a selector that is 1 on all rows except +/// the last one. +/// +/// Note: This applies the selector to ALL transition constraints, including +/// those that are NOT wrapped in `when_transition()` in Plonky3. For valid +/// traces where all constraints evaluate to 0, this doesn't matter. For +/// invalid traces or debugging, be aware of this difference. +pub fn evaluate_winterfell_transition_at_row_with_selector( + air: &A, + trace: &[Vec], + row: usize, + num_rows: usize, +) -> Vec +where + A: Air, +{ + // Use the version with periodic values, passing empty periodic values + evaluate_winterfell_transition_at_row_with_periodic(air, trace, row, num_rows, &[]) +} + +/// Evaluates Winterfell transition constraints at a specific row, +/// with periodic column values and `is_transition` selector applied. +/// +/// This is the full-featured version that supports periodic columns. +/// +/// # Arguments +/// +/// * `air` - The Winterfell AIR instance +/// * `trace` - The trace in column-major format +/// * `row` - The row to evaluate at +/// * `num_rows` - Total number of rows in the trace +/// * `periodic_values` - The periodic column values evaluated at this row +pub fn evaluate_winterfell_transition_at_row_with_periodic( + air: &A, + trace: &[Vec], + row: usize, + num_rows: usize, + periodic_values: &[WinterfellFelt], +) -> Vec +where + A: Air, +{ + let trace_width = trace.len(); + let trace_length = trace[0].len(); + + // Build current and next row data + let current: Vec = (0..trace_width).map(|col| trace[col][row]).collect(); + + let next_row = (row + 1) % trace_length; + let next: Vec = (0..trace_width).map(|col| trace[col][next_row]).collect(); + + // Create evaluation frame + let frame = EvaluationFrame::from_rows(current, next); + + // Allocate result buffer based on number of transition constraints + let num_constraints = air.context().num_transition_constraints(); + let mut result = vec![WinterfellFelt::ZERO; num_constraints]; + + // Evaluate transition constraints with periodic values + air.evaluate_transition(&frame, periodic_values, &mut result); + + // Apply is_transition selector: 1 for rows 0..n-1, 0 for row n-1 + let is_transition = if row < num_rows - 1 { + WinterfellFelt::ONE + } else { + WinterfellFelt::ZERO + }; + + // Convert to canonical u64, applying the selector + result.iter().map(|e| (is_transition * *e).to_canonical_u64()).collect() +} + +/// Evaluates periodic column values at a specific row. +/// +/// Periodic columns repeat with a given period. At row `r`, the value is +/// `column[r % period]` where `period` is the length of the column. +/// +/// # Arguments +/// +/// * `periodic_columns` - Vec of periodic column definitions, each as Vec +/// * `row` - The row index to evaluate at +/// +/// # Returns +/// +/// A vector of field elements, one for each periodic column, evaluated at the given row. +pub fn evaluate_periodic_values_at_row( + periodic_columns: &[Vec], + row: usize, +) -> Vec { + periodic_columns + .iter() + .map(|column| { + if column.is_empty() { + F::ZERO + } else { + let period = column.len(); + let idx = row % period; + F::from_u64(column[idx]) + } + }) + .collect() +} + +/// Evaluates periodic column values at a specific row for Winterfell (WinterfellFelt). +pub fn evaluate_winterfell_periodic_at_row( + periodic_columns: &[Vec], + row: usize, +) -> Vec { + periodic_columns + .iter() + .map(|column| { + if column.is_empty() { + WinterfellFelt::ZERO + } else { + let period = column.len(); + let idx = row % period; + WinterfellFelt::new(column[idx]) + } + }) + .collect() +} + +/// Gets Winterfell boundary constraint info. +/// Returns (column, row, expected_value) for each assertion. +pub fn get_winterfell_boundary_assertions(air: &A) -> Vec<(usize, usize, u64)> +where + A: Air, +{ + air.get_assertions() + .iter() + .map(|assertion| { + // For single assertions, get the value directly + // The assertion contains: column index, step (row), and expected value + let col = assertion.column(); + let step = assertion.first_step(); + // Get values from the assertion - for single value assertions + let values = assertion.values(); + let expected = if !values.is_empty() { + values[0].to_canonical_u64() + } else { + 0 + }; + (col, step, expected) + }) + .collect() +} + +/// Evaluates boundary constraints at a specific row for Winterfell. +/// Returns the constraint evaluation (actual - expected) for each boundary constraint +/// that applies to this row, multiplied by the first_row indicator (like Plonky3 does). +pub fn evaluate_winterfell_boundary_at_row( + air: &A, + trace: &[Vec], + row: usize, + num_rows: usize, +) -> Vec +where + A: Air, +{ + let assertions = get_winterfell_boundary_assertions(air); + let mut results = Vec::new(); + + for (col, assertion_row, expected) in assertions { + // Compute (actual - expected) + let actual = trace[col][row].to_canonical_u64(); + + // For first row constraints: multiply by is_first_row indicator + // For last row constraints: multiply by is_last_row indicator + let is_first_row = if row == 0 { 1u64 } else { 0u64 }; + let is_last_row = if row == num_rows - 1 { 1u64 } else { 0u64 }; + + if assertion_row == 0 { + // First row boundary constraint + // Plonky3 computes: is_first_row * (actual - expected) + // We need to do the same arithmetic in the field + let actual_felt = WinterfellFelt::new(actual); + let expected_felt = WinterfellFelt::new(expected); + let is_first_felt = WinterfellFelt::new(is_first_row); + let diff = actual_felt - expected_felt; + let result = is_first_felt * diff; + results.push(result.to_canonical_u64()); + } else if assertion_row == num_rows - 1 { + // Last row boundary constraint + let actual_felt = WinterfellFelt::new(actual); + let expected_felt = WinterfellFelt::new(expected); + let is_last_felt = WinterfellFelt::new(is_last_row); + let diff = actual_felt - expected_felt; + let result = is_last_felt * diff; + results.push(result.to_canonical_u64()); + } + } + + results +} + +/// A view into two consecutive rows of the trace matrix for constraint evaluation. +pub struct TwoRowMatrixView { + current_row: Vec, + next_row: Vec, +} + +impl TwoRowMatrixView { + pub fn new(current_row: Vec, next_row: Vec) -> Self { + Self { current_row, next_row } + } +} + +impl Matrix for TwoRowMatrixView { + fn width(&self) -> usize { + self.current_row.len() + } + + fn height(&self) -> usize { + 2 + } + + fn row_slice(&self, r: usize) -> Option> { + match r { + 0 => Some(self.current_row.clone()), + 1 => Some(self.next_row.clone()), + _ => None, + } + } +} + +/// A builder that captures constraint evaluation values instead of asserting them. +/// +/// This implements `MidenAirBuilder` and records all values passed to `assert_zero` +/// for later comparison with Winterfell's constraint evaluations. +pub struct ConstraintCapturingBuilder { + /// View of current and next rows + main_view: TwoRowMatrixView, + /// Current row index being evaluated + current_row: usize, + /// Total number of rows in the trace + num_rows: usize, + /// Public input values + public_values: Vec, + /// Periodic column evaluations (empty for simple AIRs) + periodic_values: Vec, + /// Captured constraint evaluations + captured_constraints: Vec, +} + +impl ConstraintCapturingBuilder { + /// Creates a new constraint capturing builder for a specific row. + pub fn new( + trace: &RowMajorMatrix, + row: usize, + public_values: Vec, + periodic_values: Vec, + ) -> Self { + let num_rows = trace.height(); + let width = trace.width(); + + // Get current row + let current_row: Vec = trace + .row_slice(row) + .map(|s| s.iter().cloned().collect()) + .unwrap_or_else(|| vec![F::ZERO; width]); + + // Get next row (wrap around) + let next_row_idx = (row + 1) % num_rows; + let next_row: Vec = trace + .row_slice(next_row_idx) + .map(|s| s.iter().cloned().collect()) + .unwrap_or_else(|| vec![F::ZERO; width]); + + let main_view = TwoRowMatrixView::new(current_row, next_row); + + Self { + main_view, + current_row: row, + num_rows, + public_values, + periodic_values, + captured_constraints: Vec::new(), + } + } + + /// Returns the captured constraint values as canonical u64. + pub fn get_captured_constraints(&self) -> Vec + where + F: PrimeField64, + { + self.captured_constraints.iter().map(|f| f.as_canonical_u64()).collect() + } + + fn is_first_row_value(&self) -> F { + if self.current_row == 0 { F::ONE } else { F::ZERO } + } + + fn is_last_row_value(&self) -> F { + if self.current_row == self.num_rows - 1 { + F::ONE + } else { + F::ZERO + } + } + + fn is_transition_window_value(&self, size: usize) -> F { + if self.current_row < self.num_rows.saturating_sub(size - 1) { + F::ONE + } else { + F::ZERO + } + } +} + +impl MidenAirBuilder for ConstraintCapturingBuilder { + type F = F; + type Expr = F; + type Var = F; + type M = TwoRowMatrixView; + type PublicVar = F; + type PeriodicVal = F; + type EF = F; + type ExprEF = F; + type VarEF = F; + type MP = TwoRowMatrixView; + type RandomVar = F; + + fn main(&self) -> Self::M { + TwoRowMatrixView::new(self.main_view.current_row.clone(), self.main_view.next_row.clone()) + } + + fn is_first_row(&self) -> Self::Expr { + self.is_first_row_value() + } + + fn is_last_row(&self) -> Self::Expr { + self.is_last_row_value() + } + + fn is_transition_window(&self, size: usize) -> Self::Expr { + self.is_transition_window_value(size) + } + + fn assert_zero>(&mut self, x: I) { + self.captured_constraints.push(x.into()); + } + + fn public_values(&self) -> &[Self::PublicVar] { + &self.public_values + } + + fn periodic_evals(&self) -> &[Self::PeriodicVal] { + &self.periodic_values + } + + fn preprocessed(&self) -> Self::M { + self.main() + } + + fn assert_zero_ext(&mut self, x: I) + where + I: Into, + { + self.captured_constraints.push(x.into()); + } + + fn permutation(&self) -> Self::MP { + self.main() + } + + fn permutation_randomness(&self) -> &[Self::RandomVar] { + &[] + } + + fn aux_bus_boundary_values(&self) -> &[Self::VarEF] { + &[] + } +} + +/// Converts a Plonky3 RowMajorMatrix to a Winterfell-style column-major trace. +pub fn plonky3_trace_to_winterfell(trace: &RowMajorMatrix) -> Vec> +where + F: PrimeField64 + Clone + Send + Sync, +{ + let num_rows = trace.height(); + let num_cols = trace.width(); + + (0..num_cols) + .map(|col| { + (0..num_rows) + .map(|row| { + let row_slice = trace.row_slice(row).unwrap(); + WinterfellFelt::new(row_slice[col].as_canonical_u64()) + }) + .collect() + }) + .collect() +} + +/// Converts a Winterfell column-major trace to a Plonky3 RowMajorMatrix. +pub fn winterfell_trace_to_plonky3( + trace: &[Vec], +) -> RowMajorMatrix { + if trace.is_empty() { + return RowMajorMatrix::new(vec![], 0); + } + + let num_cols = trace.len(); + let num_rows = trace[0].len(); + + let mut values = Vec::with_capacity(num_rows * num_cols); + + for row in 0..num_rows { + for col in 0..num_cols { + let val = trace[col][row].as_int(); + values.push(F::from_u64(val)); + } + } + + RowMajorMatrix::new(values, num_cols) +} + +/// Compares constraint evaluations row by row. +/// +/// Returns a ComparisonResult with any mismatches found. +pub fn compare_evaluations_by_row( + winterfell_evals: &[Vec], + plonky3_evals: &[Vec], +) -> ComparisonResult { + let total_rows = winterfell_evals.len().max(plonky3_evals.len()); + let mut mismatches = Vec::new(); + let mut total_constraints_checked = 0; + + for row in 0..total_rows { + let w_row = winterfell_evals.get(row); + let p_row = plonky3_evals.get(row); + + match (w_row, p_row) { + (Some(w), Some(p)) => { + // Check if constraint counts match + if w.len() != p.len() { + mismatches.push(ConstraintMismatch { + row, + constraint_index: usize::MAX, + winterfell_value: w.len() as u64, + plonky3_value: p.len() as u64, + }); + continue; + } + + total_constraints_checked += w.len(); + + for (idx, (w_val, p_val)) in w.iter().zip(p.iter()).enumerate() { + if w_val != p_val { + mismatches.push(ConstraintMismatch { + row, + constraint_index: idx, + winterfell_value: *w_val, + plonky3_value: *p_val, + }); + } + } + }, + (Some(w), None) => { + mismatches.push(ConstraintMismatch { + row, + constraint_index: usize::MAX, + winterfell_value: w.len() as u64, + plonky3_value: 0, + }); + }, + (None, Some(p)) => { + mismatches.push(ConstraintMismatch { + row, + constraint_index: usize::MAX, + winterfell_value: 0, + plonky3_value: p.len() as u64, + }); + }, + (None, None) => {}, + } + } + + ComparisonResult { + mismatches, + total_rows, + total_constraints_checked, + } +} + +/// Trait for configuring cross-backend comparison tests. +/// +/// Implement this trait for each AIR to enable cross-backend constraint comparison. +/// The trait provides a unified interface for building traces, public inputs, +/// and AIR instances for both Winterfell and Plonky3 backends. +/// +/// # Example +/// +/// ```ignore +/// struct MyTestConfig; +/// +/// impl CrossBackendTestConfig for MyTestConfig { +/// type WinterfellAir = MyWinterfellAir; +/// type Plonky3Air = MyPlonky3Air; +/// type WinterfellPublicInputs = MyPublicInputs; +/// +/// fn trace_width(&self) -> usize { 2 } +/// fn trace_length(&self) -> usize { 64 } +/// // ... other methods +/// } +/// ``` +pub trait CrossBackendTestConfig { + /// The Winterfell AIR type. + type WinterfellAir: Air; + + /// The Plonky3 AIR type. + type Plonky3Air: MidenAir; + + /// The Winterfell public inputs type. + type WinterfellPublicInputs: Serializable + ToElements; + + /// Returns the trace width (number of columns). + fn trace_width(&self) -> usize; + + /// Returns the trace length (number of rows). + fn trace_length(&self) -> usize; + + /// Builds the trace in Winterfell format (column-major). + fn build_winterfell_trace(&self) -> Vec>; + + /// Builds the Winterfell public inputs. + fn build_winterfell_public_inputs(&self) -> Self::WinterfellPublicInputs; + + /// Builds the Plonky3 public inputs. + fn build_plonky3_public_inputs(&self) -> Vec; + + /// Creates the Winterfell AIR instance. + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: Self::WinterfellPublicInputs, + options: WinterProofOptions, + ) -> Self::WinterfellAir; + + /// Creates the Plonky3 AIR instance. + fn create_plonky3_air(&self) -> Self::Plonky3Air; + + /// Returns the number of public values for Plonky3. + fn num_public_values(&self) -> usize; + + /// Returns the periodic column values (empty by default). + /// + /// Each inner Vec represents a periodic column, with values that repeat + /// cyclically. The period is determined by the length of each inner Vec. + /// + /// For example, `vec![1, 0, 0, 0]` defines a periodic column with period 4 + /// that has value 1 on rows 0, 4, 8, ... and value 0 elsewhere. + /// + /// # Example + /// + /// ```ignore + /// fn periodic_column_values(&self) -> Vec> { + /// vec![ + /// vec![1, 0, 0, 0, 0, 0, 0, 0], // k0: period 8, value 1 at rows 0, 8, 16, ... + /// vec![1, 1, 1, 1, 1, 1, 1, 0], // k1: period 8, value 1 except at rows 7, 15, 23, ... + /// ] + /// } + /// ``` + fn periodic_column_values(&self) -> Vec> { + vec![] + } + + /// Builds a random trace in Winterfell format (column-major) using a seeded RNG. + /// + /// The seed is generated from the test name and iteration number using + /// [`generate_test_seed`], ensuring different tests get unique random traces + /// even when using the same iteration numbers. + /// + /// Default implementation generates uniformly random field elements in + /// the range `[0, GOLDILOCKS_MODULUS)`. Override this if your AIR has + /// specific requirements for trace structure. + /// + /// # Arguments + /// * `test_name` - Name of the test (used for seed generation) + /// * `iteration` - Iteration number (used for seed generation) + fn build_random_winterfell_trace( + &self, + test_name: &str, + iteration: u64, + ) -> Vec> { + let seed = generate_test_seed(test_name, iteration); + let mut rng = ChaCha8Rng::seed_from_u64(seed); + let width = self.trace_width(); + let length = self.trace_length(); + + (0..width) + .map(|_| { + (0..length) + .map(|_| WinterfellFelt::new(rng.random_range(0..GOLDILOCKS_MODULUS))) + .collect() + }) + .collect() + } +} + +/// Creates default proof options for testing. +pub fn default_proof_options() -> WinterProofOptions { + WinterProofOptions::new( + 32, // number of queries + 8, // blowup factor + 0, // grinding factor + FieldExtension::None, // field extension + 8, // FRI folding factor + 31, // FRI max remainder polynomial degree + BatchingMethod::Linear, // constraint composition batching + BatchingMethod::Linear, // DEEP polynomial batching + ) +} + +/// Runs a full cross-backend comparison for the given test configuration. +/// +/// This function: +/// 1. Builds the trace using the config +/// 2. Creates both Winterfell and Plonky3 AIR instances +/// 3. Evaluates all constraints at each row for both backends +/// 4. Compares the results and returns a detailed report +/// +/// # Arguments +/// +/// * `config` - The test configuration implementing `CrossBackendTestConfig` +/// +/// # Returns +/// +/// A `ComparisonResult` containing any mismatches found and statistics. +pub fn run_cross_backend_comparison(config: &C) -> ComparisonResult +where + C: CrossBackendTestConfig, +{ + let winterfell_trace = config.build_winterfell_trace(); + run_cross_backend_comparison_with_trace(config, winterfell_trace) +} + +/// Runs a full cross-backend comparison using a provided trace. +/// +/// This is useful for testing with custom or random traces instead of +/// the valid trace generated by `build_winterfell_trace`. +/// +/// # Arguments +/// +/// * `config` - The test configuration implementing `CrossBackendTestConfig` +/// * `winterfell_trace` - The trace in Winterfell format (column-major) +/// +/// # Returns +/// +/// A `ComparisonResult` containing any mismatches found and statistics. +pub fn run_cross_backend_comparison_with_trace( + config: &C, + winterfell_trace: Vec>, +) -> ComparisonResult +where + C: CrossBackendTestConfig, +{ + let trace_length = config.trace_length(); + + // Convert to Plonky3 format + let plonky3_trace: RowMajorMatrix = winterfell_trace_to_plonky3(&winterfell_trace); + + // Build public inputs + let winterfell_pub_inputs = config.build_winterfell_public_inputs(); + let plonky3_pub_inputs = config.build_plonky3_public_inputs(); + + // Create Winterfell AIR + let trace_info = TraceInfo::new(config.trace_width(), trace_length); + let proof_options = default_proof_options(); + let winterfell_air = + config.create_winterfell_air(trace_info, winterfell_pub_inputs, proof_options); + + // Create Plonky3 AIR + let plonky3_air = config.create_plonky3_air(); + + // Get the last_step for Winterfell (where last-row boundary constraints apply) + let last_step = trace_length - winterfell_air.context().num_transition_exemptions(); + + // Get periodic column definitions + let periodic_columns = config.periodic_column_values(); + + // Evaluate constraints at each row where transition constraints are enforced. + // Rows >= last_step have transition exemptions in Winterfell, so we skip them + // to ensure both backends are compared on rows with the same constraint semantics. + let mut winterfell_results: Vec> = Vec::new(); + let mut plonky3_results: Vec> = Vec::new(); + + for row in 0..last_step { + // Evaluate periodic values at this row for both backends + let winterfell_periodic = evaluate_winterfell_periodic_at_row(&periodic_columns, row); + let plonky3_periodic: Vec = + evaluate_periodic_values_at_row(&periodic_columns, row); + + // Winterfell: evaluate boundary constraints + let w_boundary = evaluate_winterfell_boundary_at_row_with_last_step( + &winterfell_air, + &winterfell_trace, + row, + trace_length, + last_step, + ); + + // Winterfell: evaluate transition constraints with is_transition selector + // and periodic values + let w_transition = evaluate_winterfell_transition_at_row_with_periodic( + &winterfell_air, + &winterfell_trace, + row, + trace_length, + &winterfell_periodic, + ); + + // Combine: boundary constraints first, then transition constraints + let mut w_all = w_boundary; + w_all.extend(w_transition); + winterfell_results.push(w_all); + + // Plonky3: create a capturing builder for this row with periodic values + let mut builder = ConstraintCapturingBuilder::new( + &plonky3_trace, + row, + plonky3_pub_inputs.clone(), + plonky3_periodic, + ); + + // Evaluate the Plonky3 AIR + MidenAir::::eval(&plonky3_air, &mut builder); + + // Get captured constraints + let p_all = builder.get_captured_constraints(); + plonky3_results.push(p_all); + } + + // Compare results + compare_evaluations_by_row(&winterfell_results, &plonky3_results) +} + +/// Runs a full cross-backend comparison using a randomly generated trace. +/// +/// This tests that both backends produce identical constraint evaluations +/// for random (invalid) inputs, verifying that the constraint logic matches +/// even when constraints are not satisfied. +/// +/// The random trace is generated deterministically from the test name and +/// iteration number, ensuring reproducible results. +/// +/// # Arguments +/// +/// * `config` - The test configuration implementing `CrossBackendTestConfig` +/// * `test_name` - Name of the test (used for seed generation) +/// * `iteration` - Iteration number (used for seed generation) +/// +/// # Returns +/// +/// A `ComparisonResult` containing any mismatches found and statistics. +pub fn run_cross_backend_comparison_random( + config: &C, + test_name: &str, + iteration: u64, +) -> ComparisonResult +where + C: CrossBackendTestConfig, +{ + let random_trace = config.build_random_winterfell_trace(test_name, iteration); + run_cross_backend_comparison_with_trace(config, random_trace) +} + +/// Evaluates boundary constraints at a specific row for Winterfell. +/// +/// This version takes the explicit `last_step` value from Winterfell's AIR, +/// which accounts for transition exemptions when determining when last-row +/// boundary constraints should apply. +pub fn evaluate_winterfell_boundary_at_row_with_last_step( + air: &A, + trace: &[Vec], + row: usize, + num_rows: usize, + last_step: usize, +) -> Vec +where + A: Air, +{ + let assertions = get_winterfell_boundary_assertions(air); + let mut results = Vec::new(); + + for (col, assertion_row, expected) in assertions { + let actual = trace[col][row].to_canonical_u64(); + + let is_first_row = if row == 0 { 1u64 } else { 0u64 }; + let is_last_row = if row == num_rows - 1 { 1u64 } else { 0u64 }; + + if assertion_row == 0 { + // First row boundary constraint + let actual_felt = WinterfellFelt::new(actual); + let expected_felt = WinterfellFelt::new(expected); + let is_first_felt = WinterfellFelt::new(is_first_row); + let diff = actual_felt - expected_felt; + let result = is_first_felt * diff; + results.push(result.to_canonical_u64()); + } else if assertion_row == last_step { + // Last row boundary constraint (using last_step from AIR) + let actual_felt = WinterfellFelt::new(actual); + let expected_felt = WinterfellFelt::new(expected); + let is_last_felt = WinterfellFelt::new(is_last_row); + let diff = actual_felt - expected_felt; + let result = is_last_felt * diff; + results.push(result.to_canonical_u64()); + } + } + + results +} diff --git a/air-script/src/test_utils/mod.rs b/air-script/src/test_utils/mod.rs new file mode 100644 index 000000000..2b4182aee --- /dev/null +++ b/air-script/src/test_utils/mod.rs @@ -0,0 +1,10 @@ +/// Macros to generate Air tester structs and tests for both Plonky3 and Winterfell backends. +pub mod air_tester_macros; +/// Code generation for tests/**/*.air files. +pub mod codegen; +/// Cross-backend constraint evaluation comparison utilities. +pub mod cross_backend_comparison; +/// Conversion utilities for test inputs (both public inputs and variable-length public inputs). +pub mod pub_inputs_conversion_utils; +/// Winterfell-specific traits +pub mod winterfell_traits; diff --git a/air-script/src/test_utils/pub_inputs_conversion_utils.rs b/air-script/src/test_utils/pub_inputs_conversion_utils.rs new file mode 100644 index 000000000..894ba75d6 --- /dev/null +++ b/air-script/src/test_utils/pub_inputs_conversion_utils.rs @@ -0,0 +1,46 @@ +type Val = p3_goldilocks::Goldilocks; + +/// Converts public inputs from u64 to Goldilocks field elements. +pub(crate) fn convert_pub_inputs_to_goldilocks(pub_inputs: &[u64]) -> Vec { + pub_inputs + .iter() + .map(|&x| ::from_u64(x)) + .collect() +} + +/// Converts variable-length public inputs from u64 to Goldilocks field elements. +/// The input should be a Vec of tables (Vec>) in a RowMajor format. +pub(crate) fn convert_var_len_pub_inputs_to_goldilocks( + var_len_pub_inputs: Vec>>, +) -> Vec>> { + let mut var_len_pub_inputs_goldilocks_vec: Vec>> = vec![]; + for arr in var_len_pub_inputs.iter() { + let mut goldilocks_arr: Vec> = vec![]; + for slice in arr.iter() { + let goldilocks_slice: Vec = slice + .iter() + .map(|&x| ::from_u64(x)) + .collect(); + goldilocks_arr.push(goldilocks_slice); + } + var_len_pub_inputs_goldilocks_vec.push(goldilocks_arr); + } + var_len_pub_inputs_goldilocks_vec +} + +/// Converts the innermost vectors of variable-length public inputs to slices. +pub(crate) fn convert_inner_vec_to_slice<'a>( + var_len_pub_inputs: &'a Vec>>, +) -> Vec> { + var_len_pub_inputs + .iter() + .map(|outer| outer.iter().map(|inner| inner.as_slice()).collect()) + .collect() +} + +/// Converts the middle vectors of variable-length public inputs to slices. +pub(crate) fn convert_mid_vec_to_slice<'a>( + var_len_pub_inputs: &'a Vec>, +) -> Vec<&'a [&'a [Val]]> { + var_len_pub_inputs.iter().map(|v| v.as_slice()).collect() +} diff --git a/air-script/tests/helpers/mod.rs b/air-script/src/test_utils/winterfell_traits.rs similarity index 99% rename from air-script/tests/helpers/mod.rs rename to air-script/src/test_utils/winterfell_traits.rs index dbabc0709..5f91493d8 100644 --- a/air-script/tests/helpers/mod.rs +++ b/air-script/src/test_utils/winterfell_traits.rs @@ -2,8 +2,6 @@ use winter_air::{BatchingMethod, EvaluationFrame, FieldExtension, ProofOptions, use winter_math::fields::f64::BaseElement as Felt; use winterfell::{AuxTraceWithMetadata, Trace, TraceTable, matrix::ColMatrix}; -pub mod macros; - /// We need to encapsulate the trace table in a struct to manually implement the `aux_trace_width` /// method of the `Table` trait. Otherwise, using only a TraceTable will return an /// `aux_trace_width` of 0 even if we provide a non-empty aux trace in `Trace::validate`, diff --git a/air-script/tests/binary/binary.air b/air-script/src/tests/binary/binary.air similarity index 84% rename from air-script/tests/binary/binary.air rename to air-script/src/tests/binary/binary.air index 50e218052..e609a69b3 100644 --- a/air-script/tests/binary/binary.air +++ b/air-script/src/tests/binary/binary.air @@ -9,7 +9,7 @@ public_inputs { } boundary_constraints { - enf a.first = 0; + enf a.first = stack_inputs[0]; } integrity_constraints { diff --git a/air-script/tests/binary/binary.rs b/air-script/src/tests/binary/binary.rs similarity index 97% rename from air-script/tests/binary/binary.rs rename to air-script/src/tests/binary/binary.rs index 8b7185bbd..b398843dc 100644 --- a/air-script/tests/binary/binary.rs +++ b/air-script/src/tests/binary/binary.rs @@ -70,7 +70,7 @@ impl Air for BinaryAir { fn get_assertions(&self) -> Vec> { let mut result = Vec::new(); - result.push(Assertion::single(0, 0, Felt::ZERO)); + result.push(Assertion::single(0, 0, self.stack_inputs[0])); result } diff --git a/air-script/src/tests/binary/binary_plonky3.rs b/air-script/src/tests/binary/binary_plonky3.rs new file mode 100644 index 000000000..9f64d933c --- /dev/null +++ b/air-script/src/tests/binary/binary_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 2; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct BinaryAir; + +impl MidenAir for BinaryAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into() - public_values[0].into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() * main_current[0].clone().into() - main_current[0].clone().into()); + builder.assert_zero(main_current[1].clone().into() * main_current[1].clone().into() - main_current[1].clone().into()); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/binary/mod.rs b/air-script/src/tests/binary/mod.rs new file mode 100644 index 000000000..14be89c53 --- /dev/null +++ b/air-script/src/tests/binary/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub mod binary; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +pub mod binary_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/binary/test_air_plonky3.rs b/air-script/src/tests/binary/test_air_plonky3.rs new file mode 100644 index 000000000..ba508f868 --- /dev/null +++ b/air-script/src/tests/binary/test_air_plonky3.rs @@ -0,0 +1,47 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::binary::binary_plonky3::{BinaryAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::from_canonical_checked(inputs[0]).unwrap(); + rows[0][1] = F::ONE; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let a_prev = rows[i - 1][0]; + let b_prev = rows[i - 1][1]; + + // Update current row based on previous values + rows[i][0] = F::ONE - a_prev; + rows[i][1] = F::ONE - b_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, BinaryAir); diff --git a/air-script/tests/binary/test_air.rs b/air-script/src/tests/binary/test_air_winterfell.rs similarity index 76% rename from air-script/tests/binary/test_air.rs rename to air-script/src/tests/binary/test_air_winterfell.rs index 4f40af405..2135c11b6 100644 --- a/air-script/tests/binary/test_air.rs +++ b/air-script/src/tests/binary/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - binary::binary::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::binary::binary::PublicInputs, }; #[derive(Clone)] @@ -39,4 +39,9 @@ impl AirTester for BinaryAirTester { } } -generate_air_test!(test_binary_air, crate::binary::binary::BinaryAir, BinaryAirTester, 1024); +generate_air_winterfell_test!( + test_binary_air, + crate::tests::binary::binary::BinaryAir, + BinaryAirTester, + 1024 +); diff --git a/air-script/tests/bitwise/bitwise.air b/air-script/src/tests/bitwise/bitwise.air similarity index 100% rename from air-script/tests/bitwise/bitwise.air rename to air-script/src/tests/bitwise/bitwise.air diff --git a/air-script/tests/bitwise/bitwise.rs b/air-script/src/tests/bitwise/bitwise.rs similarity index 100% rename from air-script/tests/bitwise/bitwise.rs rename to air-script/src/tests/bitwise/bitwise.rs diff --git a/air-script/src/tests/bitwise/bitwise_plonky3.rs b/air-script/src/tests/bitwise/bitwise_plonky3.rs new file mode 100644 index 000000000..9811db8cf --- /dev/null +++ b/air-script/src/tests/bitwise/bitwise_plonky3.rs @@ -0,0 +1,72 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 14; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 2; +pub const PERIOD: usize = 8; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct BitwiseAir; + +impl MidenAir for BitwiseAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } + + fn periodic_table(&self) -> Vec> { + vec![ + vec![F::from_u64(1), F::from_u64(0), F::from_u64(0), F::from_u64(0), F::from_u64(0), F::from_u64(0), F::from_u64(0), F::from_u64(0)], + vec![F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(0)], + ] + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[13].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() * main_current[0].clone().into() - main_current[0].clone().into()); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[1].clone().into()) * (AB::ExprEF::from(main_next[0].clone().into()) - AB::ExprEF::from(main_current[0].clone().into()))); + builder.assert_zero(main_current[3].clone().into() * main_current[3].clone().into() - main_current[3].clone().into()); + builder.assert_zero(main_current[4].clone().into() * main_current[4].clone().into() - main_current[4].clone().into()); + builder.assert_zero(main_current[5].clone().into() * main_current[5].clone().into() - main_current[5].clone().into()); + builder.assert_zero(main_current[6].clone().into() * main_current[6].clone().into() - main_current[6].clone().into()); + builder.assert_zero(main_current[7].clone().into() * main_current[7].clone().into() - main_current[7].clone().into()); + builder.assert_zero(main_current[8].clone().into() * main_current[8].clone().into() - main_current[8].clone().into()); + builder.assert_zero(main_current[9].clone().into() * main_current[9].clone().into() - main_current[9].clone().into()); + builder.assert_zero(main_current[10].clone().into() * main_current[10].clone().into() - main_current[10].clone().into()); + builder.assert_zero_ext(AB::ExprEF::from(periodic_values[0].clone().into()) * (AB::ExprEF::from(main_current[1].clone().into()) - (AB::ExprEF::from(main_current[3].clone().into()) + AB::ExprEF::from(main_current[4].clone().into()).double() + AB::ExprEF::from_u64(4) * AB::ExprEF::from(main_current[5].clone().into()) + AB::ExprEF::from_u64(8) * AB::ExprEF::from(main_current[6].clone().into())))); + builder.assert_zero_ext(AB::ExprEF::from(periodic_values[0].clone().into()) * (AB::ExprEF::from(main_current[2].clone().into()) - (AB::ExprEF::from(main_current[7].clone().into()) + AB::ExprEF::from(main_current[8].clone().into()).double() + AB::ExprEF::from_u64(4) * AB::ExprEF::from(main_current[9].clone().into()) + AB::ExprEF::from_u64(8) * AB::ExprEF::from(main_current[10].clone().into())))); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[1].clone().into()) * (AB::ExprEF::from(main_next[1].clone().into()) - (AB::ExprEF::from(main_current[1].clone().into()) * AB::ExprEF::from_u64(16) + AB::ExprEF::from(main_current[3].clone().into()) + AB::ExprEF::from(main_current[4].clone().into()).double() + AB::ExprEF::from_u64(4) * AB::ExprEF::from(main_current[5].clone().into()) + AB::ExprEF::from_u64(8) * AB::ExprEF::from(main_current[6].clone().into())))); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[1].clone().into()) * (AB::ExprEF::from(main_next[2].clone().into()) - (AB::ExprEF::from(main_current[2].clone().into()) * AB::ExprEF::from_u64(16) + AB::ExprEF::from(main_current[7].clone().into()) + AB::ExprEF::from(main_current[8].clone().into()).double() + AB::ExprEF::from_u64(4) * AB::ExprEF::from(main_current[9].clone().into()) + AB::ExprEF::from_u64(8) * AB::ExprEF::from(main_current[10].clone().into())))); + builder.assert_zero_ext(AB::ExprEF::from(periodic_values[0].clone().into()) * AB::ExprEF::from(main_current[11].clone().into())); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[1].clone().into()) * (AB::ExprEF::from(main_current[12].clone().into()) - AB::ExprEF::from(main_next[11].clone().into()))); + builder.assert_zero((AB::Expr::ONE - main_current[0].clone().into()) * (main_current[12].clone().into() - (main_current[11].clone().into() * AB::Expr::from_u64(16) + main_current[3].clone().into() * main_current[7].clone().into() + main_current[4].clone().into().double() * main_current[8].clone().into() + AB::Expr::from_u64(4) * main_current[5].clone().into() * main_current[9].clone().into() + AB::Expr::from_u64(8) * main_current[6].clone().into() * main_current[10].clone().into())) + main_current[0].clone().into() * (main_current[12].clone().into() - (main_current[11].clone().into() * AB::Expr::from_u64(16) + main_current[3].clone().into() + main_current[7].clone().into() - main_current[3].clone().into().double() * main_current[7].clone().into() + (main_current[4].clone().into() + main_current[8].clone().into() - main_current[4].clone().into().double() * main_current[8].clone().into()).double() + AB::Expr::from_u64(4) * (main_current[5].clone().into() + main_current[9].clone().into() - main_current[5].clone().into().double() * main_current[9].clone().into()) + AB::Expr::from_u64(8) * (main_current[6].clone().into() + main_current[10].clone().into() - main_current[6].clone().into().double() * main_current[10].clone().into())))); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/bitwise/mod.rs b/air-script/src/tests/bitwise/mod.rs new file mode 100644 index 000000000..bbefba2b3 --- /dev/null +++ b/air-script/src/tests/bitwise/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub mod bitwise; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +pub mod bitwise_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/bitwise/test_air_plonky3.rs b/air-script/src/tests/bitwise/test_air_plonky3.rs new file mode 100644 index 000000000..91b2c0637 --- /dev/null +++ b/air-script/src/tests/bitwise/test_air_plonky3.rs @@ -0,0 +1,85 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::bitwise::bitwise_plonky3::{BitwiseAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + let first = F::ZERO; + + // Initialize first row + rows[0][0] = first; + rows[0][1] = first; + rows[0][2] = first; + rows[0][3] = first; + rows[0][4] = first; + rows[0][5] = first; + rows[0][6] = first; + rows[0][7] = first; + rows[0][8] = first; + rows[0][9] = first; + rows[0][10] = first; + rows[0][11] = first; + rows[0][12] = first; + rows[0][13] = first; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + let col_9_prev = rows[i - 1][9]; + let col_10_prev = rows[i - 1][10]; + let col_11_prev = rows[i - 1][11]; + let col_12_prev = rows[i - 1][12]; + let col_13_prev = rows[i - 1][13]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + rows[i][9] = col_9_prev; + rows[i][10] = col_10_prev; + rows[i][11] = col_11_prev; + rows[i][12] = col_12_prev; + rows[i][13] = col_13_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, BitwiseAir); diff --git a/air-script/tests/bitwise/test_air.rs b/air-script/src/tests/bitwise/test_air_winterfell.rs similarity index 82% rename from air-script/tests/bitwise/test_air.rs rename to air-script/src/tests/bitwise/test_air_winterfell.rs index 16daca900..e563782ef 100644 --- a/air-script/tests/bitwise/test_air.rs +++ b/air-script/src/tests/bitwise/test_air_winterfell.rs @@ -5,9 +5,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{AuxTraceWithMetadata, Trace, TraceTable, matrix::ColMatrix}; use crate::{ - bitwise::bitwise::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::bitwise::bitwise::PublicInputs, }; #[derive(Clone)] @@ -50,4 +50,9 @@ impl AirTester for BitwiseAirTester { } } -generate_air_test!(test_bitwise_air, crate::bitwise::bitwise::BitwiseAir, BitwiseAirTester, 1024); +generate_air_winterfell_test!( + test_bitwise_air, + crate::tests::bitwise::bitwise::BitwiseAir, + BitwiseAirTester, + 1024 +); diff --git a/air-script/tests/buses/buses_complex.air b/air-script/src/tests/buses/buses_complex.air similarity index 80% rename from air-script/tests/buses/buses_complex.air rename to air-script/src/tests/buses/buses_complex.air index 401ca8b67..f27a2745c 100644 --- a/air-script/tests/buses/buses_complex.air +++ b/air-script/src/tests/buses/buses_complex.air @@ -1,7 +1,7 @@ def BusesAir trace_columns { - main: [a, b, s1, s2, d], + main: [a, b, s1, s2, s3, s4, d], } buses { @@ -34,7 +34,7 @@ integrity_constraints { p.insert(2, b) when 1 - s1; p.remove(2, a) when 1 - s2; - q.insert(3, a) when s1; - q.insert(3, a) when s1; - q.remove(4, b) with d; + q.insert(3, a) when s3; + q.insert(3, a) when s4; + q.remove(3, b) with d; } diff --git a/air-script/tests/buses/buses_complex.rs b/air-script/src/tests/buses/buses_complex.rs similarity index 93% rename from air-script/tests/buses/buses_complex.rs rename to air-script/src/tests/buses/buses_complex.rs index d15eeeb8f..33271a2ab 100644 --- a/air-script/tests/buses/buses_complex.rs +++ b/air-script/src/tests/buses/buses_complex.rs @@ -99,6 +99,6 @@ impl Air for BusesAir { let aux_current = aux_frame.current(); let aux_next = aux_frame.next(); result[0] = ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + (E::from(Felt::new(3_u64)) + E::from(main_current[1])) * aux_rand_elements.rand_elements()[2] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[3]) * E::from(main_current[2]) + E::ONE - E::from(main_current[2])) * ((aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * (E::ONE - E::from(main_current[2])) + E::from(main_current[2])) * aux_current[0] - ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + (E::from(Felt::new(3_u64)) + E::from(main_current[1])) * aux_rand_elements.rand_elements()[2] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[3]) * E::from(main_current[3]) + E::ONE - E::from(main_current[3])) * ((aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (E::ONE - E::from(main_current[3])) + E::from(main_current[3])) * aux_next[0]; - result[1] = (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(4_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * aux_current[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(4_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[2]) + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(4_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[2]) - ((aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(4_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * aux_next[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[4])); + result[1] = (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * aux_current[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[4]) + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[5]) - ((aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[1]) * aux_rand_elements.rand_elements()[2]) * aux_next[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(3_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[6])); } } \ No newline at end of file diff --git a/air-script/src/tests/buses/buses_complex_plonky3.rs b/air-script/src/tests/buses/buses_complex_plonky3.rs new file mode 100644 index 000000000..2d038524d --- /dev/null +++ b/air-script/src/tests/buses/buses_complex_plonky3.rs @@ -0,0 +1,141 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 7; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 2; +pub const MAX_BETA_CHALLENGE_POWER: usize = 3; + +pub struct BusesAir; + +impl MidenAir for BusesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + BusType::Logup, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[2].clone().into() * main_current[2].clone().into() - main_current[2].clone().into()); + builder.assert_zero(main_current[3].clone().into() * main_current[3].clone().into() - main_current[3].clone().into()); + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + beta_challenges[0].into() + (AB::ExprEF::from_u64(3) + AB::ExprEF::from(main_current[1].clone().into())) * beta_challenges[1].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[2].into()) * AB::ExprEF::from(main_current[2].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[2].clone().into())) * ((alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[1].into()) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[2].clone().into())) + AB::ExprEF::from(main_current[2].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into() + (AB::ExprEF::from_u64(3) + AB::ExprEF::from(main_current[1].clone().into())) * beta_challenges[1].into() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[2].into()) * AB::ExprEF::from(main_current[3].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[3].clone().into())) * ((alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[3].clone().into())) + AB::ExprEF::from(main_current[3].clone().into())) * AB::ExprEF::from(aux_next[0].clone().into())); + builder.when_transition().assert_zero_ext((alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(aux_current[1].clone().into()) + (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[4].clone().into()) + (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[5].clone().into()) - ((alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[1].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(aux_next[1].clone().into()) + (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[6].clone().into()))); + } +} + +impl BusesAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ONE, + EF::ZERO, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + beta_challenges[0] + (EF::from_u64(3) + EF::from(main_current[1].clone())) * beta_challenges[1] + EF::from(main_current[0].clone()) * beta_challenges[2]) * EF::from(main_current[2].clone()) + EF::ONE - EF::from(main_current[2].clone())) * ((alpha + beta_challenges[0].double() + EF::from(main_current[1].clone()) * beta_challenges[1]) * (EF::ONE - EF::from(main_current[2].clone())) + EF::from(main_current[2].clone())) * EF::from(aux_current[0].clone())) * (((alpha + beta_challenges[0] + (EF::from_u64(3) + EF::from(main_current[1].clone())) * beta_challenges[1] + EF::from(main_current[1].clone()) * beta_challenges[2]) * EF::from(main_current[3].clone()) + EF::ONE - EF::from(main_current[3].clone())) * ((alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (EF::ONE - EF::from(main_current[3].clone())) + EF::from(main_current[3].clone()))).inverse(), + ((alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[1].clone()) * beta_challenges[1]) * EF::from(aux_current[1].clone()) + (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[1].clone()) * beta_challenges[1]) * EF::from(main_current[4].clone()) + (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[1].clone()) * beta_challenges[1]) * EF::from(main_current[5].clone()) - (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * EF::from(main_current[6].clone())) * ((alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + EF::from_u64(3) * beta_challenges[0] + EF::from(main_current[1].clone()) * beta_challenges[1])).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/tests/buses/buses_simple.air b/air-script/src/tests/buses/buses_simple.air similarity index 100% rename from air-script/tests/buses/buses_simple.air rename to air-script/src/tests/buses/buses_simple.air diff --git a/air-script/tests/buses/buses_simple.rs b/air-script/src/tests/buses/buses_simple.rs similarity index 100% rename from air-script/tests/buses/buses_simple.rs rename to air-script/src/tests/buses/buses_simple.rs diff --git a/air-script/src/tests/buses/buses_simple_plonky3.rs b/air-script/src/tests/buses/buses_simple_plonky3.rs new file mode 100644 index 000000000..65efcf8fb --- /dev/null +++ b/air-script/src/tests/buses/buses_simple_plonky3.rs @@ -0,0 +1,137 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 1; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 2; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct BusesAir; + +impl MidenAir for BusesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + BusType::Logup, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + + // Main integrity/transition constraints + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + beta_challenges[0].into()) * AB::ExprEF::from(main_current[0].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into()) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) + AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(aux_next[0].clone().into())); + builder.when_transition().assert_zero_ext((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_current[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_next[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()).double())); + } +} + +impl BusesAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ZERO, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + beta_challenges[0]) * EF::from(main_current[0].clone()) + EF::ONE - EF::from(main_current[0].clone())) * EF::from(aux_current[0].clone())) * ((alpha + beta_challenges[0]) * (EF::ONE - EF::from(main_current[0].clone())) + EF::from(main_current[0].clone())).inverse(), + ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(aux_current[1].clone()) + (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) - (alpha + beta_challenges[0] + beta_challenges[1].double()).double()) * ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double())).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/tests/buses/buses_simple_with_evaluators.air b/air-script/src/tests/buses/buses_simple_with_evaluators.air similarity index 100% rename from air-script/tests/buses/buses_simple_with_evaluators.air rename to air-script/src/tests/buses/buses_simple_with_evaluators.air diff --git a/air-script/src/tests/buses/buses_simple_with_evaluators.rs b/air-script/src/tests/buses/buses_simple_with_evaluators.rs new file mode 100644 index 000000000..b0012a15a --- /dev/null +++ b/air-script/src/tests/buses/buses_simple_with_evaluators.rs @@ -0,0 +1,100 @@ +use winter_air::{Air, AirContext, Assertion, AuxRandElements, EvaluationFrame, ProofOptions as WinterProofOptions, TransitionConstraintDegree, TraceInfo}; +use winter_math::fields::f64::BaseElement as Felt; +use winter_math::{ExtensionOf, FieldElement, ToElements}; +use winter_utils::{ByteWriter, Serializable}; + +pub struct PublicInputs { + inputs: [Felt; 2], +} + +impl PublicInputs { + pub fn new(inputs: [Felt; 2]) -> Self { + Self { inputs } + } +} + +impl Serializable for PublicInputs { + fn write_into(&self, target: &mut W) { + self.inputs.write_into(target); + } +} + +impl ToElements for PublicInputs { + fn to_elements(&self) -> Vec { + let mut elements = Vec::new(); + elements.extend_from_slice(&self.inputs); + elements + } +} + +pub struct BusesAir { + context: AirContext, + inputs: [Felt; 2], +} + +impl BusesAir { + pub fn last_step(&self) -> usize { + self.trace_length() - self.context().num_transition_exemptions() + } +} + +impl Air for BusesAir { + type BaseField = Felt; + type PublicInputs = PublicInputs; + + fn context(&self) -> &AirContext { + &self.context + } + + fn new(trace_info: TraceInfo, public_inputs: PublicInputs, options: WinterProofOptions) -> Self { + let main_degrees = vec![]; + let aux_degrees = vec![TransitionConstraintDegree::new(2), TransitionConstraintDegree::new(1)]; + let num_main_assertions = 0; + let num_aux_assertions = 3; + + let context = AirContext::new_multi_segment( + trace_info, + main_degrees, + aux_degrees, + num_main_assertions, + num_aux_assertions, + options, + ) + .set_num_transition_exemptions(2); + Self { context, inputs: public_inputs.inputs } + } + + fn get_periodic_column_values(&self) -> Vec> { + vec![] + } + + fn get_assertions(&self) -> Vec> { + let mut result = Vec::new(); + result + } + + fn get_aux_assertions>(&self, aux_rand_elements: &AuxRandElements) -> Vec> { + let mut result = Vec::new(); + result.push(Assertion::single(0, self.last_step(), E::ONE)); + result.push(Assertion::single(1, 0, E::ZERO)); + result.push(Assertion::single(1, self.last_step(), E::ZERO)); + result + } + + fn evaluate_transition>(&self, frame: &EvaluationFrame, periodic_values: &[E], result: &mut [E]) { + let main_current = frame.current(); + let main_next = frame.next(); + } + + fn evaluate_aux_transition(&self, main_frame: &EvaluationFrame, aux_frame: &EvaluationFrame, _periodic_values: &[F], aux_rand_elements: &AuxRandElements, result: &mut [E]) + where F: FieldElement, + E: FieldElement + ExtensionOf, + { + let main_current = main_frame.current(); + let main_next = main_frame.next(); + let aux_current = aux_frame.current(); + let aux_next = aux_frame.next(); + result[0] = ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1]) * E::from(main_current[0]) + E::ONE - E::from(main_current[0])) * aux_current[0] - ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1]) * (E::ONE - E::from(main_current[0])) + E::from(main_current[0])) * aux_next[0]; + result[1] = (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * aux_current[1] + (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[0]) - ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * aux_next[1] + (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * E::from(Felt::new(2_u64))); + } +} \ No newline at end of file diff --git a/air-script/src/tests/buses/buses_simple_with_evaluators_plonky3.rs b/air-script/src/tests/buses/buses_simple_with_evaluators_plonky3.rs new file mode 100644 index 000000000..7ad2d398f --- /dev/null +++ b/air-script/src/tests/buses/buses_simple_with_evaluators_plonky3.rs @@ -0,0 +1,108 @@ +use crate::test_utils::plonky3_traits::{AirScriptAir, AirScriptBuilder}; +use p3_air::{Air, AirBuilder, BaseAir, BaseAirWithPublicValues, ExtensionBuilder}; +use p3_field::{Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; + +pub const MAIN_WIDTH: usize = 1; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 2; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct BusesAir; + +impl BaseAir for BusesAir { + fn width(&self) -> usize { + MAIN_WIDTH + } +} + +impl BaseAirWithPublicValues for BusesAir { + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } +} + +impl> AirScriptAir for BusesAir { + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn max_beta_challenge_powers(&self) -> usize { + MAX_BETA_CHALLENGE_POWER + } + + fn periodic_table(&self) -> Vec> { + vec![] + } + + fn eval(&self, builder: &mut AB) { + let public_values: [_; NUM_PUBLIC_VALUES] = + builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = + builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + let main = builder.main(); + let (main_current, main_next) = (main.row_slice(0).unwrap(), main.row_slice(1).unwrap()); + let alpha = builder.alpha(); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = + builder.beta_powers().try_into().expect("Wrong number of beta challenges"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder + .aux_bus_boundary_values() + .try_into() + .expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = (aux.row_slice(0).unwrap(), aux.row_slice(1).unwrap()); + + // Main boundary constraints + + // Main integrity/transition constraints + + // Aux boundary constraints + builder + .when_last_row() + .assert_zero_ext(AB::ExprEF::from(aux_current[0].clone().into()) - AB::ExprEF::ONE); + builder + .when_first_row() + .assert_zero_ext(AB::ExprEF::from(aux_current[1].clone().into())); + builder + .when_last_row() + .assert_zero_ext(AB::ExprEF::from(aux_current[1].clone().into())); + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext( + ((alpha.into() + beta_challenges[0].into()) + * AB::ExprEF::from(main_current[0].clone().into()) + + AB::ExprEF::ONE + - AB::ExprEF::from(main_current[0].clone().into())) + * AB::ExprEF::from(aux_current[0].clone().into()) + - ((alpha.into() + beta_challenges[0].into()) + * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) + + AB::ExprEF::from(main_current[0].clone().into())) + * AB::ExprEF::from(aux_next[0].clone().into()), + ); + builder.when_transition().assert_zero_ext( + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) + * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) + * AB::ExprEF::from(aux_current[1].clone().into()) + + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) + * AB::ExprEF::from(main_current[0].clone().into()) + - ((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) + * (alpha.into() + + beta_challenges[0].into() + + beta_challenges[1].into().double()) + * AB::ExprEF::from(aux_next[1].clone().into()) + + (alpha.into() + + beta_challenges[0].into() + + beta_challenges[1].into().double()) + .double()), + ); + } +} + +impl Air for BusesAir { + fn eval(&self, builder: &mut AB) { + >::eval(self, builder); + } +} + diff --git a/air-script/tests/buses/buses_varlen_boundary_both.air b/air-script/src/tests/buses/buses_varlen_boundary_both.air similarity index 100% rename from air-script/tests/buses/buses_varlen_boundary_both.air rename to air-script/src/tests/buses/buses_varlen_boundary_both.air diff --git a/air-script/tests/buses/buses_varlen_boundary_both.rs b/air-script/src/tests/buses/buses_varlen_boundary_both.rs similarity index 100% rename from air-script/tests/buses/buses_varlen_boundary_both.rs rename to air-script/src/tests/buses/buses_varlen_boundary_both.rs diff --git a/air-script/src/tests/buses/buses_varlen_boundary_both_plonky3.rs b/air-script/src/tests/buses/buses_varlen_boundary_both_plonky3.rs new file mode 100644 index 000000000..b32d738e1 --- /dev/null +++ b/air-script/src/tests/buses/buses_varlen_boundary_both_plonky3.rs @@ -0,0 +1,138 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 1; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 0; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct BusesAir; + +impl MidenAir for BusesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + BusType::Logup, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + + // Main integrity/transition constraints + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + beta_challenges[0].into()) * AB::ExprEF::from(main_current[0].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into()) * (AB::ExprEF::from(main_current[0].clone().into()) - AB::ExprEF::ONE) + AB::ExprEF::ONE - (AB::ExprEF::from(main_current[0].clone().into()) - AB::ExprEF::ONE)) * AB::ExprEF::from(aux_next[0].clone().into())); + builder.when_transition().assert_zero_ext((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_current[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_next[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()).double())); + } +} + +impl BusesAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ZERO, + EF::ZERO, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + beta_challenges[0]) * EF::from(main_current[0].clone()) + EF::ONE - EF::from(main_current[0].clone())) * EF::from(aux_current[0].clone())) * ((alpha + beta_challenges[0]) * (EF::from(main_current[0].clone()) - EF::ONE) + EF::ONE - (EF::from(main_current[0].clone()) - EF::ONE)).inverse(), + ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(aux_current[1].clone()) + (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) + (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) - (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()).double()) * ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double())).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/tests/buses/buses_varlen_boundary_first.air b/air-script/src/tests/buses/buses_varlen_boundary_first.air similarity index 100% rename from air-script/tests/buses/buses_varlen_boundary_first.air rename to air-script/src/tests/buses/buses_varlen_boundary_first.air diff --git a/air-script/tests/buses/buses_varlen_boundary_first.rs b/air-script/src/tests/buses/buses_varlen_boundary_first.rs similarity index 100% rename from air-script/tests/buses/buses_varlen_boundary_first.rs rename to air-script/src/tests/buses/buses_varlen_boundary_first.rs diff --git a/air-script/src/tests/buses/buses_varlen_boundary_first_plonky3.rs b/air-script/src/tests/buses/buses_varlen_boundary_first_plonky3.rs new file mode 100644 index 000000000..b32d738e1 --- /dev/null +++ b/air-script/src/tests/buses/buses_varlen_boundary_first_plonky3.rs @@ -0,0 +1,138 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 1; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 0; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct BusesAir; + +impl MidenAir for BusesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + BusType::Logup, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + + // Main integrity/transition constraints + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + beta_challenges[0].into()) * AB::ExprEF::from(main_current[0].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into()) * (AB::ExprEF::from(main_current[0].clone().into()) - AB::ExprEF::ONE) + AB::ExprEF::ONE - (AB::ExprEF::from(main_current[0].clone().into()) - AB::ExprEF::ONE)) * AB::ExprEF::from(aux_next[0].clone().into())); + builder.when_transition().assert_zero_ext((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_current[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) - ((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(aux_next[1].clone().into()) + (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()).double())); + } +} + +impl BusesAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ZERO, + EF::ZERO, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + beta_challenges[0]) * EF::from(main_current[0].clone()) + EF::ONE - EF::from(main_current[0].clone())) * EF::from(aux_current[0].clone())) * ((alpha + beta_challenges[0]) * (EF::from(main_current[0].clone()) - EF::ONE) + EF::ONE - (EF::from(main_current[0].clone()) - EF::ONE)).inverse(), + ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(aux_current[1].clone()) + (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) + (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) - (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()).double()) * ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double()) * (alpha + beta_challenges[0] + beta_challenges[1].double())).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/src/tests/buses/buses_varlen_boundary_last.air b/air-script/src/tests/buses/buses_varlen_boundary_last.air new file mode 100644 index 000000000..39e48144a --- /dev/null +++ b/air-script/src/tests/buses/buses_varlen_boundary_last.air @@ -0,0 +1,30 @@ +def BusesAir + +trace_columns { + main: [a, sp_insert, sp_remove, sq_insert_twice, sq_remove], +} + +buses { + multiset p, + logup q, +} + +public_inputs { + outputs_p: [[2]], + outputs_q: [[2]], +} + +boundary_constraints { + enf p.first = null; + enf q.first = null; + enf p.last = outputs_p; + enf q.last = outputs_q; +} + +integrity_constraints { + p.insert(a) when sp_insert; + p.remove(a) when sp_remove; + q.insert(2, a) when sq_insert_twice; + q.insert(2, a) when sq_insert_twice; + q.remove(2, a) with sq_remove; +} diff --git a/air-script/tests/buses/buses_varlen_boundary_last.rs b/air-script/src/tests/buses/buses_varlen_boundary_last.rs similarity index 52% rename from air-script/tests/buses/buses_varlen_boundary_last.rs rename to air-script/src/tests/buses/buses_varlen_boundary_last.rs index 686604e08..65dbbda92 100644 --- a/air-script/tests/buses/buses_varlen_boundary_last.rs +++ b/air-script/src/tests/buses/buses_varlen_boundary_last.rs @@ -4,32 +4,36 @@ use winter_math::{ExtensionOf, FieldElement, ToElements}; use winter_utils::{ByteWriter, Serializable}; pub struct PublicInputs { - outputs: Vec<[Felt; 2]>, + outputs_p: Vec<[Felt; 2]>, + outputs_q: Vec<[Felt; 2]>, } impl PublicInputs { - pub fn new(outputs: Vec<[Felt; 2]>) -> Self { - Self { outputs } + pub fn new(outputs_p: Vec<[Felt; 2]>, outputs_q: Vec<[Felt; 2]>) -> Self { + Self { outputs_p, outputs_q } } } impl Serializable for PublicInputs { fn write_into(&self, target: &mut W) { - self.outputs.write_into(target); + self.outputs_p.write_into(target); + self.outputs_q.write_into(target); } } impl ToElements for PublicInputs { fn to_elements(&self) -> Vec { let mut elements = Vec::new(); - self.outputs.iter().for_each(|row| elements.extend_from_slice(row)); + self.outputs_p.iter().for_each(|row| elements.extend_from_slice(row)); + self.outputs_q.iter().for_each(|row| elements.extend_from_slice(row)); elements } } pub struct BusesAir { context: AirContext, - outputs: Vec<[Felt; 2]>, + outputs_p: Vec<[Felt; 2]>, + outputs_q: Vec<[Felt; 2]>, } impl BusesAir { @@ -75,7 +79,7 @@ impl Air for BusesAir { fn new(trace_info: TraceInfo, public_inputs: PublicInputs, options: WinterProofOptions) -> Self { let main_degrees = vec![]; - let aux_degrees = vec![TransitionConstraintDegree::new(2), TransitionConstraintDegree::new(1)]; + let aux_degrees = vec![TransitionConstraintDegree::new(3), TransitionConstraintDegree::new(4)]; let num_main_assertions = 0; let num_aux_assertions = 4; @@ -88,7 +92,7 @@ impl Air for BusesAir { options, ) .set_num_transition_exemptions(2); - Self { context, outputs: public_inputs.outputs } + Self { context, outputs_p: public_inputs.outputs_p, outputs_q: public_inputs.outputs_q } } fn get_periodic_column_values(&self) -> Vec> { @@ -102,12 +106,12 @@ impl Air for BusesAir { fn get_aux_assertions>(&self, aux_rand_elements: &AuxRandElements) -> Vec> { let mut result = Vec::new(); - let reduced_outputs_multiset = Self::bus_multiset_boundary_varlen(aux_rand_elements, &self.outputs); - let reduced_outputs_logup = Self::bus_logup_boundary_varlen(aux_rand_elements, &self.outputs); + let reduced_outputs_p_multiset = Self::bus_multiset_boundary_varlen(aux_rand_elements, &self.outputs_p); + let reduced_outputs_q_logup = Self::bus_logup_boundary_varlen(aux_rand_elements, &self.outputs_q); result.push(Assertion::single(0, 0, E::ONE)); - result.push(Assertion::single(0, self.last_step(), reduced_outputs_multiset)); + result.push(Assertion::single(0, self.last_step(), reduced_outputs_p_multiset)); result.push(Assertion::single(1, 0, E::ZERO)); - result.push(Assertion::single(1, self.last_step(), reduced_outputs_logup)); + result.push(Assertion::single(1, self.last_step(), reduced_outputs_q_logup)); result } @@ -124,7 +128,7 @@ impl Air for BusesAir { let main_next = main_frame.next(); let aux_current = aux_frame.current(); let aux_next = aux_frame.next(); - result[0] = ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1]) * E::from(main_current[0]) + E::ONE - E::from(main_current[0])) * aux_current[0] - ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1]) * (E::from(main_current[0]) - E::ONE) + E::ONE - (E::from(main_current[0]) - E::ONE)) * aux_next[0]; - result[1] = (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * aux_current[1] + (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[0]) + (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[0]) - ((aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * aux_next[1] + (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + aux_rand_elements.rand_elements()[1] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[2]) * E::from(Felt::new(2_u64))); + result[0] = ((aux_rand_elements.rand_elements()[0] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[1]) * E::from(main_current[1]) + E::ONE - E::from(main_current[1])) * aux_current[0] - ((aux_rand_elements.rand_elements()[0] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[1]) * E::from(main_current[2]) + E::ONE - E::from(main_current[2])) * aux_next[0]; + result[1] = (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * aux_current[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[3]) + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[3]) - ((aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * aux_next[1] + (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * (aux_rand_elements.rand_elements()[0] + E::from(Felt::new(2_u64)) * aux_rand_elements.rand_elements()[1] + E::from(main_current[0]) * aux_rand_elements.rand_elements()[2]) * E::from(main_current[4])); } } \ No newline at end of file diff --git a/air-script/src/tests/buses/buses_varlen_boundary_last_plonky3.rs b/air-script/src/tests/buses/buses_varlen_boundary_last_plonky3.rs new file mode 100644 index 000000000..dba927c11 --- /dev/null +++ b/air-script/src/tests/buses/buses_varlen_boundary_last_plonky3.rs @@ -0,0 +1,138 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 5; +pub const AUX_WIDTH: usize = 2; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 0; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct BusesAir; + +impl MidenAir for BusesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + BusType::Logup, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + + // Main integrity/transition constraints + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[0].into()) * AB::ExprEF::from(main_current[1].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[1].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[0].into()) * AB::ExprEF::from(main_current[2].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[2].clone().into())) * AB::ExprEF::from(aux_next[0].clone().into())); + builder.when_transition().assert_zero_ext((alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(aux_current[1].clone().into()) + (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[3].clone().into()) + (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[3].clone().into()) - ((alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(aux_next[1].clone().into()) + (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * (alpha.into() + beta_challenges[0].into().double() + AB::ExprEF::from(main_current[0].clone().into()) * beta_challenges[1].into()) * AB::ExprEF::from(main_current[4].clone().into()))); + } +} + +impl BusesAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ONE, + EF::ZERO, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + EF::from(main_current[0].clone()) * beta_challenges[0]) * EF::from(main_current[1].clone()) + EF::ONE - EF::from(main_current[1].clone())) * EF::from(aux_current[0].clone())) * ((alpha + EF::from(main_current[0].clone()) * beta_challenges[0]) * EF::from(main_current[2].clone()) + EF::ONE - EF::from(main_current[2].clone())).inverse(), + ((alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * EF::from(aux_current[1].clone()) + (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * EF::from(main_current[3].clone()) + (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * EF::from(main_current[3].clone()) - (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * EF::from(main_current[4].clone())) * ((alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1]) * (alpha + beta_challenges[0].double() + EF::from(main_current[0].clone()) * beta_challenges[1])).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/src/tests/buses/mod.rs b/air-script/src/tests/buses/mod.rs new file mode 100644 index 000000000..5c0ca958c --- /dev/null +++ b/air-script/src/tests/buses/mod.rs @@ -0,0 +1,40 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod buses_complex; +#[rustfmt::skip] +#[allow(clippy::all)] +mod buses_simple; +#[rustfmt::skip] +#[allow(clippy::all)] +mod buses_varlen_boundary_both; +#[rustfmt::skip] +#[allow(clippy::all)] +mod buses_varlen_boundary_first; +#[rustfmt::skip] +#[allow(clippy::all)] +mod buses_varlen_boundary_last; + +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod buses_complex_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod buses_simple_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod buses_varlen_boundary_both_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod buses_varlen_boundary_first_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod buses_varlen_boundary_last_plonky3; + +mod test_air_plonky3; +mod test_air_plonky3_varlen_boundary_last; +mod test_air_winterfell; diff --git a/air-script/src/tests/buses/test_air_plonky3.rs b/air-script/src/tests/buses/test_air_plonky3.rs new file mode 100644 index 000000000..676d87701 --- /dev/null +++ b/air-script/src/tests/buses/test_air_plonky3.rs @@ -0,0 +1,62 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::buses::buses_complex_plonky3::{BusesAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let a_prev = rows[i - 1][0]; + let b_prev = rows[i - 1][1]; + let c_prev = rows[i - 1][2]; + let d_prev = rows[i - 1][3]; + let e_prev = rows[i - 1][4]; + let f_prev = rows[i - 1][5]; + let g_prev = rows[i - 1][6]; + + // Update current row based on previous values + rows[i][0] = F::ZERO; + rows[i][1] = F::ZERO; + rows[i][2] = if i > 3 && i < 8 { F::ONE } else { F::ZERO }; // s1 is true 4 times + rows[i][3] = if i > 5 && i < 10 { F::ONE } else { F::ZERO }; // s2 is true 4 times + rows[i][4] = if i > 4 && i < 10 { F::ONE } else { F::ZERO }; // s3 is true 5 times + rows[i][5] = if i > 5 && i < 13 { F::ONE } else { F::ZERO }; // s4 is true 7 times + rows[i][6] = if i > 15 && i < 20 { F::from_u64(3) } else { F::ZERO }; // d is set to 3 four times + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 2] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![vec![], vec![], vec![]] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, BusesAir); diff --git a/air-script/src/tests/buses/test_air_plonky3_varlen_boundary_last.rs b/air-script/src/tests/buses/test_air_plonky3_varlen_boundary_last.rs new file mode 100644 index 000000000..6415f669e --- /dev/null +++ b/air-script/src/tests/buses/test_air_plonky3_varlen_boundary_last.rs @@ -0,0 +1,64 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::buses::buses_varlen_boundary_last_plonky3::{BusesAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ONE; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let a_prev = rows[i - 1][0]; + let b_prev = rows[i - 1][1]; + let c_prev = rows[i - 1][2]; + let d_prev = rows[i - 1][3]; + let e_prev = rows[i - 1][4]; + + // Update current row based on previous values + rows[i][0] = F::ONE; + rows[i][1] = if i > 3 && i < 8 { F::ONE } else { F::ZERO }; // sp_insert is true 4 times + rows[i][2] = if i > 3 && i < 7 { F::ONE } else { F::ZERO }; // sp_remove is true 3 times + rows[i][3] = if i > 4 && i < 10 { F::ONE } else { F::ZERO }; // sq_insert_twice is true 5 times + rows[i][4] = if i > 5 && i < 10 { + F::from_canonical_checked(2).unwrap() + } else { + F::ZERO + }; // sq_remove has value "2" 4 times + } + + trace +} + +fn generate_inputs() -> Vec { + vec![] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + // At the end, the bus p will have the tuple (a) (that equals (1)) inserted once + let var_len_p = vec![vec![1]]; + // At the end, the bus q will have the tuple (2, a) (that equals (2, 1)) inserted twice + let var_len_q = vec![vec![2, 1], vec![2, 1]]; + vec![var_len_p, var_len_q] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, BusesAir); diff --git a/air-script/tests/buses/test_air.rs b/air-script/src/tests/buses/test_air_winterfell.rs similarity index 78% rename from air-script/tests/buses/test_air.rs rename to air-script/src/tests/buses/test_air_winterfell.rs index 4c27304a2..150617152 100644 --- a/air-script/tests/buses/test_air.rs +++ b/air-script/src/tests/buses/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{AuxTraceWithMetadata, Trace, TraceTable, matrix::ColMatrix}; use crate::{ - buses::buses_complex::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::buses::buses_complex::PublicInputs, }; #[derive(Clone)] @@ -15,7 +15,7 @@ impl AirTester for BusesAirTester { type PubInputs = PublicInputs; fn build_main_trace(&self, length: usize) -> MyTraceTable { - let trace_width = 5; + let trace_width = 7; let start = Felt::new(0); let mut trace = TraceTable::new(trace_width, length); @@ -26,6 +26,8 @@ impl AirTester for BusesAirTester { state[2] = start; state[3] = start; state[4] = start; + state[5] = start; + state[6] = start; }, |_, state| { state[0] = Felt::new(1) - state[0]; @@ -33,6 +35,8 @@ impl AirTester for BusesAirTester { state[2] = Felt::new(1) - state[2]; state[3] = Felt::new(1) - state[3]; state[4] = Felt::new(1) - state[4]; + state[5] = Felt::new(1) - state[4]; + state[6] = Felt::new(1) - state[4]; }, ); @@ -57,4 +61,9 @@ impl AirTester for BusesAirTester { } } -generate_air_test!(test_buses_air, crate::buses::buses_complex::BusesAir, BusesAirTester, 1024); +generate_air_winterfell_test!( + test_buses_air, + crate::tests::buses::buses_complex::BusesAir, + BusesAirTester, + 1024 +); diff --git a/air-script/src/tests/comparison/binary.rs b/air-script/src/tests/comparison/binary.rs new file mode 100644 index 000000000..e3d495fd1 --- /dev/null +++ b/air-script/src/tests/comparison/binary.rs @@ -0,0 +1,166 @@ +//! Cross-backend comparison test for the Binary AIR. +//! +//! This test verifies that Winterfell and Plonky3 produce equivalent +//! constraint evaluations for the Binary AIR at every row of the trace. + +use p3_field::PrimeCharacteristicRing; +use p3_goldilocks::Goldilocks; +use winter_air::{Air, ProofOptions as WinterProofOptions, TraceInfo}; +use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; + +use crate::{ + test_utils::cross_backend_comparison::{ + CrossBackendTestConfig, run_cross_backend_comparison, run_cross_backend_comparison_random, + }, + tests::binary::{ + binary::{BinaryAir as WinterfellBinaryAir, PublicInputs}, + binary_plonky3::BinaryAir as Plonky3BinaryAir, + }, +}; + +/// Configuration for Binary AIR cross-backend comparison tests. +struct BinaryTestConfig { + /// The starting value for the binary trace (0 or 1). + start_value: u64, + /// The trace length. + trace_length: usize, +} + +impl BinaryTestConfig { + fn new(start_value: u64, trace_length: usize) -> Self { + Self { start_value, trace_length } + } + + /// Build a trace for the Binary AIR. + /// + /// The Binary AIR has 2 columns (a, b) that alternate between 0 and 1: + /// - Row 0: (start, start) + /// - Row 1: (1-start, 1-start) + /// - Row 2: (start, start) + /// - ... + fn build_trace(&self) -> Vec> { + let length = self.trace_length; + let mut col_a = vec![Felt::ZERO; length]; + let mut col_b = vec![Felt::ZERO; length]; + + let start = Felt::new(self.start_value); + let one = Felt::ONE; + + col_a[0] = start; + col_b[0] = start; + + for i in 1..length { + col_a[i] = one - col_a[i - 1]; + col_b[i] = one - col_b[i - 1]; + } + + vec![col_a, col_b] + } +} + +impl CrossBackendTestConfig for BinaryTestConfig { + type WinterfellAir = WinterfellBinaryAir; + type Plonky3Air = Plonky3BinaryAir; + type WinterfellPublicInputs = PublicInputs; + + fn trace_width(&self) -> usize { + 2 + } + + fn trace_length(&self) -> usize { + self.trace_length + } + + fn build_winterfell_trace(&self) -> Vec> { + self.build_trace() + } + + fn build_winterfell_public_inputs(&self) -> PublicInputs { + let mut inputs = [Felt::ZERO; 16]; + inputs[0] = Felt::new(self.start_value); + PublicInputs::new(inputs) + } + + fn build_plonky3_public_inputs(&self) -> Vec { + (0..16) + .map(|i| { + if i == 0 { + Goldilocks::from_u64(self.start_value) + } else { + Goldilocks::ZERO + } + }) + .collect() + } + + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: PublicInputs, + options: WinterProofOptions, + ) -> WinterfellBinaryAir { + WinterfellBinaryAir::new(trace_info, pub_inputs, options) + } + + fn create_plonky3_air(&self) -> Plonky3BinaryAir { + Plonky3BinaryAir + } + + fn num_public_values(&self) -> usize { + 16 + } +} + +#[test] +fn test_binary_air_constraint_comparison() { + let config = BinaryTestConfig::new(0, 64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Binary AIR comparison passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_binary_air_constraint_comparison_start_one() { + let config = BinaryTestConfig::new(1, 64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Binary AIR comparison (start=1) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_binary_air_constraint_comparison_random_inputs() { + let config = BinaryTestConfig::new(0, 64); + + // Test with iterations 0 through 50 for thorough coverage + for iteration in 0u64..=50 { + let result = run_cross_backend_comparison_random( + &config, + "test_binary_air_constraint_comparison_random_inputs", + iteration, + ); + + if !result.is_ok() { + panic!( + "Random constraint evaluation comparison failed (iteration={})!\n\n{}", + iteration, + result.format_report() + ); + } + } + + println!("Binary AIR random comparison passed for all 51 iterations (0-50)"); +} diff --git a/air-script/src/tests/comparison/bitwise.rs b/air-script/src/tests/comparison/bitwise.rs new file mode 100644 index 000000000..06231c640 --- /dev/null +++ b/air-script/src/tests/comparison/bitwise.rs @@ -0,0 +1,175 @@ +//! Cross-backend comparison test for the Bitwise AIR. +//! +//! This test verifies that Winterfell and Plonky3 produce equivalent +//! constraint evaluations for the Bitwise AIR at every row of the trace. +//! +//! The Bitwise AIR is important because it uses **periodic columns**: +//! - k0: `[1, 0, 0, 0, 0, 0, 0, 0]` (period 8) - active on rows 0, 8, 16, ... +//! - k1: `[1, 1, 1, 1, 1, 1, 1, 0]` (period 8) - inactive only on rows 7, 15, 23, ... +//! +//! This test validates that the periodic column evaluation works correctly +//! in the cross-backend comparison framework. + +use p3_field::PrimeCharacteristicRing; +use p3_goldilocks::Goldilocks; +use winter_air::{Air, ProofOptions as WinterProofOptions, TraceInfo}; +use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; + +use crate::{ + test_utils::cross_backend_comparison::{ + CrossBackendTestConfig, run_cross_backend_comparison, run_cross_backend_comparison_random, + }, + tests::bitwise::{ + bitwise::{BitwiseAir as WinterfellBitwiseAir, PublicInputs}, + bitwise_plonky3::BitwiseAir as Plonky3BitwiseAir, + }, +}; + +/// Configuration for Bitwise AIR cross-backend comparison tests. +struct BitwiseTestConfig { + /// The trace length (must be divisible by the period 8). + trace_length: usize, +} + +impl BitwiseTestConfig { + fn new(trace_length: usize) -> Self { + // Trace length should be divisible by 8 (the periodic column period) + assert!(trace_length % 8 == 0, "Trace length must be divisible by 8 for bitwise AIR"); + Self { trace_length } + } + + /// Build a trace for the Bitwise AIR. + /// + /// The Bitwise AIR has 14 columns. For testing, we use an all-zeros trace + /// which satisfies all the constraints (binary checks, decomposition, etc.). + /// + /// The constraints include: + /// - Binary checks: col[i]^2 - col[i] = 0 for binary columns + /// - Decomposition constraints using periodic column k0 + /// - Transition constraints using periodic column k1 + fn build_trace(&self) -> Vec> { + let length = self.trace_length; + let trace_width = 14; + + // Initialize all columns to zero + // This satisfies all binary constraints (0^2 - 0 = 0) + // and most other constraints (they become 0 * something = 0) + let trace: Vec> = vec![vec![Felt::ZERO; length]; trace_width]; + + // Column 13 must be 0 at first row (boundary constraint) + // Already zero, so nothing to do + + trace + } +} + +impl CrossBackendTestConfig for BitwiseTestConfig { + type WinterfellAir = WinterfellBitwiseAir; + type Plonky3Air = Plonky3BitwiseAir; + type WinterfellPublicInputs = PublicInputs; + + fn trace_width(&self) -> usize { + 14 + } + + fn trace_length(&self) -> usize { + self.trace_length + } + + fn build_winterfell_trace(&self) -> Vec> { + self.build_trace() + } + + fn build_winterfell_public_inputs(&self) -> PublicInputs { + PublicInputs::new([Felt::ZERO; 16]) + } + + fn build_plonky3_public_inputs(&self) -> Vec { + vec![Goldilocks::ZERO; 16] + } + + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: PublicInputs, + options: WinterProofOptions, + ) -> WinterfellBitwiseAir { + WinterfellBitwiseAir::new(trace_info, pub_inputs, options) + } + + fn create_plonky3_air(&self) -> Plonky3BitwiseAir { + Plonky3BitwiseAir + } + + fn num_public_values(&self) -> usize { + 16 + } + + /// Returns the periodic column values for the Bitwise AIR. + /// + /// Two periodic columns with period 8: + /// - k0: `[1, 0, 0, 0, 0, 0, 0, 0]` - marks the first row of each 8-row block + /// - k1: `[1, 1, 1, 1, 1, 1, 1, 0]` - active on all rows except the last of each block + fn periodic_column_values(&self) -> Vec> { + vec![ + vec![1, 0, 0, 0, 0, 0, 0, 0], // k0 + vec![1, 1, 1, 1, 1, 1, 1, 0], // k1 + ] + } +} + +#[test] +fn test_bitwise_air_constraint_comparison() { + // Use 64 rows (8 complete periods) + let config = BitwiseTestConfig::new(64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Bitwise AIR comparison passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_bitwise_air_constraint_comparison_larger_trace() { + // Use 512 rows (64 complete periods) - same as the Winterfell test + let config = BitwiseTestConfig::new(512); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Bitwise AIR comparison (512 rows) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_bitwise_air_constraint_comparison_random_inputs() { + let config = BitwiseTestConfig::new(64); + + // Test with iterations 0 through 50 for thorough coverage + for iteration in 0u64..=50 { + let result = run_cross_backend_comparison_random( + &config, + "test_bitwise_air_constraint_comparison_random_inputs", + iteration, + ); + + if !result.is_ok() { + panic!( + "Random constraint evaluation comparison failed (iteration={})!\n\n{}", + iteration, + result.format_report() + ); + } + } + + println!("Bitwise AIR random comparison passed for all 51 iterations (0-50)"); +} diff --git a/air-script/src/tests/comparison/constants.rs b/air-script/src/tests/comparison/constants.rs new file mode 100644 index 000000000..2d15c55ab --- /dev/null +++ b/air-script/src/tests/comparison/constants.rs @@ -0,0 +1,179 @@ +//! Cross-backend comparison test for the Constants AIR. +//! +//! This test verifies that Winterfell and Plonky3 produce equivalent +//! constraint evaluations for the Constants AIR at every row of the trace. +//! +//! The Constants AIR tests: +//! - `when_first_row()` boundary constraints (5 constraints) +//! - `when_last_row()` boundary constraints (1 constraint) +//! - `when_transition()` transition constraints (4 constraints) +//! - Global integrity constraints (1 constraint without `when_transition()`) + +use p3_field::PrimeCharacteristicRing; +use p3_goldilocks::Goldilocks; +use winter_air::{Air, ProofOptions as WinterProofOptions, TraceInfo}; +use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; + +use crate::{ + test_utils::cross_backend_comparison::{ + CrossBackendTestConfig, run_cross_backend_comparison, run_cross_backend_comparison_random, + }, + tests::constants::{ + constants::{ConstantsAir as WinterfellConstantsAir, PublicInputs}, + constants_plonky3::ConstantsAir as Plonky3ConstantsAir, + }, +}; + +/// Configuration for Constants AIR cross-backend comparison tests. +struct ConstantsTestConfig { + /// The trace length. + trace_length: usize, +} + +impl ConstantsTestConfig { + fn new(trace_length: usize) -> Self { + Self { trace_length } + } + + /// Build a trace for the Constants AIR. + /// + /// The trace has 7 columns with the following constraints: + /// - Boundary (first row): col[0]=1, col[1]=1, col[2]=0, col[3]=1, col[4]=1 + /// - Boundary (last row): col[6]=0 + /// - Transition: col[0]' = col[0] + 1, col[1]' = 0, col[2]' = col[2], col[5]' = col[5] + 1 + /// - Integrity (global): col[4] = 1 + fn build_trace(&self) -> Vec> { + let length = self.trace_length; + + // Initialize columns + let mut col0 = vec![Felt::ZERO; length]; // Increments by 1 each row + let mut col1 = vec![Felt::ZERO; length]; // 1 at first row, 0 elsewhere + let mut col2 = vec![Felt::ZERO; length]; // Always 0 + let mut col3 = vec![Felt::ZERO; length]; // 1 at first row (no transition constraint) + let mut col4 = vec![Felt::ZERO; length]; // Always 1 (global integrity) + let mut col5 = vec![Felt::ZERO; length]; // Increments by 1 each row + let mut col6 = vec![Felt::ZERO; length]; // Always 0 (last row boundary) + + // First row values + col0[0] = Felt::ONE; + col1[0] = Felt::ONE; + col2[0] = Felt::ZERO; + col3[0] = Felt::ONE; + col4[0] = Felt::ONE; + col5[0] = Felt::ZERO; + col6[0] = Felt::ZERO; + + // Fill subsequent rows based on transition constraints + for i in 1..length { + col0[i] = col0[i - 1] + Felt::ONE; // col[0]' = col[0] + 1 + col1[i] = Felt::ZERO; // col[1]' = 0 + col2[i] = col2[i - 1]; // col[2]' = col[2] + col3[i] = col3[i - 1]; // No constraint, keep same + col4[i] = Felt::ONE; // Global: col[4] = 1 + col5[i] = col5[i - 1] + Felt::ONE; // col[5]' = col[5] + 1 + col6[i] = Felt::ZERO; // Last row should be 0 + } + + vec![col0, col1, col2, col3, col4, col5, col6] + } +} + +impl CrossBackendTestConfig for ConstantsTestConfig { + type WinterfellAir = WinterfellConstantsAir; + type Plonky3Air = Plonky3ConstantsAir; + type WinterfellPublicInputs = PublicInputs; + + fn trace_width(&self) -> usize { + 7 + } + + fn trace_length(&self) -> usize { + self.trace_length + } + + fn build_winterfell_trace(&self) -> Vec> { + self.build_trace() + } + + fn build_winterfell_public_inputs(&self) -> PublicInputs { + // The Constants AIR has 32 public inputs but doesn't use them in constraints + // (it uses literal constants like Felt::ONE instead) + PublicInputs::new([Felt::ZERO; 4], [Felt::ZERO; 4], [Felt::ZERO; 4], [Felt::ZERO; 20]) + } + + fn build_plonky3_public_inputs(&self) -> Vec { + vec![Goldilocks::ZERO; 32] + } + + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: PublicInputs, + options: WinterProofOptions, + ) -> WinterfellConstantsAir { + WinterfellConstantsAir::new(trace_info, pub_inputs, options) + } + + fn create_plonky3_air(&self) -> Plonky3ConstantsAir { + Plonky3ConstantsAir + } + + fn num_public_values(&self) -> usize { + 32 + } +} + +#[test] +fn test_constants_air_constraint_comparison() { + let config = ConstantsTestConfig::new(64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Constants AIR comparison passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_constants_air_constraint_comparison_longer_trace() { + // Test with a longer trace to exercise more rows + let config = ConstantsTestConfig::new(128); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Constants AIR comparison (128 rows) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_constants_air_constraint_comparison_random_inputs() { + let config = ConstantsTestConfig::new(64); + + // Test with iterations 0 through 50 for thorough coverage + for iteration in 0u64..=50 { + let result = run_cross_backend_comparison_random( + &config, + "test_constants_air_constraint_comparison_random_inputs", + iteration, + ); + + if !result.is_ok() { + panic!( + "Random constraint evaluation comparison failed (iteration={})!\n\n{}", + iteration, + result.format_report() + ); + } + } + + println!("Constants AIR random comparison passed for all 51 iterations (0-50)"); +} diff --git a/air-script/src/tests/comparison/constraint_comprehension.rs b/air-script/src/tests/comparison/constraint_comprehension.rs new file mode 100644 index 000000000..8c2f9b169 --- /dev/null +++ b/air-script/src/tests/comparison/constraint_comprehension.rs @@ -0,0 +1,191 @@ +//! Cross-backend comparison test for the ConstraintComprehension AIR. +//! +//! This test verifies that Winterfell and Plonky3 produce equivalent +//! constraint evaluations for the ConstraintComprehension AIR at every row of the trace. +//! +//! The ConstraintComprehension AIR tests list comprehension in constraints: +//! - Boundary: `c[2].first = 0` (column 8 at row 0) +//! - Integrity: `c = d for (c, d) in (c, d)` expands to c[i] = d[i] for i in 0..4 +//! +//! Trace columns: [clk, fmp[2], ctx, a, b, c[4], d[4]] (14 total) +//! Column indices: clk=0, fmp=1-2, ctx=3, a=4, b=5, c=6-9, d=10-13 + +use p3_field::PrimeCharacteristicRing; +use p3_goldilocks::Goldilocks; +use winter_air::{Air, ProofOptions as WinterProofOptions, TraceInfo}; +use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; + +use crate::{ + test_utils::cross_backend_comparison::{ + CrossBackendTestConfig, run_cross_backend_comparison, run_cross_backend_comparison_random, + }, + tests::constraint_comprehension::{ + constraint_comprehension::{ + ConstraintComprehensionAir as WinterfellConstraintComprehensionAir, PublicInputs, + }, + constraint_comprehension_plonky3::ConstraintComprehensionAir as Plonky3ConstraintComprehensionAir, + }, +}; + +/// Configuration for ConstraintComprehension AIR cross-backend comparison tests. +struct ConstraintComprehensionTestConfig { + /// The trace length. + trace_length: usize, +} + +impl ConstraintComprehensionTestConfig { + fn new(trace_length: usize) -> Self { + Self { trace_length } + } + + /// Build a trace for the ConstraintComprehension AIR. + /// + /// The trace has 14 columns: [clk, fmp[2], ctx, a, b, c[4], d[4]] + /// - Indices: clk=0, fmp=1-2, ctx=3, a=4, b=5, c=6-9, d=10-13 + /// + /// Constraints: + /// - Boundary: c[2].first = 0 → column 8 at row 0 must be 0 + /// - Integrity: c[i] = d[i] for all rows and i in 0..4 + /// + /// We build a meaningful trace where c and d have matching non-zero values. + fn build_trace(&self) -> Vec> { + let length = self.trace_length; + + // Initialize all 14 columns + let mut trace: Vec> = vec![vec![Felt::ZERO; length]; 14]; + + // Fill with meaningful values + for row in 0..length { + // clk increments + trace[0][row] = Felt::new(row as u64); + + // fmp[0], fmp[1] - some values + trace[1][row] = Felt::new(row as u64 * 2); + trace[2][row] = Felt::new(row as u64 * 3); + + // ctx + trace[3][row] = Felt::new(100); + + // a, b - some values + trace[4][row] = Felt::new(row as u64 + 10); + trace[5][row] = Felt::new(row as u64 + 20); + + // c[0..4] - columns 6-9 + // c[2] (column 8) must be 0 at first row due to boundary constraint + trace[6][row] = Felt::new(row as u64 + 1); // c[0] + trace[7][row] = Felt::new(row as u64 + 2); // c[1] + trace[8][row] = if row == 0 { + Felt::ZERO // c[2] must be 0 at first row + } else { + Felt::new(row as u64 + 3) + }; + trace[9][row] = Felt::new(row as u64 + 4); // c[3] + + // d[0..4] - columns 10-13 + // Must equal c[0..4] due to integrity constraint: c[i] = d[i] + trace[10][row] = trace[6][row]; // d[0] = c[0] + trace[11][row] = trace[7][row]; // d[1] = c[1] + trace[12][row] = trace[8][row]; // d[2] = c[2] + trace[13][row] = trace[9][row]; // d[3] = c[3] + } + + trace + } +} + +impl CrossBackendTestConfig for ConstraintComprehensionTestConfig { + type WinterfellAir = WinterfellConstraintComprehensionAir; + type Plonky3Air = Plonky3ConstraintComprehensionAir; + type WinterfellPublicInputs = PublicInputs; + + fn trace_width(&self) -> usize { + 14 + } + + fn trace_length(&self) -> usize { + self.trace_length + } + + fn build_winterfell_trace(&self) -> Vec> { + self.build_trace() + } + + fn build_winterfell_public_inputs(&self) -> PublicInputs { + PublicInputs::new([Felt::ZERO; 16]) + } + + fn build_plonky3_public_inputs(&self) -> Vec { + vec![Goldilocks::ZERO; 16] + } + + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: PublicInputs, + options: WinterProofOptions, + ) -> WinterfellConstraintComprehensionAir { + WinterfellConstraintComprehensionAir::new(trace_info, pub_inputs, options) + } + + fn create_plonky3_air(&self) -> Plonky3ConstraintComprehensionAir { + Plonky3ConstraintComprehensionAir + } + + fn num_public_values(&self) -> usize { + 16 + } +} + +#[test] +fn test_constraint_comprehension_air_constraint_comparison() { + let config = ConstraintComprehensionTestConfig::new(64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "ConstraintComprehension AIR comparison passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_constraint_comprehension_air_constraint_comparison_small_trace() { + let config = ConstraintComprehensionTestConfig::new(16); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "ConstraintComprehension AIR comparison (16 rows) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_constraint_comprehension_air_constraint_comparison_random_inputs() { + let config = ConstraintComprehensionTestConfig::new(64); + + // Test with iterations 0 through 50 for thorough coverage + for iteration in 0u64..=50 { + let result = run_cross_backend_comparison_random( + &config, + "test_constraint_comprehension_air_constraint_comparison_random_inputs", + iteration, + ); + + if !result.is_ok() { + panic!( + "Random constraint evaluation comparison failed (iteration={})!\n\n{}", + iteration, + result.format_report() + ); + } + } + + println!("ConstraintComprehension AIR random comparison passed for all 51 iterations (0-50)"); +} diff --git a/air-script/src/tests/comparison/fibonacci.rs b/air-script/src/tests/comparison/fibonacci.rs new file mode 100644 index 000000000..b14c981d0 --- /dev/null +++ b/air-script/src/tests/comparison/fibonacci.rs @@ -0,0 +1,204 @@ +//! Cross-backend comparison test for the Fibonacci AIR. +//! +//! This test verifies that Winterfell and Plonky3 produce equivalent +//! constraint evaluations for the Fibonacci AIR at every row of the trace. +//! +//! The Fibonacci AIR computes the Fibonacci sequence with constraints: +//! - Boundary: `a.first = stack_inputs[0]`, `b.first = stack_inputs[1]`, `b.last = stack_output[0]` +//! - Transition: `b' = a + b`, `a' = b` + +use p3_field::PrimeCharacteristicRing; +use p3_goldilocks::Goldilocks; +use winter_air::{Air, ProofOptions as WinterProofOptions, TraceInfo}; +use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; + +use crate::{ + test_utils::cross_backend_comparison::{ + CrossBackendTestConfig, run_cross_backend_comparison, run_cross_backend_comparison_random, + }, + tests::fibonacci::{ + fibonacci::{FibonacciAir as WinterfellFibonacciAir, PublicInputs}, + fibonacci_plonky3::FibonacciAir as Plonky3FibonacciAir, + }, +}; + +/// Configuration for Fibonacci AIR cross-backend comparison tests. +struct FibonacciTestConfig { + /// The first Fibonacci number (fib_0). + fib_0: u64, + /// The second Fibonacci number (fib_1). + fib_1: u64, + /// The trace length. + trace_length: usize, +} + +impl FibonacciTestConfig { + fn new(fib_0: u64, fib_1: u64, trace_length: usize) -> Self { + Self { fib_0, fib_1, trace_length } + } + + /// Build a trace for the Fibonacci AIR. + /// + /// The Fibonacci AIR has 2 columns (a, b) with the recurrence: + /// - `a' = b` (next row's a equals current row's b) + /// - `b' = a + b` (next row's b equals sum of current row's a and b) + /// + /// Starting with `a[0] = fib_0`, `b[0] = fib_1`: + /// ```text + /// Row | a | b + /// ----|----------|---------- + /// 0 | fib_0 | fib_1 + /// 1 | fib_1 | fib_0 + fib_1 + /// 2 | fib_2 | fib_3 + /// ... + /// ``` + fn build_trace(&self) -> Vec> { + let length = self.trace_length; + let mut col_a = vec![Felt::ZERO; length]; + let mut col_b = vec![Felt::ZERO; length]; + + col_a[0] = Felt::new(self.fib_0); + col_b[0] = Felt::new(self.fib_1); + + for i in 1..length { + // a' = b (next a is current b) + col_a[i] = col_b[i - 1]; + // b' = a + b (next b is sum of current a and b) + col_b[i] = col_a[i - 1] + col_b[i - 1]; + } + + vec![col_a, col_b] + } + + /// Compute the expected value of `b` at the last step. + /// + /// The last step is `trace_length - num_transition_exemptions` where + /// `num_transition_exemptions = 2` for the Fibonacci AIR. + fn expected_output(&self) -> Felt { + let trace = self.build_trace(); + let last_step = self.trace_length - 2; // num_transition_exemptions = 2 + trace[1][last_step] // column b at last_step + } +} + +impl CrossBackendTestConfig for FibonacciTestConfig { + type WinterfellAir = WinterfellFibonacciAir; + type Plonky3Air = Plonky3FibonacciAir; + type WinterfellPublicInputs = PublicInputs; + + fn trace_width(&self) -> usize { + 2 + } + + fn trace_length(&self) -> usize { + self.trace_length + } + + fn build_winterfell_trace(&self) -> Vec> { + self.build_trace() + } + + fn build_winterfell_public_inputs(&self) -> PublicInputs { + let stack_inputs = [Felt::new(self.fib_0), Felt::new(self.fib_1)]; + let stack_output = [self.expected_output()]; + PublicInputs::new(stack_inputs, stack_output) + } + + fn build_plonky3_public_inputs(&self) -> Vec { + // Plonky3 public inputs: [stack_inputs[0], stack_inputs[1], stack_output[0]] + vec![ + Goldilocks::from_u64(self.fib_0), + Goldilocks::from_u64(self.fib_1), + Goldilocks::from_u64(self.expected_output().as_int()), + ] + } + + fn create_winterfell_air( + &self, + trace_info: TraceInfo, + pub_inputs: PublicInputs, + options: WinterProofOptions, + ) -> WinterfellFibonacciAir { + WinterfellFibonacciAir::new(trace_info, pub_inputs, options) + } + + fn create_plonky3_air(&self) -> Plonky3FibonacciAir { + Plonky3FibonacciAir + } + + fn num_public_values(&self) -> usize { + 3 // stack_inputs[0], stack_inputs[1], stack_output[0] + } +} + +#[test] +fn test_fibonacci_air_constraint_comparison() { + // Standard Fibonacci starting with 0, 1 + let config = FibonacciTestConfig::new(0, 1, 64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Fibonacci AIR comparison passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_fibonacci_air_constraint_comparison_different_start() { + // Fibonacci-like sequence starting with 1, 1 + let config = FibonacciTestConfig::new(1, 1, 64); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Fibonacci AIR comparison (start=1,1) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_fibonacci_air_constraint_comparison_larger_values() { + // Test with larger starting values to exercise field arithmetic + let config = FibonacciTestConfig::new(100, 200, 32); + let result = run_cross_backend_comparison(&config); + + if !result.is_ok() { + panic!("Constraint evaluation comparison failed!\n\n{}", result.format_report()); + } + + println!( + "Fibonacci AIR comparison (start=100,200) passed: {} constraints checked across {} rows", + result.total_constraints_checked, result.total_rows + ); +} + +#[test] +fn test_fibonacci_air_constraint_comparison_random_inputs() { + let config = FibonacciTestConfig::new(0, 1, 64); + + // Test with iterations 0 through 50 for thorough coverage + for iteration in 0u64..=50 { + let result = run_cross_backend_comparison_random( + &config, + "test_fibonacci_air_constraint_comparison_random_inputs", + iteration, + ); + + if !result.is_ok() { + panic!( + "Random constraint evaluation comparison failed (iteration={})!\n\n{}", + iteration, + result.format_report() + ); + } + } + + println!("Fibonacci AIR random comparison passed for all 51 iterations (0-50)"); +} diff --git a/air-script/src/tests/comparison/mod.rs b/air-script/src/tests/comparison/mod.rs new file mode 100644 index 000000000..69bd73734 --- /dev/null +++ b/air-script/src/tests/comparison/mod.rs @@ -0,0 +1,10 @@ +//! Cross-backend comparison tests. +//! +//! This module contains tests that verify Winterfell and Plonky3 backends +//! produce equivalent constraint evaluations for the same AIR and trace data. + +mod binary; +mod bitwise; +mod constants; +mod constraint_comprehension; +mod fibonacci; diff --git a/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.air b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.air new file mode 100644 index 000000000..a21bbcbd5 --- /dev/null +++ b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.air @@ -0,0 +1,19 @@ +def ComprehensionPeriodicBindingTest + +use lib::test_comprehension; + +trace_columns { + main: [a, b], +} + +public_inputs { + stack_inputs: [1], +} + +boundary_constraints { + enf a.first = 0; +} + +integrity_constraints { + enf test_comprehension([a, b]); +} diff --git a/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.rs b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.rs new file mode 100644 index 000000000..9e56fa9e2 --- /dev/null +++ b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding.rs @@ -0,0 +1,97 @@ +use winter_air::{Air, AirContext, Assertion, AuxRandElements, EvaluationFrame, ProofOptions as WinterProofOptions, TransitionConstraintDegree, TraceInfo}; +use winter_math::fields::f64::BaseElement as Felt; +use winter_math::{ExtensionOf, FieldElement, ToElements}; +use winter_utils::{ByteWriter, Serializable}; + +pub struct PublicInputs { + stack_inputs: [Felt; 1], +} + +impl PublicInputs { + pub fn new(stack_inputs: [Felt; 1]) -> Self { + Self { stack_inputs } + } +} + +impl Serializable for PublicInputs { + fn write_into(&self, target: &mut W) { + self.stack_inputs.write_into(target); + } +} + +impl ToElements for PublicInputs { + fn to_elements(&self) -> Vec { + let mut elements = Vec::new(); + elements.extend_from_slice(&self.stack_inputs); + elements + } +} + +pub struct ComprehensionPeriodicBindingTest { + context: AirContext, + stack_inputs: [Felt; 1], +} + +impl ComprehensionPeriodicBindingTest { + pub fn last_step(&self) -> usize { + self.trace_length() - self.context().num_transition_exemptions() + } +} + +impl Air for ComprehensionPeriodicBindingTest { + type BaseField = Felt; + type PublicInputs = PublicInputs; + + fn context(&self) -> &AirContext { + &self.context + } + + fn new(trace_info: TraceInfo, public_inputs: PublicInputs, options: WinterProofOptions) -> Self { + let main_degrees = vec![TransitionConstraintDegree::with_cycles(1, vec![2, 2])]; + let aux_degrees = vec![]; + let num_main_assertions = 1; + let num_aux_assertions = 0; + + let context = AirContext::new_multi_segment( + trace_info, + main_degrees, + aux_degrees, + num_main_assertions, + num_aux_assertions, + options, + ) + .set_num_transition_exemptions(2); + Self { context, stack_inputs: public_inputs.stack_inputs } + } + + fn get_periodic_column_values(&self) -> Vec> { + vec![vec![Felt::ONE, Felt::new(2)], vec![Felt::new(3), Felt::new(4)]] + } + + fn get_assertions(&self) -> Vec> { + let mut result = Vec::new(); + result.push(Assertion::single(0, 0, Felt::ZERO)); + result + } + + fn get_aux_assertions>(&self, aux_rand_elements: &AuxRandElements) -> Vec> { + let mut result = Vec::new(); + result + } + + fn evaluate_transition>(&self, frame: &EvaluationFrame, periodic_values: &[E], result: &mut [E]) { + let main_current = frame.current(); + let main_next = frame.next(); + result[0] = main_next[0] - (main_current[0] * periodic_values[0] + main_current[1] * periodic_values[1]); + } + + fn evaluate_aux_transition(&self, main_frame: &EvaluationFrame, aux_frame: &EvaluationFrame, _periodic_values: &[F], aux_rand_elements: &AuxRandElements, result: &mut [E]) + where F: FieldElement, + E: FieldElement + ExtensionOf, + { + let main_current = main_frame.current(); + let main_next = main_frame.next(); + let aux_current = aux_frame.current(); + let aux_next = aux_frame.next(); + } +} \ No newline at end of file diff --git a/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding_plonky3.rs b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding_plonky3.rs new file mode 100644 index 000000000..a095ec7f0 --- /dev/null +++ b/air-script/src/tests/comprehension_periodic_binding/comprehension_periodic_binding_plonky3.rs @@ -0,0 +1,56 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 2; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 2; +pub const PERIOD: usize = 2; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ComprehensionPeriodicBindingTest; + +impl MidenAir for ComprehensionPeriodicBindingTest +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } + + fn periodic_table(&self) -> Vec> { + vec![ + vec![F::from_u64(1), F::from_u64(2)], + vec![F::from_u64(3), F::from_u64(4)], + ] + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero_ext(AB::ExprEF::from(main_next[0].clone().into()) - (AB::ExprEF::from(main_current[0].clone().into()) * AB::ExprEF::from(periodic_values[0].clone().into()) + AB::ExprEF::from(main_current[1].clone().into()) * AB::ExprEF::from(periodic_values[1].clone().into()))); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/comprehension_periodic_binding/lib.air b/air-script/src/tests/comprehension_periodic_binding/lib.air new file mode 100644 index 000000000..8667dc9ae --- /dev/null +++ b/air-script/src/tests/comprehension_periodic_binding/lib.air @@ -0,0 +1,16 @@ +mod lib + +periodic_columns { + k0: [1, 2], + k1: [3, 4], +} + +ev test_comprehension([a, b]) { + # Create local variable holding periodic column references + let cols = [k0, k1]; + let vals = [a, b]; + + # Iterate over the local variable - binding 'k' gets typed as PeriodicColumn + # but it's actually a local variable holding a value + enf a' = sum([x * k for (x, k) in (vals, cols)]); +} diff --git a/air-script/tests/evaluators/mod.rs b/air-script/src/tests/comprehension_periodic_binding/mod.rs similarity index 52% rename from air-script/tests/evaluators/mod.rs rename to air-script/src/tests/comprehension_periodic_binding/mod.rs index cf380f979..83244b98e 100644 --- a/air-script/tests/evaluators/mod.rs +++ b/air-script/src/tests/comprehension_periodic_binding/mod.rs @@ -1,4 +1,3 @@ #[rustfmt::skip] #[allow(clippy::all)] -mod evaluators; -mod test_air; +mod comprehension_periodic_binding; diff --git a/air-script/tests/computed_indices/computed_indices_complex.air b/air-script/src/tests/computed_indices/computed_indices_complex.air similarity index 100% rename from air-script/tests/computed_indices/computed_indices_complex.air rename to air-script/src/tests/computed_indices/computed_indices_complex.air diff --git a/air-script/tests/computed_indices/computed_indices_complex.rs b/air-script/src/tests/computed_indices/computed_indices_complex.rs similarity index 100% rename from air-script/tests/computed_indices/computed_indices_complex.rs rename to air-script/src/tests/computed_indices/computed_indices_complex.rs diff --git a/air-script/src/tests/computed_indices/computed_indices_complex_plonky3.rs b/air-script/src/tests/computed_indices/computed_indices_complex_plonky3.rs new file mode 100644 index 000000000..6b87e196b --- /dev/null +++ b/air-script/src/tests/computed_indices/computed_indices_complex_plonky3.rs @@ -0,0 +1,42 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ComputedIndicesAir; + +impl MidenAir for ComputedIndicesAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[2].clone().into() * AB::Expr::from_u64(3) + main_current[3].clone().into() * AB::Expr::from_u64(4)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/computed_indices/computed_indices_simple.air b/air-script/src/tests/computed_indices/computed_indices_simple.air similarity index 100% rename from air-script/tests/computed_indices/computed_indices_simple.air rename to air-script/src/tests/computed_indices/computed_indices_simple.air diff --git a/air-script/tests/computed_indices/computed_indices_simple.rs b/air-script/src/tests/computed_indices/computed_indices_simple.rs similarity index 100% rename from air-script/tests/computed_indices/computed_indices_simple.rs rename to air-script/src/tests/computed_indices/computed_indices_simple.rs diff --git a/air-script/src/tests/computed_indices/computed_indices_simple_plonky3.rs b/air-script/src/tests/computed_indices/computed_indices_simple_plonky3.rs new file mode 100644 index 000000000..1ab566b59 --- /dev/null +++ b/air-script/src/tests/computed_indices/computed_indices_simple_plonky3.rs @@ -0,0 +1,49 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 8; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ComputedIndicesAir; + +impl MidenAir for ComputedIndicesAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into()); + builder.assert_zero(main_current[1].clone().into() - AB::Expr::from_u64(2)); + builder.assert_zero(main_current[2].clone().into() - AB::Expr::from_u64(4)); + builder.assert_zero(main_current[3].clone().into() - AB::Expr::from_u64(6)); + builder.when_transition().assert_zero(main_next[4].clone().into()); + builder.when_transition().assert_zero(main_next[5].clone().into() - main_current[5].clone().into().double()); + builder.when_transition().assert_zero(main_next[6].clone().into() - AB::Expr::from_u64(6) * main_current[6].clone().into()); + builder.when_transition().assert_zero(main_next[7].clone().into() - AB::Expr::from_u64(12) * main_current[7].clone().into()); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/computed_indices/mod.rs b/air-script/src/tests/computed_indices/mod.rs new file mode 100644 index 000000000..948afee49 --- /dev/null +++ b/air-script/src/tests/computed_indices/mod.rs @@ -0,0 +1,18 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod computed_indices_complex; +#[rustfmt::skip] +#[allow(clippy::all)] +mod computed_indices_simple; + +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod computed_indices_complex_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod computed_indices_simple_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/computed_indices/test_air_plonky3.rs b/air-script/src/tests/computed_indices/test_air_plonky3.rs new file mode 100644 index 000000000..0ebed55a3 --- /dev/null +++ b/air-script/src/tests/computed_indices/test_air_plonky3.rs @@ -0,0 +1,65 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::computed_indices::computed_indices_simple_plonky3::{ComputedIndicesAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::from_canonical_checked(2).unwrap(); + rows[0][2] = F::from_canonical_checked(4).unwrap(); + rows[0][3] = F::from_canonical_checked(6).unwrap(); + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + rows[0][7] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev * F::ZERO; + rows[i][5] = col_5_prev * F::from_canonical_checked(2).unwrap(); + rows[i][6] = col_6_prev * F::from_canonical_checked(6).unwrap(); + rows[i][7] = col_7_prev * F::from_canonical_checked(12).unwrap(); + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ComputedIndicesAir); diff --git a/air-script/tests/computed_indices/test_air.rs b/air-script/src/tests/computed_indices/test_air_winterfell.rs similarity index 63% rename from air-script/tests/computed_indices/test_air.rs rename to air-script/src/tests/computed_indices/test_air_winterfell.rs index 5a953f6e5..1f7e35ed3 100644 --- a/air-script/tests/computed_indices/test_air.rs +++ b/air-script/src/tests/computed_indices/test_air_winterfell.rs @@ -3,8 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - computed_indices::computed_indices_simple::{ComputedIndicesAir, PublicInputs}, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::computed_indices::computed_indices_simple::PublicInputs, }; #[derive(Clone)] @@ -45,17 +46,9 @@ impl AirTester for ComputedIndicesAirTester { } } -#[test] -fn test_computed_indices_air() { - let air_tester = Box::new(ComputedIndicesAirTester {}); - let length = 1024; - - let main_trace = air_tester.build_main_trace(length); - let aux_trace = air_tester.build_aux_trace(length); - let pub_inputs = air_tester.public_inputs(); - let trace_info = air_tester.build_trace_info(length); - let options = air_tester.build_proof_options(); - - let air = ComputedIndicesAir::new(trace_info, pub_inputs, options); - main_trace.validate::(&air, aux_trace.as_ref()); -} +generate_air_winterfell_test!( + test_computed_indices_air, + crate::tests::computed_indices::computed_indices_simple::ComputedIndicesAir, + ComputedIndicesAirTester, + 1024 +); diff --git a/air-script/tests/constant_in_range/constant_in_range.air b/air-script/src/tests/constant_in_range/constant_in_range.air similarity index 100% rename from air-script/tests/constant_in_range/constant_in_range.air rename to air-script/src/tests/constant_in_range/constant_in_range.air diff --git a/air-script/tests/constant_in_range/constant_in_range.rs b/air-script/src/tests/constant_in_range/constant_in_range.rs similarity index 100% rename from air-script/tests/constant_in_range/constant_in_range.rs rename to air-script/src/tests/constant_in_range/constant_in_range.rs diff --git a/air-script/tests/constant_in_range/constant_in_range_module.air b/air-script/src/tests/constant_in_range/constant_in_range_module.air similarity index 100% rename from air-script/tests/constant_in_range/constant_in_range_module.air rename to air-script/src/tests/constant_in_range/constant_in_range_module.air diff --git a/air-script/src/tests/constant_in_range/constant_in_range_plonky3.rs b/air-script/src/tests/constant_in_range/constant_in_range_plonky3.rs new file mode 100644 index 000000000..134d141eb --- /dev/null +++ b/air-script/src/tests/constant_in_range/constant_in_range_plonky3.rs @@ -0,0 +1,42 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 12; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ConstantInRangeAir; + +impl MidenAir for ConstantInRangeAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[6].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() - (main_current[1].clone().into() - main_current[4].clone().into() - main_current[8].clone().into() + AB::Expr::ONE + main_current[2].clone().into() - main_current[5].clone().into() - main_current[9].clone().into() + AB::Expr::from_u64(2) + main_current[3].clone().into() - main_current[6].clone().into() - main_current[10].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/constant_in_range/mod.rs b/air-script/src/tests/constant_in_range/mod.rs new file mode 100644 index 000000000..68a0d2cbd --- /dev/null +++ b/air-script/src/tests/constant_in_range/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod constant_in_range; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod constant_in_range_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/constant_in_range/test_air_plonky3.rs b/air-script/src/tests/constant_in_range/test_air_plonky3.rs new file mode 100644 index 000000000..e1959450e --- /dev/null +++ b/air-script/src/tests/constant_in_range/test_air_plonky3.rs @@ -0,0 +1,66 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::constant_in_range::constant_in_range_plonky3::{ConstantInRangeAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::from_canonical_checked(3).unwrap(); + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + let col_9_prev = rows[i - 1][9]; + let col_10_prev = rows[i - 1][10]; + let col_11_prev = rows[i - 1][11]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + rows[i][9] = col_9_prev; + rows[i][10] = col_10_prev; + rows[i][11] = col_11_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ConstantInRangeAir); diff --git a/air-script/tests/constant_in_range/test_air.rs b/air-script/src/tests/constant_in_range/test_air_winterfell.rs similarity index 81% rename from air-script/tests/constant_in_range/test_air.rs rename to air-script/src/tests/constant_in_range/test_air_winterfell.rs index 064e5eb19..c08fa18ab 100644 --- a/air-script/tests/constant_in_range/test_air.rs +++ b/air-script/src/tests/constant_in_range/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - constant_in_range::constant_in_range::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::constant_in_range::constant_in_range::PublicInputs, }; #[derive(Clone)] @@ -46,9 +46,9 @@ impl AirTester for ConstantInRangeAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_constant_in_range_air, - crate::constant_in_range::constant_in_range::ConstantInRangeAir, + crate::tests::constant_in_range::constant_in_range::ConstantInRangeAir, ConstantInRangeAirTester, 1024 ); diff --git a/air-script/tests/constants/constants.air b/air-script/src/tests/constants/constants.air similarity index 100% rename from air-script/tests/constants/constants.air rename to air-script/src/tests/constants/constants.air diff --git a/air-script/tests/constants/constants.rs b/air-script/src/tests/constants/constants.rs similarity index 100% rename from air-script/tests/constants/constants.rs rename to air-script/src/tests/constants/constants.rs diff --git a/air-script/src/tests/constants/constants_plonky3.rs b/air-script/src/tests/constants/constants_plonky3.rs new file mode 100644 index 000000000..72218cfbb --- /dev/null +++ b/air-script/src/tests/constants/constants_plonky3.rs @@ -0,0 +1,51 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 7; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 32; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ConstantsAir; + +impl MidenAir for ConstantsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into() - AB::Expr::ONE); + builder.when_first_row().assert_zero(main_current[1].clone().into() - AB::Expr::ONE); + builder.when_first_row().assert_zero(main_current[2].clone().into()); + builder.when_first_row().assert_zero(main_current[3].clone().into() - AB::Expr::ONE); + builder.when_first_row().assert_zero(main_current[4].clone().into() - AB::Expr::ONE); + builder.when_last_row().assert_zero(main_current[6].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[0].clone().into() - (main_current[0].clone().into() + AB::Expr::ONE)); + builder.when_transition().assert_zero(main_next[1].clone().into()); + builder.when_transition().assert_zero(main_next[2].clone().into() - main_current[2].clone().into()); + builder.when_transition().assert_zero(main_next[5].clone().into() - (main_current[5].clone().into() + AB::Expr::ONE)); + builder.assert_zero(main_current[4].clone().into() - AB::Expr::ONE); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/constants/mod.rs b/air-script/src/tests/constants/mod.rs new file mode 100644 index 000000000..f412a995b --- /dev/null +++ b/air-script/src/tests/constants/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub mod constants; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +pub mod constants_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/constants/test_air_plonky3.rs b/air-script/src/tests/constants/test_air_plonky3.rs new file mode 100644 index 000000000..5c906ff3b --- /dev/null +++ b/air-script/src/tests/constants/test_air_plonky3.rs @@ -0,0 +1,62 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::constants::constants_plonky3::{ConstantsAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ONE; + rows[0][1] = F::ONE; + rows[0][2] = F::ZERO; + rows[0][3] = F::ONE; + rows[0][4] = F::ONE; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + + // Update current row based on previous values + rows[i][0] = col_0_prev + F::ONE; + rows[i][1] = F::ZERO; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev + F::ONE; + rows[i][6] = col_6_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 32] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ConstantsAir); diff --git a/air-script/tests/constants/test_air.rs b/air-script/src/tests/constants/test_air_winterfell.rs similarity index 83% rename from air-script/tests/constants/test_air.rs rename to air-script/src/tests/constants/test_air_winterfell.rs index 97a048a31..926c8c77c 100644 --- a/air-script/tests/constants/test_air.rs +++ b/air-script/src/tests/constants/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - constants::constants::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::constants::constants::PublicInputs, }; #[derive(Clone)] @@ -44,9 +44,9 @@ impl AirTester for ConstantsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_constants_air, - crate::constants::constants::ConstantsAir, + crate::tests::constants::constants::ConstantsAir, ConstantsAirTester, 1024 ); diff --git a/air-script/tests/constraint_comprehension/cc_with_evaluators.air b/air-script/src/tests/constraint_comprehension/cc_with_evaluators.air similarity index 100% rename from air-script/tests/constraint_comprehension/cc_with_evaluators.air rename to air-script/src/tests/constraint_comprehension/cc_with_evaluators.air diff --git a/air-script/tests/constraint_comprehension/constraint_comprehension.air b/air-script/src/tests/constraint_comprehension/constraint_comprehension.air similarity index 100% rename from air-script/tests/constraint_comprehension/constraint_comprehension.air rename to air-script/src/tests/constraint_comprehension/constraint_comprehension.air diff --git a/air-script/tests/constraint_comprehension/constraint_comprehension.rs b/air-script/src/tests/constraint_comprehension/constraint_comprehension.rs similarity index 100% rename from air-script/tests/constraint_comprehension/constraint_comprehension.rs rename to air-script/src/tests/constraint_comprehension/constraint_comprehension.rs diff --git a/air-script/src/tests/constraint_comprehension/constraint_comprehension_plonky3.rs b/air-script/src/tests/constraint_comprehension/constraint_comprehension_plonky3.rs new file mode 100644 index 000000000..b34c6181b --- /dev/null +++ b/air-script/src/tests/constraint_comprehension/constraint_comprehension_plonky3.rs @@ -0,0 +1,45 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 14; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ConstraintComprehensionAir; + +impl MidenAir for ConstraintComprehensionAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[8].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[6].clone().into() - main_current[10].clone().into()); + builder.assert_zero(main_current[7].clone().into() - main_current[11].clone().into()); + builder.assert_zero(main_current[8].clone().into() - main_current[12].clone().into()); + builder.assert_zero(main_current[9].clone().into() - main_current[13].clone().into()); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/constraint_comprehension/mod.rs b/air-script/src/tests/constraint_comprehension/mod.rs new file mode 100644 index 000000000..7095e16b9 --- /dev/null +++ b/air-script/src/tests/constraint_comprehension/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub mod constraint_comprehension; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +pub mod constraint_comprehension_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/constraint_comprehension/test_air_plonky3.rs b/air-script/src/tests/constraint_comprehension/test_air_plonky3.rs new file mode 100644 index 000000000..09f942eb3 --- /dev/null +++ b/air-script/src/tests/constraint_comprehension/test_air_plonky3.rs @@ -0,0 +1,49 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::constraint_comprehension::constraint_comprehension_plonky3::{ + ConstraintComprehensionAir, MAIN_WIDTH, + }, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::from_canonical_checked(inputs[0]).unwrap(); + rows[0][1] = F::ONE; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let a_prev = rows[i - 1][0]; + let b_prev = rows[i - 1][1]; + + // Update current row based on previous values + rows[i][0] = F::ONE - a_prev; + rows[i][1] = F::ONE - b_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ConstraintComprehensionAir); diff --git a/air-script/tests/constraint_comprehension/test_air.rs b/air-script/src/tests/constraint_comprehension/test_air_winterfell.rs similarity index 81% rename from air-script/tests/constraint_comprehension/test_air.rs rename to air-script/src/tests/constraint_comprehension/test_air_winterfell.rs index 6bdb9cc8d..d4bec18d1 100644 --- a/air-script/tests/constraint_comprehension/test_air.rs +++ b/air-script/src/tests/constraint_comprehension/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - constraint_comprehension::constraint_comprehension::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::constraint_comprehension::constraint_comprehension::PublicInputs, }; #[derive(Clone)] @@ -48,9 +48,9 @@ impl AirTester for ConstraintComprehensionAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_constraint_comprehension_air, - crate::constraint_comprehension::constraint_comprehension::ConstraintComprehensionAir, + crate::tests::constraint_comprehension::constraint_comprehension::ConstraintComprehensionAir, ConstraintComprehensionAirTester, 1024 ); diff --git a/air-script/src/tests/cross_module_constants/constants_lib.air b/air-script/src/tests/cross_module_constants/constants_lib.air new file mode 100644 index 000000000..25ef19270 --- /dev/null +++ b/air-script/src/tests/cross_module_constants/constants_lib.air @@ -0,0 +1,21 @@ +mod constants_lib + +# Constants used in comprehension iterables +const WEIGHTS = [1, 2, 3, 4]; + +# Pure function that uses constants in a comprehension +fn weighted_sum(values: felt[4]) -> felt { + return sum([v * w for (v, w) in (values, WEIGHTS)]); +} + +# Another function that calls the first +fn compute_result(a: felt, b: felt, c: felt, d: felt) -> felt { + let values = [a, b, c, d]; + return weighted_sum(values); +} + +# Evaluator that uses the chain of functions +ev apply_computation([cols[5]]) { + let result = compute_result(cols[0], cols[1], cols[2], cols[3]); + enf cols[4] = result; +} diff --git a/air-script/src/tests/cross_module_constants/cross_mod_constants.rs b/air-script/src/tests/cross_module_constants/cross_mod_constants.rs new file mode 100644 index 000000000..5de652283 --- /dev/null +++ b/air-script/src/tests/cross_module_constants/cross_mod_constants.rs @@ -0,0 +1,97 @@ +use winter_air::{Air, AirContext, Assertion, AuxRandElements, EvaluationFrame, ProofOptions as WinterProofOptions, TransitionConstraintDegree, TraceInfo}; +use winter_math::fields::f64::BaseElement as Felt; +use winter_math::{ExtensionOf, FieldElement, ToElements}; +use winter_utils::{ByteWriter, Serializable}; + +pub struct PublicInputs { + expected: [Felt; 1], +} + +impl PublicInputs { + pub fn new(expected: [Felt; 1]) -> Self { + Self { expected } + } +} + +impl Serializable for PublicInputs { + fn write_into(&self, target: &mut W) { + self.expected.write_into(target); + } +} + +impl ToElements for PublicInputs { + fn to_elements(&self) -> Vec { + let mut elements = Vec::new(); + elements.extend_from_slice(&self.expected); + elements + } +} + +pub struct CrossModuleConstantsTest { + context: AirContext, + expected: [Felt; 1], +} + +impl CrossModuleConstantsTest { + pub fn last_step(&self) -> usize { + self.trace_length() - self.context().num_transition_exemptions() + } +} + +impl Air for CrossModuleConstantsTest { + type BaseField = Felt; + type PublicInputs = PublicInputs; + + fn context(&self) -> &AirContext { + &self.context + } + + fn new(trace_info: TraceInfo, public_inputs: PublicInputs, options: WinterProofOptions) -> Self { + let main_degrees = vec![TransitionConstraintDegree::new(1)]; + let aux_degrees = vec![]; + let num_main_assertions = 1; + let num_aux_assertions = 0; + + let context = AirContext::new_multi_segment( + trace_info, + main_degrees, + aux_degrees, + num_main_assertions, + num_aux_assertions, + options, + ) + .set_num_transition_exemptions(2); + Self { context, expected: public_inputs.expected } + } + + fn get_periodic_column_values(&self) -> Vec> { + vec![] + } + + fn get_assertions(&self) -> Vec> { + let mut result = Vec::new(); + result.push(Assertion::single(0, 0, Felt::ZERO)); + result + } + + fn get_aux_assertions>(&self, aux_rand_elements: &AuxRandElements) -> Vec> { + let mut result = Vec::new(); + result + } + + fn evaluate_transition>(&self, frame: &EvaluationFrame, periodic_values: &[E], result: &mut [E]) { + let main_current = frame.current(); + let main_next = frame.next(); + result[0] = main_current[4] - (main_current[0] + main_current[1] * E::from(Felt::new(2_u64)) + main_current[2] * E::from(Felt::new(3_u64)) + main_current[3] * E::from(Felt::new(4_u64))); + } + + fn evaluate_aux_transition(&self, main_frame: &EvaluationFrame, aux_frame: &EvaluationFrame, _periodic_values: &[F], aux_rand_elements: &AuxRandElements, result: &mut [E]) + where F: FieldElement, + E: FieldElement + ExtensionOf, + { + let main_current = main_frame.current(); + let main_next = main_frame.next(); + let aux_current = aux_frame.current(); + let aux_next = aux_frame.next(); + } +} \ No newline at end of file diff --git a/air-script/src/tests/cross_module_constants/cross_mod_constants_plonky3.rs b/air-script/src/tests/cross_module_constants/cross_mod_constants_plonky3.rs new file mode 100644 index 000000000..47dc1b334 --- /dev/null +++ b/air-script/src/tests/cross_module_constants/cross_mod_constants_plonky3.rs @@ -0,0 +1,42 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 5; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct CrossModuleConstantsTest; + +impl MidenAir for CrossModuleConstantsTest { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[4].clone().into() - (main_current[0].clone().into() + main_current[1].clone().into().double() + main_current[2].clone().into() * AB::Expr::from_u64(3) + main_current[3].clone().into() * AB::Expr::from_u64(4))); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/cross_module_constants/cross_module_constants.air b/air-script/src/tests/cross_module_constants/cross_module_constants.air new file mode 100644 index 000000000..87315ecd6 --- /dev/null +++ b/air-script/src/tests/cross_module_constants/cross_module_constants.air @@ -0,0 +1,21 @@ +def CrossModuleConstantsTest + +# Import evaluator that uses a chain: apply_computation -> compute_result -> weighted_sum -> WEIGHTS constant +use constants_lib::apply_computation; + +trace_columns { + main: [a, b, c, d, result], +} + +public_inputs { + expected: [1], +} + +boundary_constraints { + enf a.first = 0; +} + +integrity_constraints { + # Use imported evaluator that internally uses constants in comprehensions + enf apply_computation([a, b, c, d, result]); +} diff --git a/air-script/tests/system/mod.rs b/air-script/src/tests/cross_module_constants/mod.rs similarity index 60% rename from air-script/tests/system/mod.rs rename to air-script/src/tests/cross_module_constants/mod.rs index 10cac5f2b..e10ee53c7 100644 --- a/air-script/tests/system/mod.rs +++ b/air-script/src/tests/cross_module_constants/mod.rs @@ -1,4 +1,3 @@ #[rustfmt::skip] #[allow(clippy::all)] -mod system; -mod test_air; +mod cross_mod_constants; diff --git a/air-script/tests/docs_sync.rs b/air-script/src/tests/docs_sync.rs similarity index 100% rename from air-script/tests/docs_sync.rs rename to air-script/src/tests/docs_sync.rs diff --git a/air-script/tests/evaluators/evaluators.air b/air-script/src/tests/evaluators/evaluators.air similarity index 100% rename from air-script/tests/evaluators/evaluators.air rename to air-script/src/tests/evaluators/evaluators.air diff --git a/air-script/tests/evaluators/evaluators.rs b/air-script/src/tests/evaluators/evaluators.rs similarity index 100% rename from air-script/tests/evaluators/evaluators.rs rename to air-script/src/tests/evaluators/evaluators.rs diff --git a/air-script/tests/evaluators/evaluators_nested_slice_call.air b/air-script/src/tests/evaluators/evaluators_nested_slice_call.air similarity index 100% rename from air-script/tests/evaluators/evaluators_nested_slice_call.air rename to air-script/src/tests/evaluators/evaluators_nested_slice_call.air diff --git a/air-script/tests/evaluators/evaluators_nested_slice_call.rs b/air-script/src/tests/evaluators/evaluators_nested_slice_call.rs similarity index 100% rename from air-script/tests/evaluators/evaluators_nested_slice_call.rs rename to air-script/src/tests/evaluators/evaluators_nested_slice_call.rs diff --git a/air-script/src/tests/evaluators/evaluators_nested_slice_call_plonky3.rs b/air-script/src/tests/evaluators/evaluators_nested_slice_call_plonky3.rs new file mode 100644 index 000000000..45e132b6c --- /dev/null +++ b/air-script/src/tests/evaluators/evaluators_nested_slice_call_plonky3.rs @@ -0,0 +1,65 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 20; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct EvaluatorsSliceAir; + +impl MidenAir for EvaluatorsSliceAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + builder.when_first_row().assert_zero(main_current[1].clone().into()); + builder.when_first_row().assert_zero(main_current[2].clone().into()); + builder.when_first_row().assert_zero(main_current[3].clone().into()); + builder.when_first_row().assert_zero(main_current[4].clone().into()); + builder.when_first_row().assert_zero(main_current[5].clone().into()); + builder.when_first_row().assert_zero(main_current[6].clone().into()); + builder.when_first_row().assert_zero(main_current[7].clone().into()); + builder.when_first_row().assert_zero(main_current[8].clone().into()); + builder.when_first_row().assert_zero(main_current[9].clone().into()); + builder.when_first_row().assert_zero(main_current[10].clone().into()); + builder.when_first_row().assert_zero(main_current[11].clone().into()); + builder.when_first_row().assert_zero(main_current[12].clone().into()); + builder.when_first_row().assert_zero(main_current[13].clone().into()); + builder.when_first_row().assert_zero(main_current[14].clone().into()); + builder.when_first_row().assert_zero(main_current[15].clone().into()); + builder.when_first_row().assert_zero(main_current[16].clone().into()); + builder.when_first_row().assert_zero(main_current[17].clone().into()); + builder.when_first_row().assert_zero(main_current[18].clone().into()); + builder.when_first_row().assert_zero(main_current[19].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[5].clone().into() * main_current[5].clone().into() - main_current[5].clone().into()); + builder.assert_zero(main_current[5].clone().into() * (main_current[6].clone().into() * main_current[6].clone().into() - main_current[6].clone().into())); + builder.assert_zero(main_current[5].clone().into() * main_current[6].clone().into() * (main_current[7].clone().into() * main_current[7].clone().into() - main_current[7].clone().into())); + builder.assert_zero(main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() * (main_current[8].clone().into() * main_current[8].clone().into() - main_current[8].clone().into())); + builder.assert_zero(main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() * main_current[8].clone().into() * (main_current[9].clone().into() * main_current[9].clone().into() - main_current[9].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/evaluators/evaluators_plonky3.rs b/air-script/src/tests/evaluators/evaluators_plonky3.rs new file mode 100644 index 000000000..5c0ad3895 --- /dev/null +++ b/air-script/src/tests/evaluators/evaluators_plonky3.rs @@ -0,0 +1,51 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 7; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct EvaluatorsAir; + +impl MidenAir for EvaluatorsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[0].clone().into() - main_current[0].clone().into()); + builder.when_transition().assert_zero(main_next[2].clone().into() - main_current[2].clone().into()); + builder.when_transition().assert_zero(main_next[6].clone().into() - main_current[6].clone().into()); + builder.assert_zero(main_current[0].clone().into() * main_current[0].clone().into() - main_current[0].clone().into()); + builder.assert_zero(main_current[1].clone().into() * main_current[1].clone().into() - main_current[1].clone().into()); + builder.assert_zero(main_current[2].clone().into() * main_current[2].clone().into() - main_current[2].clone().into()); + builder.assert_zero(main_current[3].clone().into() * main_current[3].clone().into() - main_current[3].clone().into()); + builder.assert_zero(main_current[4].clone().into()); + builder.assert_zero(main_current[5].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[6].clone().into() - AB::Expr::from_u64(4)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/evaluators/evaluators_slice.air b/air-script/src/tests/evaluators/evaluators_slice.air similarity index 100% rename from air-script/tests/evaluators/evaluators_slice.air rename to air-script/src/tests/evaluators/evaluators_slice.air diff --git a/air-script/tests/evaluators/evaluators_slice.rs b/air-script/src/tests/evaluators/evaluators_slice.rs similarity index 100% rename from air-script/tests/evaluators/evaluators_slice.rs rename to air-script/src/tests/evaluators/evaluators_slice.rs diff --git a/air-script/src/tests/evaluators/evaluators_slice_plonky3.rs b/air-script/src/tests/evaluators/evaluators_slice_plonky3.rs new file mode 100644 index 000000000..cd19cef43 --- /dev/null +++ b/air-script/src/tests/evaluators/evaluators_slice_plonky3.rs @@ -0,0 +1,65 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 20; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct EvaluatorsSliceAir; + +impl MidenAir for EvaluatorsSliceAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + builder.when_first_row().assert_zero(main_current[1].clone().into()); + builder.when_first_row().assert_zero(main_current[2].clone().into()); + builder.when_first_row().assert_zero(main_current[3].clone().into()); + builder.when_first_row().assert_zero(main_current[4].clone().into()); + builder.when_first_row().assert_zero(main_current[5].clone().into()); + builder.when_first_row().assert_zero(main_current[6].clone().into()); + builder.when_first_row().assert_zero(main_current[7].clone().into()); + builder.when_first_row().assert_zero(main_current[8].clone().into()); + builder.when_first_row().assert_zero(main_current[9].clone().into()); + builder.when_first_row().assert_zero(main_current[10].clone().into()); + builder.when_first_row().assert_zero(main_current[11].clone().into()); + builder.when_first_row().assert_zero(main_current[12].clone().into()); + builder.when_first_row().assert_zero(main_current[13].clone().into()); + builder.when_first_row().assert_zero(main_current[14].clone().into()); + builder.when_first_row().assert_zero(main_current[15].clone().into()); + builder.when_first_row().assert_zero(main_current[16].clone().into()); + builder.when_first_row().assert_zero(main_current[17].clone().into()); + builder.when_first_row().assert_zero(main_current[18].clone().into()); + builder.when_first_row().assert_zero(main_current[19].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() * main_current[0].clone().into() - main_current[0].clone().into()); + builder.assert_zero(main_current[0].clone().into() * (main_current[1].clone().into() * main_current[1].clone().into() - main_current[1].clone().into())); + builder.assert_zero(main_current[0].clone().into() * main_current[1].clone().into() * (main_current[2].clone().into() * main_current[2].clone().into() - main_current[2].clone().into())); + builder.assert_zero(main_current[0].clone().into() * main_current[1].clone().into() * main_current[2].clone().into() * (main_current[3].clone().into() * main_current[3].clone().into() - main_current[3].clone().into())); + builder.assert_zero(main_current[0].clone().into() * main_current[1].clone().into() * main_current[2].clone().into() * main_current[3].clone().into() * (main_current[4].clone().into() * main_current[4].clone().into() - main_current[4].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/evaluators/evaluators_slice_slicing.air b/air-script/src/tests/evaluators/evaluators_slice_slicing.air similarity index 100% rename from air-script/tests/evaluators/evaluators_slice_slicing.air rename to air-script/src/tests/evaluators/evaluators_slice_slicing.air diff --git a/air-script/src/tests/evaluators/mod.rs b/air-script/src/tests/evaluators/mod.rs new file mode 100644 index 000000000..8513a7ba3 --- /dev/null +++ b/air-script/src/tests/evaluators/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod evaluators; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod evaluators_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/evaluators/test_air_plonky3.rs b/air-script/src/tests/evaluators/test_air_plonky3.rs new file mode 100644 index 000000000..1d70dcb45 --- /dev/null +++ b/air-script/src/tests/evaluators/test_air_plonky3.rs @@ -0,0 +1,62 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::evaluators::evaluators_plonky3::{EvaluatorsAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ONE; + rows[0][6] = F::from_canonical_checked(4).unwrap(); + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, EvaluatorsAir); diff --git a/air-script/tests/evaluators/test_air.rs b/air-script/src/tests/evaluators/test_air_winterfell.rs similarity index 81% rename from air-script/tests/evaluators/test_air.rs rename to air-script/src/tests/evaluators/test_air_winterfell.rs index 6bf42ed83..bbaeb5ecd 100644 --- a/air-script/tests/evaluators/test_air.rs +++ b/air-script/src/tests/evaluators/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::{FieldElement, fields::f64::BaseElement as Felt}; use winterfell::{Trace, TraceTable}; use crate::{ - evaluators::evaluators::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::evaluators::evaluators::PublicInputs, }; #[derive(Clone)] @@ -41,9 +41,9 @@ impl AirTester for EvaluatorsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_evaluators_air, - crate::evaluators::evaluators::EvaluatorsAir, + crate::tests::evaluators::evaluators::EvaluatorsAir, EvaluatorsAirTester, 1024 ); diff --git a/air-script/tests/fibonacci/fibonacci.air b/air-script/src/tests/fibonacci/fibonacci.air similarity index 100% rename from air-script/tests/fibonacci/fibonacci.air rename to air-script/src/tests/fibonacci/fibonacci.air diff --git a/air-script/tests/fibonacci/fibonacci.rs b/air-script/src/tests/fibonacci/fibonacci.rs similarity index 100% rename from air-script/tests/fibonacci/fibonacci.rs rename to air-script/src/tests/fibonacci/fibonacci.rs diff --git a/air-script/src/tests/fibonacci/fibonacci_plonky3.rs b/air-script/src/tests/fibonacci/fibonacci_plonky3.rs new file mode 100644 index 000000000..15e40b79d --- /dev/null +++ b/air-script/src/tests/fibonacci/fibonacci_plonky3.rs @@ -0,0 +1,45 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 2; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 3; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct FibonacciAir; + +impl MidenAir for FibonacciAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into() - public_values[0].into()); + builder.when_first_row().assert_zero(main_current[1].clone().into() - public_values[1].into()); + builder.when_last_row().assert_zero(main_current[1].clone().into() - public_values[2].into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[1].clone().into() - (main_current[0].clone().into() + main_current[1].clone().into())); + builder.when_transition().assert_zero(main_next[0].clone().into() - main_current[1].clone().into()); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/fibonacci/mod.rs b/air-script/src/tests/fibonacci/mod.rs new file mode 100644 index 000000000..335c18956 --- /dev/null +++ b/air-script/src/tests/fibonacci/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub mod fibonacci; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +pub mod fibonacci_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/fibonacci/test_air_plonky3.rs b/air-script/src/tests/fibonacci/test_air_plonky3.rs new file mode 100644 index 000000000..e41da1047 --- /dev/null +++ b/air-script/src/tests/fibonacci/test_air_plonky3.rs @@ -0,0 +1,79 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::fibonacci::fibonacci_plonky3::{FibonacciAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::from_canonical_checked(inputs[0]).unwrap(); + rows[0][1] = F::from_canonical_checked(inputs[1]).unwrap(); + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let cur_a = rows[i - 1][0]; + let cur_b = rows[i - 1][1]; + + // Update current row based on previous values + rows[i][0] = cur_b; + rows[i][1] = cur_a + cur_b; + } + + trace +} + +fn generate_inputs() -> Vec { + let zero = 0; + let one = 1; + let last = fibonacci_field::(512).as_canonical_u64(); // 512nd Fibonacci number in Goldilock's field + vec![zero, one, last] +} + +fn fibonacci_field(n: i32) -> F { + if n < 0 { + panic!("{} is negative!", n); + } else if n == 0 { + return F::ZERO; + } else if n == 1 { + return F::ONE; + } + + let mut sum = F::ZERO; + let mut last = F::ZERO; + let mut curr = F::ONE; + for _i in 1..n { + sum = last + curr; + last = curr; + curr = sum; + } + sum +} + +#[test] +fn test_goldilocks_fibonacci_computation() { + type F = p3_goldilocks::Goldilocks; + let f_32 = fibonacci_field::(32); + let f_512 = fibonacci_field::(512); + assert_eq!(f_32, F::new(2178309)); + assert_eq!(f_512, F::new(12556846397060607923)); +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, FibonacciAir); diff --git a/air-script/tests/fibonacci/test_air.rs b/air-script/src/tests/fibonacci/test_air_winterfell.rs similarity index 84% rename from air-script/tests/fibonacci/test_air.rs rename to air-script/src/tests/fibonacci/test_air_winterfell.rs index e71a8dc1f..9d2313823 100644 --- a/air-script/tests/fibonacci/test_air.rs +++ b/air-script/src/tests/fibonacci/test_air_winterfell.rs @@ -5,9 +5,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{AuxTraceWithMetadata, Trace, TraceTable, matrix::ColMatrix}; use crate::{ - fibonacci::fibonacci::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::fibonacci::fibonacci::PublicInputs, }; #[derive(Clone)] @@ -30,8 +30,8 @@ impl AirTester for FibonacciAirTester { |_, state| { let cur_a = state[0]; let cur_b = state[1]; - state[1] = cur_a + cur_b; state[0] = cur_b; + state[1] = cur_a + cur_b; }, ); @@ -45,9 +45,9 @@ impl AirTester for FibonacciAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_fibonacci_air, - crate::fibonacci::fibonacci::FibonacciAir, + crate::tests::fibonacci::fibonacci::FibonacciAir, FibonacciAirTester, 32 ); diff --git a/air-script/tests/function_import/function_import.air b/air-script/src/tests/function_import/function_import.air similarity index 100% rename from air-script/tests/function_import/function_import.air rename to air-script/src/tests/function_import/function_import.air diff --git a/air-script/tests/function_import/function_import.rs b/air-script/src/tests/function_import/function_import.rs similarity index 100% rename from air-script/tests/function_import/function_import.rs rename to air-script/src/tests/function_import/function_import.rs diff --git a/air-script/tests/function_import/mod.rs b/air-script/src/tests/function_import/mod.rs similarity index 100% rename from air-script/tests/function_import/mod.rs rename to air-script/src/tests/function_import/mod.rs diff --git a/air-script/tests/function_import/utils.air b/air-script/src/tests/function_import/utils.air similarity index 100% rename from air-script/tests/function_import/utils.air rename to air-script/src/tests/function_import/utils.air diff --git a/air-script/tests/functions/functions_complex.air b/air-script/src/tests/functions/functions_complex.air similarity index 92% rename from air-script/tests/functions/functions_complex.air rename to air-script/src/tests/functions/functions_complex.air index 2b512e262..e427b5b93 100644 --- a/air-script/tests/functions/functions_complex.air +++ b/air-script/src/tests/functions/functions_complex.air @@ -29,7 +29,7 @@ boundary_constraints { integrity_constraints { let f = get_multiplicity_flags(s0, s1); - let z = v^7 * f[3] + v^2 * f[2] + v * f[1] + f[0]; + let z = v^5 * f[3] + v^2 * f[2] + v * f[1] + f[0]; enf b_range' = b_range * (z * t - t + 1); let y = fold_scalar_and_vec(v, b); enf v' = y; diff --git a/air-script/tests/functions/functions_complex.rs b/air-script/src/tests/functions/functions_complex.rs similarity index 89% rename from air-script/tests/functions/functions_complex.rs rename to air-script/src/tests/functions/functions_complex.rs index f165c2d0e..9a438bb07 100644 --- a/air-script/tests/functions/functions_complex.rs +++ b/air-script/src/tests/functions/functions_complex.rs @@ -47,7 +47,7 @@ impl Air for FunctionsAir { } fn new(trace_info: TraceInfo, public_inputs: PublicInputs, options: WinterProofOptions) -> Self { - let main_degrees = vec![TransitionConstraintDegree::new(11), TransitionConstraintDegree::new(1)]; + let main_degrees = vec![TransitionConstraintDegree::new(9), TransitionConstraintDegree::new(1)]; let aux_degrees = vec![]; let num_main_assertions = 1; let num_aux_assertions = 0; @@ -82,7 +82,7 @@ impl Air for FunctionsAir { fn evaluate_transition>(&self, frame: &EvaluationFrame, periodic_values: &[E], result: &mut [E]) { let main_current = frame.current(); let main_next = frame.next(); - result[0] = main_next[16] - main_current[16] * ((main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[1] * main_current[2] + main_current[3] * main_current[3] * (E::ONE - main_current[1]) * main_current[2] + main_current[3] * main_current[1] * (E::ONE - main_current[2]) + (E::ONE - main_current[1]) * (E::ONE - main_current[2])) * main_current[0] - main_current[0] + E::ONE); + result[0] = main_next[16] - main_current[16] * ((main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[3] * main_current[1] * main_current[2] + main_current[3] * main_current[3] * (E::ONE - main_current[1]) * main_current[2] + main_current[3] * main_current[1] * (E::ONE - main_current[2]) + (E::ONE - main_current[1]) * (E::ONE - main_current[2])) * main_current[0] - main_current[0] + E::ONE); result[1] = main_next[3] - (main_current[4] + main_current[5] + main_current[6] + main_current[7] + main_current[8] + main_current[9] + main_current[10] + main_current[11] + main_current[12] + main_current[13] + main_current[14] + main_current[15] + E::ONE) * E::from(Felt::new(2_u64)); } diff --git a/air-script/src/tests/functions/functions_complex_plonky3.rs b/air-script/src/tests/functions/functions_complex_plonky3.rs new file mode 100644 index 000000000..238cc3741 --- /dev/null +++ b/air-script/src/tests/functions/functions_complex_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 17; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct FunctionsAir; + +impl MidenAir for FunctionsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[3].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[16].clone().into() - main_current[16].clone().into() * ((main_current[3].clone().into() * main_current[3].clone().into() * main_current[3].clone().into() * main_current[3].clone().into() * main_current[3].clone().into() * main_current[1].clone().into() * main_current[2].clone().into() + main_current[3].clone().into() * main_current[3].clone().into() * (AB::Expr::ONE - main_current[1].clone().into()) * main_current[2].clone().into() + main_current[3].clone().into() * main_current[1].clone().into() * (AB::Expr::ONE - main_current[2].clone().into()) + (AB::Expr::ONE - main_current[1].clone().into()) * (AB::Expr::ONE - main_current[2].clone().into())) * main_current[0].clone().into() - main_current[0].clone().into() + AB::Expr::ONE)); + builder.when_transition().assert_zero(main_next[3].clone().into() - (main_current[4].clone().into() + main_current[5].clone().into() + main_current[6].clone().into() + main_current[7].clone().into() + main_current[8].clone().into() + main_current[9].clone().into() + main_current[10].clone().into() + main_current[11].clone().into() + main_current[12].clone().into() + main_current[13].clone().into() + main_current[14].clone().into() + main_current[15].clone().into() + AB::Expr::ONE).double()); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/functions/functions_simple.air b/air-script/src/tests/functions/functions_simple.air similarity index 100% rename from air-script/tests/functions/functions_simple.air rename to air-script/src/tests/functions/functions_simple.air diff --git a/air-script/tests/functions/functions_simple.rs b/air-script/src/tests/functions/functions_simple.rs similarity index 100% rename from air-script/tests/functions/functions_simple.rs rename to air-script/src/tests/functions/functions_simple.rs diff --git a/air-script/src/tests/functions/functions_simple_plonky3.rs b/air-script/src/tests/functions/functions_simple_plonky3.rs new file mode 100644 index 000000000..1b72a7dfb --- /dev/null +++ b/air-script/src/tests/functions/functions_simple_plonky3.rs @@ -0,0 +1,49 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 9; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct FunctionsAir; + +impl MidenAir for FunctionsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[3].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() * main_current[3].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[4].clone().into() * main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() * main_current[3].clone().into() - AB::Expr::ONE); + builder.assert_zero((main_current[4].clone().into() + main_current[5].clone().into() + main_current[6].clone().into() + main_current[7].clone().into()) * main_current[4].clone().into() * main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[4].clone().into() * main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[0].clone().into() * main_current[4].clone().into() * main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[1].clone().into() + (main_current[4].clone().into() + main_current[5].clone().into() + main_current[6].clone().into() + main_current[7].clone().into()) * main_current[4].clone().into() * main_current[5].clone().into() * main_current[6].clone().into() * main_current[7].clone().into() - AB::Expr::ONE); + builder.assert_zero(main_current[4].clone().into() + main_current[5].clone().into() + main_current[6].clone().into() + main_current[7].clone().into() - AB::Expr::ONE); + builder.assert_zero((main_current[4].clone().into() + main_current[5].clone().into() + main_current[6].clone().into() + main_current[7].clone().into()) * AB::Expr::from_u64(4) - AB::Expr::ONE); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/functions/inlined_functions_simple.air b/air-script/src/tests/functions/inlined_functions_simple.air similarity index 100% rename from air-script/tests/functions/inlined_functions_simple.air rename to air-script/src/tests/functions/inlined_functions_simple.air diff --git a/air-script/src/tests/functions/mod.rs b/air-script/src/tests/functions/mod.rs new file mode 100644 index 000000000..af284a496 --- /dev/null +++ b/air-script/src/tests/functions/mod.rs @@ -0,0 +1,17 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod functions_complex; +#[rustfmt::skip] +#[allow(clippy::all)] +mod functions_simple; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod functions_complex_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod functions_simple_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/functions/test_air_plonky3.rs b/air-script/src/tests/functions/test_air_plonky3.rs new file mode 100644 index 000000000..d9f6c39d6 --- /dev/null +++ b/air-script/src/tests/functions/test_air_plonky3.rs @@ -0,0 +1,92 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::functions::functions_complex_plonky3::{FunctionsAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + rows[0][7] = F::ZERO; + rows[0][8] = F::ZERO; + rows[0][9] = F::ZERO; + rows[0][10] = F::ZERO; + rows[0][11] = F::ZERO; + rows[0][12] = F::ZERO; + rows[0][13] = F::ZERO; + rows[0][14] = F::ZERO; + rows[0][15] = F::ZERO; + rows[0][16] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + let col_9_prev = rows[i - 1][9]; + let col_10_prev = rows[i - 1][10]; + let col_11_prev = rows[i - 1][11]; + let col_12_prev = rows[i - 1][12]; + let col_13_prev = rows[i - 1][13]; + let col_14_prev = rows[i - 1][14]; + let col_15_prev = rows[i - 1][15]; + let col_16_prev = rows[i - 1][16]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = F::from_canonical_checked(2).unwrap(); + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + rows[i][9] = col_9_prev; + rows[i][10] = col_10_prev; + rows[i][11] = col_11_prev; + rows[i][12] = col_12_prev; + rows[i][13] = col_13_prev; + rows[i][14] = col_14_prev; + rows[i][15] = col_15_prev; + rows[i][16] = col_16_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, FunctionsAir); diff --git a/air-script/tests/functions/test_air.rs b/air-script/src/tests/functions/test_air_winterfell.rs similarity index 89% rename from air-script/tests/functions/test_air.rs rename to air-script/src/tests/functions/test_air_winterfell.rs index ae578bf0d..76aa0b6f3 100644 --- a/air-script/tests/functions/test_air.rs +++ b/air-script/src/tests/functions/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - functions::functions_complex::PublicInputs, - generate_air_test, - helpers::{AirTester, MyTraceTable}, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::functions::functions_complex::PublicInputs, }; #[derive(Clone)] @@ -68,9 +68,9 @@ impl AirTester for FunctionsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_functions_complex_air, - crate::functions::functions_complex::FunctionsAir, + crate::tests::functions::functions_complex::FunctionsAir, FunctionsAirTester, 1024 ); diff --git a/air-script/tests/indexed_trace_access/indexed_trace_access.air b/air-script/src/tests/indexed_trace_access/indexed_trace_access.air similarity index 100% rename from air-script/tests/indexed_trace_access/indexed_trace_access.air rename to air-script/src/tests/indexed_trace_access/indexed_trace_access.air diff --git a/air-script/tests/indexed_trace_access/indexed_trace_access.rs b/air-script/src/tests/indexed_trace_access/indexed_trace_access.rs similarity index 100% rename from air-script/tests/indexed_trace_access/indexed_trace_access.rs rename to air-script/src/tests/indexed_trace_access/indexed_trace_access.rs diff --git a/air-script/src/tests/indexed_trace_access/indexed_trace_access_plonky3.rs b/air-script/src/tests/indexed_trace_access/indexed_trace_access_plonky3.rs new file mode 100644 index 000000000..9b1bea28c --- /dev/null +++ b/air-script/src/tests/indexed_trace_access/indexed_trace_access_plonky3.rs @@ -0,0 +1,42 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct TraceAccessAir; + +impl MidenAir for TraceAccessAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[0].clone().into() - (main_current[1].clone().into() + AB::Expr::ONE)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/indexed_trace_access/mod.rs b/air-script/src/tests/indexed_trace_access/mod.rs new file mode 100644 index 000000000..f23106386 --- /dev/null +++ b/air-script/src/tests/indexed_trace_access/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod indexed_trace_access; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod indexed_trace_access_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/indexed_trace_access/test_air_plonky3.rs b/air-script/src/tests/indexed_trace_access/test_air_plonky3.rs new file mode 100644 index 000000000..74d0b60d4 --- /dev/null +++ b/air-script/src/tests/indexed_trace_access/test_air_plonky3.rs @@ -0,0 +1,49 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::indexed_trace_access::indexed_trace_access_plonky3::{MAIN_WIDTH, TraceAccessAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + #[allow(clippy::needless_range_loop)] + for i in 1..num_rows { + // Update current row + rows[i][0] = F::ONE; + rows[i][1] = F::ZERO; + rows[i][2] = F::ZERO; + rows[i][3] = F::ZERO; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, TraceAccessAir); diff --git a/air-script/tests/indexed_trace_access/test_air.rs b/air-script/src/tests/indexed_trace_access/test_air_winterfell.rs similarity index 78% rename from air-script/tests/indexed_trace_access/test_air.rs rename to air-script/src/tests/indexed_trace_access/test_air_winterfell.rs index 707dbba10..44b7dfcec 100644 --- a/air-script/tests/indexed_trace_access/test_air.rs +++ b/air-script/src/tests/indexed_trace_access/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - indexed_trace_access::indexed_trace_access::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::indexed_trace_access::indexed_trace_access::PublicInputs, }; #[derive(Clone)] @@ -40,9 +40,9 @@ impl AirTester for TraceAccessAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_indexed_trace_access_air, - crate::indexed_trace_access::indexed_trace_access::TraceAccessAir, + crate::tests::indexed_trace_access::indexed_trace_access::TraceAccessAir, TraceAccessAirTester, 1024 ); diff --git a/air-script/tests/list_comprehension/list_comprehension.air b/air-script/src/tests/list_comprehension/list_comprehension.air similarity index 100% rename from air-script/tests/list_comprehension/list_comprehension.air rename to air-script/src/tests/list_comprehension/list_comprehension.air diff --git a/air-script/tests/list_comprehension/list_comprehension.rs b/air-script/src/tests/list_comprehension/list_comprehension.rs similarity index 100% rename from air-script/tests/list_comprehension/list_comprehension.rs rename to air-script/src/tests/list_comprehension/list_comprehension.rs diff --git a/air-script/tests/list_comprehension/list_comprehension_nested.air b/air-script/src/tests/list_comprehension/list_comprehension_nested.air similarity index 100% rename from air-script/tests/list_comprehension/list_comprehension_nested.air rename to air-script/src/tests/list_comprehension/list_comprehension_nested.air diff --git a/air-script/tests/list_comprehension/list_comprehension_nested.rs b/air-script/src/tests/list_comprehension/list_comprehension_nested.rs similarity index 100% rename from air-script/tests/list_comprehension/list_comprehension_nested.rs rename to air-script/src/tests/list_comprehension/list_comprehension_nested.rs diff --git a/air-script/src/tests/list_comprehension/list_comprehension_nested_plonky3.rs b/air-script/src/tests/list_comprehension/list_comprehension_nested_plonky3.rs new file mode 100644 index 000000000..5525f2068 --- /dev/null +++ b/air-script/src/tests/list_comprehension/list_comprehension_nested_plonky3.rs @@ -0,0 +1,44 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 2; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ListComprehensionAir; + +impl MidenAir for ListComprehensionAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() + main_current[1].clone().into().double() - AB::Expr::from_u64(3)); + builder.assert_zero(main_current[0].clone().into().double() + main_current[1].clone().into() * AB::Expr::from_u64(3) - AB::Expr::from_u64(5)); + builder.assert_zero(main_current[0].clone().into() * AB::Expr::from_u64(3) + main_current[1].clone().into() * AB::Expr::from_u64(4) - AB::Expr::from_u64(7)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/list_comprehension/list_comprehension_plonky3.rs b/air-script/src/tests/list_comprehension/list_comprehension_plonky3.rs new file mode 100644 index 000000000..9d13e2e8b --- /dev/null +++ b/air-script/src/tests/list_comprehension/list_comprehension_plonky3.rs @@ -0,0 +1,47 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 16; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ListComprehensionAir; + +impl MidenAir for ListComprehensionAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[10].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() - main_current[2].clone().into()); + builder.assert_zero(main_current[4].clone().into() - main_current[0].clone().into() * AB::Expr::from_u64(8) * main_current[11].clone().into()); + builder.when_transition().assert_zero(main_current[4].clone().into() - main_current[0].clone().into() * (main_next[8].clone().into() - main_next[12].clone().into())); + builder.assert_zero(main_current[6].clone().into() - main_current[0].clone().into() * (main_current[9].clone().into() - main_current[14].clone().into())); + builder.assert_zero(main_current[1].clone().into() - (main_current[5].clone().into() - main_current[8].clone().into() - main_current[12].clone().into() + AB::Expr::from_u64(10) + main_current[6].clone().into() - main_current[9].clone().into() - main_current[13].clone().into() + AB::Expr::from_u64(20) + main_current[7].clone().into() - main_current[10].clone().into() - main_current[14].clone().into())); + builder.assert_zero(main_current[14].clone().into() - AB::Expr::from_u64(10)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/list_comprehension/mod.rs b/air-script/src/tests/list_comprehension/mod.rs new file mode 100644 index 000000000..e33f216df --- /dev/null +++ b/air-script/src/tests/list_comprehension/mod.rs @@ -0,0 +1,17 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod list_comprehension_nested; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod list_comprehension_nested_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +mod list_comprehension; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod list_comprehension_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/list_comprehension/test_air_plonky3.rs b/air-script/src/tests/list_comprehension/test_air_plonky3.rs new file mode 100644 index 000000000..42080ad1f --- /dev/null +++ b/air-script/src/tests/list_comprehension/test_air_plonky3.rs @@ -0,0 +1,89 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::list_comprehension::list_comprehension_plonky3::{ListComprehensionAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::from_canonical_checked(20).unwrap(); + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + rows[0][7] = F::ZERO; + rows[0][8] = F::ZERO; + rows[0][9] = F::ZERO; + rows[0][10] = F::ZERO; + rows[0][11] = F::ZERO; + rows[0][12] = F::ZERO; + rows[0][13] = F::ZERO; + rows[0][14] = F::from_canonical_checked(10).unwrap(); + rows[0][15] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + let col_9_prev = rows[i - 1][9]; + let col_10_prev = rows[i - 1][10]; + let col_11_prev = rows[i - 1][11]; + let col_12_prev = rows[i - 1][12]; + let col_13_prev = rows[i - 1][13]; + let col_14_prev = rows[i - 1][14]; + let col_15_prev = rows[i - 1][15]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = F::from_canonical_checked(2).unwrap(); + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + rows[i][9] = col_9_prev; + rows[i][10] = col_10_prev; + rows[i][11] = col_11_prev; + rows[i][12] = col_12_prev; + rows[i][13] = col_13_prev; + rows[i][14] = col_14_prev; + rows[i][15] = col_15_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ListComprehensionAir); diff --git a/air-script/tests/list_comprehension/test_air.rs b/air-script/src/tests/list_comprehension/test_air_winterfell.rs similarity index 83% rename from air-script/tests/list_comprehension/test_air.rs rename to air-script/src/tests/list_comprehension/test_air_winterfell.rs index c3e9b396f..749541299 100644 --- a/air-script/tests/list_comprehension/test_air.rs +++ b/air-script/src/tests/list_comprehension/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - list_comprehension::list_comprehension::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::list_comprehension::list_comprehension::PublicInputs, }; #[derive(Clone)] @@ -52,9 +52,9 @@ impl AirTester for ListComprehensionAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_list_comprehension_air, - crate::list_comprehension::list_comprehension::ListComprehensionAir, + crate::tests::list_comprehension::list_comprehension::ListComprehensionAir, ListComprehensionAirTester, 1024 ); diff --git a/air-script/tests/list_folding/list_folding.air b/air-script/src/tests/list_folding/list_folding.air similarity index 100% rename from air-script/tests/list_folding/list_folding.air rename to air-script/src/tests/list_folding/list_folding.air diff --git a/air-script/tests/list_folding/list_folding.rs b/air-script/src/tests/list_folding/list_folding.rs similarity index 100% rename from air-script/tests/list_folding/list_folding.rs rename to air-script/src/tests/list_folding/list_folding.rs diff --git a/air-script/src/tests/list_folding/list_folding_plonky3.rs b/air-script/src/tests/list_folding/list_folding_plonky3.rs new file mode 100644 index 000000000..44f4753a3 --- /dev/null +++ b/air-script/src/tests/list_folding/list_folding_plonky3.rs @@ -0,0 +1,45 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 17; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct ListFoldingAir; + +impl MidenAir for ListFoldingAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[11].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[5].clone().into() - (main_current[9].clone().into() + main_current[10].clone().into() + main_current[11].clone().into() + main_current[12].clone().into() + main_current[13].clone().into() * main_current[14].clone().into() * main_current[15].clone().into() * main_current[16].clone().into())); + builder.when_transition().assert_zero(main_next[6].clone().into() - (main_current[9].clone().into() + main_current[10].clone().into() + main_current[11].clone().into() + main_current[12].clone().into() + main_current[13].clone().into() * main_current[14].clone().into() * main_current[15].clone().into() * main_current[16].clone().into())); + builder.when_transition().assert_zero(main_next[7].clone().into() - (main_current[9].clone().into() * main_current[13].clone().into() + main_current[10].clone().into() * main_current[14].clone().into() + main_current[11].clone().into() * main_current[15].clone().into() + main_current[12].clone().into() * main_current[16].clone().into() + (main_current[9].clone().into() + main_current[13].clone().into()) * (main_current[10].clone().into() + main_current[14].clone().into()) * (main_current[11].clone().into() + main_current[15].clone().into()) * (main_current[12].clone().into() + main_current[16].clone().into()))); + builder.when_transition().assert_zero(main_next[8].clone().into() - (main_current[1].clone().into() + main_current[9].clone().into() * main_current[13].clone().into() + main_current[10].clone().into() * main_current[14].clone().into() + main_current[11].clone().into() * main_current[15].clone().into() + main_current[12].clone().into() * main_current[16].clone().into() + main_current[9].clone().into() * main_current[13].clone().into() + main_current[10].clone().into() * main_current[14].clone().into() + main_current[11].clone().into() * main_current[15].clone().into() + main_current[12].clone().into() * main_current[16].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/list_folding/mod.rs b/air-script/src/tests/list_folding/mod.rs new file mode 100644 index 000000000..0339dce98 --- /dev/null +++ b/air-script/src/tests/list_folding/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod list_folding; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod list_folding_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/src/tests/list_folding/test_air_plonky3.rs b/air-script/src/tests/list_folding/test_air_plonky3.rs new file mode 100644 index 000000000..144e4f27b --- /dev/null +++ b/air-script/src/tests/list_folding/test_air_plonky3.rs @@ -0,0 +1,92 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::list_folding::list_folding_plonky3::{ListFoldingAir, MAIN_WIDTH}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + rows[0][7] = F::ZERO; + rows[0][8] = F::ZERO; + rows[0][9] = F::ZERO; + rows[0][10] = F::ZERO; + rows[0][11] = F::ZERO; + rows[0][12] = F::ZERO; + rows[0][13] = F::ZERO; + rows[0][14] = F::ZERO; + rows[0][15] = F::ZERO; + rows[0][16] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + let col_9_prev = rows[i - 1][9]; + let col_10_prev = rows[i - 1][10]; + let col_11_prev = rows[i - 1][11]; + let col_12_prev = rows[i - 1][12]; + let col_13_prev = rows[i - 1][13]; + let col_14_prev = rows[i - 1][14]; + let col_15_prev = rows[i - 1][15]; + let col_16_prev = rows[i - 1][16]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = F::from_canonical_checked(2).unwrap(); + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + rows[i][9] = col_9_prev; + rows[i][10] = col_10_prev; + rows[i][11] = col_11_prev; + rows[i][12] = col_12_prev; + rows[i][13] = col_13_prev; + rows[i][14] = col_14_prev; + rows[i][15] = col_15_prev; + rows[i][16] = col_16_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, ListFoldingAir); diff --git a/air-script/tests/list_folding/test_air.rs b/air-script/src/tests/list_folding/test_air_winterfell.rs similarity index 85% rename from air-script/tests/list_folding/test_air.rs rename to air-script/src/tests/list_folding/test_air_winterfell.rs index 9d79fad28..a957f18f9 100644 --- a/air-script/tests/list_folding/test_air.rs +++ b/air-script/src/tests/list_folding/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - list_folding::list_folding::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::list_folding::list_folding::PublicInputs, }; #[derive(Clone)] @@ -53,9 +53,9 @@ impl AirTester for ListFoldingAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_list_folding_air, - crate::list_folding::list_folding::ListFoldingAir, + crate::tests::list_folding::list_folding::ListFoldingAir, ListFoldingAirTester, 1024 ); diff --git a/air-script/tests/mod.rs b/air-script/src/tests/mod.rs similarity index 86% rename from air-script/tests/mod.rs rename to air-script/src/tests/mod.rs index e96e603d8..a0603be81 100644 --- a/air-script/tests/mod.rs +++ b/air-script/src/tests/mod.rs @@ -1,7 +1,3 @@ -mod codegen; - -pub mod helpers; - #[allow(unused_variables, dead_code, unused_mut)] mod binary; #[allow(unused_variables, dead_code, unused_mut)] @@ -9,6 +5,8 @@ mod bitwise; #[allow(unused_variables, dead_code, unused_mut)] mod buses; #[allow(unused_variables, dead_code, unused_mut)] +mod comprehension_periodic_binding; +#[allow(unused_variables, dead_code, unused_mut)] mod computed_indices; #[allow(unused_variables, dead_code, unused_mut)] mod constant_in_range; @@ -17,6 +15,8 @@ mod constants; #[allow(unused_variables, dead_code, unused_mut)] mod constraint_comprehension; #[allow(unused_variables, dead_code, unused_mut)] +mod cross_module_constants; +#[allow(unused_variables, dead_code, unused_mut)] mod evaluators; #[allow(unused_variables, dead_code, unused_mut)] mod fibonacci; @@ -43,4 +43,7 @@ mod trace_col_groups; #[allow(unused_variables, dead_code, unused_mut)] mod variables; +mod comparison; mod docs_sync; +mod plonky3; +mod winterfell; diff --git a/air-script/src/tests/periodic_columns/mod.rs b/air-script/src/tests/periodic_columns/mod.rs new file mode 100644 index 000000000..0053e1aa8 --- /dev/null +++ b/air-script/src/tests/periodic_columns/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod periodic_columns; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod periodic_columns_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/tests/periodic_columns/periodic_columns.air b/air-script/src/tests/periodic_columns/periodic_columns.air similarity index 100% rename from air-script/tests/periodic_columns/periodic_columns.air rename to air-script/src/tests/periodic_columns/periodic_columns.air diff --git a/air-script/tests/periodic_columns/periodic_columns.rs b/air-script/src/tests/periodic_columns/periodic_columns.rs similarity index 100% rename from air-script/tests/periodic_columns/periodic_columns.rs rename to air-script/src/tests/periodic_columns/periodic_columns.rs diff --git a/air-script/src/tests/periodic_columns/periodic_columns_plonky3.rs b/air-script/src/tests/periodic_columns/periodic_columns_plonky3.rs new file mode 100644 index 000000000..df36acbd7 --- /dev/null +++ b/air-script/src/tests/periodic_columns/periodic_columns_plonky3.rs @@ -0,0 +1,57 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 3; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 2; +pub const PERIOD: usize = 8; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct PeriodicColumnsAir; + +impl MidenAir for PeriodicColumnsAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } + + fn periodic_table(&self) -> Vec> { + vec![ + vec![F::from_u64(1), F::from_u64(0), F::from_u64(0), F::from_u64(0)], + vec![F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(0)], + ] + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero_ext(AB::ExprEF::from(periodic_values[0].clone().into()) * (AB::ExprEF::from(main_current[1].clone().into()) + AB::ExprEF::from(main_current[2].clone().into()))); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[1].clone().into()) * (AB::ExprEF::from(main_next[0].clone().into()) - AB::ExprEF::from(main_current[0].clone().into()))); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/periodic_columns/test_air_plonky3.rs b/air-script/src/tests/periodic_columns/test_air_plonky3.rs new file mode 100644 index 000000000..d732ffbed --- /dev/null +++ b/air-script/src/tests/periodic_columns/test_air_plonky3.rs @@ -0,0 +1,50 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::periodic_columns::periodic_columns_plonky3::{MAIN_WIDTH, PeriodicColumnsAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, PeriodicColumnsAir); diff --git a/air-script/tests/periodic_columns/test_air.rs b/air-script/src/tests/periodic_columns/test_air_winterfell.rs similarity index 77% rename from air-script/tests/periodic_columns/test_air.rs rename to air-script/src/tests/periodic_columns/test_air_winterfell.rs index f157a98df..cf92e1101 100644 --- a/air-script/tests/periodic_columns/test_air.rs +++ b/air-script/src/tests/periodic_columns/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - periodic_columns::periodic_columns::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::periodic_columns::periodic_columns::PublicInputs, }; #[derive(Clone)] @@ -37,9 +37,9 @@ impl AirTester for PeriodicColumnsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_periodic_columns_air, - crate::periodic_columns::periodic_columns::PeriodicColumnsAir, + crate::tests::periodic_columns::periodic_columns::PeriodicColumnsAir, PeriodicColumnsAirTester, 1024 ); diff --git a/air-script/src/tests/plonky3.rs b/air-script/src/tests/plonky3.rs new file mode 100644 index 000000000..9a685d06d --- /dev/null +++ b/air-script/src/tests/plonky3.rs @@ -0,0 +1,397 @@ +use expect_test::expect_file; + +use crate::test_utils::codegen::{Target, Test}; + +#[test] +fn binary() { + let generated_air = Test::new("src/tests/binary/binary.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["binary/binary_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn bitwise() { + let generated_air = Test::new("src/tests/bitwise/bitwise.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["bitwise/bitwise_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_complex() { + let generated_air = Test::new("src/tests/buses/buses_complex.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_complex_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_simple() { + let generated_air = Test::new("src/tests/buses/buses_simple.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} +#[test] +fn buses_simple_with_evaluators() { + let generated_air = Test::new("src/tests/buses/buses_simple_with_evaluators.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_both() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_both.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_both_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_first() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_first.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_first_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_last() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_last.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_last_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn comprehension_periodic_binding() { + // Test that comprehension bindings over periodic columns are typed as Local, not PeriodicColumn + // This pattern is used when iterating over a vector containing periodic column references + let generated_air = Test::new( + "src/tests/comprehension_periodic_binding/comprehension_periodic_binding.air".to_string(), + ) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = + expect_file!["comprehension_periodic_binding/comprehension_periodic_binding_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn computed_indices_complex() { + let generated_air = + Test::new("src/tests/computed_indices/computed_indices_complex.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["computed_indices/computed_indices_complex_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn computed_indices_simple() { + let generated_air = + Test::new("src/tests/computed_indices/computed_indices_simple.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["computed_indices/computed_indices_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constant_in_range() { + let generated_air = Test::new("src/tests/constant_in_range/constant_in_range.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["constant_in_range/constant_in_range_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constants() { + let generated_air = Test::new("src/tests/constants/constants.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["constants/constants_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn cross_module_constants() { + // Test that constants used in comprehension iterables work across module boundaries + let generated_air = + Test::new("src/tests/cross_module_constants/cross_module_constants.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["cross_module_constants/cross_mod_constants_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn evaluators_nested_slice_call() { + let generated_air = + Test::new("src/tests/evaluators/evaluators_nested_slice_call.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators_nested_slice_call_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +// TODO: add support for nested slicing in general expressions. +// +// #[test] +// fn evaluators_slice_slicing() { +// let generated_air = +// Test::new("src/tests/evaluators/evaluators_slice_slicing.air".to_string()) +// .transpile(Target::Plonky3) +// .unwrap(); +// +// let expected = expect_file!["evaluators/evaluators_slice_slicing_plonky3.rs"]; +// expected.assert_eq(&generated_air); +// } + +#[test] +fn evaluators_slice() { + let generated_air = Test::new("src/tests/evaluators/evaluators_slice.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators_slice_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn evaluators() { + let generated_air = Test::new("src/tests/evaluators/evaluators.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn fibonacci() { + let generated_air = Test::new("src/tests/fibonacci/fibonacci.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["fibonacci/fibonacci_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_complex() { + let generated_air = Test::new("src/tests/functions/functions_complex.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["functions/functions_complex_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_simple() { + let generated_air = Test::new("src/tests/functions/functions_simple.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["functions/functions_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_simple_inlined() { + // make sure that the constraints generated using inlined functions are the same as the ones + // generated using regular functions + let generated_air = Test::new("src/tests/functions/inlined_functions_simple.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["functions/functions_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn indexed_trace_access() { + let generated_air = + Test::new("src/tests/indexed_trace_access/indexed_trace_access.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["indexed_trace_access/indexed_trace_access_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_comprehension_nested() { + let generated_air = + Test::new("src/tests/list_comprehension/list_comprehension_nested.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["list_comprehension/list_comprehension_nested_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_comprehension() { + let generated_air = + Test::new("src/tests/list_comprehension/list_comprehension.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["list_comprehension/list_comprehension_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_folding() { + let generated_air = Test::new("src/tests/list_folding/list_folding.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["list_folding/list_folding_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn periodic_columns() { + let generated_air = Test::new("src/tests/periodic_columns/periodic_columns.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["periodic_columns/periodic_columns_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn pub_inputs() { + let generated_air = Test::new("src/tests/pub_inputs/pub_inputs.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["pub_inputs/pub_inputs_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_complex() { + let generated_air = Test::new("src/tests/selectors/selectors_combine_complex.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_complex_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_simple() { + let generated_air = Test::new("src/tests/selectors/selectors_combine_simple.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_simple_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_with_list_comprehensions() { + let generated_air = + Test::new("src/tests/selectors/selectors_combine_with_list_comprehensions.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_with_list_comprehensions_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors() { + let generated_air = Test::new("src/tests/selectors/selectors.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["selectors/selectors_plonky3.rs"]; + expected.assert_eq(&generated_air); + + let generated_air = Test::new("src/tests/selectors/selectors_with_evaluators.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["selectors/selectors_with_evaluators_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn system() { + let generated_air = Test::new("src/tests/system/system.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["system/system_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn trace_col_groups() { + let generated_air = Test::new("src/tests/trace_col_groups/trace_col_groups.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["trace_col_groups/trace_col_groups_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn variables() { + let generated_air = Test::new("src/tests/variables/variables.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["variables/variables_plonky3.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constraint_comprehension() { + let generated_air = + Test::new("src/tests/constraint_comprehension/constraint_comprehension.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["constraint_comprehension/constraint_comprehension_plonky3.rs"]; + expected.assert_eq(&generated_air); + + let generated_air = + Test::new("src/tests/constraint_comprehension/cc_with_evaluators.air".to_string()) + .transpile(Target::Plonky3) + .unwrap(); + + let expected = expect_file!["constraint_comprehension/constraint_comprehension_plonky3.rs"]; + expected.assert_eq(&generated_air); +} diff --git a/air-script/src/tests/pub_inputs/mod.rs b/air-script/src/tests/pub_inputs/mod.rs new file mode 100644 index 000000000..139601bc2 --- /dev/null +++ b/air-script/src/tests/pub_inputs/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod pub_inputs; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod pub_inputs_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/tests/pub_inputs/pub_inputs.air b/air-script/src/tests/pub_inputs/pub_inputs.air similarity index 100% rename from air-script/tests/pub_inputs/pub_inputs.air rename to air-script/src/tests/pub_inputs/pub_inputs.air diff --git a/air-script/tests/pub_inputs/pub_inputs.rs b/air-script/src/tests/pub_inputs/pub_inputs.rs similarity index 100% rename from air-script/tests/pub_inputs/pub_inputs.rs rename to air-script/src/tests/pub_inputs/pub_inputs.rs diff --git a/air-script/src/tests/pub_inputs/pub_inputs_plonky3.rs b/air-script/src/tests/pub_inputs/pub_inputs_plonky3.rs new file mode 100644 index 000000000..4cc281a74 --- /dev/null +++ b/air-script/src/tests/pub_inputs/pub_inputs_plonky3.rs @@ -0,0 +1,49 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 32; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct PubInputsAir; + +impl MidenAir for PubInputsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into() - public_values[8].into()); + builder.when_first_row().assert_zero(main_current[1].clone().into() - public_values[9].into()); + builder.when_first_row().assert_zero(main_current[2].clone().into() - public_values[10].into()); + builder.when_first_row().assert_zero(main_current[3].clone().into() - public_values[11].into()); + builder.when_last_row().assert_zero(main_current[0].clone().into() - public_values[12].into()); + builder.when_last_row().assert_zero(main_current[1].clone().into() - public_values[13].into()); + builder.when_last_row().assert_zero(main_current[2].clone().into() - public_values[14].into()); + builder.when_last_row().assert_zero(main_current[3].clone().into() - public_values[15].into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[0].clone().into() - (main_current[1].clone().into() + main_current[2].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/pub_inputs/test_air_plonky3.rs b/air-script/src/tests/pub_inputs/test_air_plonky3.rs new file mode 100644 index 000000000..2a7ad24d0 --- /dev/null +++ b/air-script/src/tests/pub_inputs/test_air_plonky3.rs @@ -0,0 +1,53 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::pub_inputs::pub_inputs_plonky3::{MAIN_WIDTH, PubInputsAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![0; 32] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, PubInputsAir); diff --git a/air-script/tests/pub_inputs/test_air.rs b/air-script/src/tests/pub_inputs/test_air_winterfell.rs similarity index 80% rename from air-script/tests/pub_inputs/test_air.rs rename to air-script/src/tests/pub_inputs/test_air_winterfell.rs index ad929a857..62b8be849 100644 --- a/air-script/tests/pub_inputs/test_air.rs +++ b/air-script/src/tests/pub_inputs/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - pub_inputs::pub_inputs::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::pub_inputs::pub_inputs::PublicInputs, }; #[derive(Clone)] @@ -38,9 +38,9 @@ impl AirTester for PubInputsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_pub_inputs_air, - crate::pub_inputs::pub_inputs::PubInputsAir, + crate::tests::pub_inputs::pub_inputs::PubInputsAir, PubInputsAirTester, 1024 ); diff --git a/air-script/src/tests/selectors/mod.rs b/air-script/src/tests/selectors/mod.rs new file mode 100644 index 000000000..a8007ac23 --- /dev/null +++ b/air-script/src/tests/selectors/mod.rs @@ -0,0 +1,38 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod selectors; +#[rustfmt::skip] +#[allow(clippy::all)] +mod selectors_combine_simple; +#[rustfmt::skip] +#[allow(clippy::all)] +mod selectors_combine_complex; +#[rustfmt::skip] +#[allow(clippy::all)] +mod selectors_combine_with_list_comprehensions; +#[rustfmt::skip] +#[allow(clippy::all)] +mod selectors_with_evaluators; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod selectors_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod selectors_combine_simple_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod selectors_combine_complex_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod selectors_with_evaluators_plonky3; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod selectors_combine_with_list_comprehensions_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/tests/selectors/selectors.air b/air-script/src/tests/selectors/selectors.air similarity index 100% rename from air-script/tests/selectors/selectors.air rename to air-script/src/tests/selectors/selectors.air diff --git a/air-script/tests/selectors/selectors.rs b/air-script/src/tests/selectors/selectors.rs similarity index 100% rename from air-script/tests/selectors/selectors.rs rename to air-script/src/tests/selectors/selectors.rs diff --git a/air-script/tests/selectors/selectors_combine_complex.air b/air-script/src/tests/selectors/selectors_combine_complex.air similarity index 100% rename from air-script/tests/selectors/selectors_combine_complex.air rename to air-script/src/tests/selectors/selectors_combine_complex.air diff --git a/air-script/tests/selectors/selectors_combine_complex.rs b/air-script/src/tests/selectors/selectors_combine_complex.rs similarity index 100% rename from air-script/tests/selectors/selectors_combine_complex.rs rename to air-script/src/tests/selectors/selectors_combine_complex.rs diff --git a/air-script/src/tests/selectors/selectors_combine_complex_plonky3.rs b/air-script/src/tests/selectors/selectors_combine_complex_plonky3.rs new file mode 100644 index 000000000..a89f39d38 --- /dev/null +++ b/air-script/src/tests/selectors/selectors_combine_complex_plonky3.rs @@ -0,0 +1,138 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 6; +pub const AUX_WIDTH: usize = 1; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 2; + +pub struct SelectorsAir; + +impl MidenAir for SelectorsAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_randomness(&self) -> usize { + 1 + MAX_BETA_CHALLENGE_POWER + } + + fn aux_width(&self) -> usize { + AUX_WIDTH + } + + fn bus_types(&self) -> Vec { + vec![ + BusType::Multiset, + ] + } + + fn build_aux_trace(&self, _main: &RowMajorMatrix, _challenges: &[EF]) -> Option> { + // Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders. + + let num_rows = _main.height(); + let trace_length = num_rows * AUX_WIDTH; + let mut long_trace = EF::zero_vec(trace_length); + let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH); + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + // Initialize first row + let initial_values = Self::buses_initial_values::(); + for j in 0..AUX_WIDTH { + rows[0][j] = initial_values[j]; + } + // Fill subsequent rows using direct access to the rows array + for i in 0..num_rows-1 { + let i_next = (i + 1) % num_rows; + let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail. + let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail. + let main = VerticalPair::new( + RowMajorMatrixView::new_row(&*main_local), + RowMajorMatrixView::new_row(&*main_next), + ); + let periodic_values: [_; NUM_PERIODIC_VALUES] = >::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect("Wrong number of periodic values"); + let prev_row = &rows[i]; + let next_row = Self::buses_transitions::( + &main, + _challenges, + &periodic_values, + prev_row, + ); + for j in 0..AUX_WIDTH { + rows[i+1][j] = next_row[j]; + } + } + let trace_f = trace.flatten_to_base(); + Some(trace_f) + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect("Wrong number of aux bus boundary values"); + let aux = builder.permutation(); + let (aux_current, aux_next) = ( + aux.row_slice(0).unwrap(), + aux.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[5].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero((main_current[0].clone().into() + (AB::Expr::ONE - main_current[0].clone().into()) * main_current[1].clone().into()) * (main_current[3].clone().into() - AB::Expr::from_u64(16)) + (AB::Expr::ONE - main_current[0].clone().into()) * (AB::Expr::ONE - main_current[1].clone().into()) * (main_current[4].clone().into() - AB::Expr::from_u64(5))); + builder.assert_zero((AB::Expr::ONE - main_current[0].clone().into()) * (main_current[5].clone().into() - AB::Expr::from_u64(5)) + main_current[0].clone().into() * (main_current[4].clone().into() - AB::Expr::from_u64(4))); + builder.assert_zero(main_current[0].clone().into() * (main_current[5].clone().into() - AB::Expr::from_u64(20)) + (AB::Expr::ONE - main_current[0].clone().into()) * main_current[1].clone().into() * (main_current[4].clone().into() - AB::Expr::from_u64(31))); + + // Aux integrity/transition constraints + builder.when_transition().assert_zero_ext(((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * AB::ExprEF::from(main_current[0].clone().into()) * AB::ExprEF::from(main_current[5].clone().into()) + AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into()) * AB::ExprEF::from(main_current[5].clone().into())) * ((alpha.into() + beta_challenges[0].into() + beta_challenges[1].into().double()) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(main_current[1].clone().into()) * AB::ExprEF::from(main_current[5].clone().into()) + AB::ExprEF::ONE - (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * AB::ExprEF::from(main_current[1].clone().into()) * AB::ExprEF::from(main_current[5].clone().into())) * AB::ExprEF::from(aux_current[0].clone().into()) - ((alpha.into() + AB::ExprEF::from_u64(3) * beta_challenges[0].into() + AB::ExprEF::from_u64(4) * beta_challenges[1].into()) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[1].clone().into())) * AB::ExprEF::from(main_current[4].clone().into()) + AB::ExprEF::ONE - (AB::ExprEF::ONE - AB::ExprEF::from(main_current[0].clone().into())) * (AB::ExprEF::ONE - AB::ExprEF::from(main_current[1].clone().into())) * AB::ExprEF::from(main_current[4].clone().into())) * AB::ExprEF::from(aux_next[0].clone().into())); + } +} + +impl SelectorsAir { + fn buses_initial_values() -> Vec + where F: Field, + EF: ExtensionField, + { + vec![ + EF::ONE, + ] + } + + fn buses_transitions(main: &VerticalPair, RowMajorMatrixView>, challenges: &[EF], periodic_evals: &[F], aux_current: &[EF]) -> Vec + where F: Field, + EF: ExtensionField, + { + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + let (&alpha, beta_challenges) = challenges.split_first().expect("Wrong number of randomness"); + let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect("Wrong number of randomness"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect("Wrong number of periodic values"); + vec![ + (((alpha + beta_challenges[0] + beta_challenges[1].double()) * EF::from(main_current[0].clone()) * EF::from(main_current[5].clone()) + EF::ONE - EF::from(main_current[0].clone()) * EF::from(main_current[5].clone())) * ((alpha + beta_challenges[0] + beta_challenges[1].double()) * (EF::ONE - EF::from(main_current[0].clone())) * EF::from(main_current[1].clone()) * EF::from(main_current[5].clone()) + EF::ONE - (EF::ONE - EF::from(main_current[0].clone())) * EF::from(main_current[1].clone()) * EF::from(main_current[5].clone())) * EF::from(aux_current[0].clone())) * ((alpha + EF::from_u64(3) * beta_challenges[0] + EF::from_u64(4) * beta_challenges[1]) * (EF::ONE - EF::from(main_current[0].clone())) * (EF::ONE - EF::from(main_current[1].clone())) * EF::from(main_current[4].clone()) + EF::ONE - (EF::ONE - EF::from(main_current[0].clone())) * (EF::ONE - EF::from(main_current[1].clone())) * EF::from(main_current[4].clone())).inverse(), + ] + } +} \ No newline at end of file diff --git a/air-script/tests/selectors/selectors_combine_simple.air b/air-script/src/tests/selectors/selectors_combine_simple.air similarity index 100% rename from air-script/tests/selectors/selectors_combine_simple.air rename to air-script/src/tests/selectors/selectors_combine_simple.air diff --git a/air-script/tests/selectors/selectors_combine_simple.rs b/air-script/src/tests/selectors/selectors_combine_simple.rs similarity index 100% rename from air-script/tests/selectors/selectors_combine_simple.rs rename to air-script/src/tests/selectors/selectors_combine_simple.rs diff --git a/air-script/src/tests/selectors/selectors_combine_simple_plonky3.rs b/air-script/src/tests/selectors/selectors_combine_simple_plonky3.rs new file mode 100644 index 000000000..ed223440f --- /dev/null +++ b/air-script/src/tests/selectors/selectors_combine_simple_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct SelectorsAir; + +impl MidenAir for SelectorsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[3].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[1].clone().into() - main_current[2].clone().into()); + builder.when_transition().assert_zero(main_current[3].clone().into() * (main_next[0].clone().into() - (main_current[0].clone().into() + main_current[1].clone().into())) + (AB::Expr::ONE - main_current[3].clone().into()) * (main_next[0].clone().into() - main_current[0].clone().into() * main_current[1].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/selectors/selectors_combine_with_list_comprehensions.air b/air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.air similarity index 100% rename from air-script/tests/selectors/selectors_combine_with_list_comprehensions.air rename to air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.air diff --git a/air-script/tests/selectors/selectors_combine_with_list_comprehensions.rs b/air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.rs similarity index 100% rename from air-script/tests/selectors/selectors_combine_with_list_comprehensions.rs rename to air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.rs diff --git a/air-script/src/tests/selectors/selectors_combine_with_list_comprehensions_plonky3.rs b/air-script/src/tests/selectors/selectors_combine_with_list_comprehensions_plonky3.rs new file mode 100644 index 000000000..d482f2c9d --- /dev/null +++ b/air-script/src/tests/selectors/selectors_combine_with_list_comprehensions_plonky3.rs @@ -0,0 +1,44 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 6; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 1; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct SelectorsAir; + +impl MidenAir for SelectorsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[5].clone().into()); + + // Main integrity/transition constraints + builder.assert_zero((main_current[0].clone().into() + (AB::Expr::ONE - main_current[0].clone().into()) * main_current[1].clone().into()) * main_current[3].clone().into() + (AB::Expr::ONE - main_current[0].clone().into()) * (AB::Expr::ONE - main_current[1].clone().into()) * (main_current[4].clone().into() - AB::Expr::from_u64(8))); + builder.assert_zero((AB::Expr::ONE - main_current[0].clone().into()) * (main_current[5].clone().into() - AB::Expr::from_u64(8)) + main_current[0].clone().into() * (main_current[4].clone().into() - AB::Expr::from_u64(2))); + builder.assert_zero(main_current[0].clone().into() * (main_current[5].clone().into() - AB::Expr::from_u64(4)) + (AB::Expr::ONE - main_current[0].clone().into()) * main_current[1].clone().into() * (main_current[4].clone().into() - AB::Expr::from_u64(6))); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/selectors/selectors_plonky3.rs b/air-script/src/tests/selectors/selectors_plonky3.rs new file mode 100644 index 000000000..e16eb2f28 --- /dev/null +++ b/air-script/src/tests/selectors/selectors_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct SelectorsAir; + +impl MidenAir for SelectorsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[3].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_current[0].clone().into() * (AB::Expr::ONE - main_current[1].clone().into()) * main_next[3].clone().into()); + builder.when_transition().assert_zero(main_current[0].clone().into() * main_current[1].clone().into() * main_current[2].clone().into() * (main_next[3].clone().into() - main_current[3].clone().into()) + (AB::Expr::ONE - main_current[1].clone().into()) * (AB::Expr::ONE - main_current[2].clone().into()) * (main_next[3].clone().into() - AB::Expr::ONE)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/tests/selectors/selectors_with_evaluators.air b/air-script/src/tests/selectors/selectors_with_evaluators.air similarity index 100% rename from air-script/tests/selectors/selectors_with_evaluators.air rename to air-script/src/tests/selectors/selectors_with_evaluators.air diff --git a/air-script/tests/selectors/selectors_with_evaluators.rs b/air-script/src/tests/selectors/selectors_with_evaluators.rs similarity index 100% rename from air-script/tests/selectors/selectors_with_evaluators.rs rename to air-script/src/tests/selectors/selectors_with_evaluators.rs diff --git a/air-script/src/tests/selectors/selectors_with_evaluators_plonky3.rs b/air-script/src/tests/selectors/selectors_with_evaluators_plonky3.rs new file mode 100644 index 000000000..42affcf13 --- /dev/null +++ b/air-script/src/tests/selectors/selectors_with_evaluators_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct SelectorsAir; + +impl MidenAir for SelectorsAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[3].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_current[0].clone().into() * (AB::Expr::ONE - main_current[1].clone().into()) * main_next[3].clone().into()); + builder.when_transition().assert_zero(main_current[1].clone().into() * main_current[2].clone().into() * main_current[0].clone().into() * (main_next[3].clone().into() - main_current[3].clone().into()) + (AB::Expr::ONE - main_current[1].clone().into()) * (AB::Expr::ONE - main_current[2].clone().into()) * (main_next[3].clone().into() - AB::Expr::ONE)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/selectors/test_air_plonky3.rs b/air-script/src/tests/selectors/test_air_plonky3.rs new file mode 100644 index 000000000..c8cfbffa2 --- /dev/null +++ b/air-script/src/tests/selectors/test_air_plonky3.rs @@ -0,0 +1,53 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::selectors::selectors_with_evaluators_plonky3::{MAIN_WIDTH, SelectorsAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + rows[i][3] = F::ONE; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, SelectorsAir); diff --git a/air-script/tests/selectors/test_air.rs b/air-script/src/tests/selectors/test_air_winterfell.rs similarity index 78% rename from air-script/tests/selectors/test_air.rs rename to air-script/src/tests/selectors/test_air_winterfell.rs index 4f2f3378b..3bb3ac337 100644 --- a/air-script/tests/selectors/test_air.rs +++ b/air-script/src/tests/selectors/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - selectors::selectors_with_evaluators::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::selectors::selectors_with_evaluators::PublicInputs, }; #[derive(Clone)] @@ -40,9 +40,9 @@ impl AirTester for SelectorsAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_selectors_with_evaluators_air, - crate::selectors::selectors_with_evaluators::SelectorsAir, + crate::tests::selectors::selectors_with_evaluators::SelectorsAir, SelectorsAirTester, 1024 ); diff --git a/air-script/src/tests/system/mod.rs b/air-script/src/tests/system/mod.rs new file mode 100644 index 000000000..7d8522c99 --- /dev/null +++ b/air-script/src/tests/system/mod.rs @@ -0,0 +1,10 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +mod system; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod system_plonky3; + +mod test_air_plonky3; +mod test_air_winterfell; diff --git a/air-script/tests/system/system.air b/air-script/src/tests/system/system.air similarity index 100% rename from air-script/tests/system/system.air rename to air-script/src/tests/system/system.air diff --git a/air-script/tests/system/system.rs b/air-script/src/tests/system/system.rs similarity index 100% rename from air-script/tests/system/system.rs rename to air-script/src/tests/system/system.rs diff --git a/air-script/src/tests/system/system_plonky3.rs b/air-script/src/tests/system/system_plonky3.rs new file mode 100644 index 000000000..acc5e98be --- /dev/null +++ b/air-script/src/tests/system/system_plonky3.rs @@ -0,0 +1,42 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 3; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct SystemAir; + +impl MidenAir for SystemAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[0].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[0].clone().into() - (main_current[0].clone().into() + AB::Expr::ONE)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/system/test_air_plonky3.rs b/air-script/src/tests/system/test_air_plonky3.rs new file mode 100644 index 000000000..952a7412f --- /dev/null +++ b/air-script/src/tests/system/test_air_plonky3.rs @@ -0,0 +1,50 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::system::system_plonky3::{MAIN_WIDTH, SystemAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + + // Update current row based on previous values + rows[i][0] = col_0_prev + F::ONE; + rows[i][1] = col_1_prev; + rows[i][2] = col_2_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, SystemAir); diff --git a/air-script/tests/system/test_air.rs b/air-script/src/tests/system/test_air_winterfell.rs similarity index 76% rename from air-script/tests/system/test_air.rs rename to air-script/src/tests/system/test_air_winterfell.rs index 3aa6815c6..99a717f78 100644 --- a/air-script/tests/system/test_air.rs +++ b/air-script/src/tests/system/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - system::system::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::system::system::PublicInputs, }; #[derive(Clone)] @@ -39,4 +39,9 @@ impl AirTester for SystemAirTester { } } -generate_air_test!(test_system_air, crate::system::system::SystemAir, SystemAirTester, 1024); +generate_air_winterfell_test!( + test_system_air, + crate::tests::system::system::SystemAir, + SystemAirTester, + 1024 +); diff --git a/air-script/src/tests/trace_col_groups/mod.rs b/air-script/src/tests/trace_col_groups/mod.rs new file mode 100644 index 000000000..c2bb1aa0a --- /dev/null +++ b/air-script/src/tests/trace_col_groups/mod.rs @@ -0,0 +1,10 @@ +mod test_air_plonky3; +mod test_air_winterfell; + +#[rustfmt::skip] +#[allow(clippy::all)] +mod trace_col_groups; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod trace_col_groups_plonky3; diff --git a/air-script/src/tests/trace_col_groups/test_air_plonky3.rs b/air-script/src/tests/trace_col_groups/test_air_plonky3.rs new file mode 100644 index 000000000..920a7f3db --- /dev/null +++ b/air-script/src/tests/trace_col_groups/test_air_plonky3.rs @@ -0,0 +1,68 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::trace_col_groups::trace_col_groups_plonky3::{MAIN_WIDTH, TraceColGroupAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ZERO; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + rows[0][4] = F::ZERO; + rows[0][5] = F::ZERO; + rows[0][6] = F::ZERO; + rows[0][7] = F::ZERO; + rows[0][8] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + let col_4_prev = rows[i - 1][4]; + let col_5_prev = rows[i - 1][5]; + let col_6_prev = rows[i - 1][6]; + let col_7_prev = rows[i - 1][7]; + let col_8_prev = rows[i - 1][8]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = col_1_prev - F::ONE; + rows[i][2] = col_2_prev + F::ONE; + rows[i][3] = col_3_prev; + rows[i][4] = col_4_prev; + rows[i][5] = col_5_prev; + rows[i][6] = col_6_prev; + rows[i][7] = col_7_prev; + rows[i][8] = col_8_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 16] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, TraceColGroupAir); diff --git a/air-script/tests/trace_col_groups/test_air.rs b/air-script/src/tests/trace_col_groups/test_air_winterfell.rs similarity index 82% rename from air-script/tests/trace_col_groups/test_air.rs rename to air-script/src/tests/trace_col_groups/test_air_winterfell.rs index cb62434e9..53a5af721 100644 --- a/air-script/tests/trace_col_groups/test_air.rs +++ b/air-script/src/tests/trace_col_groups/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - trace_col_groups::trace_col_groups::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::trace_col_groups::trace_col_groups::PublicInputs, }; #[derive(Clone)] @@ -46,9 +46,9 @@ impl AirTester for TraceColGroupAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_trace_col_groups_air, - crate::trace_col_groups::trace_col_groups::TraceColGroupAir, + crate::tests::trace_col_groups::trace_col_groups::TraceColGroupAir, TraceColGroupAirTester, 1024 ); diff --git a/air-script/tests/trace_col_groups/trace_col_groups.air b/air-script/src/tests/trace_col_groups/trace_col_groups.air similarity index 100% rename from air-script/tests/trace_col_groups/trace_col_groups.air rename to air-script/src/tests/trace_col_groups/trace_col_groups.air diff --git a/air-script/tests/trace_col_groups/trace_col_groups.rs b/air-script/src/tests/trace_col_groups/trace_col_groups.rs similarity index 100% rename from air-script/tests/trace_col_groups/trace_col_groups.rs rename to air-script/src/tests/trace_col_groups/trace_col_groups.rs diff --git a/air-script/src/tests/trace_col_groups/trace_col_groups_plonky3.rs b/air-script/src/tests/trace_col_groups/trace_col_groups_plonky3.rs new file mode 100644 index 000000000..5a0b38979 --- /dev/null +++ b/air-script/src/tests/trace_col_groups/trace_col_groups_plonky3.rs @@ -0,0 +1,43 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 9; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 0; +pub const PERIOD: usize = 0; +pub const NUM_PUBLIC_VALUES: usize = 16; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct TraceColGroupAir; + +impl MidenAir for TraceColGroupAir { + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[8].clone().into()); + + // Main integrity/transition constraints + builder.when_transition().assert_zero(main_next[2].clone().into() - (main_current[2].clone().into() + AB::Expr::ONE)); + builder.when_transition().assert_zero(main_next[1].clone().into() - (main_current[1].clone().into() - AB::Expr::ONE)); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/variables/mod.rs b/air-script/src/tests/variables/mod.rs new file mode 100644 index 000000000..98685f537 --- /dev/null +++ b/air-script/src/tests/variables/mod.rs @@ -0,0 +1,9 @@ +mod test_air_plonky3; +mod test_air_winterfell; +#[rustfmt::skip] +#[allow(clippy::all)] +mod variables; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(unused_imports)] +mod variables_plonky3; diff --git a/air-script/src/tests/variables/test_air_plonky3.rs b/air-script/src/tests/variables/test_air_plonky3.rs new file mode 100644 index 000000000..356a4d9fb --- /dev/null +++ b/air-script/src/tests/variables/test_air_plonky3.rs @@ -0,0 +1,53 @@ +use p3_field::PrimeField64; +use p3_miden_air::RowMajorMatrix; + +use crate::{ + generate_air_plonky3_test_with_airscript_traits, + tests::variables::variables_plonky3::{MAIN_WIDTH, VariablesAir}, +}; + +pub fn generate_trace_rows(inputs: Vec) -> RowMajorMatrix { + let num_rows = 512; + let trace_length = num_rows * MAIN_WIDTH; + + let mut long_trace = F::zero_vec(trace_length); + + let mut trace = RowMajorMatrix::new(long_trace, MAIN_WIDTH); + + let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[F; MAIN_WIDTH]>() }; + assert!(prefix.is_empty(), "Alignment should match"); + assert!(suffix.is_empty(), "Alignment should match"); + assert_eq!(rows.len(), num_rows); + + // Initialize first row + rows[0][0] = F::ONE; + rows[0][1] = F::ZERO; + rows[0][2] = F::ZERO; + rows[0][3] = F::ZERO; + + // Fill subsequent rows using direct access to the rows array + for i in 1..num_rows { + let col_0_prev = rows[i - 1][0]; + let col_1_prev = rows[i - 1][1]; + let col_2_prev = rows[i - 1][2]; + let col_3_prev = rows[i - 1][3]; + + // Update current row based on previous values + rows[i][0] = col_0_prev; + rows[i][1] = F::ONE; + rows[i][2] = col_2_prev; + rows[i][3] = col_3_prev; + } + + trace +} + +fn generate_inputs() -> Vec { + vec![1; 32] +} + +fn generate_var_len_pub_inputs<'a>() -> Vec>> { + vec![] +} + +generate_air_plonky3_test_with_airscript_traits!(test_air_plonky3, VariablesAir); diff --git a/air-script/tests/variables/test_air.rs b/air-script/src/tests/variables/test_air_winterfell.rs similarity index 81% rename from air-script/tests/variables/test_air.rs rename to air-script/src/tests/variables/test_air_winterfell.rs index c45e18516..d384975b3 100644 --- a/air-script/tests/variables/test_air.rs +++ b/air-script/src/tests/variables/test_air_winterfell.rs @@ -3,9 +3,9 @@ use winter_math::fields::f64::BaseElement as Felt; use winterfell::{Trace, TraceTable}; use crate::{ - generate_air_test, - helpers::{AirTester, MyTraceTable}, - variables::variables::PublicInputs, + generate_air_winterfell_test, + test_utils::winterfell_traits::{AirTester, MyTraceTable}, + tests::variables::variables::PublicInputs, }; #[derive(Clone)] @@ -40,9 +40,9 @@ impl AirTester for VariablesAirTester { } } -generate_air_test!( +generate_air_winterfell_test!( test_variables_air, - crate::variables::variables::VariablesAir, + crate::tests::variables::variables::VariablesAir, VariablesAirTester, 1024 ); diff --git a/air-script/tests/variables/variables.air b/air-script/src/tests/variables/variables.air similarity index 100% rename from air-script/tests/variables/variables.air rename to air-script/src/tests/variables/variables.air diff --git a/air-script/tests/variables/variables.rs b/air-script/src/tests/variables/variables.rs similarity index 100% rename from air-script/tests/variables/variables.rs rename to air-script/src/tests/variables/variables.rs diff --git a/air-script/src/tests/variables/variables_plonky3.rs b/air-script/src/tests/variables/variables_plonky3.rs new file mode 100644 index 000000000..57cbc460d --- /dev/null +++ b/air-script/src/tests/variables/variables_plonky3.rs @@ -0,0 +1,59 @@ +use p3_field::{ExtensionField, Field, PrimeCharacteristicRing}; +use p3_matrix::Matrix; +use p3_matrix::dense::RowMajorMatrixView; +use p3_matrix::stack::VerticalPair; +use p3_miden_air::{BusType, MidenAir, MidenAirBuilder, RowMajorMatrix}; + +pub const MAIN_WIDTH: usize = 4; +pub const AUX_WIDTH: usize = 0; +pub const NUM_PERIODIC_VALUES: usize = 1; +pub const PERIOD: usize = 8; +pub const NUM_PUBLIC_VALUES: usize = 32; +pub const MAX_BETA_CHALLENGE_POWER: usize = 0; + +pub struct VariablesAir; + +impl MidenAir for VariablesAir +where F: Field, + EF: ExtensionField, +{ + fn width(&self) -> usize { + MAIN_WIDTH + } + + fn num_public_values(&self) -> usize { + NUM_PUBLIC_VALUES + } + + fn periodic_table(&self) -> Vec> { + vec![ + vec![F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(1), F::from_u64(0)], + ] + } + + fn eval(&self, builder: &mut AB) + where AB: MidenAirBuilder, + { + let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect("Wrong number of public values"); + let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect("Wrong number of periodic values"); + // Note: for now, we do not have any preprocessed values + // let preprocessed = builder.preprocessed(); + let main = builder.main(); + let (main_current, main_next) = ( + main.row_slice(0).unwrap(), + main.row_slice(1).unwrap(), + ); + + // Main boundary constraints + builder.when_first_row().assert_zero(main_current[1].clone().into()); + builder.when_last_row().assert_zero(main_current[1].clone().into() - AB::Expr::ONE); + + // Main integrity/transition constraints + builder.assert_zero(main_current[0].clone().into() * main_current[0].clone().into() - main_current[0].clone().into()); + builder.when_transition().assert_zero_ext(AB::ExprEF::from(periodic_values[0].clone().into()) * (AB::ExprEF::from(main_next[0].clone().into()) - AB::ExprEF::from(main_current[0].clone().into()))); + builder.assert_zero((AB::Expr::ONE - main_current[0].clone().into()) * (main_current[3].clone().into() - main_current[1].clone().into() - main_current[2].clone().into()) - (AB::Expr::from_u64(6) - (AB::Expr::from_u64(7) - main_current[0].clone().into()))); + builder.when_transition().assert_zero(main_current[0].clone().into() * (main_current[3].clone().into() - main_current[1].clone().into() * main_current[2].clone().into()) - (AB::Expr::ONE - main_next[0].clone().into())); + + // Aux integrity/transition constraints + } +} \ No newline at end of file diff --git a/air-script/src/tests/winterfell.rs b/air-script/src/tests/winterfell.rs new file mode 100644 index 000000000..1c2b0b6af --- /dev/null +++ b/air-script/src/tests/winterfell.rs @@ -0,0 +1,397 @@ +use expect_test::expect_file; + +use crate::test_utils::codegen::{Target, Test}; + +#[test] +fn binary() { + let generated_air = Test::new("src/tests/binary/binary.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["binary/binary.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn bitwise() { + let generated_air = Test::new("src/tests/bitwise/bitwise.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["bitwise/bitwise.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_complex() { + let generated_air = Test::new("src/tests/buses/buses_complex.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_complex.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_simple() { + let generated_air = Test::new("src/tests/buses/buses_simple.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_simple_with_evaluators() { + let generated_air = Test::new("src/tests/buses/buses_simple_with_evaluators.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_both() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_both.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_both.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_first() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_first.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_first.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn buses_varlen_boundary_last() { + let generated_air = Test::new("src/tests/buses/buses_varlen_boundary_last.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["buses/buses_varlen_boundary_last.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn comprehension_periodic_binding() { + // Test that comprehension bindings over periodic columns are typed as Local, not PeriodicColumn + // This pattern is used when iterating over a vector containing periodic column references + let generated_air = Test::new( + "src/tests/comprehension_periodic_binding/comprehension_periodic_binding.air".to_string(), + ) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["comprehension_periodic_binding/comprehension_periodic_binding.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn computed_indices_complex() { + let generated_air = + Test::new("src/tests/computed_indices/computed_indices_complex.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["computed_indices/computed_indices_complex.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn computed_indices_simple() { + let generated_air = + Test::new("src/tests/computed_indices/computed_indices_simple.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["computed_indices/computed_indices_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constant_in_range() { + let generated_air = Test::new("src/tests/constant_in_range/constant_in_range.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["constant_in_range/constant_in_range.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constants() { + let generated_air = Test::new("src/tests/constants/constants.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["constants/constants.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn constraint_comprehension() { + let generated_air = + Test::new("src/tests/constraint_comprehension/constraint_comprehension.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["constraint_comprehension/constraint_comprehension.rs"]; + expected.assert_eq(&generated_air); + + let generated_air = + Test::new("src/tests/constraint_comprehension/cc_with_evaluators.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["constraint_comprehension/constraint_comprehension.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn cross_module_constants() { + // Test that constants used in comprehension iterables work across module boundaries + let generated_air = + Test::new("src/tests/cross_module_constants/cross_module_constants.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["cross_module_constants/cross_mod_constants.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn evaluators_nested_slice_call() { + let generated_air = + Test::new("src/tests/evaluators/evaluators_nested_slice_call.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators_nested_slice_call.rs"]; + expected.assert_eq(&generated_air); +} + +// TODO: add support for nested slicing in general expressions. +// +// #[test] +// fn evaluators_slice_slicing() { +// let generated_air = +// Test::new("src/tests/evaluators/evaluators_slice_slicing.air".to_string()) +// .transpile(Target::Winterfell) +// .unwrap(); +// +// let expected = expect_file!["evaluators/evaluators_slice_slicing.rs"]; +// expected.assert_eq(&generated_air); +// } + +#[test] +fn evaluators_slice() { + let generated_air = Test::new("src/tests/evaluators/evaluators_slice.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators_slice.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn evaluators() { + let generated_air = Test::new("src/tests/evaluators/evaluators.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["evaluators/evaluators.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn fibonacci() { + let generated_air = Test::new("src/tests/fibonacci/fibonacci.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["fibonacci/fibonacci.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_complex() { + let generated_air = Test::new("src/tests/functions/functions_complex.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["functions/functions_complex.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_simple() { + let generated_air = Test::new("src/tests/functions/functions_simple.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["functions/functions_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn functions_simple_inlined() { + // make sure that the constraints generated using inlined functions are the same as the ones + // generated using regular functions + let generated_air = Test::new("src/tests/functions/inlined_functions_simple.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["functions/functions_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn indexed_trace_access() { + let generated_air = + Test::new("src/tests/indexed_trace_access/indexed_trace_access.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["indexed_trace_access/indexed_trace_access.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_comprehension() { + let generated_air = + Test::new("src/tests/list_comprehension/list_comprehension.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["list_comprehension/list_comprehension.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_comprehension_nested() { + let generated_air = + Test::new("src/tests/list_comprehension/list_comprehension_nested.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["list_comprehension/list_comprehension_nested.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn list_folding() { + let generated_air = Test::new("src/tests/list_folding/list_folding.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["list_folding/list_folding.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn periodic_columns() { + let generated_air = Test::new("src/tests/periodic_columns/periodic_columns.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["periodic_columns/periodic_columns.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn pub_inputs() { + let generated_air = Test::new("src/tests/pub_inputs/pub_inputs.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["pub_inputs/pub_inputs.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors() { + let generated_air = Test::new("src/tests/selectors/selectors.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["selectors/selectors.rs"]; + expected.assert_eq(&generated_air); + + let generated_air = Test::new("src/tests/selectors/selectors_with_evaluators.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["selectors/selectors_with_evaluators.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_simple() { + let generated_air = Test::new("src/tests/selectors/selectors_combine_simple.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_simple.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_complex() { + let generated_air = Test::new("src/tests/selectors/selectors_combine_complex.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_complex.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn selectors_combine_with_list_comprehensions() { + let generated_air = + Test::new("src/tests/selectors/selectors_combine_with_list_comprehensions.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["selectors/selectors_combine_with_list_comprehensions.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn system() { + let generated_air = Test::new("src/tests/system/system.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["system/system.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn trace_col_groups() { + let generated_air = Test::new("src/tests/trace_col_groups/trace_col_groups.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["trace_col_groups/trace_col_groups.rs"]; + expected.assert_eq(&generated_air); +} + +#[test] +fn variables() { + let generated_air = Test::new("src/tests/variables/variables.air".to_string()) + .transpile(Target::Winterfell) + .unwrap(); + + let expected = expect_file!["variables/variables.rs"]; + expected.assert_eq(&generated_air); +} diff --git a/air-script/tests/binary/mod.rs b/air-script/tests/binary/mod.rs deleted file mode 100644 index 0582511b7..000000000 --- a/air-script/tests/binary/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod binary; -mod test_air; diff --git a/air-script/tests/bitwise/mod.rs b/air-script/tests/bitwise/mod.rs deleted file mode 100644 index 5798d90dc..000000000 --- a/air-script/tests/bitwise/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod bitwise; -mod test_air; diff --git a/air-script/tests/buses/buses_varlen_boundary_last.air b/air-script/tests/buses/buses_varlen_boundary_last.air deleted file mode 100644 index 9819cbc21..000000000 --- a/air-script/tests/buses/buses_varlen_boundary_last.air +++ /dev/null @@ -1,29 +0,0 @@ -def BusesAir - -trace_columns { - main: [a], -} - -buses { - multiset p, - logup q, -} - -public_inputs { - outputs: [[2]], -} - -boundary_constraints { - enf p.first = null; - enf q.first = null; - enf p.last = outputs; - enf q.last = outputs; -} - -integrity_constraints { - p.insert(1) when a; - p.remove(1) when (a - 1); - q.insert(1, 2) when a; - q.insert(1, 2) when a; - q.remove(1, 2) with 2; -} diff --git a/air-script/tests/buses/mod.rs b/air-script/tests/buses/mod.rs deleted file mode 100644 index 933d13ee5..000000000 --- a/air-script/tests/buses/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod buses_complex; -#[rustfmt::skip] -#[allow(clippy::all)] -mod buses_simple; -#[rustfmt::skip] -#[allow(clippy::all)] -mod buses_varlen_boundary_both; -#[rustfmt::skip] -#[allow(clippy::all)] -mod buses_varlen_boundary_first; -#[rustfmt::skip] -#[allow(clippy::all)] -mod buses_varlen_boundary_last; -mod test_air; diff --git a/air-script/tests/codegen/mod.rs b/air-script/tests/codegen/mod.rs deleted file mode 100644 index d802f40b1..000000000 --- a/air-script/tests/codegen/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod helpers; -mod winterfell; diff --git a/air-script/tests/codegen/winterfell.rs b/air-script/tests/codegen/winterfell.rs deleted file mode 100644 index 2fa96ccb8..000000000 --- a/air-script/tests/codegen/winterfell.rs +++ /dev/null @@ -1,367 +0,0 @@ -use expect_test::expect_file; - -use super::helpers::{Target, Test}; - -#[test] -fn binary() { - let generated_air = Test::new("tests/binary/binary.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../binary/binary.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn bitwise() { - let generated_air = Test::new("tests/bitwise/bitwise.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../bitwise/bitwise.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_complex() { - let generated_air = Test::new("tests/buses/buses_complex.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_complex.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_simple() { - let generated_air = Test::new("tests/buses/buses_simple.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_simple_with_evaluators() { - let generated_air = Test::new("tests/buses/buses_simple_with_evaluators.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_varlen_boundary_both() { - let generated_air = Test::new("tests/buses/buses_varlen_boundary_both.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_varlen_boundary_both.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_varlen_boundary_first() { - let generated_air = Test::new("tests/buses/buses_varlen_boundary_first.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_varlen_boundary_first.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn buses_varlen_boundary_last() { - let generated_air = Test::new("tests/buses/buses_varlen_boundary_last.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../buses/buses_varlen_boundary_last.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn computed_indices_complex() { - let generated_air = - Test::new("tests/computed_indices/computed_indices_complex.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../computed_indices/computed_indices_complex.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn computed_indices_simple() { - let generated_air = Test::new("tests/computed_indices/computed_indices_simple.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../computed_indices/computed_indices_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn constant_in_range() { - let generated_air = Test::new("tests/constant_in_range/constant_in_range.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../constant_in_range/constant_in_range.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn constants() { - let generated_air = Test::new("tests/constants/constants.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../constants/constants.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn constraint_comprehension() { - let generated_air = - Test::new("tests/constraint_comprehension/constraint_comprehension.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../constraint_comprehension/constraint_comprehension.rs"]; - expected.assert_eq(&generated_air); - - let generated_air = - Test::new("tests/constraint_comprehension/cc_with_evaluators.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../constraint_comprehension/constraint_comprehension.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn evaluators() { - let generated_air = Test::new("tests/evaluators/evaluators.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../evaluators/evaluators.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn evaluators_slice() { - let generated_air = Test::new("tests/evaluators/evaluators_slice.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../evaluators/evaluators_slice.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn evaluators_nested_slice_call() { - let generated_air = Test::new("tests/evaluators/evaluators_nested_slice_call.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../evaluators/evaluators_nested_slice_call.rs"]; - expected.assert_eq(&generated_air); -} - -// TODO: add support for nested slicing in general expressions. -// -// #[test] -// fn evaluators_slice_slicing() { -// let generated_air = Test::new("tests/evaluators/evaluators_slice_slicing.air".to_string()) -// .transpile(Target::Winterfell) -// .unwrap(); -// -// let expected = expect_file!["../evaluators/evaluators_slice_slicing.rs"]; -// expected.assert_eq(&generated_air); -// } - -#[test] -fn fibonacci() { - let generated_air = Test::new("tests/fibonacci/fibonacci.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../fibonacci/fibonacci.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn functions_complex() { - let generated_air = Test::new("tests/functions/functions_complex.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../functions/functions_complex.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn functions_simple() { - let generated_air = Test::new("tests/functions/functions_simple.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../functions/functions_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn functions_simple_inlined() { - // make sure that the constraints generated using inlined functions are the same as the ones - // generated using regular functions - let generated_air = Test::new("tests/functions/inlined_functions_simple.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../functions/functions_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn indexed_trace_access() { - let generated_air = - Test::new("tests/indexed_trace_access/indexed_trace_access.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../indexed_trace_access/indexed_trace_access.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn list_comprehension() { - let generated_air = Test::new("tests/list_comprehension/list_comprehension.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../list_comprehension/list_comprehension.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn list_comprehension_nested() { - let generated_air = - Test::new("tests/list_comprehension/list_comprehension_nested.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../list_comprehension/list_comprehension_nested.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn list_folding() { - let generated_air = Test::new("tests/list_folding/list_folding.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../list_folding/list_folding.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn periodic_columns() { - let generated_air = Test::new("tests/periodic_columns/periodic_columns.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../periodic_columns/periodic_columns.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn pub_inputs() { - let generated_air = Test::new("tests/pub_inputs/pub_inputs.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../pub_inputs/pub_inputs.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn selectors() { - let generated_air = Test::new("tests/selectors/selectors.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../selectors/selectors.rs"]; - expected.assert_eq(&generated_air); - - let generated_air = Test::new("tests/selectors/selectors_with_evaluators.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../selectors/selectors_with_evaluators.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn selectors_combine_simple() { - let generated_air = Test::new("tests/selectors/selectors_combine_simple.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../selectors/selectors_combine_simple.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn selectors_combine_complex() { - let generated_air = Test::new("tests/selectors/selectors_combine_complex.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../selectors/selectors_combine_complex.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn selectors_combine_with_list_comprehensions() { - let generated_air = - Test::new("tests/selectors/selectors_combine_with_list_comprehensions.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../selectors/selectors_combine_with_list_comprehensions.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn system() { - let generated_air = Test::new("tests/system/system.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../system/system.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn trace_col_groups() { - let generated_air = Test::new("tests/trace_col_groups/trace_col_groups.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../trace_col_groups/trace_col_groups.rs"]; - expected.assert_eq(&generated_air); -} - -#[test] -fn variables() { - let generated_air = Test::new("tests/variables/variables.air".to_string()) - .transpile(Target::Winterfell) - .unwrap(); - - let expected = expect_file!["../variables/variables.rs"]; - expected.assert_eq(&generated_air); -} diff --git a/air-script/tests/computed_indices/mod.rs b/air-script/tests/computed_indices/mod.rs deleted file mode 100644 index 5f582b136..000000000 --- a/air-script/tests/computed_indices/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod computed_indices_complex; -#[rustfmt::skip] -#[allow(clippy::all)] -mod computed_indices_simple; -mod test_air; diff --git a/air-script/tests/constant_in_range/mod.rs b/air-script/tests/constant_in_range/mod.rs deleted file mode 100644 index a071cd958..000000000 --- a/air-script/tests/constant_in_range/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod constant_in_range; -mod test_air; diff --git a/air-script/tests/constants/mod.rs b/air-script/tests/constants/mod.rs deleted file mode 100644 index 526f76f5e..000000000 --- a/air-script/tests/constants/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod constants; -mod test_air; diff --git a/air-script/tests/constraint_comprehension/mod.rs b/air-script/tests/constraint_comprehension/mod.rs deleted file mode 100644 index 16222fbd1..000000000 --- a/air-script/tests/constraint_comprehension/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod constraint_comprehension; -mod test_air; diff --git a/air-script/tests/fibonacci/mod.rs b/air-script/tests/fibonacci/mod.rs deleted file mode 100644 index 8fa9af072..000000000 --- a/air-script/tests/fibonacci/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod fibonacci; -mod test_air; diff --git a/air-script/tests/functions/mod.rs b/air-script/tests/functions/mod.rs deleted file mode 100644 index 4a85b16cc..000000000 --- a/air-script/tests/functions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod functions_complex; -#[rustfmt::skip] -#[allow(clippy::all)] -mod functions_simple; -mod test_air; diff --git a/air-script/tests/helpers/macros.rs b/air-script/tests/helpers/macros.rs deleted file mode 100644 index e134b8d48..000000000 --- a/air-script/tests/helpers/macros.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Helper macros for test generation - -/// Generates an AIR test function with the standard boilerplate -/// -/// # Arguments -/// * `test_name` - The identifier for the test function (e.g., `test_binary_air`) -/// * `air_name` - The identifier for the AIR struct (e.g., `BinaryAir`) -/// * `tester_name` - The identifier for the `AirTester` struct (e.g., `BinaryAirTester`) -/// * `trace_length` - The length of the trace for the test (e.g., `32` or `1024`) -#[macro_export] -macro_rules! generate_air_test { - ($test_name:ident, $air_name:path, $tester_name:ident, $trace_length:expr) => { - #[test] - fn $test_name() { - use winter_math::fields::f64::BaseElement as Felt; - let air_tester = Box::new($tester_name {}); - let length = $trace_length; - - let main_trace = air_tester.build_main_trace(length); - let aux_trace = air_tester.build_aux_trace(length); - let pub_inputs = air_tester.public_inputs(); - let trace_info = air_tester.build_trace_info(length); - let options = air_tester.build_proof_options(); - - let air = <$air_name>::new(trace_info, pub_inputs, options); - main_trace.validate::<$air_name, Felt>(&air, aux_trace.as_ref()); - } - }; -} diff --git a/air-script/tests/indexed_trace_access/mod.rs b/air-script/tests/indexed_trace_access/mod.rs deleted file mode 100644 index 7aee9d4e8..000000000 --- a/air-script/tests/indexed_trace_access/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod indexed_trace_access; -mod test_air; diff --git a/air-script/tests/list_comprehension/mod.rs b/air-script/tests/list_comprehension/mod.rs deleted file mode 100644 index 5f090bea2..000000000 --- a/air-script/tests/list_comprehension/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod list_comprehension; -mod test_air; diff --git a/air-script/tests/list_folding/mod.rs b/air-script/tests/list_folding/mod.rs deleted file mode 100644 index 5e992bc22..000000000 --- a/air-script/tests/list_folding/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod list_folding; -mod test_air; diff --git a/air-script/tests/periodic_columns/mod.rs b/air-script/tests/periodic_columns/mod.rs deleted file mode 100644 index fd9a501e7..000000000 --- a/air-script/tests/periodic_columns/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod periodic_columns; -mod test_air; diff --git a/air-script/tests/pub_inputs/mod.rs b/air-script/tests/pub_inputs/mod.rs deleted file mode 100644 index afb27e904..000000000 --- a/air-script/tests/pub_inputs/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod pub_inputs; -mod test_air; diff --git a/air-script/tests/selectors/mod.rs b/air-script/tests/selectors/mod.rs deleted file mode 100644 index 9e7f0b7ff..000000000 --- a/air-script/tests/selectors/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[rustfmt::skip] -#[allow(clippy::all)] -mod selectors; -#[rustfmt::skip] -#[allow(clippy::all)] -mod selectors_combine_simple; -#[rustfmt::skip] -#[allow(clippy::all)] -mod selectors_combine_complex; -#[rustfmt::skip] -#[allow(clippy::all)] -mod selectors_with_evaluators; -#[rustfmt::skip] -#[allow(clippy::all)] -mod selectors_combine_with_list_comprehensions; -mod test_air; diff --git a/air-script/tests/trace_col_groups/mod.rs b/air-script/tests/trace_col_groups/mod.rs deleted file mode 100644 index 706feb822..000000000 --- a/air-script/tests/trace_col_groups/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod test_air; -#[rustfmt::skip] -#[allow(clippy::all)] -mod trace_col_groups; diff --git a/air-script/tests/variables/mod.rs b/air-script/tests/variables/mod.rs deleted file mode 100644 index 31656aa8c..000000000 --- a/air-script/tests/variables/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod test_air; -#[rustfmt::skip] -#[allow(clippy::all)] -mod variables; diff --git a/air/src/ir/mod.rs b/air/src/ir/mod.rs index 0393558ba..06758392c 100644 --- a/air/src/ir/mod.rs +++ b/air/src/ir/mod.rs @@ -162,7 +162,7 @@ use alloc::collections::BTreeMap; use miden_diagnostics::{SourceSpan, Spanned}; -use crate::graph::AlgebraicGraph; +use crate::{NodeIndex, graph::AlgebraicGraph}; /// The intermediate representation of a complete AirScript program /// @@ -195,6 +195,18 @@ pub struct Air { /// /// Only their name, type, and the first and last boundary constraints are stored here. pub buses: BTreeMap, + /// Buses initial values used for auxiliary trace generation (indexed by bus index) + pub buses_initial_values: BTreeMap, + /// Buses transition expressions used for auxiliary trace generation (indexed by bus index) + /// The tuple contains the numerator and an optional denominator operation (p_prime = numerator + /// if None or numerator / denominator), computed in the `ExpandBuses` Air pass depending on + /// the bus type + /// - for multiset buses: p' = p * columns_inserted_in_bus / columns_removed_from_bus + /// - for logup buses, if u corresponds to the columns inserted when s1 and v to the columns + /// removed when s2 + /// q' = q + s1 / u - s2 / v + /// = (q * u * v + s1 * v - s2 * u) / (u * v) + pub buses_transitions: BTreeMap)>, } impl Default for Air { fn default() -> Self { @@ -217,6 +229,8 @@ impl Air { num_random_values: 0, constraints: Default::default(), buses: Default::default(), + buses_initial_values: Default::default(), + buses_transitions: Default::default(), } } diff --git a/air/src/passes/common_subexpression_elimination.rs b/air/src/passes/common_subexpression_elimination.rs index bc162b0da..38711c81c 100644 --- a/air/src/passes/common_subexpression_elimination.rs +++ b/air/src/passes/common_subexpression_elimination.rs @@ -32,6 +32,28 @@ impl Pass for CommonSubexpressionElimination<'_> { // Update constraints with the new node indices ir.constraints.renumber_and_deduplicate_constraints(&renumbering_map); + // Iterate over all bus transition expression and renumber their node indices + for (_, value) in ir.buses_initial_values.iter_mut() { + let new_value_index = *renumbering_map + .get(value) + .expect("Error: cannot find value index in renumbering map"); + *value = new_value_index; + } + + // Iterate over all bus transition expression and renumber their node indices + for (_, (numerator, denominator)) in ir.buses_transitions.iter_mut() { + let new_numerator_index = *renumbering_map + .get(numerator) + .expect("Error: cannot find numerator index in renumbering map"); + *numerator = new_numerator_index; + if let Some(denominator) = denominator { + let new_denominator_index = *renumbering_map + .get(denominator) + .expect("Error: cannot find denominator index in renumbering map"); + *denominator = new_denominator_index; + } + } + Ok(ir) } } diff --git a/air/src/passes/expand_buses.rs b/air/src/passes/expand_buses.rs index ff02487dd..fb5a33513 100644 --- a/air/src/passes/expand_buses.rs +++ b/air/src/passes/expand_buses.rs @@ -63,6 +63,7 @@ impl Pass for BusOpExpand<'_> { bus_ops, bus_access, bus_access_with_offset, + bus_index, ); }, BusType::Logup => { @@ -71,6 +72,7 @@ impl Pass for BusOpExpand<'_> { bus_ops, bus_access, bus_access_with_offset, + bus_index, ); }, } @@ -139,6 +141,19 @@ impl<'a> BusOpExpand<'a> { }; // Store the generated constraint ir.constraints.insert_constraint(TraceSegmentId::Aux, root, domain); + + // Also store the initial value for auxiliary trace generation + if boundary == Boundary::First { + // TODO: May be invalid? For now, we put its value to zero + if let BusBoundary::PublicInputTable(_) = bus_boundary { + let value = ir + .constraint_graph_mut() + .insert_node(Operation::Value(crate::Value::Constant(0))); + ir.buses_initial_values.insert(bus_index, value); + } else { + ir.buses_initial_values.insert(bus_index, value); + } + } } /// Helper function to expand the integrity constraint of a multiset bus @@ -148,6 +163,7 @@ impl<'a> BusOpExpand<'a> { bus_ops: Vec, bus_access: NodeIndex, bus_access_with_offset: NodeIndex, + bus_index: usize, ) { let graph = ir.constraint_graph_mut(); @@ -234,8 +250,14 @@ impl<'a> BusOpExpand<'a> { // 6. Create the resulting constraint and insert it into the graph let root = graph.insert_node(Operation::Sub(p_prod, p_prime_prod)); - ir.constraints - .insert_constraint(TraceSegmentId::Aux, root, ConstraintDomain::EveryRow); + ir.constraints.insert_constraint( + TraceSegmentId::Aux, + root, + ConstraintDomain::EveryFrame(2), + ); + + // Also store the expression to computed p_prime for auxiliary trace generation + ir.buses_transitions.insert(bus_index, (p_prod, p_prime_factor)); } /// Helper function to expand the integrity constraint of a logup bus @@ -245,6 +267,7 @@ impl<'a> BusOpExpand<'a> { bus_ops: Vec, bus_access: NodeIndex, bus_access_with_offset: NodeIndex, + bus_index: usize, ) { let graph = ir.constraint_graph_mut(); // Example: @@ -372,7 +395,23 @@ impl<'a> BusOpExpand<'a> { // 5. Create the resulting constraint let root = graph.insert_node(Operation::Sub(q_term, q_prime_term)); - ir.constraints - .insert_constraint(TraceSegmentId::Aux, root, ConstraintDomain::EveryRow); + + // Also store the expression to computed q_prime for auxiliary trace generation + // Note: TODO: Potentially adapt CSE to handle this properly, otherwise indices might + // change... + let numerator = match terms_removed_from_bus { + Some(terms_removed_from_bus) => { + graph.insert_node(Operation::Sub(q_term, terms_removed_from_bus)) + }, + None => q_term, + }; + + ir.constraints.insert_constraint( + TraceSegmentId::Aux, + root, + ConstraintDomain::EveryFrame(2), + ); + + ir.buses_transitions.insert(bus_index, (numerator, total_factors)); } } diff --git a/codegen/plonky3/Cargo.toml b/codegen/plonky3/Cargo.toml new file mode 100644 index 000000000..eb7766ea2 --- /dev/null +++ b/codegen/plonky3/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "air-codegen-plonky3" +version = "0.5.0" +description = "Plonky3 code generator for the AirScript language" +authors.workspace = true +readme = "README.md" +license.workspace = true +repository.workspace = true +categories = ["compilers", "cryptography"] +keywords = ["air", "stark", "plonky3", "zero-knowledge", "zkp"] +edition.workspace = true +rust-version.workspace = true + +[dependencies] +air-ir = { package = "air-ir", path = "../../air", version = "0.5" } +anyhow = { workspace = true } +codegen = "0.2" diff --git a/codegen/plonky3/README.md b/codegen/plonky3/README.md new file mode 100644 index 000000000..8ee666dfd --- /dev/null +++ b/codegen/plonky3/README.md @@ -0,0 +1,3 @@ +# Plonky3 Code Generator + +This crate contains a code generator targeting the [Plonky3 prover](https://github.com/Plonky3/Plonky3) Rust library. diff --git a/codegen/plonky3/src/air/boundary_constraints.rs b/codegen/plonky3/src/air/boundary_constraints.rs new file mode 100644 index 000000000..79a4f4f7a --- /dev/null +++ b/codegen/plonky3/src/air/boundary_constraints.rs @@ -0,0 +1,29 @@ +use air_ir::{Air, TraceSegmentId}; +use codegen::Function; + +use crate::air::graph::constraint_to_string; + +/// Adds the main boundary constraints to the generated code. +pub(super) fn add_main_boundary_constraints(eval_func: &mut Function, ir: &Air) { + eval_func.line(""); + eval_func.line("// Main boundary constraints"); + for constraint in ir.boundary_constraints(TraceSegmentId::Main) { + let assertion = constraint_to_string(ir, constraint, true); + eval_func.line(assertion); + } +} + +#[allow(dead_code)] +/// Adds the aux boundary constraints to the generated code. +pub(super) fn add_aux_boundary_constraints(eval_func: &mut Function, ir: &Air) { + eval_func.line(""); + eval_func.line("// Aux boundary constraints"); + for constraint in ir.boundary_constraints(TraceSegmentId::Aux) { + let assertion = constraint_to_string(ir, constraint, true); + eval_func.line(assertion); + + // TODO: better check assumptions on aux boundary constraints: + // - start only with empty buses, + // - end with values derived from builder.aux_bus_boundary_value() + } +} diff --git a/codegen/plonky3/src/air/graph.rs b/codegen/plonky3/src/air/graph.rs new file mode 100644 index 000000000..f9c27ed7c --- /dev/null +++ b/codegen/plonky3/src/air/graph.rs @@ -0,0 +1,222 @@ +use air_ir::{ + Air, ConstraintDomain, ConstraintRoot, NodeIndex, Operation, TraceAccess, TraceSegmentId, Value, +}; + +use crate::air::ElemType; + +// RUST STRING GENERATION FOR THE CONSTRAINT GRAPH +// ================================================================================================ + +/// Code generation trait for generating Rust code strings from IR types related to constraints and +/// the [AlgebraicGraph]. +pub trait Codegen { + fn to_string(&self, ir: &Air, elem_type: ElemType) -> String; +} + +impl Codegen for TraceAccess { + fn to_string(&self, _ir: &Air, elem_type: ElemType) -> String { + let frame = self.segment.to_string(); + let row_offset = match self.row_offset { + 0 => { + format!("current[{}]", self.column) + }, + 1 => { + format!("next[{}]", self.column) + }, + _ => panic!("Plonky3 doesn't support row offsets greater than 1."), + }; + match elem_type { + ElemType::Base => format!("{frame}_{row_offset}.clone().into()"), + ElemType::Ext => format!("AB::ExprEF::from({frame}_{row_offset}.clone().into())"), + ElemType::ExtFieldElem => format!("EF::from({frame}_{row_offset}.clone())"), + } + } +} + +impl Codegen for NodeIndex { + fn to_string(&self, ir: &Air, elem_type: ElemType) -> String { + let op = ir.constraint_graph().node(self).op(); + op.to_string(ir, elem_type) + } +} + +impl Codegen for Operation { + fn to_string(&self, ir: &Air, elem_type: ElemType) -> String { + match self { + Operation::Value(value) => value.to_string(ir, elem_type), + Operation::Add(..) => binary_op_to_string(ir, elem_type, self), + Operation::Sub(..) => binary_op_to_string(ir, elem_type, self), + Operation::Mul(..) => binary_op_to_string(ir, elem_type, self), + } + } +} + +impl Codegen for Value { + fn to_string(&self, ir: &Air, elem_type: ElemType) -> String { + match self { + Value::Constant(0) => match elem_type { + ElemType::Base => format!("AB::Expr::ZERO"), + ElemType::Ext => format!("AB::ExprEF::ZERO"), + ElemType::ExtFieldElem => format!("EF::ZERO"), + }, + Value::Constant(1) => match elem_type { + ElemType::Base => format!("AB::Expr::ONE"), + ElemType::Ext => format!("AB::ExprEF::ONE"), + ElemType::ExtFieldElem => format!("EF::ONE"), + }, + Value::Constant(value) => match elem_type { + ElemType::Base => format!("AB::Expr::from_u64({value})"), + ElemType::Ext => format!("AB::ExprEF::from_u64({value})"), + ElemType::ExtFieldElem => format!("EF::from_u64({value})"), + }, + Value::TraceAccess(trace_access) => trace_access.to_string(ir, elem_type), + Value::PublicInput(air_ir::PublicInputAccess { name, index }) => { + let get_public_input_offset = |name: &str| { + ir.public_inputs() + .take_while(|pi| pi.name() != name) + .map(|pi| pi.size()) + .sum::() + }; + format!("public_values[{}].into()", get_public_input_offset(name.as_str()) + index) + }, + Value::PeriodicColumn(pc) => { + let index = + ir.periodic_columns.iter().position(|(qid, _)| qid == &pc.name).unwrap(); + match elem_type { + ElemType::Base => format!("AB::Expr::from(periodic_values[{index}].clone())"), + ElemType::Ext => { + format!("AB::ExprEF::from(periodic_values[{index}].clone().into())") + }, + ElemType::ExtFieldElem => { + format!("AB::EF::from(periodic_values[{index}].clone())") + }, + } + }, + Value::PublicInputTable(public_input_table_access) => { + let idx = ir + .reduced_public_input_table_accesses() + .iter() + .position(|pi| pi == public_input_table_access) + .unwrap(); + format!("aux_bus_boundary_values[{idx}].into()") + }, + Value::RandomValue(idx) => { + if *idx == 0 { + if let ElemType::ExtFieldElem = elem_type { + format!("alpha") + } else { + format!("alpha.into()") + } + } else { + if let ElemType::ExtFieldElem = elem_type { + format!("beta_challenges[{}]", idx - 1) + } else { + format!("beta_challenges[{}].into()", idx - 1) + } + } + }, + } + } +} + +/// Returns a string representation of a binary operation. +fn binary_op_to_string(ir: &Air, elem_type: ElemType, op: &Operation) -> String { + match op { + Operation::Add(l_idx, r_idx) => { + let lhs = l_idx.to_string(ir, elem_type); + let rhs = r_idx.to_string(ir, elem_type); + format!("{lhs} + {rhs}") + }, + Operation::Sub(l_idx, r_idx) => { + let lhs = l_idx.to_string(ir, elem_type); + let rhs = if ir.constraint_graph().node(r_idx).op().precedence() <= op.precedence() { + format!("({})", r_idx.to_string(ir, elem_type)) + } else { + r_idx.to_string(ir, elem_type) + }; + format!("{lhs} - {rhs}") + }, + Operation::Mul(l_idx, r_idx) => { + let lhs_op = ir.constraint_graph().node(l_idx).op(); + let rhs_op = ir.constraint_graph().node(r_idx).op(); + + let lhs = if lhs_op.precedence() < op.precedence() { + format!("({})", l_idx.to_string(ir, elem_type)) + } else { + l_idx.to_string(ir, elem_type) + }; + let rhs = if rhs_op.precedence() < op.precedence() { + format!("({})", r_idx.to_string(ir, elem_type)) + } else { + r_idx.to_string(ir, elem_type) + }; + + match (lhs_op, rhs_op) { + (_, Operation::Value(Value::Constant(2))) => format!("{lhs}.double()"), + (Operation::Value(Value::Constant(2)), _) => format!("{rhs}.double()"), + _ => format!("{lhs} * {rhs}"), + } + }, + _ => panic!("unsupported operation"), + } +} + +/// Recursively determines if the expression depends on extension field values (i.e., aux trace, +/// random values, periodic columns, or public input tables). +pub fn needs_extension_field(ir: &Air, expr_root: NodeIndex) -> bool { + let op = ir.constraint_graph().node(&expr_root).op(); + match op { + Operation::Value(value) => match value { + Value::TraceAccess(trace_access) => trace_access.segment == TraceSegmentId::Aux, + Value::Constant(_) => false, + Value::PeriodicColumn(_) => true, + Value::PublicInput(_) => false, + Value::PublicInputTable(_) => true, + Value::RandomValue(_) => true, + }, + Operation::Add(lhs, rhs) | Operation::Sub(lhs, rhs) | Operation::Mul(lhs, rhs) => { + needs_extension_field(ir, *lhs) || needs_extension_field(ir, *rhs) + }, + } +} + +/// Returns the appropriate domain flag string for the given [ConstraintDomain]. +fn get_boundary_domain_flag_str(domain: &ConstraintDomain) -> &'static str { + match domain { + ConstraintDomain::FirstRow => ".when_first_row()", + ConstraintDomain::LastRow => ".when_last_row()", + _ => unreachable!("Invalid domain for boundary constraints"), + } +} + +/// Returns the appropriate domain flag string for the given [ConstraintDomain]. +fn get_integrity_domain_flag_str(domain: &ConstraintDomain) -> &'static str { + match domain { + ConstraintDomain::EveryFrame(_) => ".when_transition()", + ConstraintDomain::EveryRow => "", + _ => unreachable!("Invalid domain for integrity constraints"), + } +} + +pub fn constraint_to_string(ir: &Air, constraint: &ConstraintRoot, in_boundary: bool) -> String { + let expr_root = constraint.node_index(); + let needs_extension_field = needs_extension_field(ir, *expr_root); + let elem_type = if needs_extension_field { + ElemType::Ext + } else { + ElemType::Base + }; + let expr_root_string = expr_root.to_string(ir, elem_type); + + // If the constraint is a transition constraint (depends on the next row), we do not + // evaluate it in the last row, with the `when_transition` method. + let domain_flag = if in_boundary { + get_boundary_domain_flag_str(&constraint.domain()) + } else { + get_integrity_domain_flag_str(&constraint.domain()) + }; + let extension_field_flag = if needs_extension_field { "_ext" } else { "" }; + let assertion = + format!("builder{domain_flag}.assert_zero{extension_field_flag}({expr_root_string});"); + assertion +} diff --git a/codegen/plonky3/src/air/integrity_constraints.rs b/codegen/plonky3/src/air/integrity_constraints.rs new file mode 100644 index 000000000..bdcce45e1 --- /dev/null +++ b/codegen/plonky3/src/air/integrity_constraints.rs @@ -0,0 +1,24 @@ +use air_ir::{Air, TraceSegmentId}; +use codegen::Function; + +use crate::air::graph::constraint_to_string; + +/// Adds the main integrity constraints to the generated code. +pub(super) fn add_main_integrity_constraints(eval_func: &mut Function, ir: &Air) { + eval_func.line(""); + eval_func.line("// Main integrity/transition constraints"); + for constraint in ir.integrity_constraints(TraceSegmentId::Main) { + let assertion = constraint_to_string(ir, constraint, false); + eval_func.line(assertion); + } +} + +/// Adds the aux integrity constraints to the generated code. +pub(super) fn add_aux_integrity_constraints(eval_func: &mut Function, ir: &Air) { + eval_func.line(""); + eval_func.line("// Aux integrity/transition constraints"); + for constraint in ir.integrity_constraints(TraceSegmentId::Aux) { + let assertion = constraint_to_string(ir, constraint, false); + eval_func.line(assertion); + } +} diff --git a/codegen/plonky3/src/air/mod.rs b/codegen/plonky3/src/air/mod.rs new file mode 100644 index 000000000..32a585ce9 --- /dev/null +++ b/codegen/plonky3/src/air/mod.rs @@ -0,0 +1,278 @@ +mod boundary_constraints; +mod graph; +mod integrity_constraints; + +use air_ir::Air; + +use super::Scope; +use crate::air::{ + boundary_constraints::add_main_boundary_constraints, + graph::Codegen, + integrity_constraints::{add_aux_integrity_constraints, add_main_integrity_constraints}, +}; + +#[derive(Debug, Clone, Copy)] +pub enum ElemType { + Base, + Ext, + ExtFieldElem, +} + +// HELPERS TO GENERATE AN IMPLEMENTATION OF THE PLONKY3 AIR TRAIT +// ================================================================================================ + +/// Updates the provided scope with a new Air struct and Plonky3 Air trait implementation +/// which are equivalent the provided AirIR. +pub(super) fn add_air(scope: &mut Scope, ir: &Air) { + let name = ir.name(); + + // add the constants needed (outside any traits for object safety). + add_constants(scope, ir); + + // add the Air struct and its base implementation. + add_air_struct(scope, ir, name); + + // add the aux trace generation utils if needed + if ir.num_random_values > 0 { + add_aux_trace_utils(scope, ir, name); + } +} + +/// Updates the provided scope with constants needed for the custom Air struct and trait +/// implementations. +fn add_constants(scope: &mut Scope, ir: &Air) { + let main_width = ir.trace_segment_widths[0]; + let aux_width = ir.trace_segment_widths.get(1).cloned().unwrap_or(0); + let num_periodic_values = ir.periodic_columns().count(); + let period = ir.periodic_columns().map(|col| col.period()).max().unwrap_or(0); + let num_public_values = ir + .public_inputs() + .map(|public_input| match public_input { + air_ir::PublicInput::Vector { size, .. } => size, + air_ir::PublicInput::Table { .. } => &0, + }) + .sum::(); + let max_beta_challenge_power = ir.num_random_values.saturating_sub(1); + + let constants = [ + format!("pub const MAIN_WIDTH: usize = {main_width};"), + format!("pub const AUX_WIDTH: usize = {aux_width};"), + format!("pub const NUM_PERIODIC_VALUES: usize = {num_periodic_values};"), + format!("pub const PERIOD: usize = {period};"), + format!("pub const NUM_PUBLIC_VALUES: usize = {num_public_values};"), + format!("pub const MAX_BETA_CHALLENGE_POWER: usize = {max_beta_challenge_power};"), + ]; + + scope.raw(constants.join("\n")); +} + +/// Updates the provided scope with a custom Air struct. +fn add_air_struct(scope: &mut Scope, ir: &Air, name: &str) { + // define the custom Air struct. + scope.new_struct(name).vis("pub"); + + // add the custom MidenAir implementation block + let miden_air_impl = + scope.new_impl(name).generic("F").generic("EF").impl_trait("MidenAir"); + + if ir.num_random_values > 0 || ir.periodic_columns().count() > 0 { + miden_air_impl.bound("F", "Field").bound("EF", "ExtensionField"); + } + + // add the width function + miden_air_impl.new_fn("width").arg_ref_self().ret("usize").line("MAIN_WIDTH"); + + // add the num_public_values function if needed + if ir.periodic_columns().count() > 0 { + // add the custom BaseAirWithPublicValues implementation block + miden_air_impl + .new_fn("num_public_values") + .arg_ref_self() + .ret("usize") + .line("NUM_PUBLIC_VALUES"); + } + + // add the periodic_table function if needed + if ir.periodic_columns().count() > 0 { + let periodic_table_func = + miden_air_impl.new_fn("periodic_table").arg_ref_self().ret("Vec>"); + periodic_table_func.line("vec!["); + for col in ir.periodic_columns() { + let values_str = col.values + .iter() + .map(|v| format!("F::from_u64({v})")) // or use a custom formatter if needed + .collect::>() + .join(", "); + periodic_table_func.line(format!(" vec![{values_str}],")); + } + periodic_table_func.line("]"); + } + + // add the num_randomness and aux_width functions if needed + if ir.num_random_values > 0 { + miden_air_impl + .new_fn("num_randomness") + .arg_ref_self() + .ret("usize") + .line("1 + MAX_BETA_CHALLENGE_POWER"); + + miden_air_impl.new_fn("aux_width").arg_ref_self().ret("usize").line("AUX_WIDTH"); + + let bus_types_fn = miden_air_impl.new_fn("bus_types").arg_ref_self().ret("Vec"); + bus_types_fn.line("vec!["); + for (_id, bus) in &ir.buses { + let bus_type_str = match bus.bus_type { + air_ir::BusType::Multiset => "BusType::Multiset", + air_ir::BusType::Logup => "BusType::Logup", + }; + bus_types_fn.line(format!(" {bus_type_str},")); + } + bus_types_fn.line("]"); + } + + // add the build_aux_trace function if needed + if ir.num_random_values > 0 { + let build_aux_trace_func = miden_air_impl + .new_fn("build_aux_trace") + .arg_ref_self() + .arg("_main", "&RowMajorMatrix") + .arg("_challenges", "&[EF]") + .ret("Option>"); + build_aux_trace_func.line("// Note: consider using Some(build_aux_trace_with_miden_vm::(_main, _challenges, module)) if you want to build the aux trace using Miden VM aux trace builders."); + build_aux_trace_func.line(""); + build_aux_trace_func.line("let num_rows = _main.height();"); + build_aux_trace_func.line("let trace_length = num_rows * AUX_WIDTH;"); + build_aux_trace_func.line("let mut long_trace = EF::zero_vec(trace_length);"); + build_aux_trace_func.line("let mut trace = RowMajorMatrix::new(long_trace, AUX_WIDTH);"); + build_aux_trace_func.line("let (prefix, rows, suffix) = unsafe { trace.values.align_to_mut::<[EF; AUX_WIDTH]>() };"); + build_aux_trace_func.line("assert!(prefix.is_empty(), \"Alignment should match\");"); + build_aux_trace_func.line("assert!(suffix.is_empty(), \"Alignment should match\");"); + build_aux_trace_func.line("assert_eq!(rows.len(), num_rows);"); + build_aux_trace_func.line("// Initialize first row"); + build_aux_trace_func.line("let initial_values = Self::buses_initial_values::();"); + build_aux_trace_func.line("for j in 0..AUX_WIDTH {"); + build_aux_trace_func.line(" rows[0][j] = initial_values[j];"); + build_aux_trace_func.line("}"); + build_aux_trace_func.line("// Fill subsequent rows using direct access to the rows array"); + build_aux_trace_func.line("for i in 0..num_rows-1 {"); + build_aux_trace_func.line(" let i_next = (i + 1) % num_rows;"); + build_aux_trace_func.line(" let main_local = _main.row_slice(i).unwrap(); // i < height so unwrap should never fail."); + build_aux_trace_func.line(" let main_next = _main.row_slice(i_next).unwrap(); // i_next < height so unwrap should never fail."); + build_aux_trace_func.line(" let main = VerticalPair::new("); + build_aux_trace_func.line(" RowMajorMatrixView::new_row(&*main_local),"); + build_aux_trace_func.line(" RowMajorMatrixView::new_row(&*main_next),"); + build_aux_trace_func.line(" );"); + build_aux_trace_func.line(format!(" let periodic_values: [_; NUM_PERIODIC_VALUES] = <{name} as MidenAir>::periodic_table(self).iter().map(|col| col[i % col.len()]).collect::>().try_into().expect(\"Wrong number of periodic values\");")); + build_aux_trace_func.line(" let prev_row = &rows[i];"); + build_aux_trace_func.line(" let next_row = Self::buses_transitions::("); + build_aux_trace_func.line(" &main,"); + build_aux_trace_func.line(" _challenges,"); + build_aux_trace_func.line(" &periodic_values,"); + build_aux_trace_func.line(" prev_row,"); + build_aux_trace_func.line(" );"); + build_aux_trace_func.line(" for j in 0..AUX_WIDTH {"); + build_aux_trace_func.line(" rows[i+1][j] = next_row[j];"); + build_aux_trace_func.line(" }"); + build_aux_trace_func.line("}"); + build_aux_trace_func.line("let trace_f = trace.flatten_to_base();"); + build_aux_trace_func.line("Some(trace_f)"); + } + + // add the eval function + let eval_func = miden_air_impl + .new_fn("eval") + .generic("AB") + .bound("AB", "MidenAirBuilder") + .arg_ref_self() + .arg("builder", "&mut AB"); + eval_func.line("let public_values: [_; NUM_PUBLIC_VALUES] = builder.public_values().try_into().expect(\"Wrong number of public values\");"); + eval_func.line("let periodic_values: [_; NUM_PERIODIC_VALUES] = builder.periodic_evals().try_into().expect(\"Wrong number of periodic values\");"); + + eval_func.line("// Note: for now, we do not have any preprocessed values"); + eval_func.line("// let preprocessed = builder.preprocessed();"); + + eval_func.line("let main = builder.main();"); + eval_func.line("let (main_current, main_next) = ("); + eval_func.line(" main.row_slice(0).unwrap(),"); + eval_func.line(" main.row_slice(1).unwrap(),"); + eval_func.line(");"); + + // Only add aux if there are random values + if ir.num_random_values > 0 { + eval_func.line("let (&alpha, beta_challenges) = builder.permutation_randomness().split_first().expect(\"Wrong number of randomness\");"); + eval_func.line("let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect(\"Wrong number of randomness\");"); + eval_func.line("let aux_bus_boundary_values: [_; AUX_WIDTH] = builder.aux_bus_boundary_values().try_into().expect(\"Wrong number of aux bus boundary values\");"); + eval_func.line("let aux = builder.permutation();"); + eval_func.line("let (aux_current, aux_next) = ("); + eval_func.line(" aux.row_slice(0).unwrap(),"); + eval_func.line(" aux.row_slice(1).unwrap(),"); + eval_func.line(");"); + } + + add_main_boundary_constraints(eval_func, ir); + + add_main_integrity_constraints(eval_func, ir); + + // Note: Plonky3 automatically adds aux boundary constraints + //add_aux_boundary_constraints(eval_func, ir); + + add_aux_integrity_constraints(eval_func, ir); +} + +/// Updates the provided scope with aux trace generation utilities. +fn add_aux_trace_utils(scope: &mut Scope, ir: &Air, name: &str) { + let aux_generation_impl = scope.new_impl(name); + + // add the bus_initial_values function + let buses_initial_values_func = aux_generation_impl + .new_fn("buses_initial_values") + .generic("F") + .generic("EF") + .bound("F", "Field") + .bound("EF", "ExtensionField") + .ret("Vec"); + buses_initial_values_func.line("vec!["); + for (_bus_id, value) in ir.buses_initial_values.iter() { + let value_str = value.to_string(ir, ElemType::ExtFieldElem); + + buses_initial_values_func.line(format!(" {},", value_str)); + } + buses_initial_values_func.line("]"); + + // add the bus_transitions function + let buses_transitions_func = aux_generation_impl + .new_fn("buses_transitions") + .generic("F") + .generic("EF") + .bound("F", "Field") + .bound("EF", "ExtensionField") + .arg("main", "&VerticalPair, RowMajorMatrixView>") + .arg("challenges", "&[EF]") + .arg("periodic_evals", "&[F]") + .arg("aux_current", "&[EF]") + .ret("Vec"); + + buses_transitions_func.line("let (main_current, main_next) = ("); + buses_transitions_func.line(" main.row_slice(0).unwrap(),"); + buses_transitions_func.line(" main.row_slice(1).unwrap(),"); + buses_transitions_func.line(");"); + buses_transitions_func.line("let (&alpha, beta_challenges) = challenges.split_first().expect(\"Wrong number of randomness\");"); + buses_transitions_func.line("let beta_challenges: [_; MAX_BETA_CHALLENGE_POWER] = beta_challenges.try_into().expect(\"Wrong number of randomness\");"); + + buses_transitions_func.line("let periodic_values: [_; NUM_PERIODIC_VALUES] = periodic_evals.try_into().expect(\"Wrong number of periodic values\");"); + + buses_transitions_func.line("vec!["); + for (_bus_id, (numerator, denominator)) in ir.buses_transitions.iter() { + let numerator_str = numerator.to_string(ir, ElemType::ExtFieldElem); + + let aux_next_value_str = if let Some(denom) = denominator { + let denominator_str = denom.to_string(ir, ElemType::ExtFieldElem); + format!("({}) * ({}).inverse()", numerator_str, denominator_str) + } else { + numerator_str + }; + + buses_transitions_func.line(format!(" {},", aux_next_value_str)); + } + buses_transitions_func.line("]"); +} diff --git a/codegen/plonky3/src/imports.rs b/codegen/plonky3/src/imports.rs new file mode 100644 index 000000000..d96c0dd40 --- /dev/null +++ b/codegen/plonky3/src/imports.rs @@ -0,0 +1,16 @@ +use super::Scope; + +/// Adds the required imports to the provided scope. +pub(super) fn add_imports(scope: &mut Scope) { + // add plonky3 imports + scope.import("p3_field", "ExtensionField"); + scope.import("p3_field", "Field"); + scope.import("p3_field", "PrimeCharacteristicRing"); + scope.import("p3_matrix", "Matrix"); + scope.import("p3_matrix::dense", "RowMajorMatrixView"); + scope.import("p3_matrix::stack", "VerticalPair"); + scope.import("p3_miden_air", "BusType"); + scope.import("p3_miden_air", "MidenAir"); + scope.import("p3_miden_air", "MidenAirBuilder"); + scope.import("p3_miden_air", "RowMajorMatrix"); +} diff --git a/codegen/plonky3/src/lib.rs b/codegen/plonky3/src/lib.rs new file mode 100644 index 000000000..703a8ee94 --- /dev/null +++ b/codegen/plonky3/src/lib.rs @@ -0,0 +1,28 @@ +use air_ir::Air; +use codegen::Scope; + +mod air; +mod imports; + +// GENERATE RUST CODE FOR WINTERFELL AIR +// ================================================================================================ + +/// CodeGenerator is used to generate a Rust implementation of the Plonky3 STARK prover library's +/// Air trait. The generated Air expresses the constraints specified by the AirIR used to build the +/// CodeGenerator. +pub struct CodeGenerator; +impl air_ir::CodeGenerator for CodeGenerator { + type Output = String; + + fn generate(&self, ir: &Air) -> anyhow::Result { + let mut scope = Scope::new(); + + // add plonky3 imports. + imports::add_imports(&mut scope); + + // add an Air struct and plonky3 Air trait implementation for the provided AirIR. + air::add_air(&mut scope, ir); + + Ok(scope.to_string()) + } +} diff --git a/constraints/Cargo.toml b/constraints/Cargo.toml new file mode 100644 index 000000000..2263eadd9 --- /dev/null +++ b/constraints/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "constraints" +version = "0.1.0" +edition = "2021" + +[dependencies] +air-ir = { path = "../air", version = "0.5" } +air-parser = { path = "../parser", version = "0.5" } +air-pass = { path = "../pass", version = "0.5" } +air-mir = { path = "../mir", version = "0.5" } +air-codegen-ace = { path = "../codegen/ace", version = "0.5" } +miden-diagnostics = { workspace = true } +miden-core = { package = "miden-core", version = "0.13", default-features = false } + +winter-air = { package = "winter-air", version = "0.12", default-features = false } +winter-math = { package = "winter-math", version = "0.12", default-features = false } +winter-utils = { package = "winter-utils", version = "0.12", default-features = false } +winter-prover = { package = "winter-prover", version = "0.12", default-features = false } +winter-verifier = { package = "winter-verifier", version = "0.12", default-features = false } +winterfell = { package = "winterfell", version = "0.12", default-features = false } + +[dev-dependencies] +anyhow = "1" \ No newline at end of file diff --git a/constraints/ace.air b/constraints/ace.air new file mode 100644 index 000000000..f822adbb3 --- /dev/null +++ b/constraints/ace.air @@ -0,0 +1,313 @@ +########################################################################################## +# ACE CHIPLET CONSTRAINTS MODULE +########################################################################################## +# +# The ACE (Arithmetic Circuit Evaluation) chiplet reduces the number of cycles required +# when recursively verifying a STARK proof in Miden assembly by evaluating arithmetic +# circuits and ensuring they evaluate to zero over given inputs. +# +# OVERVIEW: +# The ACE chiplet operates in two phases: +# 1. READ blocks: Load extension field elements from memory and assign node IDs +# 2. EVAL blocks: Execute arithmetic operations on previously loaded nodes +# +# Each section starts with READ operations to load inputs, followed by EVAL operations +# to compute intermediate and final results. The final result must evaluate to zero +# to represent a valid constraint satisfaction. +# +# ACE TRACE TABLE LAYOUT (16 columns): +# +# ┌─────────────┬────────────────────────────────────────────────────────────────────────────┐ +# │ Column │ Purpose │ +# ├─────────────┼────────────────────────────────────────────────────────────────────────────┤ +# │ sstart │ Section start flag (binary): 1 = first row of section, 0 = otherwise │ +# │ sblock │ Block selector (binary): 0 = READ block, 1 = EVAL block │ +# │ ctx │ Memory access context (constant throughout section) │ +# │ ptr │ Memory pointer: increments by +4 in READ, +1 in EVAL blocks │ +# │ clk │ Memory access clock cycle (constant within section) │ +# │ op │ Operation type (EVAL only): -1 = SUB, 0 = MUL, 1 = ADD │ +# │ id0 │ Result node ID: decrements by -2 in READ, -1 in EVAL │ +# │ v0_0 │ Result node value (extension field element, coefficient 0) │ +# │ v0_1 │ Result node value (extension field element, coefficient 1) │ +# │ id1 │ First operand node ID │ +# │ v1_0 │ First operand value (extension field element, coefficient 0) │ +# │ v1_1 │ First operand value (extension field element, coefficient 1) │ +# │ id2 / n_eval│ Dual use: 2nd operand node ID (EVAL) / #evaluations in section (READ) │ +# │ v2_0 │ Second operand value (extension field element, coefficient 0) │ +# │ v2_1 │ Second operand value (extension field element, coefficient 1) │ +# │ m0 │ Wire bus multiplicity (for node tracking, not yet implemented) │ +# └─────────────┴────────────────────────────────────────────────────────────────────────────┘ +# +# STATUS: Core constraints implemented, wire bus constraints not implemented +# +# REFERENCES: +# - ACE Design: https://0xmiden.github.io/miden-vm/design/chiplets/ace.html +########################################################################################## + +mod ace + +use chiplets::*; +use utils::*; + +########################################################################################## +# ACE CHIPLET CONSTRAINTS +########################################################################################## + +# Enforces constraints on all rows of ACE chiplet. +ev ace_chiplet_constraints_all_rows([s3, ace[16]]) { + let sstart = ace[0]; # Section start flag + let sblock = ace[1]; # Block selector (0=READ, 1=EVAL) + let ctx = ace[2]; # Memory access context + let ptr = ace[3]; # Memory pointer + let clk = ace[4]; # Memory access clock + let op = ace[5]; # Operation type (for EVAL block) + let id0 = ace[6]; # First node identifier + let v0_0 = ace[7]; # First node value (element 0) + let v0_1 = ace[8]; # First node value (element 1) + let id1 = ace[9]; # Second node identifier + let v1_0 = ace[10]; # Second node value (element 0) + let v1_1 = ace[11]; # Second node value (element 1) + let id2 = ace[12]; # Third node identifier (result node in EVAL) + let n_eval = ace[12]; # Total number of arithmetic operations + let v2_0 = ace[13]; # Third node value (element 0) + let v2_1 = ace[14]; # Third node value (element 1) + let m0 = ace[15]; # Multiplicity for wire bus operations + + let flag_ace_next = flag_ace_current_and_next(s3'); + let flag_ace_last = flag_ace_last(s3'); + + # Section and block flags constraints + enf section_block_flags_constraints([s3, sstart, sstart', sblock, id0, n_eval]); + + # Section general constraints + enf section_constraints([sstart, sblock, ctx, ptr, clk, id0]) when flag_ace_next; + + # Section specific constraints: READ block + enf enforce_read_block_constraints([sblock, id0, id1]); + + # Section specific constraints: EVAL block + enf enforce_eval_block_constraints([sblock, op, v0_0, v0_1, v1_0, v1_1, v2_0, v2_1]); + + # Finalization constraints + let f_end = binary_or(binary_and(flag_ace_next, sstart'), flag_ace_last); + enf finalization_constraints([id0, v0_0, v0_1]) when f_end; +} + +# Enforces that on the first row of ACE chiplet we should have sstart' = 1 +ev ace_chiplet_constraints_first_row([ace[16]]) { + let sstart = ace[0]; # Section start flag + + enf sstart' = 1; +} + +########################################################################################## +# CONSTRAINT EVALUATORS +########################################################################################## + +# Section and block flag management +# +# This evaluator enforces the proper sequencing of ACE chiplet operations: +# - Sections must start with READ blocks and end with EVAL blocks +# - Block transitions follow valid patterns (READ→EVAL within sections) +# - Section boundaries are correctly marked with sstart flags +# - READ→EVAL transition occurs when n_eval matches id0 +ev section_block_flags_constraints([s3, sstart, sstart_next, sblock, id0, n_eval]) { + let flag_ace_next = flag_ace_current_and_next(s3'); + let flag_ace_last = flag_ace_last(s3'); + + let section_flags = section_flags(s3', sstart, sstart_next); + let f_start = section_flags[0]; + let f_next = section_flags[1]; + let f_end = section_flags[2]; + + let block_flags = block_flags(sblock); + let block_flags_next = block_flags(sblock'); + let f_read = block_flags[0]; + let f_eval = block_flags[1]; + let f_read_next = block_flags_next[0]; + let f_eval_next = block_flags_next[1]; + + # Binary constraints for section and block flags + enf enforce_binary_columns([sstart, sblock]); + + # Last row of ACE chiplet cannot be section start + enf sstart = 0 when flag_ace_last; + + # Prevent consecutive section starts within ACE chiplet + enf binary_and(sstart, sstart') = 0 when flag_ace_next; + + # Sections must start with READ blocks (not EVAL) + enf f_eval = 0 when f_start; + + # EVAL blocks cannot be followed by READ blocks within same section + enf f_read_next = 0 when binary_and(f_next, f_eval); + + # Sections must end with EVAL blocks (not READ) + enf f_read = 0 when f_end; + + # In a READ block, n_eval stays constant. + # When transitioning to an EVAL block, the next id0 must equal n_eval - 1. + # This ensures proper sequencing: READ loads nodes with IDs, EVAL starts processing + # from the highest loaded node ID (n_eval - 1). + enf select(f_read_next, n_eval', id0' + 1) = n_eval when f_read; +} + +# Constrains binary columns for flags and selectors +ev enforce_binary_columns([sstart, sblock]) { + # Section start flag must be binary + enf is_binary([sstart]); + + # Block selector must be binary + enf is_binary([sblock]); +} + +# Section-level constraints (common to both READ and EVAL blocks) +# +# These constraints apply throughout an entire section regardless of block type: +# - Context (ctx) and clock (clk) remain constant within a section +# - Memory pointer (ptr) increments with fixed increments: +4 in READ, +1 in EVAL +# - Node identifiers (id0) decrement with fixed decrements: -2 in READ, -1 in EVAL +# +# Note: These constraints are active when the next row is NOT the start of a new section, +# i.e., all but the last rows of the section (intra-section transitions only). +ev section_constraints([sstart, sblock, ctx, ptr, clk, id0]) { + let flag_within_section = binary_not(sstart'); + let flag_read = !sblock; + let flag_eval = sblock; + + # Context consistency within a section + enf ctx' = ctx when flag_within_section; + + # Clock consistency within a section + enf clk' = clk when flag_within_section; + + # Memory pointer increases by 4 in READ blocks and by 1 in EVAL blocks + enf ptr' = ptr + 4 * flag_read + flag_eval when flag_within_section; + + # Node identifiers decrease by 2 in READ blocks and by 1 in EVAL blocks + enf id0 = id0' + 2 * flag_read + flag_eval when flag_within_section; +} + +# Finalization constraints (ensure final result is zero) +ev finalization_constraints([id0, v0_0, v0_1]) { + # The final result of the arithmetic circuit evaluation must be zero + # This ensures the circuit represents a valid constraint + enf v0_0 = 0; + enf v0_1 = 0; + + # Final node ID should be the root node + enf id0 = 0; +} + +# READ block operation constraints +# +# The only specific constraint to READ blocks is a constraint ensuring that the IDs of the two +# loaded extension field elements is consecutive. +ev enforce_read_block_constraints([sblock, id0, id1]) { + let is_read_block = binary_not(sblock); + + # In READ block, we read two extension field elements and assign node IDs + # The two node IDs should be consecutive. Note that the node IDs are decreasing. + enf id1 = id0 - 1 when is_read_block; +} + +# EVAL block operation constraints +# +# Enforces arithmetic circuit evaluation during EVAL operations: +# - Validates operation codes (op ∈ {-1, 0, 1} for SUB, MUL, ADD) +# - Performs extension field arithmetic on input nodes (v1, v2) +ev enforce_eval_block_constraints([sblock, op, v0_0, v0_1, v1_0, v1_1, v2_0, v2_1]) { + let is_eval_block = sblock; + + # In EVAL block, we decode instruction and perform arithmetic operation + # The 'op' column contains the operation type and must be equal to either -1, 0, or 1 + enf op * (op - 1) * (op + 1) = 0 when is_eval_block; + + # Arithmetic operation constraint based on op type + enf enforce_arithmetic_operation([op, v0_0, v0_1, v1_0, v1_1, v2_0, v2_1]) when is_eval_block; +} + + +# Arithmetic operation evaluation for EVAL block +# +# Performs extension field arithmetic based on operation code: +# - op = -1: Subtraction (v0 = v1 - v2) +# - op = 0: Multiplication (v0 = v1 × v2) +# - op = 1: Addition (v0 = v1 + v2) +# +# All arithmetic is in the quadratic extension field 𝔽ₚ[x]/(x² - x + 2) +ev enforce_arithmetic_operation([op, v0_0, v0_1, v1_0, v1_1, v2_0, v2_1]) { + # Decode operation and enforce arithmetic constraint for extension field elements + # Extension field elements are represented as (a_0, a_1) where element = a_0 + a_1 * α + + let linear_op = compute_linear_op(op, v1_0, v1_1, v2_0, v2_1); + let linear_op_0 = linear_op[0]; + let linear_op_1 = linear_op[1]; + + let non_linear_op = compute_non_linear_op(v1_0, v1_1, v2_0, v2_1); + let non_linear_op_0 = non_linear_op[0]; + let non_linear_op_1 = non_linear_op[1]; + + let op_square = op^2; + + enf op_square * (linear_op_0 - non_linear_op_0) + non_linear_op_0 = v0_0; + enf op_square * (linear_op_1 - non_linear_op_1) + non_linear_op_1 = v0_1; +} + +# Computes linear operations (addition and subtraction) in extension field +# - op = -1: Subtraction (v0 = v1 - v2) +# - op = 1: Addition (v0 = v1 + v2) +fn compute_linear_op(op: felt, v1_0: felt, v1_1: felt, v2_0: felt, v2_1: felt) -> felt[2] { + let res0 = v1_0 + op * v2_0; + let res1 = v1_1 + op * v2_1; + return [res0, res1]; +} + +# Multiplication in the quadratic extension field. +# +# The extension is 𝔽ₚ[x]/(x² - x + 2) +fn compute_non_linear_op(v1_0: felt, v1_1: felt, v2_0: felt, v2_1: felt) -> felt[2] { + let a0 = v1_0; + let a1 = v1_1; + let b0 = v2_0; + let b1 = v2_1; + + let res0 = a0 * b0 - 2 * a1 * b1; + let res1 = (b0 + b1) * (a0 + a1) - a0 * b0; + + return [res0, res1]; +} + +########################################################################################## +# HELPER FUNCTIONS +########################################################################################## + +# ACE chiplet active in current row and continuing to next row +fn flag_ace_current_and_next(s3_next: felt) -> felt { + return !s3_next; +} + +# ACE chiplet active in current row but transitioning out in next row +fn flag_ace_last(s3_next: felt) -> felt { + return s3_next; +} + +# Computes section-level flags: [f_start, f_next, f_end] +fn section_flags(s3_next: felt, s_start: felt, s_start_next: felt) -> felt[3] { + let f_ace_next = flag_ace_current_and_next(s3_next); + let f_ace_last = flag_ace_last(s3_next); + + let f_start = s_start; + let f_next = !s_start_next; + let f_end = binary_or(binary_and(f_ace_next, s_start_next), f_ace_last); + + return [f_start, f_next, f_end]; +} + +# Computes block-level flags: [f_read, f_eval] +fn block_flags(s_block: felt) -> felt[2] { + let f_read = !s_block; + let f_eval = s_block; + + return [f_read, f_eval]; +} \ No newline at end of file diff --git a/constraints/bitwise.air b/constraints/bitwise.air new file mode 100644 index 000000000..78efb50a6 --- /dev/null +++ b/constraints/bitwise.air @@ -0,0 +1,158 @@ +########################################################################################## +# BITWISE OPERATIONS CHIPLET +########################################################################################## +# +# The Bitwise chiplet handles bitwise logical operations (AND, XOR) on 32-bit values. +# It decomposes field elements into their binary representation and performs bit-level +# operations efficiently using field arithmetic. +# +# Each bitwise operation spans exactly 8 rows, with limbs processed in little-endian order. +# +# STATUS: Partially implemented (missing bus constraints) +# +# REFERENCES: +# - Bitwise Chiplet: https://0xmiden.github.io/miden-vm/design/chiplets/bitwise.html +########################################################################################## + +mod bitwise + +use utils::*; + +########################################################################################## +# BITWISE OPERATION CONSTANTS (Periodic Columns) +########################################################################################## +# +# Periodic constants contain values needed to switch various constraints on or off. +# +periodic_columns { + k_first: [1, 0, 0, 0, 0, 0, 0, 0], + k_transition: [1, 1, 1, 1, 1, 1, 1, 0], +} + +########################################################################################## +# BITWISE CHIPLET TRANSITION CONSTRAINTS +########################################################################################## +# +# The bitwise chiplet processes 32-bit integers by decomposing them into individual +# bits, applying bitwise operations, and recomposing the results. This approach +# enables verification of bitwise logic within the field arithmetic constraint system. + +# Enforces the constraints on the bitwise chiplet given its columns. +# +# Max constraint degree: 4 +ev bitwise_chiplet_constraints([op_flag, a, b, a_limb[4], b_limb[4], zp, z]) { + enf bitwise_op_flag([op_flag]); + enf input_decomposition([a, b, a_limb, b_limb]); + enf output_aggregation([op_flag, a, b, a_limb, b_limb, zp, z]); +} + +########################################################################################## +# BUS CONSTRAINTS +########################################################################################## +# +# The bitwise chiplet communicates with the stack component using the chiplets bus. +# +# MESSAGE FORMAT: +# +# Each bitwise operation message contains: +# - Operation type (AND/XOR) +# - Input operands (a, b) as 32-bit values +# - Expected result (c) +# +########################################################################################## + +########################################################################################## +# HELPERS +########################################################################################## + +### Helper evaluators ############################################################################# + +# Enforces that the bitwise operation flag is valid. +# +# Max constraint degree: 2 +ev bitwise_op_flag([op_flag]) { + # Enforce operation flag is binary (0 for AND, 1 for XOR). + # Constraint degree: 2 + enf is_binary([op_flag]); + + # Enforce operation flag should stay the same throughout the 8-row cycle. + # Constraint degree: 2 + enf op_flag' = op_flag when k_transition; +} + + +# Enforces that the input to the bitwise chiplet is decomposed into limbs correctly. +# +# Max constraint degree: 2 +ev input_decomposition([a, b, a_limb[4], b_limb[4]]) { + # Enforce that the input is decomposed into valid bits. + # Constraints degree: 2 + enf is_binary([a]) for a in a_limb; + enf is_binary([b]) for b in b_limb; + + # Enforce that the value in the first row of column `a` of the current 8-row cycle should be + # the aggregation of the decomposed bit columns `a_limb` (little-endian). + let a_aggr = aggregate_limbs(a_limb); + # Constraint degree: 2 + enf a = a_aggr when k_first; + + # Enforce that the value in the first row of column `b` of the current 8-row cycle should be + # the aggregation of the decomposed bit columns `b_limb` (little-endian). + let b_aggr = aggregate_limbs(b_limb); + # Constraint degree: 2 + enf b = b_aggr when k_first; + + # Enforce that for all rows in an 8-row cycle, except for the last one, the values in a and b + # columns are increased by the values contained in the individual bit columns a_limb and + # b_limb. + # Constraints degree: 2 + enf a' = a * 16 + a_aggr when k_transition; + enf b' = b * 16 + b_aggr when k_transition; +} + + +# Enforces that the output of the bitwise operation is aggregated correctly from the decomposed +# limbs. +# +# Max constraint degree: 3 +ev output_aggregation([op_flag, a, b, a_limb[4], b_limb[4], zp, z]) { + # Enforce that in the first row, the aggregated output value of the previous row should be 0. + # Constraint degree: 2 + enf zp = 0 when k_first; + + # Enforce that for each row except the last, the aggregated output value must equal the + # previous aggregated output value in the next row. + # Constraint degree: 2 + enf zp' = z when k_transition; + + # Enforce that for all rows the value in the z column is computed by multiplying the previous + # output value (from the zp column in the current row) by 16 and then adding it to the bitwise + # operation applied to the row's set of bits of a_limb and b_limb. The entire constraint must + # also be multiplied by the operation selector flag to ensure it is only applied for the + # appropriate operation. The constraint for AND is enforced when op_flag = 0 and the constraint for + # XOR is enforced when op_flag = 1. Because the selectors for the AND and XOR operations are mutually + # exclusive, the constraints for different operations can be aggregated into the same result + # indices. + # Constraints degree: 3 + let a_and_b = compute_limb_and(a_limb, b_limb); + let a_xor_b = compute_limb_xor(a_limb, b_limb); + + enf z = zp * 16 + op_flag * (a_xor_b - a_and_b) + a_and_b; +} + +### Helper functions ############################################################################## + +# Returns value aggregated from limbs in little-endian order. +fn aggregate_limbs(limbs: felt[4]) -> felt { + return sum([2^i * limb for (i, limb) in (0..4, limbs)]); +} + +# Computes AND operation result for a 4-bit limb using fold/reduce approach. +fn compute_limb_and(a_limb: felt[4], b_limb: felt[4]) -> felt { + return sum([2^i * binary_and(a_bit, b_bit) for (i, a_bit, b_bit) in (0..4, a_limb, b_limb)]); +} + +# Computes XOR operation result for a 4-bit limb using fold/reduce approach. +fn compute_limb_xor(a_limb: felt[4], b_limb: felt[4]) -> felt { + return sum([2^i * binary_xor(a_bit, b_bit) for (i, a_bit, b_bit) in (0..4, a_limb, b_limb)]); +} diff --git a/constraints/chiplets.air b/constraints/chiplets.air new file mode 100644 index 000000000..37d6a6b9b --- /dev/null +++ b/constraints/chiplets.air @@ -0,0 +1,173 @@ +########################################################################################## +# CHIPLETS CONSTRAINTS MODULE +########################################################################################## +# +# The Chiplets module contains specialized computation units that handle complex operations +# like cryptographic hashing, bitwise operations, and memory access. Each chiplet uses a +# hierarchical selector system to identify which operations are active. +# +# CHIPLETS COLUMN LAYOUT (20 columns): +# ┌─────────┬──────────────────────────────────────────────────────────────────────┐ +# │ Columns │ Purpose │ +# ├─────────┼──────────────────────────────────────────────────────────────────────┤ +# │ 0-4 │ s[5] - Hierarchical selector flags │ +# │ 5-19 │ Chiplet-specific data (hasher, bitwise ops, memory, ACE, kernel ROM) │ +# └─────────┴──────────────────────────────────────────────────────────────────────┘ +# +# STATUS: Not fully implemented +# +# REFERENCES: +# - Chiplets Design: https://0xmiden.github.io/miden-vm/design/chiplets/main.html +########################################################################################## + +mod chiplets + +use ace::ace_chiplet_constraints_all_rows; +use ace::ace_chiplet_constraints_first_row; + +use hasher::hash_chiplet; + +use bitwise::bitwise_chiplet_constraints; + +use memory::memory_chiplet_constraints_all_rows; +use memory::memory_chiplet_constraints_all_rows_except_last; +use memory::flag_memory_active_not_last_row; +use memory::flag_next_row_first_row_memory; +use memory::memory_chiplet_constraints_first_row; + +use kernel_rom::ker_rom_chiplet_constraints; +use kernel_rom::kernel_rom_chiplet_constraints_first_row; + +use utils::*; + +########################################################################################## +# CHIPLETS CONSTRAINTS +########################################################################################## + +ev chiplets_constraints([chiplets[20]]) { + # Chiplets' flag constraints + let s0 = chiplets[0]; + let s1 = chiplets[1]; + let s2 = chiplets[2]; + let s3 = chiplets[3]; + let s4 = chiplets[4]; + enf chiplet_selectors([s0, s1, s2, s3, s4]); + + # MAIN CHIPLET CONSTRAINT: + + # The chiplet system uses a hierarchical binary selector scheme where each selector + # bit (s0, s1, s2, s3, s4) determines which chiplet is active at any given row. + # + # This hierarchy ensures exactly one chiplet is active per row and provides + # deterministic transitions between chiplets based on selector state changes. + let hash_active = hasher_chiplet_flag(s0); # Active when: !s0 + let bitwise_active = bitwise_chiplet_flag(s0, s1); # Active when: s0 * !s1 + let memory_active = memory_chiplet_flag(s0, s1, s2); # Active when: s0 * s1 * !s2 + let ace_active = ace_chiplet_flag(s0, s1, s2, s3); # Active when: s0 * s1 * s2 * !s3 + let ker_rom_active = ker_rom_chiplet_flag(s0, s1, s2, s3, s4); # Active when: s0 * s1 * s2 * s3 * !s4 + + # Apply chiplet-specific constraints based on hierarchical selector state. + enf match { + case hash_active: hash_chiplet([chiplets[1..17]]), + case bitwise_active: bitwise_chiplet_constraints([chiplets[2..15]]), + case memory_active: memory_chiplet_constraints_all_rows([chiplets[3..18]]), + case ace_active: ace_chiplet_constraints_all_rows([chiplets[3..20]]), + case ker_rom_active: ker_rom_chiplet_constraints([chiplets[4..10]]), + }; + + # CHIPLET CONSTRAINTS REQUIRING SPECIAL HANDLING + + ## MEMORY + + ### The memory chiplet requires special handling for both initialization and transition constraints + ### to ensure proper memory access sequencing and state transitions. + let flag_next_row_first_row_memory = flag_next_row_first_row_memory(s0, s1, s2); # Transitioning into memory chiplet + let flag_memory_active_not_last_row = flag_memory_active_not_last_row(s0, s1, s2); # Active in memory, not exiting + + ### MEMORY INITIALIZATION CONSTRAINTS: + ### Apply specialized initialization constraints when first entering the memory chiplet. + enf memory_chiplet_constraints_first_row([chiplets[3..18]]) when flag_next_row_first_row_memory; + + ### MEMORY TRANSITION CONSTRAINTS ON ALL BUT LAST ROW: + ### Apply standard transition constraints while active in memory chiplet, excluding the final row. + enf memory_chiplet_constraints_all_rows_except_last([chiplets[3..18]]) when flag_memory_active_not_last_row; + + ## ACE + + ### Apply ACE chiplet initialization constraints at the transition point + let next_row_first_ace = binary_and(memory_active, s2'); # Transitioning into ACE chiplet + enf ace_chiplet_constraints_first_row([chiplets[4..20]]) when next_row_first_ace; + + ## KERNEL ROM + + ### The kernel ROM chiplet requires initialization constraints to ensure proper startup. + ### We force sfirst' = 1 on the first kernel ROM row, establishing proper digest initialization. + let next_row_first_kernel_rom = binary_and(ace_active, s3'); # Transitioning into kernel ROM chiplet + enf kernel_rom_chiplet_constraints_first_row([chiplets[4..10]]) when next_row_first_kernel_rom; + +} + +########################################################################################## +# CHIPLET SELECTOR SYSTEM +########################################################################################## + +# Hierarchical chiplet selector constraints +# +# CONSTRAINT DEGREE: 2 (quadratic due to binary constraints) +# +ev chiplet_selectors([s[5]]) { + #################################################################################### + # BINARY CONSTRAINTS - Ensure all selectors are valid binary values + #################################################################################### + + enf is_binary([s[0]]); + + enf is_binary([s[1]]) when s[0]; + enf is_binary([s[2]]) when s[0] & s[1]; + enf is_binary([s[3]]) when s[0] & s[1] & s[2]; + enf is_binary([s[4]]) when s[0] & s[1] & s[2] & s[3]; + + #################################################################################### + # STABILITY CONSTRAINTS - Prevent deactivation (forbids 1→0 transitions) + #################################################################################### + + # Once a selector level becomes active (1), it must remain active + # This ensures chiplet operations maintain consistent state throughout execution + enf s[0]' = s[0] when s[0]; + enf s[1]' = s[1] when s[0] & s[1]; + enf s[2]' = s[2] when s[0] & s[1] & s[2]; + enf s[3]' = s[3] when s[0] & s[1] & s[2] & s[3]; + enf s[4]' = s[4] when s[0] & s[1] & s[2] & s[3] & s[4]; +} + +########################################################################################## +# CHIPLET ACTIVATION FUNCTIONS +########################################################################################## + +# These functions decode the hierarchical selector pattern to identify active chiplets. +# Each chiplet activates when its selector pattern matches and the next level is inactive. + +# Hasher chiplet: Active when root selector is inactive +fn hasher_chiplet_flag(s_0: felt) -> felt { + return !s_0; +} + +# Bitwise chiplet: Active when s0=1, s1=0 +fn bitwise_chiplet_flag(s_0: felt, s_1: felt) -> felt { + return s_0 * !s_1; +} + +# Memory chiplet: Active when s0=1, s1=1, s2=0 +fn memory_chiplet_flag(s_0: felt, s_1: felt, s_2: felt) -> felt { + return s_0 * s_1 * !s_2; +} + +# ACE chiplet: Active when s0=1, s1=1, s2=1, s3=0 +fn ace_chiplet_flag(s_0: felt, s_1: felt, s_2: felt, s_3: felt) -> felt { + return s_0 * s_1 * s_2 * !s_3; +} + +# Kernel ROM chiplet: Active when s0=1, s1=1, s2=1, s3=1, s4=0 +fn ker_rom_chiplet_flag(s_0: felt, s_1: felt, s_2: felt, s_3: felt, s_4: felt) -> felt { + return s_0 * s_1 * s_2 * s_3 * !s_4; +} diff --git a/constraints/decoder.air b/constraints/decoder.air new file mode 100644 index 000000000..6a20ea287 --- /dev/null +++ b/constraints/decoder.air @@ -0,0 +1,14 @@ +########################################################################################## +# DECODER CONSTRAINTS +########################################################################################## +# +# Miden VM program decoder is responsible for ensuring that a program with a given MAST +# root is executed by the VM. +# +# STATUS: Not implemented +# +# REFERENCES: +# - Decoder Design: https://0xmiden.github.io/miden-vm/design/decoder/main.html +########################################################################################## + +mod decoder diff --git a/constraints/hasher.air b/constraints/hasher.air new file mode 100644 index 000000000..5d92137f4 --- /dev/null +++ b/constraints/hasher.air @@ -0,0 +1,257 @@ +########################################################################################## +# HASHER CONSTRAINTS MODULE +########################################################################################## +# +# The Hasher module is responsible for all hash-related operations, which includes: +# +# 1. A single permutation of Rescue Prime Optimized (RPO). +# 2. A simple 2-to-1 hash. +# 3. A linear hash of n field elements. +# 4. Merkle path verification. +# 5. Merkle root update. +# +# STATUS: Partially implemented (missing bus interactions) +# +# REFERENCES: +# - Hasher chiplet design: https://0xmiden.github.io/miden-vm/design/chiplets/hasher.html +########################################################################################## + +mod hasher + +use utils::*; +use rpo::enforce_rpo_round; + +########################################################################################## +# HASHER CHIPLET TRANSITION CONSTRAINTS +########################################################################################## + +# Enforces the constraints on the hash chiplet given its columns. +ev hash_chiplet([s[3], h[12], i]) { + # Selector columns constraints + enf selector_columns([s]); + + # Node index constraints + enf node_index([s, i]); + + # Hasher state constraints + enf hasher_state([s, h, i]); +} + +########################################################################################## +# HELPER EVALUATORS +########################################################################################## + +# Enforce selector columns constraints +ev selector_columns([s[3]]) { + # Enforce that selector columns are binary. + enf is_binary([selector]) for selector in s; + + # Compute relevant flags (passing periodic column values as parameters) + let f_abp = get_f_abp(s, cycle_row_7); + let f_mpa = get_f_mpa(s, cycle_row_7); + let f_mva = get_f_mva(s, cycle_row_7); + let f_mua = get_f_mua(s, cycle_row_7); + let f_out = get_f_out(s, cycle_row_7); + let f_out_next = get_f_out_next(s, cycle_row_6, s[0]', s[1]'); + + # Enforce that unless f_out = 1 or f_out' = 1, the values in columns s[1] and s[2] are copied + # over to the next row. + # This encodes the fact that we can change the op flags only at the end of a cycle in order + # to output a result, or at the start of a new cycle to initiate a new operation. + enf is_unchanged([s[1]]) when !f_out & !f_out_next; + enf is_unchanged([s[2]]) when !f_out & !f_out_next; + + # Flag that is true when the performed operation is one of the operations represented by flags + # f_abp, f_mpa, f_mva or f_mua + let f_comp = f_abp + f_mpa + f_mva + f_mua; + + # Enforce that if any of f_abp, f_mpa, f_mva, f_mua flags is set to 1, the next value of s[0] + # is 0. + # This basically enforces the exclusion of all op which initiate a new op (at the start of a new + # cycle). Note that f_comp is a flag that is set only on rows which are 1 less than a multiple of 8 + # and hence the following constrains the 0-th selector at the start of a new cycle. + enf s[0]' = 0 when f_comp; + + # Enforce that no invalid combinations of flags are allowed. + # This enforces that if s[0] is 0 then the either f_hout or f_sout is set. + enf s[1] = 0 when binary_and(cycle_row_7, binary_not(s[0])); +} + +# Enforce node index constraints +ev node_index([s[3], i]) { + # Compute relevant flags (passing periodic column values as parameters) + let f_mp = get_f_mp(s, cycle_row_0); + let f_mv = get_f_mv(s, cycle_row_0); + let f_mu = get_f_mu(s, cycle_row_0); + let f_mpa = get_f_mpa(s, cycle_row_7); + let f_mva = get_f_mva(s, cycle_row_7); + let f_mua = get_f_mua(s, cycle_row_7); + let f_out = get_f_out(s, cycle_row_7); + + # Flag indicating to enforce the constraint that b is binary only when a new node is absorbed into + # the hasher state (when the hash operation is either one of Merkle path verification or + # Merkle root update) + let f_an = f_mp + f_mv + f_mu + f_mpa + f_mva + f_mua; + + # b is the value of the bit which is discarded during shift by one bit to the right. + let b = i - 2 * i'; + + # Enforce that b is binary only when a new node is absorbed into the hasher state. + enf b^2 - b = 0 when f_an; + + # Enforce that when a computation is finished i = 0. + enf i = 0 when f_out; + + # Enforce that the value in i is copied over to the next row unless we are absorbing a new row + # or the computation is finished. + let not_absorbing_nor_comp_finished = 1 - (f_an + f_out); + enf is_unchanged([i]) when not_absorbing_nor_comp_finished; +} + +# Enforce hasher state constraints +ev hasher_state([s[3], h[12], i]) { + # Enforce the RPO permutation round constraints + enf enforce_rpo_round([h]) when !cycle_row_7; + + # Compute relevant flags (passing periodic column values as parameters) + let f_mp = get_f_mp(s, cycle_row_0); + let f_mv = get_f_mv(s, cycle_row_0); + let f_mu = get_f_mu(s, cycle_row_0); + let f_abp = get_f_abp(s, cycle_row_7); + + # Flag that is true when the performed operation includes absorbing the next node during Merkle + # path computation. + let f_absorb_node = f_mp + f_mv + f_mu; + + # b is the value of the bit which is discarded during shift by one bit to the right. + let b = i - 2 * i'; + + # Enforce that when absorbing the next set of elements into the state during linear hash + # computation (i.e. f_abp = 1) the first 4 elements (the capacity portion) are carried over to + # the next row. + enf f_abp * (h' - h) = 0 for h in h; + + # TODO: Double check the following and fix both docs and VM if there is a typo + # + # Enforce that when absorbing the next node during Merkle path computation + # (i.e. f_mp + f_mv + f_mu = 1), the result of the previous hash (h[4], ..., h[7]) are copied + # over either to (h[4]', ..., h[7]') or to (h[8]', ..., h[11]') depending on the value of b. + # + # TODO: uncomment when computed indices are supported + # enf match { + # !b & f_absorb_node: is_unchanged(h[j + 4]) for j in 0..4, + # b & f_absorb_node: h[j + 8]' = h[j + 4] for j in 0..4 + # } +} + +########################################################################################## +# HELPER FUNCTIONS +########################################################################################## + +########################################################################################## +# INSTRUCTION FLAGS - DETAILED DESCRIPTIONS +########################################################################################## +# +# The hasher chiplet uses selector columns s[0], s[1], s[2] to encode different operations: +# +# INITIALIZATION FLAGS (on rows which are multiples of 8 - cycle_row_0): +# • f_bp: (1,0,0) - Begin Permutation +# Initiates: single permutation, 2-to-1 hash, or linear hash computation +# • f_mp: (1,0,1) - Merkle Path verification +# Starts standard Merkle path verification computation +# • f_mv: (1,1,0) - Merkle path Verification for "old" node +# Begins verification for old leaf value during Merkle root update +# • f_mu: (1,1,1) - Merkle path verification for "new" node +# Starts verification for new leaf value during Merkle root update +# +# ABSORPTION FLAGS (on rows 1 less than multiple of 8 - cycle_row_7): +# • f_abp: (1,0,0) - Absorb elements for linear hash (continuing computation) +# Absorbs next set of elements into hasher state during linear hash +# • f_mpa: (1,0,1) - Merkle Path Absorb during standard verification +# Absorbs next Merkle path node during standard verification +# • f_mva: (1,1,0) - Merkle path absorb for "old" node verification +# Absorbs next node during "old" leaf verification (Merkle root update) +# • f_mua: (1,1,1) - Merkle path absorb for "new" node verification +# Absorbs next node during "new" leaf verification (Merkle root update) +# +# OUTPUT FLAGS (on rows 1 less than multiple of 8 - cycle_row_7): +# • f_hout: (0,0,0) - Hash Output +# Returns the result of the currently running hash computation +# • f_sout: (0,0,1) - State Output +# Returns the entire 12-element hasher state +# • f_out: Combined flag (f_hout | f_sout) - any output operation +# +########################################################################################## + +# f_mp: Merkle Path verification flag (1,0,1) on cycle_row_0 +# Initiates standard Merkle path verification computation. +fn get_f_mp(s: felt[3], row0: felt) -> felt { + return row0 & s[0] & binary_not(s[1]) & s[2]; +} + +# f_mv: Merkle path Verification for "old" node flag (1,1,0) on cycle_row_0 +# Begins verification for old leaf value during Merkle root update computation. +fn get_f_mv(s: felt[3], row0: felt) -> felt { + return row0 & s[0] & s[1] & binary_not(s[2]); +} + +# f_mu: Merkle path verification for "new" node flag (1,1,1) on cycle_row_0 +# Starts verification for new leaf value during Merkle root update computation. +fn get_f_mu(s: felt[3], row0: felt) -> felt { + return row0 & s[0] & s[1] & s[2]; +} + +# f_abp: Absorb elements for linear hash flag (1,0,0) on cycle_row_7 +# Absorbs next set of elements into hasher state during linear hash computation. +fn get_f_abp(s: felt[3], row7: felt) -> felt { + return row7 & s[0] & binary_not(s[1]) & binary_not(s[2]); +} + +# f_mpa: Merkle Path Absorb flag (1,0,1) on cycle_row_7 +# Absorbs next Merkle path node during standard verification computation. +fn get_f_mpa(s: felt[3], row7: felt) -> felt { + return row7 & s[0] & binary_not(s[1]) & s[2]; +} + +# f_mva: Merkle path absorb for "old" node flag (1,1,0) on cycle_row_7 +# Absorbs next node during "old" leaf verification (Merkle root update computation). +fn get_f_mva(s: felt[3], row7: felt) -> felt { + return row7 & s[0] & s[1] & binary_not(s[2]); +} + +# f_mua: Merkle path absorb for "new" node flag (1,1,1) on cycle_row_7 +# Absorbs next node during "new" leaf verification (Merkle root update computation). +fn get_f_mua(s: felt[3], row7: felt) -> felt { + return row7 & s[0] & s[1] & s[2]; +} + +# We can define two flags: +# 1. Flag f_hout = cycle_row_7 & binary_not(s[0]) & binary_not(s[1]) & binary_not(s[2]), +# which is set to 1 when selector flags are (0,0,0) on rows which are 1 less than a multiple +# of 8. This flag is for the instruction that returns the resulting digest of the currently +# running computation. +# 2. Flag f_sout = cycle_row_7 & binary_not(s[0]) & binary_not(s[1]) & s[2], which is set to 1 +# when selector flags are (0,0,1) on rows which are 1 less than a multiple of 8. This flag is +# for the instruction that returns the whole hasher state. +# +# Flag f_out is set to 1 when either f_hout = 1 or f_sout = 1 in the current row. +fn get_f_out(s: felt[3], row7: felt) -> felt { + return row7 & binary_not(s[0]) & binary_not(s[1]); +} + +# Flag f_out_next is set to 1 when either f_hout = 1 or f_sout = 1 in the next row. +fn get_f_out_next(s: felt[3], row6: felt, s0_next: felt, s1_next: felt) -> felt { + return row6 & binary_not(s0_next) & binary_not(s1_next); +} + +########################################################################################## +# PERIODIC COLUMNS +########################################################################################## +# +# There are 3 periodic columns used to help select the instruction executed at a given row +# +periodic_columns { + cycle_row_0: [1, 0, 0, 0, 0, 0, 0, 0], + cycle_row_6: [0, 0, 0, 0, 0, 0, 1, 0], + cycle_row_7: [0, 0, 0, 0, 0, 0, 0, 1], +} diff --git a/constraints/kernel_rom.air b/constraints/kernel_rom.air new file mode 100644 index 000000000..49b83f463 --- /dev/null +++ b/constraints/kernel_rom.air @@ -0,0 +1,77 @@ +########################################################################################## +# KERNEL ROM CHIPLET +########################################################################################## +# +# The Kernel ROM chiplet is responsible for tracking execution of kernel (system) calls. +# It maintains a record of all kernel procedure digests and ensures proper initialization +# of kernel procedures while preserving privacy about call frequency. +# +# The chiplet contains digest values for all kernel procedures and enforces contiguity +# constraints to ensure proper execution tracking. +# +# STATUS: Core constraints implemented, bus constraints not implemented +# +# REFERENCES: +# - Kernel ROM Chiplet: https://0xmiden.github.io/miden-vm/design/chiplets/kernel_rom.html +########################################################################################## + +mod kernel_rom + +use utils::*; + +########################################################################################## +# KERNEL ROM CHIPLET TRANSITION CONSTRAINTS +########################################################################################## + +# Enforces the constraints on the kernel ROM chiplet given its columns. +# +# Parameters: +# - s4: Chiplet selector flag +# - sfirst: Section first flag (1 = start of new digest block, 0 = continuation of a given block) +# - r[4]: Kernel procedure root/digest (4 field elements) +# +# Max constraint degree: 3 +ev ker_rom_chiplet_constraints([s4, sfirst, r[4]]) { + enf kernel_rom_selector([sfirst]); + enf kernel_rom_digest_contiguity([s4, sfirst, r]); +} + +# Enforces initialization constraints for the first row of kernel ROM chiplet execution. +# +# According to the official specification, the first row must have sfirst = 1 to ensure +# proper digest matching and initialization of kernel procedure tracking. +# +# Max constraint degree: 1 +ev kernel_rom_chiplet_constraints_first_row([s4, sfirst, r[4]]) { + # FIRST ROW INITIALIZATION CONSTRAINT: + # The first row of any kernel ROM chiplet execution must have sfirst' = 1 + # This ensures proper initialization of kernel procedure digest tracking. + # Without this, the chiplet could start in an invalid intermediate state. + enf sfirst' = 1; +} + +########################################################################################## +# HELPERS +########################################################################################## + +# Enforces that the kernel ROM selector is valid. +# +# Max constraint degree: 2 +ev kernel_rom_selector([sfirst]) { + # Enforce that sfirst is binary (0 or 1) + enf is_binary([sfirst]); +} + +# Enforces digest contiguity constraints for the kernel ROM. +# +# When sfirst' = 0 (not starting a new digest block), the digest values must remain +# unchanged from the current row to the next row. This ensures contiguous blocks of +# identical digest values for proper tracking of kernel procedure executions. +# +# Max constraint degree: 3 +ev kernel_rom_digest_contiguity([s4, sfirst, r[4]]) { + # Constraint is active when: + # - sfirst' = 0 (next row is not the start of a new digest block) + # - s4' = 0 (next row is still within the kernel ROM chiplet) + enf is_unchanged([r_i]) for r_i in r when binary_and(binary_not(s4'), binary_not(sfirst')); +} diff --git a/constraints/memory.air b/constraints/memory.air new file mode 100644 index 000000000..efb9579d4 --- /dev/null +++ b/constraints/memory.air @@ -0,0 +1,237 @@ +########################################################################################## +# MEMORY CONSTRAINTS MODULE +########################################################################################## +# +# The Memory chiplet provides linear read-write random access memory for the Miden VM. +# Memory is element-addressable with addresses in range [0, 2^32), supporting both individual +# element access and optimized word-aligned batch operations (4 elements per word). +# +# MEMORY TABLE LAYOUT: +# ┌─────────────────┬────────────────────────────────────────────────────────────────┐ +# │ Column │ Purpose │ +# ├─────────────────┼────────────────────────────────────────────────────────────────┤ +# │ is_read │ Read/write selector: 1=read, 0=write │ +# │ is_word_access │ Element/word access selector: 0=element, 1=word │ +# │ ctx │ Context ID for execution context separation │ +# │ addr │ Memory address (word-aligned for word access) │ +# │ idx0/1 │ Element index within word [0,3] - binary decomposition │ +# │ clk │ Clock cycle when operation occurred │ +# │ v0-v3 │ Four field elements stored in memory word │ +# │ d0/d1 │ Delta tracking columns for monotonicity verification │ +# │ d_inv │ Inverse delta column for consecutive transitions │ +# │ f_scw │ Same context/word flag for sequential access optimization │ +# └─────────────────┴────────────────────────────────────────────────────────────────┘ +# +# STATUS: Partially implemented (missing bus integration) +# +# REFERENCES: +# - Memory Design: https://0xmiden.github.io/miden-vm/design/chiplets/memory.html +########################################################################################## + +mod memory + +use utils::*; + +########################################################################################## +# MEMORY CHIPLET CONSTRAINTS +########################################################################################## + +# Enforces proper memory initialization when entering memory chiplet +ev memory_chiplet_constraints_first_row([memory[15]]) { + let is_read = memory[0]; # Read(1)/Write(0) selector + let is_word_access = memory[1]; # Element(0)/Word(1) access selector + let ctx = memory[2]; # Context ID + let addr = memory[3]; # Memory address + let idx0 = memory[4]; # Element index bit 0 + let idx1 = memory[5]; # Element index bit 1 + let v0 = memory[7]; # Memory value 0 + let v1 = memory[8]; # Memory value 1 + let v2 = memory[9]; # Memory value 2 + let v3 = memory[10]; # Memory value 3 + + let is_constrained_value = compute_element_access_flags(idx0', idx1', is_read', is_word_access'); + + # Enforce that when v'[i] is not written to, then v'[i] must be 0. + enf v0' = 0 when is_constrained_value[0]; + enf v1' = 0 when is_constrained_value[1]; + enf v2' = 0 when is_constrained_value[2]; + enf v3' = 0 when is_constrained_value[3]; +} + +# Enforces constraints that apply to every row in the memory chiplet +# These are basic validity constraints for selectors and indices +ev memory_chiplet_constraints_all_rows([memory[15]]) { + let is_read = memory[0]; # Read(1)/Write(0) selector + let is_word_access = memory[1]; # Element(0)/Word(1) access selector + let idx0 = memory[4]; # Element index bit 0 + let idx1 = memory[5]; # Element index bit 1 + + # Read/write selector must be binary + enf is_binary([is_read]); + + # Element/word access selector must be binary + enf is_binary([is_word_access]); + + # Index bit 0 must be binary + enf is_binary([idx0]); + + # Index bit 1 must be binary + enf is_binary([idx1]); +} + +# Enforces memory state transition constraints for all rows except the final row +# Includes monotonicity, value consistency, and proper read/write semantics +ev memory_chiplet_constraints_all_rows_except_last([memory[15]]) { + let is_read = memory[0]; # Read(1)/Write(0) selector + let is_word_access = memory[1]; # Element(0)/Word(1) access selector + let ctx = memory[2]; # Context ID + let addr = memory[3]; # Memory address + let idx0 = memory[4]; # Element index bit 0 + let idx1 = memory[5]; # Element index bit 1 + let clk = memory[6]; # Clock cycle + let v0 = memory[7]; # Memory value 0 + let v1 = memory[8]; # Memory value 1 + let v2 = memory[9]; # Memory value 2 + let v3 = memory[10]; # Memory value 3 + let d0 = memory[11]; # Context delta + let d1 = memory[12]; # Address delta + let d_inv = memory[13]; # Delta inverse + let f_scw = memory[14]; # Same context/word flag + + # Delta inverse constraints + enf enforce_d_inv([ctx, addr, clk, d0, d1, d_inv]); + + # Context/address/clock delta constraints + enf enforce_delta([ctx, addr, clk, d0, d1, d_inv]); + + # Same context/word flag constraints + enf enforce_flag_same_context_and_word([ctx, addr, d_inv, f_scw]); + + # Same context/word/addr/clock access constraints + enf enforce_same_context_word_addr_and_clock([is_read, clk, f_scw, d_inv]); + + # Memory value constraints + enf enforce_values_consistency([is_read, is_word_access, idx0, idx1, v0, v1, v2, v3, f_scw]); +} + +########################################################################################## +# HELPER EVALUATORS +########################################################################################## + +# Constrains the delta inverse column +ev enforce_d_inv([ctx, addr, clk, d0, d1, d_inv]) { + let ctx_delta = ctx' - ctx; + let addr_delta = addr' - addr; + let is_ctx_changed = ctx_delta * d_inv'; + let is_addr_changed = addr_delta * d_inv'; + + # is_ctx_changed is binary + enf binary_constraint(is_ctx_changed) = 0; + + # When context changes, is_ctx_changed must be 1 + enf ctx_delta = 0 when !is_ctx_changed; + + # When is_ctx_changed is 0 then is_addr_changed is binary + enf binary_constraint(is_addr_changed) = 0 when !is_ctx_changed; + + # When is_ctx_changed and is_addr_changed are both 0, then address must not change + enf addr_delta = 0 when binary_not(is_ctx_changed) * binary_not(is_addr_changed); +} + +# Enforces monotonicity constraints for context, address, and clock transitions +ev enforce_delta([ctx, addr, clk, d0, d1, d_inv]) { + let ctx_delta = ctx' - ctx; + let addr_delta = addr' - addr; + let clk_delta = clk' - clk; + let delta_next = d1' * 2^16 + d0'; + let is_ctx_changed = ctx_delta * d_inv'; + let is_addr_changed = addr_delta * d_inv'; + + enf is_ctx_changed * ctx_delta + binary_not(is_ctx_changed) * (is_addr_changed * addr_delta + binary_not(is_addr_changed) * clk_delta) = delta_next; +} + +# Enforces correct f_scw flag computation +ev enforce_flag_same_context_and_word([ctx, addr, d_inv, f_scw]) { + let ctx_delta = ctx' - ctx; + let addr_delta = addr' - addr; + let is_ctx_changed = ctx_delta * d_inv'; + let is_addr_changed = addr_delta * d_inv'; + + enf f_scw' = binary_not(is_ctx_changed) * binary_not(is_addr_changed); +} + +# Enforces that accesses to same context, word address, and clock must be reads +ev enforce_same_context_word_addr_and_clock([is_read, clk, f_scw, d_inv]) { + let clk_delta = clk' - clk; + let clk_no_change = binary_not(clk_delta * d_inv'); + + enf f_scw' * clk_no_change * binary_not(is_read) * binary_not(is_read') = 0; +} + +# Enforces memory value consistency, and proper read/write semantics +ev enforce_values_consistency([is_read, is_word_access, idx0, idx1, v0, v1, v2, v3, f_scw]) { + let is_constrained_value = compute_element_access_flags(idx0', idx1', is_read', is_word_access'); + + # Non-first row constraints: if v[i]' is not written to and, + # - (f_scw' = 1) then its value needs to be copied over from the previous row, + # - (f_scw' = 0) then its value needs to be set to 0. + enf f_scw' * (v0' - v0) + binary_not(f_scw') * v0' = 0 when is_constrained_value[0]; + enf f_scw' * (v1' - v1) + binary_not(f_scw') * v1' = 0 when is_constrained_value[1]; + enf f_scw' * (v2' - v2) + binary_not(f_scw') * v2' = 0 when is_constrained_value[2]; + enf f_scw' * (v3' - v3) + binary_not(f_scw') * v3' = 0 when is_constrained_value[3]; +} + +########################################################################################## +# HELPER FUNCTIONS +########################################################################################## + +# Memory chiplet active flag +fn flag_all_rows(s0: felt, s1: felt, s2: felt) -> felt { + return s0 * s1 * binary_not(s2); +} + +# Memory chiplet active flag for next row +fn flag_all_rows_next(s0: felt, s1: felt, s2: felt) -> felt { + return s0' * s1' * binary_not(s2'); +} + +# Memory chiplet active in current row and row is not the last one of the memory chiplet +fn flag_memory_active_not_last_row(s0: felt, s1: felt, s2: felt) -> felt { + return s0 * s1 * binary_not(s2'); +} + +# First row of memory chiplet (transitioning from bitwise to memory) +fn flag_next_row_first_row_memory(s0: felt, s1: felt, s2: felt) -> felt { + return (1 - s1) * flag_all_rows_next(s0, s1, s2); +} + +# Computes constraint flag: 1 if value needs to be constrained, 0 otherwise +fn compute_constrained_values(is_accessed_i: felt, is_read_next: felt, is_word_access_next: felt) -> felt { + let z_i = binary_not(is_word_access_next) * binary_not(is_accessed_i); + return is_read_next + binary_not(is_read_next) * z_i; +} + +# Computes which memory elements need to be constrained based on access pattern +# Returns array of 4 flags indicating whether each element (v0-v3) needs constraining +# +# is_constrainted_value_i is set to 1 when `v'[i]` is not written to, and 0 otherwise. +# +# In other words, is_constrainted_value_i is set to 1 when `v'[i]` needs to be constrained (to either 0 or `v[i]`). +# +# Note that `is_constrainted_value_i` only uses values in the "next" row. This is because it must be used to +# constrain the first row of the memory chiplet, where that row sits in the "next" position of +# the frame, and the "current" row belongs to the previous chiplet (and hence the "current" row +# must not be accessed). +# +# As a result, `is_constrainted_value_i` does not include the constraint of being in the memory chiplet, or in the +# same context and word - these must be enforced separately. +fn compute_element_access_flags(idx0_next: felt, idx1_next: felt, is_read_next: felt, is_word_access_next: felt) -> felt[4] { + # Element selection flags: is_accessed[i] = 1 when element index (2*idx1 + idx0) equals i + let is_accessed_0 = binary_not(idx1_next) * binary_not(idx0_next); + let is_accessed_1 = binary_not(idx1_next) * idx0_next; + let is_accessed_2 = idx1_next * binary_not(idx0_next); + let is_accessed_3 = idx1_next * idx0_next; + + let is_accessed = [is_accessed_0, is_accessed_1, is_accessed_2, is_accessed_3]; + return [compute_constrained_values(is_accessed_i, is_read_next, is_word_access_next) for is_accessed_i in is_accessed]; +} diff --git a/constraints/miden-vm-old/bitwise.air b/constraints/miden-vm-old/bitwise.air new file mode 100644 index 000000000..8c186367f --- /dev/null +++ b/constraints/miden-vm-old/bitwise.air @@ -0,0 +1,114 @@ +mod BitwiseAir + +### Constants and periodic columns ################################################################ + +periodic_columns { + k0: [1, 0, 0, 0, 0, 0, 0, 0] + k1: [1, 1, 1, 1, 1, 1, 1, 0] +} + + +### Helper functions ############################################################################## + +# Returns value aggregated from limbs. +fn aggregate(limb: vector[4]) -> scalar: + return sum([2^i * a for (i, a) in (0..4, limb)]) + + +### Helper evaluators ############################################################################# + +# Enforces that column must be binary. +# +# Constraint degree: 2 +ev is_binary([a]) { + enf a^2 = a +} + + +# Enforces that the bitwise selector is valid. +# +# Max constraint degree: 2 +ev bitwise_selector([s]) { + # Enforce that selector must be binary. + # Constraint degree: 2 + enf is_binary([s]) + + # Enforce that selector should stay the same throughout the cycle. + # Constraint degree: 2 + enf s' = s when k1 +} + + +# Enforces that the input to the bitwise chiplet is decomposed into limbs correctly. +# +# Max constraint degree: 2 +ev input_decomposition([a, b, a_limb[4], b_limb[4]]) { + # Enforce that the input is decomposed into valid bits. + # Constraints degree: 2 + enf is_binary([a]) for a in a_limb + enf is_binary([b]) for b in b_limb + + # Enforce that the value in the first row of column `a` of the current 8-row cycle should be + # the aggregation of the decomposed bit columns `a_limb`. + let a_aggr = aggregate(a_limb) + # Constraint degree: 2 + enf a = a_aggr when k0 + + # Enforce that the value in the first row of column `b` of the current 8-row cycle should be + # the aggregation of the decomposed bit columns `b_limb`. + let b_aggr = aggregate(b_limb) + # Constraint degree: 2 + enf b = b_aggr when k0 + + # Enforce that for all rows in an 8-row cycle, except for the last one, the values in a and b + # columns are increased by the values contained in the individual bit columns a_limb and + # b_limb. + # Constraints degree: 2 + enf a' = a * 16 + a_aggr when k1 + enf b' = b * 16 + b_aggr when k1 +} + + +# Enforces that the output of the bitwise operation is aggregated correctly from the decomposed +# limbs. +# +# Max constraint degree: 3 +ev output_aggregation([s, a, b, a_limb[4], b_limb[4], zp, z]) { + # Enforce that in the first row, the aggregated output value of the previous row should be 0. + # Constraint degree: 2 + enf zp = 0 when k0 + + # Enforce that for each row except the last, the aggregated output value must equal the + # previous aggregated output value in the next row. + # Constraint degree: 2 + enf zp' = z when k1 + + # Enforce that for all rows the value in the z column is computed by multiplying the previous + # output value (from the zp column in the current row) by 16 and then adding it to the bitwise + # operation applied to the row's set of bits of a_limb and b_limb. The entire constraint must + # also be multiplied by the operation selector flag to ensure it is only applied for the + # appropriate operation. The constraint for AND is enforced when s = 0 and the constraint for + # XOR is enforced when s = 1. Because the selectors for the AND and XOR operations are mutually + # exclusive, the constraints for different operations can be aggregated into the same result + # indices. + # Constraints degree: 3 + let a_and_b = sum([2^i * a * b for (i, a, b) in (0..4, a_limb, b_limb)]) + let a_xor_b = sum([2^i * (a + b - 2 * a * b) for (i, a, b) in (0..4, a_limb, b_limb)]) + match enf: + z = zp * 16 + a_xor_b when s + z = zp * 16 + a_and_b when !s +} + + +### Bitwise Chiplet Air Constraints ############################################################### + +# Enforces the constraints on the bitwise chiplet, given the columns of the bitwise execution +# trace. +# +# Max constraint degree: 4 +ev bitwise_chiplet([s, a, b, a_limb[4], b_limb[4], zp, z]) { + enf bitwise_selector([s]) + enf input_decomposition([a, b, a_limb, b_limb]) + enf output_aggregation([s, a, b, a_limb, b_limb, zp, z]) + # Bus constraint is implemented in a separate file +} diff --git a/constraints/miden-vm-old/chiplets.air b/constraints/miden-vm-old/chiplets.air new file mode 100644 index 000000000..2cc397ed2 --- /dev/null +++ b/constraints/miden-vm-old/chiplets.air @@ -0,0 +1,38 @@ +mod ChipletsConstraintsAir + +use bitwise::bitwise_chiplet +use hash::hash_chiplet +use memory::memory_chiplet + +### Helper evaluators ############################################################################# + +# Enforces that the provided columns must be binary. +ev is_binary([a]) { + enf a^2 = a +} + +# Enforces that the chiplet selector columns are set correctly. +ev chiplet_selectors([s[3]]) { + # Enforce that selectors are binary. + enf is_binary([s[0]]) + enf is_binary([s[1]]) when s[0] + enf is_binary([s[2]]) when s[0] & s[1] + + # Enforce that the chiplets are stacked correctly by restricting selector values so they can + # only change from 0 to 1. + enf s[0]' = s[0] when s[0] + enf s[1]' = s[1] when s[0] & s[1] + enf s[2]' = s[2] when s[0] & s[1] & s[2] +} + +### Chiplets Constraints ########################################################################## + +# Enforce the constraints on the hash, bitwise or memory chiplet, given the columns of the chiplet +# module trace. +ev chiplets([s[3], chiplet_columns[15]]) { + enf chiplet_selectors([s]) + match enf: + hash_chiplet([s[1], s[2], chiplet_columns]) when !s[0] + bitwise_chiplet([s[2], chiplet_columns]) when s[0] & !s[1] + memory_chiplet([chiplet_columns]) when s[0] & s[1] & !s[2]' +} \ No newline at end of file diff --git a/constraints/miden-vm-old/decoder.air b/constraints/miden-vm-old/decoder.air new file mode 100644 index 000000000..cdab70682 --- /dev/null +++ b/constraints/miden-vm-old/decoder.air @@ -0,0 +1,654 @@ +mod DecoderAir + +### Constants and periodic columns ################################################################ + +const HASHER_LINEAR_HASH = 3 +const HASHER_RETURN_HASH = 1 + +periodic_columns { + cycle_row_0: [1, 0, 0, 0, 0, 0, 0, 0] + cycle_row_7: [0, 0, 0, 0, 0, 0, 0, 1] +} + +### Helper functions ############################################################################## + +# Returns the f_join operation flag which is set when JOIN control operation is executed. +# +# Flag degree: 6 +fn get_f_join(b: vector[7]) -> scalar: + return b[6] & !b[5] & b[4] & b[3] & !b[2] & b[1] + + +# Returns the f_split operation flag which is set when SPLIT control operation is executed. +# +# Flag degree: 6 +fn get_f_split(b: vector[7]) -> scalar: + return b[6] & !b[5] & b[4] & b[3] & b[2] & !b[1] + + +# Returns the f_loop operation flag which is set when LOOP control operation is executed. +# +# Flag degree: 6 +fn get_f_loop(b: vector[7]) -> scalar: + return b[6] & !b[5] & b[4] & b[3] & b[2] & b[1] + + +# Returns the f_repeat operation flag which is set when REPEAT operation is executed. +# +# Flag degree: 4 +fn get_f_repeat(b: vector[7], extra: scalar) -> scalar: + return extra & b[4] & !b[3] & b[2] + + +# Returns the f_span operation flag which is set when SPAN operation is executed. +# +# Flag degree: 6 +fn get_f_span(b: vector[7]) -> scalar: + return b[6] & !b[5] & b[4] & b[3] & !b[2] & !b[1] + + +# Returns the f_respan operation flag which is set when RESPAN operation is executed. +# +# Flag degree: 4 +fn get_f_respan(b: vector[7], extra: scalar) -> scalar: + return extra & b[4] & b[3] & !b[2] + + +# Returns the f_call operation flag which is set when CALL control operation is executed. +# +# Flag degree: 4 +fn get_f_call(b: vector[7], extra: scalar) -> scalar: + return extra & !b[4] & b[3] & b[2] + + +# Returns the f_syscall operation flag which is set when SYSCALL control operation is executed. +# +# Flag degree: 4 +fn get_f_syscall(b: vector[7], extra: scalar) -> scalar: + return extra & !b[4] & b[3] & !b[2] + + +# Returns the f_end operation flag which is set when END operation is executed. +# +# Flag degree: 4 +fn get_f_end(b: vector[7], extra: scalar) -> scalar: + return extra & b[4] & !b[3] & !b[2] + + +# Returns the f_halt operation flag which is set when HALT operation is executed. +# +# Flag degree: 4 +fn get_f_halt(b: vector[7], extra: scalar) -> scalar: + return extra & b[4] & b[3] & b[2] + + +# Returns the f_push operation flag which is set when PUSH operation is executed. +# +# Flag degree: 4 +fn get_f_push(b: vector[7], extra: scalar) -> scalar: + return extra & !b[4] & !b[3] & b[2] + + +# Returns the f_ctrl flag which is set when any one of the control flow operations (JOIN, SPLIT, +# LOOP, REPEAT, SPAN, RESPAN, CALL, SYSCALL, END, HALT) is being executed. +# +# Flag degree: 4 +fn get_f_ctrl(b: vector[7], extra: scalar) -> scalar: + # flag for SPAN, JOIN, SPLIT, LOOP + let f_sjsl = b[6] & !b[5] & b[4] & b[3] + + # flag for END, REPEAT, RESPAN, HALT + let f_errh = b[6] & b[5] & b[4] + + return f_sjsl + f_errh + get_f_call(b, extra) + get_f_syscall(b, extra) + + +# Returns f_ctrli flag which is set to 1 when a control flow operation that signifies the +# initialization of a control block (JOIN, SPLIT, LOOP, CALL, SYSCALL) is being executed on the VM. +# +# Flag degree: 6 +fn get_f_ctrli(b: vector[7], extra: scalar) -> scalar: + return get_f_join(b) + get_f_split(b) + get_f_loop(b) + get_f_call(b, extra) + get_f_syscall(b, extra) + + +# Returns transition label, composed of the operation label and the periodic columns that uniquely +# identify each transition function. +fn get_transition_label(op_label: scalar) -> scalar: + return op_label + 2^4 * cycle_row_7 + 2^5 * cycle_row_0 + + +# Returns f_g8 flag which is set to 1 if there are 8 operation groups in the batch. +fn get_f_g8(op_batch_flags: vector[3]) -> scalar: + return op_batch_flags[0] + + +# Returns f_g4 flag which is set to 1 if there are 4 operation groups in the batch. +fn get_f_g4(op_batch_flags: vector[3]) -> scalar: + return !op_batch_flags[0] & op_batch_flags[1] & op_batch_flags[2] + + +# Returns f_g2 flag which is set to 1 if there are 2 operation groups in the batch. +fn get_f_g2(op_batch_flags: vector[3]) -> scalar: + return !op_batch_flags[0] & !op_batch_flags[1] & op_batch_flags[2] + + +# Returns f_g1 flag which is set to 1 if there are 1 operation groups in the batch. +fn get_f_g1(op_batch_flags: vector[3]) -> scalar: + return !op_batch_flags[0] & op_batch_flags[1] & !op_batch_flags[2] + + +### Helper evaluators ############################################################################# + +# Enforces that column must be binary. +# Constraint degree: 2 +ev is_binary([a]) { + enf a^2 = a +} + + +# Enforces that value in column is copied over to the next row. +# Constraint degree: 1 +ev is_unchanged([column]) { + enf column' = column +} + + +# Enforces decoder general constraints. +# +# Max constraint degree: 9 +ev general([addr, op_bits[7], hasher[8], in_span, s0, extra]) { + # Get flags required for the general constraints + let f_repeat = get_f_repeat(op_bits, extra) + let f_end = get_f_end(op_bits, extra) + let f_halt = get_f_halt(op_bits, extra) + + # Enforce that `extra` column is set to 1 when op_bits[6] = 1 and op_bits[5] = 1 + # Constraint degree: 3 + enf extra = 1 when op_bits[6] & op_bits[5] + + # Enforce that when SPLIT or LOOP operation is executed, the top of the operand stack must + # contain a binary value. + # Constraint degree: 8 + enf is_binary([s0]) when get_f_split(op_bits) | get_f_loop(op_bits) + + # Enforce that When REPEAT operation is executed, the value at the top of the operand stack + # must be 1. + # Constraint degree: 5 + enf s0 = 1 when f_repeat + + # Enforce that when REPEAT operation is executed, the value in hasher[4] column (the + # is_loop_body flag), must be set to 1. This ensures that REPEAT operation can be executed only + # inside a loop. + # Constraint degree: 5 + enf hasher[4] = 1 when f_repeat + + # Enforce that when RESPAN operation is executed, we need to make sure that the block ID is + # incremented by 8. + # Constraint degree: 5 + enf addr' = addr + 8 when f_respan(op_bits, extra) + + # Enforce that when END operation is executed and we are exiting a loop block (i.e., is_loop, + # value which is stored in hasher[5], is 1), the value at the top of the operand stack must be + # 0. + # Constraint degree: 6 + enf s0 = 0 when f_end & hasher[5] + + # Enforce that when END operation is executed and the next operation is REPEAT, values in + # hasher[0], ..., hasher[4] (the hash of the current block and the is_loop_body flag) must be + # copied to the next row. + # Constraint degree: 9 + enf is_unchanged([hasher[i]]) for i in 0..5 when f_end & get_f_repeat(op_bits', extra') + + # Enforce that a HALT instruction can be followed only by another HALT instruction. + # Constraint degree: 8 + enf f_halt * !get_f_halt(op_bits', extra') = 0 + + # Enforce that when a HALT operation is executed, block address column (addr) must be 0. + # Constraint degree: 5 + enf addr = 0 when f_halt + + # Enforce that values in op_bits columns must be binary. + # Constraint degree: 2 + enf is_binary([b]) for b in op_bits + + # Enforce that when the value in in_span column is set to 1, control flow operations cannot be + # executed on the VM, but when in_span flag is 0, only control flow operations can be executed + # on the VM. + # Constraint degree: 4 + enf 1 - in_span - get_f_ctrl(op_bits, extra) = 0 +} + + +# Enforces the constraint for computing block hashes. +# +# Max constraint degree: 8 +ev block_hash_computation([addr, op_bits[7], hasher[8], extra], [p[4]]) { + # Get flags required for the block hash computation constraint + let f_ctrli = get_f_ctrli(op_bits, extra) + let f_span = get_f_span(op_bits) + let f_respan = get_f_respan(op_bits, extra) + let f_end = get_f_end(op_bits, extra) + + # Label specifying that we are starting a new hash computation. + let m_bp = get_transition_label(HASHER_LINEAR_HASH) + + # Label specifying that we are absorbing the next sequence of 8 elements into an ongoing hash + # computation. + let m_abp = get_transition_label(HASHER_LINEAR_HASH) + + # Label specifying that we are reading the result of a hash computation. + let m_hout = get_transition_label(HASHER_RETURN_HASH) + + # `alpha` is the global random values array. + let rate_sum = sum([$alpha[i + 8] * hasher[i] for i in 0..8]) + let digest_sum = sum([$alpha[i + 8] * hasher[i] for i in 0..4]) + + # Variable for initiating a hasher with address addr' and absorbing 8 elements from the hasher + # state (hasher[0], ..., hasher[7]) into it. + let h_init = $alpha[0] + $alpha[1] * m_bp + $alpha[2] * addr' + rate_sum + + # Variable for the absorption. + let h_abp = $alpha[0] + $alpha[1] * m_abp + $alpha[2] * addr' + rate_sum + + # Variable for the result. + let h_res = $alpha[0] + $alpha[1] * m_hout + $alpha[2] * (addr + 7) + digest_sum + + # Opcode value of the opcode being executed on the virtual machine. + let opcode_value = sum([op_bits[i] * 2^i for i in 0..7]) + + # When a control block initializer operation (JOIN, SPLIT, LOOP, CALL, SYSCALL) is executed, a + # new hasher is initialized and the contents of hasher[0], ..., hasher[7] are absorbed into the + # hasher. + # + # Value degree: 7 + let u_ctrli = f_ctrli * (h_init + $alpha[5] * opcode_value) + + # When SPAN operation is executed, a new hasher is initialized and contents of + # hasher[0], ..., hasher[7] are absorbed into the hasher. + # + # Value degree: 7 + let u_span = f_span * h_init + + # When RESPAN operation is executed, contents of hasher[0], ..., hasher[7] (which contain the + # new operation batch) are absorbed into the hasher. + # + # Value degree: 5 + let u_respan = f_respan * h_abp + + # When END operation is executed, the hash result is copied into registers + # hasher[0], ..., hasher[3]. + # + # Value degree: 5 + let u_end = f_end * h_res + + # Enforce the block hash computation constraint. We need to add 1 and subtract the sum of the + # relevant operation flags to ensure that when none of the flags is set to 1, the above + # constraint reduces to p[0]' = p[0]. + # Constraint degree: 8 + enf p[0]' * (u_ctrli + u_span + u_respan + u_end + 1 - + (f_ctrli + f_span + f_respan + f_end)) = p[0] +} + + +# Enforces the constraint for updating the block stack table. +# +# Max constraint degree: 8 +ev block_stack_table([addr, op_bits[7], hasher[8], s0, extra], [p[4]]) { + # Get flags required for the block stack table constraint + let f_join = get_f_join(op_bits) + let f_split = get_f_split(op_bits) + let f_loop = get_f_loop(op_bits) + let f_span = get_f_span(op_bits) + let f_respan = get_f_respan(op_bits, extra) + let f_end = get_f_end(op_bits, extra) + + # When JOIN operation is executed, row (addr', addr, 0) is added to the block stack table. + # Value degree: 7 + let v_join = f_join * ($alpha[0] + $alpha[1] * addr' + $alpha[2] * addr) + + # When SPLIT operation is executed, row (addr', addr, 0) added to the block stack table. + # Value degree: 7 + let v_split = f_split * ($alpha[0] + $alpha[1] * addr' + $alpha[2] * addr) + + # When LOOP operation is executed, row (addr', addr, 1) is added to the block stack table if + # the value at the top of the operand stack is 1, and row (addr', addr, 0) is added to the + # block stack table if the value at the top of the operand stack is 0. + # Value degree: 7 + let v_loop = f_loop * ($alpha[0] + $alpha[1] * addr' + $alpha[2] * addr + $alpha[3] * s0) + + # When SPAN operation is executed, row (addr', addr, 0) is added to the block stack table. + # Value degree: 7 + let v_span = f_span * ($alpha[0] + $alpha[1] * addr' + $alpha[2] * addr) + + # When RESPAN operation is executed, row (addr, hasher[1]', 0) is removed from the block stack + # table, and row (addr', hasher[1]', 0) is added to the table. The prover sets the value of + # register hasher[1] at the next row to the ID of the parent block. + # Value degree: 5 + let u_respan = f_respan * ($alpha[0] + $alpha[1] * addr + $alpha[2] * hasher[1]') + # Value degree: 5 + let v_respan = f_respan * ($alpha[0] + $alpha[1] * addr' + $alpha[2] * hasher[1]') + + # When END operation is executed, row (addr, addr', hasher[5]) is removed from the block span + # table. Register hasher[5] contains the is_loop flag. + # Value degree: 5 + let u_end = f_end * + ($alpha[0] + $alpha[1] * addr + $alpha[2] * addr' + $alpha[3] * hasher[5]) + + # Enforce the block stack table constraint. We need to add 1 and subtract the sum of the + # relevant operation flags from each side to ensure that when none of the flags is set to 1, + # the above constraint reduces to p[1]' = p[1] + # Constraint degree: 8 + enf p[1]' * (u_end + u_respan + 1 - (f_end + f_respan)) = + p[1] * (v_join + v_split + v_loop + v_span + v_respan + 1 - + (f_join + f_split + f_loop + f_span + f_respan)) +} + + +# Enforces the constraint for updating the block hash table. +# +# Max constraint degree: 9 +ev block_hash_table([addr, op_bits[7], hasher[8], s0, extra], [p[4]]) { + # Get flags required for the block hash table constraint + let f_join = get_f_join(op_bits) + let f_split = get_f_split(op_bits) + let f_loop = get_f_loop(op_bits) + let f_end = get_f_end(op_bits, extra) + let f_repeat = get_f_repeat(op_bits, extra) + + # Values representing left and right children of a block. + # Value degree: 1 + let ch1 = $alpha[0] + $alpha[1] * addr' + sum([$alpha[i + 2] * hasher[i] for i in 0..4]) + # Value degree: 1 + let ch2 = $alpha[0] + $alpha[1] * addr' + sum([$alpha[i + 2] * hasher[i + 4] for i in 0..4]) + + # Value representing the result of hash computation. + # Value degree: 1 + let bh = $alpha[0] + $alpha[1] * addr + sum([$alpha[i + 2] * hasher[i]]) + $alpha[7] * hasher[4] + + # When JOIN operation is executed, hashes of both child nodes are added to the block hash + # table. We add alpha[6] term to the first child value to differentiate it from the second + # child (i.e., this sets is_first_child to 1). + # Value degree: 8 + let v_join = f_join * (ch1 + $alpha[6]) * ch2 + + # When SPLIT operation is executed and the top of the stack is 1, hash of the true branch is + # added to the block hash table, but when the top of the stack is 0, hash of the false branch + # is added to the block hash table. + # Value degree: 8 + let v_split = f_split * (s0 * ch1 + (1 - s0) * ch2) + + # When LOOP operation is executed and the top of the stack is 1, hash of loop body is added to + # the block hash table. We add alpha[7] term to indicate that the child is a body of a loop. + # The below also means that if the top of the stack is 0, nothing is added to the block hash + # table as the expression evaluates to 0. + # Value degree: 8 + let v_loop = f_loop * s0 * (ch1 + $alpha[7]) + + # When REPEAT operation is executed, hash of loop body is added to the block hash table. We add + # alpha[7] term to indicate that the child is a body of a loop. + # Value degree: 5 + let v_repeat = f_repeat * (ch1 + $alpha[7]) + + # When END operation is executed, hash of the completed block is removed from the block hash + # table. However, we also need to differentiate between removing the first and the second child + # of a join block. We do this by looking at the next operation. Specifically, if the next + # operation is neither END nor REPEAT we know that another block is about to be executed, and + # thus, we have just finished executing the first child of a join block. Thus, if the next + # operation is neither END nor REPEAT we need to set the term for alpha[6] coefficient to 1 as + # shown below. + # Value degree: 8 + let u_end = f_end * + (bh + $alpha[6] * (1 - (get_f_end(op_bits', extra') + get_f_repeat(op_bits', extra')))) + + # Enforce the block hash table constraint. We need to add 1 and subtract the sum of the + # relevant operation flags from each side to ensure that when none of the flags is set to 1, + # the above constraint reduces to p[2]' = p[2] + # Constraint degree: 9 + enf p[2]' * (u_end + 1 - f_end) = + p[2] * (v_join + v_split + v_loop + v_repeat + 1 - (f_join + f_split + f_loop + f_repeat)) + + # TODO: add boundary constraints to the p[2] column: + # 1. The first value in the column represents a row for the entire program. Specifically, the + # row tuple would be (0, program_hash, 0, 0). This row should be removed from the table + # when the last END operation is executed. + # 2. The last value in the column is 1 - i.e., the block hash table is empty. +} + + +# Enforce that values in in_span column, which is used to identify rows which execute non-control +# flow operations, are set correctly. +# +# Constraint degree: 7 +ev in_span_column([op_bits[7], in_span, extra]) { + # Get flags required for the inspan column constraint + let f_span = get_f_span(op_bits) + let f_respan = get_f_respan(op_bits, extra) + let f_respan_next = get_f_respan(op_bits', extra') + let f_end_next = get_f_end(op_bits', extra') + + # Enforce that when executing SPAN or RESPAN operation, the next value in in_span column must + # be set to 1. + # Constraint degree: 7 + enf in_span' = 1 when f_span | f_respan + + # Enforce that when the next operation is END or RESPAN, the next value in in_span column must + # be set to 0. + # Constraint degree: 5 + enf in_span' = 0 when f_end_next | f_respan_next + + # Enforce that in all other cases, the value in in_span column must be copied over to the next + # row. + # Constraint degree: 7 + enf is_unchanged(in_span) when !f_span & !f_respan & !f_end_next & !f_respan_next + + # TODO: add boundary constraint for in_span column: in_span.first = 0 +} + +# Enforce that when we are inside a span block, values in the block address column (denoted as addr) +# must remain the same. +# +# Constraint degree: 2 +ev block_address([addr, in_span]) { + enf is_unchanged(addr) when in_span +} + +# Enforce that values in group_count column, which is used to keep track of the number of operation +# groups which remains to be executed in a span block, are set correctly. +# +# Max constraint degree: 7 +ev group_count([op_bits[7], hasher[8], in_span, group_count, extra]) { + # Get value of the f_push flag + let f_push = get_f_push(op_bits, extra) + + # Enforce that inside a span block, group count can either stay the same or decrease by one. + # Constraint degree: 3 + enf (group_count' - group_count) * (group_count' - group_count - 1) = 0 when in_span + + # Enforce that when group count is decremented inside a span block, either hasher[0] must be 0 + # (we consumed all operations in a group) or we must be executing PUSH operation. + # Constraint degree: 7 + enf (1 - f_push) * hasher[0] = 0 when in_span & (group_count' - group_count) + + # Enforce that when executing a SPAN, a RESPAN, or a PUSH operation, group count must be + # decremented by 1. + # Constraint degree: 7 + enf group_count' - group_count = 1 when f_span(op_bits) | get_f_respan(op_bits, extra) | f_push + + # Enforce that if the next operation is either an END or a RESPAN, group count must remain the + # same. + # Constraint degree: 5 + enf is_unchanged(group_count) when get_f_end(op_bits', extra') | get_f_respan(op_bits', extra') + + # Enforce that when an END operation is executed, group count must be 0. + # Constraint degree: 5 + enf group_count = 0 when get_f_end(op_bits, extra) +} + +# Enforce that register hasher[0], which is used to keep track of operations to be executed in the +# current operation group, is set correctly. +# +# Max constraint degree: 7 +ev op_group_decoding([op_bits[7], in_span, group_count, extra]) { + # opcode value for the next row. + let op_next = sum([op_bits[i]' * 2^i for i in 0..7]) + + # Flag which is set to 1 when the group count within a span block does not change. We multiply + # it by sp' to make sure the flag is 0 when we are about to end decoding of an operation batch. + let f_sgc = in_span * in_span' * (1 - group_count' + group_count) + + # Enforce that when a SPAN, a RESPAN, or a PUSH operation is executed or when the group count + # does not change, the value in hasher[0] should be decremented by the value of the opcode in + # the next row. + # Constraint degree: 7 + enf hasher[0] - hasher[0]' * 2^7 - op_next = 0 + when f_span(op_bits) | get_f_respan(op_bits, extra) | get_f_push(op_bits, extra) | f_sgc + + # Enforce that when we are in a span block and the next operation is END or RESPAN, the current + # value in hasher[0] column must be 0. + # Constraint degree: 6 + enf (get_f_end(op_bits', extra') + get_f_respan(op_bits', extra')) * hasher[0] = 0 when in_span +} + +# Enforce that the values in op_index column, which tracks index of an operation within its +# operation group, are set correctly. +# +# Max constraint degree: 9 +ev op_index([op_bits[7], in_span, group_count, op_index, extra]) { + # ng is set to 1 when we are about to start executing a new operation group (i.e., group count + # is decremented but we did not execute a PUSH operation). + let ng = group_count' - group_count - get_f_push(op_bits, extra) + + # Enforce that when executing SPAN or RESPAN operations the next value of op_index must be set + # to 0. + # Constraint degree: 7 + enf op_index' = 0 when f_span(op_bits) | get_f_respan(op_bits, extra) + + # Enforce that when starting a new operation group inside a span block, the next value of + # op_index must be set to 0. + # Constraint degree: 6 + enf op_index' = 0 when in_span & ng + + # Enforce that when inside a span block but not starting a new operation group, op_index must + # be incremented by 1. + # Constraint degree: 7 + enf op_index' - op_index = 1 when in_span & in_span' & !ng + + # Enforce that values of op_index must be in the range [0, 8]. + # Constraint degree: 9 + enf prod([op_index - i for i in 0..9]) = 0 +} + +# Enforce that values in operation batch flag columns (denoted op_batch_flags[]), which are used to +# specify how many operation groups are present in an operation batch, are set correctly. +# +# Max constraint degree: 6 +ev op_batch_flags([op_bits[7], hasher[8], op_batch_flags[3], extra]) { + # Get flags required for the op batch flag constraints + let f_g1 = get_f_g1(op_batch_flags) + let f_g2 = get_f_g2(op_batch_flags) + let f_g4 = get_f_g4(op_batch_flags) + let f_g8 = get_f_g8(op_batch_flags) + + # Enforce that all batch flags are binary. + # Constraint degree: 2 + enf is_binary(bc) for bc in op_batch_flags + + # Enforce that when SPAN or RESPAN operations is executed, one of the batch flags must be set + # to 1. + # Constraint degree: 6 + enf f_g1 + f_g2 + f_g4 + f_g8 = 1 when f_span(op_bits) | get_f_respan(op_bits, extra) + + # Enforce that when we have at most 4 groups in a batch, registers h[4], ..., h[7] should be + # set to 0's. + # Constraint degree: 4 + enf hasher[i] = 0 for i in 4..8 when f_g1 | f_g2 | f_g4 + + # Enforce that When we have at most 2 groups in a batch, registers h[2] and h[3] should also be + # set to 0's. + # Constraint degree: 4 + enf hasher[i] = 0 for i in 2..4 when f_g1 | f_g2 + + # Enforce that when we have at most 1 group in a batch, register h[1] should also be set to 0. + # Constraint degree: 4 + enf hasher[1] = 0 when f_g1 +} + +# Enforce that all operation groups in a given batch are consumed before a new batch is started +# (i.e., via a RESPAN operation) or the execution of a span block is complete (i.e., via an END +# operation). +# +# Max constraint degree: 9 +ev op_group_table([addr, op_bits[7], hasher[8], in_span, group_count, op_index, op_batch_flags[3], s0, extra], [p[4]]) { + # Get value of the f_push flag + let f_push = get_f_push(op_bits, extra) + + # opcode value for the next row. + let op_next = sum([op_bits[i]' * 2^i for i in 0..7]) + + # Row value for group in hasher[1] to be added to the op group table when a SPAN or a RESPAN + # operation is executed. + # Value degree: 1 + let v_1 = $alpha[0] + $alpha[1] * addr' + $alpha[2] * (group_count - 1) + $alpha[3] * hasher[1] + + # Value degree: 1 + let prod_v_3 = prod([$alpha[0] + + $alpha[1] * addr' + + $alpha[2] * (group_count - i) + + $alpha[3] * hasher[i] for i in 1..4]) + + # Value degree: 1 + let prod_v_7 = prod([$alpha[0] + + $alpha[1] * addr' + + $alpha[2] * (group_count - i) + + $alpha[3] * hasher[i] for i in 1..8]) + + # The value of the row to be removed from the op group table. + # Value degree: 5 + let u = $alpha[0] + $alpha[1] * addr + $alpha[2] * group_count + $alpha[3] * + ((hasher[0]' * 2^7 + op_next) * (1 - f_push) + s0' * f_push) = 0 + + # A flag which is set to 1 when a group needs to be removed from the op group table. + let f_dg = in_span * (group_count' - group_count) + + # Enforce the constraint for updating op group table. The constraint specifies that when SPAN + # or RESPAN operations are executed, we add between 1 and 7 groups to the op group table, and + # when group count is decremented inside a span block, we remove a group from the op group + # table. + # Constraint degree: 9 + enf p[3]' * (f_dg * u + 1 - f_dg) = p[3] * (get_f_g2(op_batch_flags) * v_1 + + get_f_g4(op_batch_flags) * prod_v_3 + + get_f_g8(op_batch_flags) * prod_v_7 - 1 + + (f_span(op_bits) + get_f_respan(op_bits, extra))) +} + +# Enforce proper decoding of span blocks. +# +# Max constraint degree: 9 +ev span_block([addr, op_bits[7], hasher[8], in_span, group_count, op_index, op_batch_flags[3], s0, extra], [p[4]]) { + enf in_span_column([op_bits, in_span, extra]) + enf block_address([addr, in_span]) + enf group_count([op_bits, hasher, in_span, group_count, extra]) + enf op_group_decoding([op_bits, in_span, group_count, extra]) + enf op_index([op_bits, in_span, group_count, op_index, extra]) + enf op_batch_flags([op_bits, hasher, op_batch_flags, extra]) + enf op_group_table([addr, op_bits, hasher, in_span, group_count, op_index, op_batch_flags, s0, extra], [p]) +} + +### Decoder Air Constraints ####################################################################### + +# Enforces the constraints on the decoder. The register `s0` denotes the value at the top of the +# stack. `extra` denotes the register for degree reduction during flag computations, and p[4] +# columns denote multiset check columns. +# +# Max constraint degree: 9 +ev decoder_constraints([addr, op_bits[7], hasher[8], in_span, group_count, op_index, op_batch_flags[3], s0, extra], [p[4]]) { + enf general([addr, op_bits[7], hasher[8], in_span, s0, extra]) + + enf block_hash_computation([addr, op_bits[7], hasher[8], extra], [p[4]]) + + enf block_stack_table([addr, op_bits[7], hasher[8], s0, extra], [p[4]]) + + enf block_hash_table([addr, op_bits, hasher, s0, extra], [p[4]]) + + enf span_block([addr, op_bits[7], hasher[8], in_span, group_count, op_index, op_batch_flags[3], s0, extra], [p[4]]) +} \ No newline at end of file diff --git a/constraints/miden-vm-old/hash.air b/constraints/miden-vm-old/hash.air new file mode 100644 index 000000000..55165cd19 --- /dev/null +++ b/constraints/miden-vm-old/hash.air @@ -0,0 +1,208 @@ +mod HashChipletAir + +### Constants and periodic columns ################################################################ + +periodic_columns { + cycle_row_0: [1, 0, 0, 0, 0, 0, 0, 0] + cycle_row_6: [0, 0, 0, 0, 0, 0, 1, 0] + cycle_row_7: [0, 0, 0, 0, 0, 0, 0, 1] +} + +### Helper functions ############################################################################## + +# Returns binary negation of the value. +fn binary_not(value: scalar) -> scalar: + return 1 - value + + +# Set to 1 when selector flags are (1,0,1) on rows which are multiples of 8. This is flag of +# the instruction that initiates Merkle path verification computation. +fn get_f_mp(s: vector[3]) -> scalar: + return cycle_row_0 & s[0] & binary_not(s[1]) & s[2] + + +# Set to 1 when selector flags are (1,1,0) on rows which are multiples of 8. This is flag of +# the instruction that initiates Merkle path verification for the "old" node value during +# Merkle root update computation. +fn get_f_mv(s: vector[3]) -> scalar: + return cycle_row_0 & s[0] & s[1] & binary_not(s[2]) + + +# Set to 1 when selector flags are (1,1,1) on rows which are multiples of 8. This is flag of +# the instruction that initiates Merkle path verification for the "new" node value during +# Merkle root update computation. +fn get_f_mu(s: vector[3]) -> scalar: + return cycle_row_0 & s[0] & s[1] & s[2] + + +# Set to 1 when selector flags are (1,0,0) on rows which are 1 less than a multiple of 8. This +# is flag of the instruction that absorbs a new set of elements into the hasher state when +# computing a linear hash of many elements. +fn get_f_abp(s: vector[3]) -> scalar: + return cycle_row_7 & s[0] & binary_not(s[1]) & binary_not(s[2]) + + +# Set to 1 when selector flags are (1,0,1) on rows which are 1 less than a multiple of 8. This +# is flag of the instruction that absorbs the next Merkle path node into the hasher state +# during Merkle path verification computation. +fn get_f_mpa(s: vector[3]) -> scalar: + return cycle_row_7 & s[0] & binary_not(s[1]) & s[2] + + +# Set to 1 when selector flags are (1,1,0) on rows which are 1 less than a multiple of 8. This +# is flag of the instruction that absorbs the next Merkle path node into the hasher state +# during Merkle path verification for the "old" node value during Merkle root update +# computation. +fn get_f_mva(s: vector[3]) -> scalar: + return cycle_row_7 & s[0] & s[1] & binary_not(s[2]) + + +# Set to 1 when selector flags are (1,1,1) on rows which are 1 less than a multiple of 8. This +# is flag of the instruction that absorbs the next Merkle path node into the hasher state +# during Merkle path verification for the "new" node value during Merkle root update +# computation. +fn get_f_mua(s: vector[3]) -> scalar: + return cycle_row_7 & s[0] & s[1] & s[2] + + +# We can define two flags: +# 1. Flag f_hout = cycle_row_7 & binary_not(s[0]) & binary_not(s[1]) & binary_not(s[2]), +# which is set to 1 when selector flags are (0,0,0) on rows which are 1 less than a multiple +# of 8. This is flag of the instruction that returns the result of the currently running +# computation. +# 2. Flag f_sout = cycle_row_7 & binary_not(s[0]) & binary_not(s[1]) & s[2], which is set to 1 +# when selector flags are (0,0,1) on rows which are 1 less than a multiple of 8. This is flag +# of the instruction that returns the whole hasher state. +# +# Flag f_out is set to 1 when either f_hout = 1 or f_sout = 1 in the current row. +fn get_f_out(s: vector[3]) -> scalar: + return cycle_row_7 & binary_not(s[0]) & binary_not(s[1]) + + +# Flag f_out_next is set to 1 when either f_hout = 1 or f_sout = 1 in the next row. +fn get_f_out_next(s: vector[3]) -> scalar: + return cycle_row_6 & binary_not(s[0]') & binary_not(s[1]') + + +### Helper evaluators ############################################################################# + +# Enforces that column must be binary. +ev is_binary(main: [a]) { + enf a^2 = a +} + + +# Enforces that value in column is copied over to the next row. +ev is_unchanged(main: [column]) { + ev column' = column +} + + +# Enforce selector columns constraints +ev selector_columns(main: [s[3]]) { + let f_out = get_f_out(s) + let f_out_next = get_f_out_next(s) + let f_abp = get_f_abp(s) + let f_mpa = get_f_mpa(s) + let f_mva = get_f_mva(s) + let f_mua = get_f_mua(s) + + # Flag that is true when the performed operation is one of the represented by flags f_abp, + # f_mpa, f_mva or f_mua + let f_comp = f_abp + f_mpa + f_mva + f_mua + + # Enforce that selector columns are binary. + enf is_binary([selector]) for selector in s + + # Enforce that unless f_out = 1 or f_out' = 1, the values in columns s[1] and s[2] are copied + # over to the nex row. + enf is_unchanged([s[1]]) when !f_out & !f_out_next + enf is_unchanged([s[2]]) when !f_out & !f_out_next + + # Enforce that if any of f_abp, f_mpa, f_mva, f_mua flags is set to 1, the next value of s[0] + # is 0. + enf s[0]' * f_comp = 0 + + # Enforce that no invalid combinations of flags are allowed. + enf cycle_row_7 * binary_not(s[0]) * s[1] = 0 +} + +# Enforce node index constraints +ev node_index(main: [s[3], i]) { + let f_out = get_f_out(s) + let f_mp = get_f_mp(s) + let f_mv = get_f_mv(s) + let f_mu = get_f_mu(s) + let f_mpa = get_f_mpa(s) + let f_mva = get_f_mva(s) + let f_mua = get_f_mua(s) + + # b is the value of the bit which is discarded during shift by one bit to the right. + let b = i - 2 * i' + + # Flag that allows to enforce constraint that b is binary only when a new node is absorbed into + # the hasher state (when the hash operation is one of Merkle path verification operations or + # next Merkle path node absorption operations) + let f_an = f_mp + f_mv + f_mu + f_mpa + f_mva + f_mua + + # Enforce that b is binary only when a new node is absorbed into the hasher state. + enf f_an * (b^2 - b) = 0 + + # Enforce that when a computation is finished i = 0. + enf f_out * i = 0 + + # Enforce that the value in i is copied over to the next row unless we are absorbing a new row + # or the computation is finished. + let absorbing_or_comp_finished = 1 - f_an - f_out + enf is_unchanged([i]) when absorbing_or_comp_finished +} + +# Enforce hasher state constraints +ev hasher_state(main: [s[3], h[12], i]) { + let f_mp = get_f_mp(s) + let f_mv = get_f_mv(s) + let f_mu = get_f_mu(s) + let f_abp = get_f_abp(s) + + # Flag that is true when the performed operation includes absorbing the next node during Merkle + # path computation. + let f_absorb_node = f_mp + f_mv + f_mu + + # b is the value of the bit which is discarded during shift by one bit to the right. + let b = i - 2 * i' + + # Enforce that when absorbing the next set of elements into the state during linear hash + # computation (i.e. f_abp = 1) the first 4 elements (the capacity portion) are carried over to + # the next row. + enf f_abp * (h[j]' - h[j]) = 0 for j in 0..4 + + # Enforce that when absorbing the next node during Merkle path computation + # (i.e. f_mp + f_mv + f_mu = 1), the result of the previous hash (h[4], ..., h[7]) are copied + # over either to (h[4]', ..., h[7]') or to (h[8]', ..., h[11]') depending on the value of b. + match enf: + is_unchanged(h[j + 4]) for j in 0..4 when !b & f_absorb_node + h[j + 8]' = h[j + 4] for j in 0..4 when b & f_absorb_node +} + +### Hash Chiplet Air Constraints ################################################################## + +# Enforces the constraints on the hash chiplet, given the columns of the hash execution trace. +ev hash_chiplet(main: [s[3], r, h[12], i]) { + ## Row address constraint ## + # TODO: Apply row address constraints: + # 1. Boundary constraint `enf r.first = 1` + # 2. Transition constraint. It requires chiplets module's selector flag s0. + + ## Selector columns constraints ## + enf selector_columns([s]) + + ## Node index constraints ## + enf node_index([s, i]) + + ## Hasher state constraints ## + # TODO: apply RPO constraints to the hasher state + enf hasher_state([s, h, i]) + + # Multiset check constraints + # TODO: Apply multiset check constraints +} \ No newline at end of file diff --git a/constraints/miden-vm-old/memory.air b/constraints/miden-vm-old/memory.air new file mode 100644 index 000000000..17da938a5 --- /dev/null +++ b/constraints/miden-vm-old/memory.air @@ -0,0 +1,120 @@ +mod MemoryChipletAir + +### Helper functions ############################################################################## + +# Returns the n0 flag which is set to 1 when context changes and 0 otherwise. +fn get_n0(ctx: scalar, ctx_next: scalar, t_next: scalar) -> scalar: + return (ctx_next - ctx) * t_next + + +# Returns the n1 flag. If context remains the same, n1 = 1 when address changes and 0 otherwise. +fn get_n1(addr: scalar, addr_next: scalar, t_next: scalar) -> scalar: + return (addr_next - addr) * t_next + + +### Helper evaluators ############################################################################# + +# Enforces that column must be binary. +# Constraint degree: 2 +ev is_binary([a]) { + enf a^2 = a +} + + +# Enforces that value in column is copied over to the next row. +# Constraint degree: 1 +ev is_unchanged([column]) { + enf column' = column +} + + +# Enforces that the provided columns must be zero. +ev is_zero([column]) { + enf column = 0 +} + + +# Enforces that created flags have valid values during the program execution. +ev flags_validity([ctx, addr, t]) { + # n0 = 1 when context changes and 0 otherwise. + let n0 = get_n0(ctx, ctx', t') + + # if context remains the same, n1 = 1 when address changes and 0 otherwise. + let n1 = get_n1(addr, addr', t') + + # Enforce that n0 must be binary. + enf n0^2 = n0 + + # Enforce that when context changes, n0 = 1 (or when n0 = 0, context remains the same). + enf ctx' = ctx when !n0 + + # Enforce that n1 must be binary. An additional condition ensures that the check of n1 + # occurs only if the context does not change (n0 = 0). + enf n1^2 = n1 when !n0 + + # Enforce that if context remains the same, n1 = 1 when address changes and 0 otherwise. + enf addr' = addr when !n0 & !n1 +} + +# Enforces that selectors take the correct values under certain conditions. +ev enforce_selectors([s[2], ctx, addr, t]) { + # Enforce that values in the selectior columns must be binary. + # s[0] is set to 0 for write operations and to 1 for read operations. + enf is_binary([selector]) for selector in s + + # n0 = 1 when context changes and 0 otherwise. + let n0 = get_n0(ctx, ctx', t') + + # if context remains the same, n1 = 1 when address changes and 0 otherwise. + let n1 = get_n1(addr, addr', t') + + # Enforce that s[1]' = 1 when the operation is a read and `ctx` and `addr` columns are both + # unchanged. + enf s[1]' = 1 when !n0 & !n1 & s[0]' + + # Enforce that s[1]' = 0 when either the context changed, the address changed, or the operation + # is a write. + enf s[1]' = 0 when n0 | n1 | !s[0]' +} + +# Enforces that the delta between two consecutive contexts, addresses, or clock cycles is updated +# and decomposed into the `d1` and `d0` columns correctly. +ev enforce_delta([ctx, addr, clk, d[2], t]) { + # n0 = 1 when context changes and 0 otherwise. + let n0 = get_n0(ctx, ctx', t') + + # if context remains the same, n1 = 1 when address changes and 0 otherwise. + let n1 = get_n1(addr, addr', t') + + let d_next_agg = 2^16 * d[1]' + d[0]' + + # Enforce that values of context (`ctx`), address (`addr`), and clock cycle (`clk`) grow + # monotonically + match enf: + d_next_agg = ctx' - ctx when n0 + d_next_agg = addr' - addr when !n0 & n1 + d_next_agg = clk' - clk - 1 when !n0 & !n1 +} + +# Enforces that memory is initialized to zero when it is read before being written and that when +# existing memory values are read they remain unchanged. +ev enforce_values([s[2], v[4]]) { + # Enforce that values at a given memory address are always initialized to 0. + enf is_zero([v_i]) for v_i in v when s[0] & !s[1] + + # Enforce that for the same context/address combination, the v columns of the current row are + # equal to the corresponding v columns of the next row + enf is_unchanged([v_i]) for v_i in v when s[1] +} + +### Memory Chiplet Air Constraints ################################################################ + +# Enforces the constraints on the memory chiplet, given the columns of the memory execution trace. +ev memory_chiplet([s[2], ctx, addr, clk, v[4], d[2], t]) { + enf flags_validity([ctx, addr, t]) + enf enforce_selectors([s, ctx, addr, t]) + enf enforce_delta([ctx, addr, clk, d, t]) + # TODO: perform range checks for values in columns d[0] and d[1] + enf enforce_values([s, v]) + # Bus constraint is implemented in a separate file +} \ No newline at end of file diff --git a/constraints/miden-vm-old/range_checker.air b/constraints/miden-vm-old/range_checker.air new file mode 100644 index 000000000..f3581de8c --- /dev/null +++ b/constraints/miden-vm-old/range_checker.air @@ -0,0 +1,62 @@ +mod RangeCheckerAir + +### Helper functions ############################################################################## + +# Returns array of mutually exclusive multiplicity flags. +# f[0] set to 1 when we don't include the value into the running product. +# f[1] set to 1 when we include the value into the running product. +# f[2] set to 1 when we include two copies of the value into the running product. +# f[3] set to 1 when we include four copies of the value into the running product. +fn get_multiplicity_flags(s0: scalar, s1: scalar) -> vector[4]: + return [!s0 & !s1, s0 & !s1, !s0 & s1, s0 & s1] + + +### Helper evaluators ############################################################################# + +# Enforces that column must be binary. +ev is_binary([v]) { + enf v^2 = v +} + +# Enforces correct transition from 8-bit to 16-bit section of the table. +ev transition_8_to_16_bit([t, v]) { + # Ensure that values in column t can flip from 0 to 1 only once + enf t * !t' = 0 + + # Ensure that when column t flips, column v must equal 255 + enf v = 255 when t' & !t + + # Ensure that when column t flips, v' must be reset to 0 + enf v' = 0 when t' & !t +} + +# The virtual table enforces an 8-bit range check for each row transition in the 16-bit section of +# the range checker, which enforces its internal correctness. +ev virtual_table([t, s0, s1, v], [p0]) { + let val = $alpha[0] + v + let f = get_multiplicity_flags(s0, s1) + + # z represents how a row in the execution trace is reduced to a single value. + let z = val^4 * f[3] + val^2 * f[2] + val * f[1] + f[0] + enf p0' * (($alpha[0] + v' - v) * t - t + 1) = p0 * (z - z * t + t) + + # TODO: add boundary constraints p0.first = 1 and p0.last = 1 +} + +### Range checker Air Constraints ################################################################# + +ev range_checker([t, s0, s1, v], [p0]) { + # Check selector flags are binary. + let selectors = [t, s0, s1] + enf is_binary([s]) for s in selectors + + # Constrain the row transitions in the 8-bit section of the table so that as we move from one + # row to the next the value either stays the same or increases by 1. + enf (v' - v) * (v' - v - 1) = 0 when !t' + + # Constrain the transition from 8-bit to 16-bit section of the table. + enf transition_8_to_16_bit([t, v]) + + # Constrain the row transitions in the 16-bit section of the table. + enf virtual_table([t, s0, s1, v], [p0]) +} \ No newline at end of file diff --git a/constraints/miden_vm.air b/constraints/miden_vm.air new file mode 100644 index 000000000..c9a324409 --- /dev/null +++ b/constraints/miden_vm.air @@ -0,0 +1,184 @@ +########################################################################################## +# MIDEN VM - ALGEBRAIC INTERMEDIATE REPRESENTATION (AIR) CONSTRAINTS +########################################################################################## +# +# The Miden Virtual Machine is a STARK-based zero-knowledge virtual machine designed for +# efficient execution and proving of arbitrary computations. This AIR specification defines +# the arithmetic constraints that govern the VM's execution. +# +# STATUS: Not fully implemented +# +# REFERENCES: +# - Miden VM implementation: https://0xmiden.github.io/miden-vm/ +# - Miden VM documentation : https://0xmiden.github.io/miden-vm/intro/main.html +########################################################################################## + +def MidenVM + +use chiplets::chiplets_constraints; +use bitwise::*; +use hasher::*; +use range_checker::*; +use system::*; +use utils::*; + +########################################################################################## +# EXECUTION TRACE LAYOUT +########################################################################################## +# +# The execution trace captures the state of the VM at each cycle. The main trace consists +# of 80 columns organized into logical segments. +# +########################################################################################## + +trace_columns { + main: [system[8], decoder[24], stack[19], range_checker[2], chiplets[20], padding[7]], +} + +########################################################################################## +# PUBLIC INPUTS +########################################################################################## +# +# Public inputs define the interface between the prover and verifier, establishing +# the computation's inputs and expected outputs that must be verified. +# +# INPUT STRUCTURE: +# ┌───────────────┬─────────┬───────────────────────────────────────────────────┐ +# │ Field │ Size │ Description │ +# ├───────────────┼─────────┼───────────────────────────────────────────────────┤ +# │ stack_inputs │ 16 │ Initial operand stack state (top 16 elements) │ +# │ stack_outputs │ 16 │ Final operand stack state (top 16 elements) │ +# │program_digest │ 4 │ MAST root hash identifying the executed program │ +# │kernel_digests │ [[5]] │ Hashes of kernel procedures (dynamic array) │ +# └───────────────┴─────────┴───────────────────────────────────────────────────┘ +# +########################################################################################## + +public_inputs { + stack_inputs: [16], # Initial operand stack state (16 field elements) + stack_outputs: [16], # Final operand stack state (16 field elements) + program_digest: [4], # MAST root hash (4 field elements) + kernel_digests: [[5]], # Kernel procedure hashes plus an op label +} + +########################################################################################## +# BUS ARCHITECTURE +########################################################################################## +# +# Buses enable efficient communication between VM components using cryptographic +# protocols like multiset checks and LogUp. They ensure data integrity and consistency +# across different execution units. +# +########################################################################################## + +buses { + # Decoder buses + multiset bus_0_decoder_p1, + multiset bus_1_decoder_p2, + multiset bus_2_decoder_p3, + + # Stack overflow table + multiset bus_3_stack_p1, + + # Range checker bus + logup bus_4_range_checker, + + # Chiplet buses + multiset bus_5_v_table, # Chiplets virtual table + multiset bus_6_chiplets_bus, # Main chiplets communication bus + + # Wiring bus + logup bus_7_wiring_bus, # ACE (Algebraic Circuit Elements) wiring +} + +########################################################################################## +# BOUNDARY CONSTRAINTS +########################################################################################## +# +# Boundary constraints establish the initial and final states of the VM execution. +# They ensure proper initialization and define the expected state at program completion. +# +########################################################################################## + +boundary_constraints { + #################################################################################### + # MAIN TRACE BOUNDARIES + #################################################################################### + + # System clock must start at 0 (VM begins execution at cycle 0) + enf system[0].first = 0; + + #################################################################################### + # AUXILIARY COLUMN BOUNDARIES + #################################################################################### + + # Decoder buses + enf bus_0_decoder_p1.first = unconstrained; + enf bus_0_decoder_p1.last = unconstrained; + enf bus_1_decoder_p2.first = unconstrained; + enf bus_1_decoder_p2.last = unconstrained; + enf bus_2_decoder_p3.first = unconstrained; + enf bus_2_decoder_p3.last = unconstrained; + + # Stack overflow table + enf bus_3_stack_p1.first = unconstrained; + enf bus_3_stack_p1.last = unconstrained; + + # Range checker bus - LogUp protocol requires null initialization/finalization + enf bus_4_range_checker.first = null; + enf bus_4_range_checker.last = null; + + # Chiplets virtual table + enf bus_5_v_table.first = unconstrained; + enf bus_5_v_table.last = unconstrained; + + # Chiplets communication bus + # Initial state contains kernel procedure digests against which the program was compiled + enf bus_6_chiplets_bus.first = kernel_digests; + enf bus_6_chiplets_bus.last = unconstrained; + + # ACE (Algebraic Circuit Elements) wiring bus + enf bus_7_wiring_bus.first = unconstrained; + enf bus_7_wiring_bus.last = unconstrained; +} + +########################################################################################## +# INTEGRITY CONSTRAINTS +########################################################################################## +# +# Integrity constraints define the state transition rules that govern VM execution. +# These constraints ensure that each step of program execution is valid according +# to the Miden VM instruction set architecture. +# +########################################################################################## + +integrity_constraints { + #################################################################################### + # MAIN TRACE CONSTRAINTS + #################################################################################### + + # System state transitions + enf system_transition([system[0..8]]); + + # TODO: Decoder constraints - Program execution and MAST verification + # enf decoder_constraints([decoder]); + + # TODO: Stack constraints - Operand stack operations and overflow handling + # enf stack_constraints([stack]); + + # TODO: Chiplet constraints - Hash, bitwise, ACE, memory operations, and kernel ROM + enf chiplets_constraints([chiplets[0..20]]); + + #################################################################################### + # BUS CONSTRAINTS + #################################################################################### + + # Range checker bus protocol - Connects stack and chiplets for 16-bit range checks + enf range_checker_constraints([decoder[0..24], range_checker[0..2], chiplets[0..20]]); + + # TODO: Additional bus constraints for complete VM verification + # enf decoder_bus_constraints([decoder]); + # enf stack_bus_constraints([stack]); + # enf chiplet_bus_constraints([chiplets]); + # enf ace_bus_constraints([chiplets]) +} diff --git a/constraints/range_checker.air b/constraints/range_checker.air new file mode 100644 index 000000000..e18b2965b --- /dev/null +++ b/constraints/range_checker.air @@ -0,0 +1,86 @@ +########################################################################################## +# RANGE CHECKER CONSTRAINTS MODULE +########################################################################################## +# +# The Range Checker provides efficient 16-bit range checking for other VM components +# using the LogUp protocol with optimized gap handling to minimize the minimal +# trace length while supporting (practically) unlimited range checks. +# +# STATUS: Not fully implemented +# +# REFERENCES: +# - Range Checker Spec: https://0xmiden.github.io/miden-vm/design/range.html +# - LogUp Protocol: https://0xmiden.github.io/miden-vm/design/lookups/main.html +########################################################################################## + +mod range_checker + +use chiplets::*; +use utils::*; + +########################################################################################## +# RANGE CHECKER BUS CONSTRAINTS +########################################################################################## + + +ev range_checker_constraints([decoder[24], range_checker[2], chiplets[20]]) { + + #################################################################################### + # Components requesting range checks + #################################################################################### + + # Stack values requiring 16-bit range checks (from decoder trace) + # These correspond to the top 4 operand stack elements during u32 operations + let sv0 = decoder[10]; # Stack value 0 + let sv1 = decoder[11]; # Stack value 1 + let sv2 = decoder[12]; # Stack value 2 + let sv3 = decoder[13]; # Stack value 3 + + # Memory chiplet values requiring 16-bit range checks + let mv0 = chiplets[14]; # Memory value 0 + let mv1 = chiplets[15]; # Memory value 1 + + #################################################################################### + # RANGE CHECKER STATE + #################################################################################### + + # Core range checker table columns + let value = range_checker[1]; # v: Current 16-bit value [0, 65535] + let multiplicity = range_checker[0]; # m: Usage count (how many times v is range-checked) + + #################################################################################### + # OPERATION FLAGS - Determine when range checks are needed + #################################################################################### + + # U32 range check operation flag + # Identifies stack operations that require 16-bit range checking + let not_4 = binary_not(decoder[5]); + let not_5 = binary_not(decoder[6]); + let u32_rc_op = decoder[7] * not_4 * not_5; + + # Memory chiplet operation flag + # Identifies memory operations that require range checking + let s_0 = chiplets[0]; + let s_1 = chiplets[1]; + let s_2 = chiplets[2]; + let chiplets_memory_flag = memory_chiplet_flag(s_0, s_1, s_2); + + #################################################################################### + # RANGE CHECKER INTERACTIONS + #################################################################################### + + # Each cycle, we insert the current value with its multiplicity + bus_4_range_checker.insert(value) with multiplicity; + + # Memory-related range checks + # When memory operations occur, verify that memory values are 16-bit + bus_4_range_checker.remove(mv0) when chiplets_memory_flag; + bus_4_range_checker.remove(mv1) when chiplets_memory_flag; + + # Stack-related range checks + # When u32 operations occur, verify that all operand components are 16-bit + bus_4_range_checker.remove(sv0) when u32_rc_op; + bus_4_range_checker.remove(sv1) when u32_rc_op; + bus_4_range_checker.remove(sv2) when u32_rc_op; + bus_4_range_checker.remove(sv3) when u32_rc_op; +} diff --git a/constraints/rpo.air b/constraints/rpo.air new file mode 100644 index 000000000..5061e8188 --- /dev/null +++ b/constraints/rpo.air @@ -0,0 +1,110 @@ + +mod rpo + +########################################################################################## +# RPO PERMUTATION ROUND CONSTRAINTS +########################################################################################## + +ev enforce_rpo_round([h[12]]){ + let ark1 = [ark1_0, ark1_1, ark1_2, ark1_3, ark1_4, ark1_5, ark1_6, ark1_7, ark1_8, ark1_9, + ark1_10, ark1_11]; + + let ark2 = [ark2_0, ark2_1, ark2_2, ark2_3, ark2_4, ark2_5, ark2_6, ark2_7, ark2_8, ark2_9, + ark2_10, ark2_11]; + + # Compute the state that should result from applying the first 5 steps of an RPO round to + # the current hasher state. + + # 1. Apply mds + let step1_initial = apply_mds(h); + + # 2. Add constants + let step1_with_constants = [s + k for (s, k) in (step1_initial, ark1)]; + + # 3. Apply sbox + let step1_with_sbox = [s^7 for s in step1_with_constants]; + + # 4. Apply mds + let step1_with_mds = apply_mds(step1_with_sbox); + + # 5. Add constants + let step1 = [s + k for (s, k) in (step1_with_mds, ark2)]; + + # Compute the state that should result from applying the inverse of the last operation of the + # RPO round to the next step of the computation. + let step2 = [s'^7 for s in h]; + + # Make sure that the results are equal. + enf s1 = s2 for (s1, s2) in (step1, step2); +} + +########################################################################################## +# HELPER FUNCTIONS +########################################################################################## + +fn apply_mds(state: felt[12]) -> felt[12]{ + # Compute dot product of state vector with each MDS row + let result0 = sum([s * m for (s, m) in (state, MDSROWA)]); + let result1 = sum([s * m for (s, m) in (state, MDSROWB)]); + let result2 = sum([s * m for (s, m) in (state, MDSROWC)]); + let result3 = sum([s * m for (s, m) in (state, MDSROWD)]); + let result4 = sum([s * m for (s, m) in (state, MDSROWE)]); + let result5 = sum([s * m for (s, m) in (state, MDSROWF)]); + let result6 = sum([s * m for (s, m) in (state, MDSROWG)]); + let result7 = sum([s * m for (s, m) in (state, MDSROWH)]); + let result8 = sum([s * m for (s, m) in (state, MDSROWI)]); + let result9 = sum([s * m for (s, m) in (state, MDSROWJ)]); + let result10 = sum([s * m for (s, m) in (state, MDSROWK)]); + let result11 = sum([s * m for (s, m) in (state, MDSROWL)]); + + return [result0, result1, result2, result3, result4, result5, + result6, result7, result8, result9, result10, result11]; +} + +########################################################################################## +# CONSTANTS AND PERIODIC COLUMNS +########################################################################################## + +# MDS matrix rows used for computing the linear layer in a RPO round +const MDSROWA = [7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8]; +const MDSROWB = [8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22, 21]; +const MDSROWC = [21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6, 22]; +const MDSROWD = [22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7, 6]; +const MDSROWE = [6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9, 7]; +const MDSROWF = [7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10, 9]; +const MDSROWG = [9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13, 10]; +const MDSROWH = [10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26, 13]; +const MDSROWI = [13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8, 26]; +const MDSROWJ = [26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23, 8]; +const MDSROWK = [8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7, 23]; +const MDSROWL = [23, 8, 26, 13, 10, 9, 7, 6, 22, 21, 8, 7]; + +periodic_columns{ + # Round constants added to the hasher state in the first half of the RPO round + ark1_0: [5789762306288267264, 12987190162843097088, 18072785500942327808, 5674685213610122240, 4887609836208846848, 16308865189192448000, 7123075680859040768, 0], + ark1_1: [6522564764413702144, 653957632802705280, 6200974112677013504, 5759084860419474432, 3027115137917284352, 11977192855656443904, 1034205548717903104, 0], + ark1_2: [17809893479458207744, 4441654670647621120, 17682092219085883392, 13943282657648898048, 9595098600469471232, 12532242556065779712, 7717824418247931904, 0], + ark1_3: [107145243989736512, 4038207883745915904, 10599526828986757120, 1352748651966375424, 10528569829048483840, 14594890931430969344, 3019070937878604288, 0], + ark1_4: [6388978042437517312, 5613464648874829824, 975003873302957312, 17110913224029904896, 7864689113198940160, 7291784239689209856, 11403792746066868224, 0], + ark1_5: [15844067734406017024, 13222989726778339328, 8264241093196931072, 1003883795902368384, 17533723827845969920, 5514718540551361536, 10280580802233112576, 0], + ark1_6: [9975000513555218432, 3037761201230264320, 10065763900435474432, 4141870621881018368, 5781638039037711360, 10025733853830934528, 337153209462421248, 0], + ark1_7: [3344984123768313344, 16683759727265179648, 2181131744534710272, 8121410972417424384, 17024078752430718976, 7293794580341021696, 13333398568519923712, 0], + ark1_8: [9959189626657347584, 8337364536491240448, 6317303992309419008, 14300518605864919040, 109659393484013504, 6728552937464861696, 3596153696935337472, 0], + ark1_9: [12960773468763564032, 3227397518293416448, 1401440938888741632, 13712227150607669248, 7158933660534805504, 6332385040983343104, 8104208463525993472, 0], + ark1_10: [9602914297752487936, 8110510111539675136, 8884468225181997056, 17021852944633065472, 2955076958026921984, 13277683694236792832, 14345062289456084992, 0], + ark1_11: [16657542370200465408, 2872078294163232256, 13066900325715521536, 6252096473787587584, 7433723648458773504, 2600778905124452864, 17036731477169661952, 0], + + # Round constants added to the hasher state in the second half of the RPO round + ark2_0: [6077062762357203968, 6202948458916100096, 8023374565629191168, 18389244934624493568, 6982293561042363392, 3736792340494631424, 17130398059294019584, 0], + ark2_1: [15277620170502010880, 17690140365333231616, 15013690343205953536, 16731736864863924224, 14065426295947720704, 577852220195055360, 519782857322262016, 0], + ark2_2: [5358738125714196480, 3595001575307484672, 4485500052507913216, 4440209734760478208, 16451845770444974080, 6689998335515780096, 9625384390925084672, 0], + ark2_3: [14233283787297595392, 373995945117666496, 12489737547229155328, 17208448209698889728, 7139138592091307008, 13886063479078012928, 1664893052631119104, 0], + ark2_4: [13792579614346651648, 1235734395091296000, 9500452585969031168, 8739495587021565952, 9012006439959783424, 14358505101923203072, 7629576092524553216, 0], + ark2_5: [11614812331536766976, 14172757457833930752, 2054001340201038848, 17000774922218162176, 14619614108529063936, 7744142531772273664, 3485239601103661568, 0], + ark2_6: [14871063686742261760, 707573103686350208, 12420704059284934656, 13533282547195531264, 1394813199588124416, 16135070735728404480, 9755891797164034048, 0], + ark2_7: [10148237148793042944, 15453217512188186624, 355990932618543744, 525402848358706240, 4635111139507788800, 12290902521256030208, 15218148195153268736, 0], + ark2_8: [4457428952329675776, 219777875004506016, 9071225051243524096, 16987541523062161408, 16217473952264204288, 12059913662657710080, 16460604813734957056, 0], + ark2_9: [15590786458219171840, 17876696346199468032, 12766199826003447808, 5466806524462796800, 10782018226466330624, 16456018495793752064, 9643968136937730048, 0], + ark2_10: [10063319113072093184, 17731621626449383424, 9045979173463557120, 14512769585918244864, 6844229992533661696, 4571485474751953408, 3611348709641382912, 0], + ark2_11: [14200078843431360512, 2897136237748376064, 12934431667190679552, 10973956031244050432, 7446486531695178752, 17200392109565784064, 18256379591337758720, 0], +} diff --git a/constraints/stack.air b/constraints/stack.air new file mode 100644 index 000000000..f2b0898d9 --- /dev/null +++ b/constraints/stack.air @@ -0,0 +1,15 @@ +########################################################################################## +# STACK CONSTRAINTS MODULE +########################################################################################## +# +# The Stack module manages Miden VM's operand stack which is practically of unlimited depth +# (up to 2³² items), though only the top 16 stack items are directly accessible, with deeper +# items stored in an overflow virtual table. +# +# STATUS: Not implemented +# +# REFERENCES: +# - Stack Design: https://0xmiden.github.io/miden-vm/design/stack/main.html +########################################################################################## + +mod stack diff --git a/constraints/system.air b/constraints/system.air new file mode 100644 index 000000000..2495ab34d --- /dev/null +++ b/constraints/system.air @@ -0,0 +1,56 @@ +########################################################################################## +# SYSTEM CONSTRAINTS MODULE +########################################################################################## +# +# System module is responsible for managing system data, including the current VM cycle +# (clk), the free memory pointer (fmp) used for specifying the region of memory available +# to procedure locals, and the current and parent execution contexts. +# +# SYSTEM COLUMN LAYOUT (8 columns): +# ┌─────────┬──────────────────────────────────────────────────────────────────────┐ +# │ Column │ Purpose │ +# ├─────────┼──────────────────────────────────────────────────────────────────────┤ +# │ 0 │ clk - VM execution clock (increments each cycle) │ +# │ 1 │ fmp - Free memory pointer (procedure local memory region) │ +# │ 2 │ ctx - Current execution context │ +# │ 3 │ in_syscall - System call execution flag │ +# │ 4-7 │ Function hash - Current/parent function digest (4 elements) │ +# └─────────┴──────────────────────────────────────────────────────────────────────┘ +# +# +# STATUS: Not fully implemented +# +# REFERENCES: +# - System Design: https://0xmiden.github.io/miden-vm/design/main.html +########################################################################################## + +mod system + +########################################################################################## +# SYSTEM CONSTRAINT IMPLEMENTATION +########################################################################################## + +# Main system constraint evaluator - ensures proper system state transitions +ev system_transition([system[8]]) { + # Clock progression constraint + enf system_clock_transition([system[0]]); + + # TODO: Free memory pointer constraints + # TODO: Execution context constraints + # TODO: System call flag constraints + # TODO: Function hash management constraints +} + +########################################################################################## +# CLOCK CONSTRAINT +########################################################################################## + +# Clock increment by 1 constraint +# +# +# CONSTRAINT DEGREE: 1 (linear) +# +# PURPOSE: Ensures VM execution cycles increase monotonically +ev system_clock_transition([clk]) { + enf clk' = clk + 1; +} diff --git a/constraints/tests/miden_vm.rs b/constraints/tests/miden_vm.rs new file mode 100644 index 000000000..8509937d1 --- /dev/null +++ b/constraints/tests/miden_vm.rs @@ -0,0 +1,45 @@ +use std::sync::Arc; + +use air_codegen_ace::{build_ace_circuit, AceCircuit, AceNode}; +use air_ir::{compile, Air}; +use miden_diagnostics::{ + term::termcolor::ColorChoice, CodeMap, DefaultEmitter, DiagnosticsHandler, +}; +use winter_math::FieldElement; + +fn generate_circuit(source: &str) -> (Air, AceCircuit, AceNode) { + let code_map = Arc::new(CodeMap::new()); + let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto)); + let diagnostics = DiagnosticsHandler::new(Default::default(), code_map.clone(), emitter); + + let air = air_parser::parse(&diagnostics, code_map, source) + .map_err(air_ir::CompileError::Parse) + .and_then(|program| compile(&diagnostics, program)) + .expect("lowering failed"); + + let (root, circuit) = build_ace_circuit(&air).expect("codegen failed"); + + (air, circuit, root) +} + +/// Loads the MidenVM AIR example +pub fn load_miden_vm_air() -> std::io::Result { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let path = format!("{}/miden_vm.air", crate_dir); + let content = std::fs::read_to_string(path)?; + + Ok(content) +} + +#[test] +fn test_miden_vm_updated_air_randomized() { + let air_string = load_miden_vm_air().expect("unable to read MidenVM AIR"); + + let (_air, circuit, root_node) = generate_circuit(&air_string); + + // Provide dummy variable assignments since we are not generating valid ACE vars here + let dummy_inputs = vec![Default::default(); circuit.layout.num_inputs]; + let eval = circuit.eval(root_node, &dummy_inputs); + + assert_eq!(eval, <_ as FieldElement>::ZERO); +} diff --git a/constraints/utils.air b/constraints/utils.air new file mode 100644 index 000000000..5af593a2c --- /dev/null +++ b/constraints/utils.air @@ -0,0 +1,142 @@ +########################################################################################## +# UTILITY CONSTRAINTS MODULE +########################################################################################## +# +# The Utils module provides fundamental constraint patterns and helper functions used +# throughout the Miden VM constraint system. These utilities ensure consistency and +# reusability across all VM components. +# +########################################################################################## + +mod utils + +########################################################################################## +# BINARY CONSTRAINT EVALUATORS +########################################################################################## + +# Ensures value is either 0 or 1 +# +# CONSTRAINT DEGREE: 2 +# +# USAGE PATTERN: +# ```air +# let flag = column[i]; +# enf is_binary([flag]); // Ensures flag ∈ {0, 1} +# ``` +ev is_binary([a]) { + enf a^2 = a; +} + +########################################################################################## +# STATE CONSISTENCY EVALUATORS +########################################################################################## + +# State persistence constraint - ensures values remain constant across cycles +# +# INTUITION: +# +# Many VM components require certain values to remain stable during specific +# operations or phases. This constraint enforces immutability when needed. +# +# CONSTRAINT DEGREE: 1 +# +# USAGE PATTERN: +# ```air +# let stable_value = column[i]; +# enf is_unchanged([stable_value]) when operation_active; +# ``` +ev is_unchanged([column]) { + enf column' = column; +} + +########################################################################################## +# LOGICAL OPERATION HELPERS +########################################################################################## + +# Binary negation function - computes logical NOT in finite field +# +# This is usually combined with another constraint to ensure booleaness +# +# CONSTRAINT DEGREE: 1 +# +# USAGE EXAMPLES: +# ```air +# let not_flag = binary_not(flag); // Simple negation +# let condition = flag * binary_not(other); // AND NOT pattern +# ``` +fn binary_not(a: felt) -> felt { + return 1 - a; +} + +# Binary constraint helper - returns 0 if input is binary (0 or 1), non-zero otherwise +# +# This function computes flag² - flag, which equals: +# - 0 when flag = 0 (since 0² - 0 = 0) +# - 0 when flag = 1 (since 1² - 1 = 0) +# - non-zero for any other value +# +# Usage: enf binary_constraint(x) = 0; // constrains x to be 0 or 1 +fn binary_constraint(flag: felt) -> felt { + return flag^2 - flag; +} + +########################################################################################## +# BOOLEAN ALGEBRA OPERATIONS +########################################################################################## + +# Logical AND: returns 1 if both a and b are 1, else 0 +# +# CONSTRAINT DEGREE: 2 +# +# USAGE EXAMPLES: +# ```air +# let result = binary_and(flag_a, flag_b); +# ``` +fn binary_and(a: felt, b: felt) -> felt { + return a * b; +} + +# Logical OR: returns 1 if either a or b is 1, else 0 +# +# Uses inclusion-exclusion principle: |A ∪ B| = |A| + |B| - |A ∩ B| +# +# CONSTRAINT DEGREE: 2 +# +# USAGE EXAMPLES: +# ```air +# let result = binary_or(flag_a, flag_b); +# ``` +fn binary_or(a: felt, b: felt) -> felt { + return a + b - a * b; +} + +# Logical XOR: returns 1 if exactly one of a or b is 1, else 0 +# +# CONSTRAINT DEGREE: 2 +# +# USAGE EXAMPLES: +# ```air +# let result = binary_xor(flag_a, flag_b); +# ``` +fn binary_xor(a: felt, b: felt) -> felt { + let ab = a * b; + return a + b - (ab + ab); +} + +########################################################################################## +# CONDITIONAL OPERATIONS +########################################################################################## + +# Conditional selection: returns if_true when condition=1, if_false when condition=0 +# +# This is a multiplexer function that selects between two values based on condition +# +# CONSTRAINT DEGREE: 2 +# +# USAGE EXAMPLES: +# ```air +# let result = select(condition, true_value, false_value); +# ``` +fn select(condition: felt, if_true: felt, if_false: felt) -> felt { + return condition * (if_true - if_false) + if_false; +} diff --git a/docs/src/backends.md b/docs/src/backends.md index ce8b023e7..1a7f883fe 100644 --- a/docs/src/backends.md +++ b/docs/src/backends.md @@ -1,14 +1,16 @@ # Backends -AirScript currently comes bundled with two backends: +AirScript currently comes bundled with three backends: - [Winterfell backend](https://github.com/0xMiden/air-script/tree/main/codegen/winterfell) which outputs `Air` trait implementation for the [Winterfell prover](https://github.com/facebook/winterfell) (Rust). +- [Plonky3 backend](https://github.com/0xMiden/air-script/tree/main/codegen/plonky3) which outputs `Air` trait implementation for the [Plonky3 prover](https://github.com/Plonky3/Plonky3) (Rust). - [ACE backend](https://github.com/0xMiden/air-script/tree/main/codegen/ace) which outputs arithmetic circuits for Miden VM's ACE (Arithmetic Circuit Evaluation) chiplet for recursive STARK proof verification. -These backends can be used programmatically as crates. +These backends can be used programmatically as crates. -The Winterfell backend can also be used via AirScript CLI by specifying `--target` flag. For example, the following will output Winterfell `Air` trait implementation for AIR constraints described in `example.air` file: +The Winterfell and Plonky3 backends can also be used via AirScript CLI by specifying `--target` flag. For example, the following will output Winterfell and Plonky3 `Air` trait implementation for AIR constraints described in `example.air` file: ```bash # Make sure to run from the project root directory ./target/release/airc transpile examples/example.air --target winterfell +./target/release/airc transpile examples/example.air --target plonky3 ``` In both cases we assumed that the CLI has been compiled as described [here](./introduction.md#cli). diff --git a/docs/src/introduction.md b/docs/src/introduction.md index b6b565286..d39ae08d3 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -7,6 +7,7 @@ Currently, AirScript is on version 0.3, which includes about 95% of features needed to describe Miden VM constraints, and supports generation of constraint evaluation code for the following backends: - **Winterfell**: Generates Rust code implementing the `Air` trait for the [Winterfell prover](https://github.com/facebook/winterfell) +- **Plonky3**: Generates Rust code implementing the `Air` trait implementation for the [Plonky3 prover](https://github.com/Plonky3/Plonky3) - **ACE**: Generates arithmetic circuits for Miden VM's ACE (Arithmetic Circuit Evaluation) chiplet for recursive proof verification AirScript includes the following features: @@ -57,6 +58,8 @@ Then, run the `airc` target with the `transpile` option. For example: ``` This will output constraint evaluation code targeted for the Winterfell prover. +Using the `--target plonky3` argument will output constraint evaluation code targeted for the Plonky3 prover instead. + You can use the `help` option to see other available options. ```ignore @@ -75,6 +78,5 @@ The following changes are some of the improvements under consideration for futur - removing unnecessary nodes from the `AlgebraicGraph` of boundary and integrity constraints. - combining integrity constraints with mutually exclusive selectors to reduce the total number of constraints. - additional language targets for simplifying verifier implementations: - - Plonky3 AirBuilder. - JSON-based constraint syntax. - formal verification diff --git a/mir/src/passes/translate.rs b/mir/src/passes/translate.rs index db342ecb7..e1e54acf9 100644 --- a/mir/src/passes/translate.rs +++ b/mir/src/passes/translate.rs @@ -695,7 +695,7 @@ impl<'a> MirBuilder<'a> { // At this point during compilation, fully-qualified identifiers can only possibly refer // to a periodic column, as all functions have been inlined, and constants propagated. ast::ResolvableIdentifier::Resolved(qual_ident) => { - if let Some(pc) = self.mir.periodic_columns.get(&qual_ident).cloned() { + if let Some(pc) = self.mir.periodic_columns.get(qual_ident).cloned() { let node = Value::builder() .value(SpannedMirValue { span: access.span(), @@ -706,7 +706,7 @@ impl<'a> MirBuilder<'a> { }) .build(); Ok(node) - } else if let Some(bus) = self.mir.constraint_graph().get_bus_link(&qual_ident) { + } else if let Some(bus) = self.mir.constraint_graph().get_bus_link(qual_ident) { let node = Value::builder() .value(SpannedMirValue { span: access.span(), @@ -714,6 +714,10 @@ impl<'a> MirBuilder<'a> { }) .build(); Ok(node) + } else if let Some(constant) = self.program.constants.get(qual_ident) { + // Handle qualified constant references that weren't inlined + // (e.g., constants used in comprehension iterables across modules) + self.translate_const(&constant.value, access.span()) } else { // This is a qualified reference that should have been eliminated // during inlining or constant propagation, but somehow slipped through. @@ -735,7 +739,7 @@ impl<'a> MirBuilder<'a> { }, // This must be one of public inputs or trace columns ast::ResolvableIdentifier::Global(ident) | ast::ResolvableIdentifier::Local(ident) => { - self.translate_symbol_access_global_or_local(&ident, access) + self.translate_symbol_access_global_or_local(ident, access) }, // These should have been eliminated by previous compiler passes ast::ResolvableIdentifier::Unresolved(_ident) => { @@ -1063,7 +1067,8 @@ impl<'a> MirBuilder<'a> { access: &'a ast::SymbolAccess, ) -> Option> { // If it's a slice access, we need to create a vector of MirAccessType::Index - if let AccessType::Slice(ast::RangeExpr { start, end, .. }) = &access.access_type { + if let AccessType::Slice(range) = &access.access_type { + let ast::RangeExpr { start, end, .. } = range.as_ref(); let ( ast::RangeBound::Const(Span { item: start, .. }), ast::RangeBound::Const(Span { item: end, .. }), diff --git a/parser/src/ast/expression.rs b/parser/src/ast/expression.rs index 97f26fa94..1ad12bb80 100644 --- a/parser/src/ast/expression.rs +++ b/parser/src/ast/expression.rs @@ -865,7 +865,7 @@ pub enum AccessType { #[default] Default, /// Access binds a sub-slice of a vector - Slice(RangeExpr), + Slice(Box), /// Access binds the value at a specific index of an aggregate value (i.e. vector or matrix) /// /// The result type may be either a scalar or a vector, depending on the type of the aggregate @@ -1073,7 +1073,7 @@ impl SymbolAccess { Err(InvalidAccessError::IndexOutOfBounds) }, Type::Vector(_) => Ok(Self { - access_type: AccessType::Slice(shifted), + access_type: AccessType::Slice(Box::new(shifted.clone())), ty: Some(Type::Vector(rlen)), ..self.clone() }), @@ -1081,7 +1081,7 @@ impl SymbolAccess { Err(InvalidAccessError::IndexOutOfBounds) }, Type::Matrix(_, cols) => Ok(Self { - access_type: AccessType::Slice(shifted), + access_type: AccessType::Slice(Box::new(shifted)), ty: Some(Type::Matrix(rlen, cols)), ..self.clone() }), diff --git a/parser/src/ast/mod.rs b/parser/src/ast/mod.rs index 1686455e7..1447c7b3c 100644 --- a/parser/src/ast/mod.rs +++ b/parser/src/ast/mod.rs @@ -508,19 +508,18 @@ impl Library { // importing module, if it was parsed from disk. If no path is available, // we default to the current working directory. - let (real_path, source_dir) = match codemap - .name(imports.first().unwrap().span().source_id()) - { - // If we have no source span, default to the current working directory - Err(_) => (false, cwd.clone()), - // If the file is virtual, then we've either already parsed imports for this module, - // or we have to fall back to the current working directory, but we have no relative - // path from which to base our search. - Ok(FileName::Virtual(_)) => (false, cwd.clone()), - Ok(FileName::Real(path)) => { - (true, path.parent().unwrap_or_else(|| Path::new(".")).to_path_buf()) - }, - }; + let (real_path, source_dir) = + match codemap.name(imports.first().unwrap().span().source_id()) { + // If we have no source span, default to the current working directory + Err(_) => (false, cwd.clone()), + // If the file is virtual, then we've either already parsed imports for this module, + // or we have to fall back to the current working directory, but we have no relative + // path from which to base our search. + Ok(FileName::Virtual(_)) => (false, cwd.clone()), + Ok(FileName::Real(path)) => { + (true, path.parent().unwrap_or_else(|| Path::new(".")).to_path_buf()) + }, + }; // For each module imported, try to load the module from the library, if it is // unavailable we must do extra work to load it into the library, as diff --git a/parser/src/ast/module.rs b/parser/src/ast/module.rs index 0b7a0eacc..bfec2ed0c 100644 --- a/parser/src/ast/module.rs +++ b/parser/src/ast/module.rs @@ -435,9 +435,6 @@ impl Module { conflicting_declaration(diagnostics, "function", prev.span(), function.name.span()); return Err(SemanticAnalysisError::NameConflict(function.name.span())); } - - println!("Declared function: {:?}", function.name); - self.functions.insert(function.name, function); Ok(()) diff --git a/parser/src/ast/trace.rs b/parser/src/ast/trace.rs index 5b26a05d0..71fab9071 100644 --- a/parser/src/ast/trace.rs +++ b/parser/src/ast/trace.rs @@ -287,7 +287,7 @@ impl TraceBinding { let range_expr1 = range_expr1.to_slice_range(); let combined_range = (range_expr.start + range_expr1.start)..(range_expr.end + range_expr1.end); - AccessType::Slice(combined_range.into()) + AccessType::Slice(Box::new(combined_range.into())) }, (AccessType::Slice(range_expr), AccessType::Index(index_expr)) => { let range_expr_usize = range_expr.to_slice_range(); diff --git a/parser/src/lexer/mod.rs b/parser/src/lexer/mod.rs index f354cf6f4..8265300ae 100644 --- a/parser/src/lexer/mod.rs +++ b/parser/src/lexer/mod.rs @@ -644,10 +644,9 @@ where match num.parse::() { Ok(i) => Token::Num(i), - Err(err) => Token::Error(LexicalError::InvalidInt { - span: self.span(), - reason: err.kind().clone(), - }), + Err(err) => { + Token::Error(LexicalError::InvalidInt { span: self.span(), reason: *err.kind() }) + }, } } } diff --git a/parser/src/parser/grammar.lalrpop b/parser/src/parser/grammar.lalrpop index b06578f68..e05958009 100644 --- a/parser/src/parser/grammar.lalrpop +++ b/parser/src/parser/grammar.lalrpop @@ -560,7 +560,7 @@ SymbolAccessBaseSpanned: Span<(Identifier, AccessType)> = { SymbolAccessBase: (Identifier, AccessType) = { => (ident, AccessType::Default), - "[" "]" => (ident, AccessType::Slice(range)), + "[" "]" => (ident, AccessType::Slice(Box::new(range))), => (ident, AccessType::Index(idx)), => (ident, AccessType::Matrix(row, col)), // accessing an identifier used in a section declaration, like a named trace segment, e.g. $main @@ -611,7 +611,7 @@ Iterables: Vec = { Iterable: Expr = { => Expr::SymbolAccess(SymbolAccess::new(ident.span(), ident, AccessType::Default, 0)), => Expr::Range(range), - "[" "]" => Expr::SymbolAccess(SymbolAccess::new(span!(l, r), ident, AccessType::Slice(range), 0)), + "[" "]" => Expr::SymbolAccess(SymbolAccess::new(span!(l, r), ident, AccessType::Slice(Box::new(range)), 0)), => if let ScalarExpr::Call(call) = function_call { Expr::Call(call) } else { diff --git a/parser/src/parser/tests/mod.rs b/parser/src/parser/tests/mod.rs index aa1a162e1..1b18c60fc 100644 --- a/parser/src/parser/tests/mod.rs +++ b/parser/src/parser/tests/mod.rs @@ -426,7 +426,7 @@ macro_rules! slice { ScalarExpr::SymbolAccess(SymbolAccess { span: miden_diagnostics::SourceSpan::UNKNOWN, name: ResolvableIdentifier::Unresolved(NamespacedIdentifier::Binding(ident!($name))), - access_type: AccessType::Slice($range.into()), + access_type: AccessType::Slice(Box::new($range.into())), offset: 0, ty: None, }) @@ -436,7 +436,7 @@ macro_rules! slice { ScalarExpr::SymbolAccess(SymbolAccess { span: miden_diagnostics::SourceSpan::UNKNOWN, name: ResolvableIdentifier::Local(ident!($name)), - access_type: AccessType::Slice($range.into()), + access_type: AccessType::Slice(Box::new($range.into())), offset: 0, ty: Some($ty), }) diff --git a/parser/src/sema/semantic_analysis.rs b/parser/src/sema/semantic_analysis.rs index 09d89426f..b4bf9597e 100644 --- a/parser/src/sema/semantic_analysis.rs +++ b/parser/src/sema/semantic_analysis.rs @@ -444,9 +444,9 @@ impl VisitMut for SemanticAnalysis<'_> { self.current_module.clone().unwrap(), NamespacedIdentifier::Function(function.name), ); - let current_item_node_index = self.deps_graph.add_node(current_item); - for (referenced_item, ref_type) in self.referenced.iter() { - let referenced_item_node_index = self.deps_graph.add_node(referenced_item.clone()); + let current_item_node_index = self.get_node_index_or_add(¤t_item); + for (referenced_item, ref_type) in self.referenced.clone().iter() { + let referenced_item_node_index = self.get_node_index_or_add(referenced_item); self.deps_graph.add_edge( current_item_node_index, referenced_item_node_index, @@ -633,6 +633,15 @@ impl VisitMut for SemanticAnalysis<'_> { 0, ))))) .expect("unexpected scalar iterable"); + // Comprehension bindings are local variables holding values, not direct + // references to module-level declarations like periodic columns or constants. + // Convert these to Local bindings to ensure proper scoping. + let binding_ty = match binding_ty { + BindingType::PeriodicColumn(_) | BindingType::Constant(_) => { + BindingType::Local(binding_ty.ty().unwrap_or(Type::Felt)) + }, + other => other, + }; binding_tys.push((binding, iterable.span(), Some(binding_ty))); }, Err(InvalidAccessError::InvalidBinding) => { diff --git a/scripts/generate_all_e2e_tests.sh b/scripts/generate_all_e2e_tests.sh new file mode 100644 index 000000000..9725910d1 --- /dev/null +++ b/scripts/generate_all_e2e_tests.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Notes: +# - Run from root of repository +# - We avoid looping on all found air-script/src/tests/**/*.air files to make it easier to notice changes + +cargo build --release + +# Winterfell Backend + +./target/release/airc transpile --target winterfell ./air-script/src/tests/binary/binary.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/bitwise/bitwise.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_complex.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_simple.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_simple_with_evaluators.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_varlen_boundary_both.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_varlen_boundary_last.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/buses/buses_varlen_boundary_first.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/constant_in_range/constant_in_range.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/constants/constants.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/constraint_comprehension/constraint_comprehension.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/evaluators/evaluators_nested_slice_call.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/evaluators/evaluators_slice.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/evaluators/evaluators.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/fibonacci/fibonacci.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/functions/functions_simple.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/functions/functions_complex.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/indexed_trace_access/indexed_trace_access.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/list_comprehension/list_comprehension_nested.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/list_comprehension/list_comprehension.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/list_folding/list_folding.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/periodic_columns/periodic_columns.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/pub_inputs/pub_inputs.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/selectors/selectors_combine_complex.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/selectors/selectors_combine_simple.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/selectors/selectors_with_evaluators.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/selectors/selectors.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/system/system.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/trace_col_groups/trace_col_groups.air +./target/release/airc transpile --target winterfell ./air-script/src/tests/variables/variables.air + +# Plonky3 Backend +./target/release/airc transpile --target plonky3 ./air-script/src/tests/binary/binary.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/bitwise/bitwise.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_complex.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_simple.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_simple_with_evaluators.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_varlen_boundary_both.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_varlen_boundary_last.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/buses/buses_varlen_boundary_first.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/computed_indices/computed_indices_complex.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/computed_indices/computed_indices_simple.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/constant_in_range/constant_in_range.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/constants/constants.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/constraint_comprehension/constraint_comprehension.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/evaluators/evaluators_nested_slice_call.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/evaluators/evaluators_slice.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/evaluators/evaluators.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/fibonacci/fibonacci.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/functions/functions_simple.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/functions/functions_complex.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/indexed_trace_access/indexed_trace_access.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/list_comprehension/list_comprehension_nested.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/list_comprehension/list_comprehension.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/list_folding/list_folding.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/periodic_columns/periodic_columns.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/pub_inputs/pub_inputs.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/selectors/selectors_combine_complex.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/selectors/selectors_combine_simple.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/selectors/selectors_combine_with_list_comprehensions.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/selectors/selectors_with_evaluators.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/selectors/selectors.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/system/system.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/trace_col_groups/trace_col_groups.air +./target/release/airc transpile --target plonky3 ./air-script/src/tests/variables/variables.air