diff --git a/crates/prover/src/core/channel/mod.rs b/crates/prover/src/core/channel/mod.rs index a0e206b3f..d810f10f1 100644 --- a/crates/prover/src/core/channel/mod.rs +++ b/crates/prover/src/core/channel/mod.rs @@ -28,7 +28,7 @@ impl ChannelTime { } pub trait Channel { - type Digest: Serializable + Copy; + type Digest: Serializable + Copy + Default; const BYTES_PER_HASH: usize; diff --git a/crates/prover/src/examples/blake/air.rs b/crates/prover/src/examples/blake/air.rs new file mode 100644 index 000000000..0185fb20d --- /dev/null +++ b/crates/prover/src/examples/blake/air.rs @@ -0,0 +1,352 @@ +use std::simd::u32x16; + +use itertools::{chain, multiunzip, Itertools}; +use tracing::{span, Level}; + +use super::round::BlakeRoundComponent; +use super::scheduler::BlakeSchedulerComponent; +use super::xor_table::XorTableComponent; +use crate::constraint_framework::constant_columns::gen_is_first; +use crate::core::air::{Air, AirProver, Component, ComponentProver}; +use crate::core::backend::simd::m31::LOG_N_LANES; +use crate::core::backend::simd::SimdBackend; +use crate::core::channel::Channel; +use crate::core::pcs::CommitmentSchemeProver; +use crate::core::poly::circle::{CanonicCoset, PolyOps}; +use crate::core::prover::{prove, StarkProof, LOG_BLOWUP_FACTOR}; +use crate::core::vcs::ops::{MerkleHasher, MerkleOps}; +use crate::core::InteractionElements; +use crate::examples::blake::round::RoundElements; +use crate::examples::blake::scheduler::{self, BlakeElements, BlakeInput}; +use crate::examples::blake::{ + round, xor_table, BlakeXorElements, XorAccums, N_ROUNDS, ROUND_LOG_SPLIT, +}; + +pub struct BlakeAir { + pub scheduler_component: BlakeSchedulerComponent, + pub round_components: Vec, + pub xor12: XorTableComponent<12, 4>, + pub xor9: XorTableComponent<9, 2>, + pub xor8: XorTableComponent<8, 2>, + pub xor7: XorTableComponent<7, 2>, + pub xor4: XorTableComponent<4, 0>, +} + +impl Air for BlakeAir { + fn components(&self) -> Vec<&dyn Component> { + chain![ + [&self.scheduler_component as &dyn Component], + self.round_components.iter().map(|c| c as &dyn Component), + [ + &self.xor12 as &dyn Component, + &self.xor9 as &dyn Component, + &self.xor8 as &dyn Component, + &self.xor7 as &dyn Component, + &self.xor4 as &dyn Component, + ] + ] + .collect() + } +} + +impl AirProver for BlakeAir { + fn component_provers(&self) -> Vec<&dyn ComponentProver> { + chain![ + [&self.scheduler_component as &dyn ComponentProver], + self.round_components + .iter() + .map(|c| c as &dyn ComponentProver), + [ + &self.xor12 as &dyn ComponentProver, + &self.xor9 as &dyn ComponentProver, + &self.xor8 as &dyn ComponentProver, + &self.xor7 as &dyn ComponentProver, + &self.xor4 as &dyn ComponentProver, + ] + ] + .collect() + } +} + +#[allow(unused)] +pub fn prove_blake(log_size: u32) -> (BlakeAir, StarkProof) +where + SimdBackend: MerkleOps, + C: Channel, + H: MerkleHasher, +{ + assert!(log_size >= LOG_N_LANES); + assert_eq!( + ROUND_LOG_SPLIT.map(|x| (1 << x)).into_iter().sum::() as usize, + N_ROUNDS + ); + + // Precompute twiddles. + let span = span!(Level::INFO, "Precompute twiddles").entered(); + const XOR_TABLE_MAX_LOG_SIZE: u32 = 16; + let log_max_rows = + (log_size + *ROUND_LOG_SPLIT.iter().max().unwrap()).max(XOR_TABLE_MAX_LOG_SIZE); + let twiddles = SimdBackend::precompute_twiddles( + CanonicCoset::new(log_max_rows + 1 + LOG_BLOWUP_FACTOR) + .circle_domain() + .half_coset, + ); + span.exit(); + + // Prepare inputs. + let blake_inputs = (0..(1 << (log_size - LOG_N_LANES))) + .map(|i| { + let v = [u32x16::from_array(std::array::from_fn(|j| (i + 2 * j) as u32)); 16]; + let m = [u32x16::from_array(std::array::from_fn(|j| (i + 2 * j + 1) as u32)); 16]; + BlakeInput { v, m } + }) + .collect_vec(); + + // Setup protocol. + let channel = &mut C::new(C::Digest::default()); + let commitment_scheme = &mut CommitmentSchemeProver::new(LOG_BLOWUP_FACTOR, &twiddles); + + let span = span!(Level::INFO, "Trace").entered(); + + // Scheduler. + let (scheduler_trace, scheduler_lookup_data, round_inputs) = + scheduler::gen_trace(log_size, &blake_inputs); + + // Rounds. + let mut xor_accums = XorAccums::default(); + let mut rest = &round_inputs[..]; + // Split round inputs to components, according to [ROUND_LOG_SPLIT]. + let (round_traces, round_lookup_datas): (Vec<_>, Vec<_>) = + multiunzip(ROUND_LOG_SPLIT.map(|l| { + let (cur_inputs, r) = rest.split_at(1 << (log_size - LOG_N_LANES + l)); + rest = r; + round::generate_trace(log_size + l, cur_inputs, &mut xor_accums) + })); + + // Xor tables. + let (xor_trace12, xor_lookup_data12) = xor_table::generate_trace(xor_accums.xor12); + let (xor_trace9, xor_lookup_data9) = xor_table::generate_trace(xor_accums.xor9); + let (xor_trace8, xor_lookup_data8) = xor_table::generate_trace(xor_accums.xor8); + let (xor_trace7, xor_lookup_data7) = xor_table::generate_trace(xor_accums.xor7); + let (xor_trace4, xor_lookup_data4) = xor_table::generate_trace(xor_accums.xor4); + + // Trace commitment. + let mut tree_builder = commitment_scheme.tree_builder(); + tree_builder.extend_evals( + chain![ + scheduler_trace, + round_traces.into_iter().flatten(), + xor_trace12, + xor_trace9, + xor_trace8, + xor_trace7, + xor_trace4, + ] + .collect_vec(), + ); + tree_builder.commit(channel); + span.exit(); + + // Draw lookup element. + let blake_lookup_elements = BlakeElements::draw(channel); + let round_lookup_elements = RoundElements::draw(channel); + let xor_lookup_elements = BlakeXorElements::draw(channel); + + // Interaction trace. + let span = span!(Level::INFO, "Interaction").entered(); + let (scheduler_trace, scheduler_claimed_sum) = scheduler::gen_interaction_trace( + log_size, + scheduler_lookup_data, + &round_lookup_elements, + &blake_lookup_elements, + ); + + let (round_traces, round_claimed_sums): (Vec<_>, Vec<_>) = multiunzip( + ROUND_LOG_SPLIT + .iter() + .zip(round_lookup_datas) + .map(|(l, lookup_data)| { + round::generate_interaction_trace( + log_size + l, + lookup_data, + &xor_lookup_elements, + &round_lookup_elements, + ) + }), + ); + + let (xor_trace12, xor_claimed_sum12) = + xor_table::generate_interaction_trace(xor_lookup_data12, &xor_lookup_elements.xor12); + let (xor_trace9, xor_claimed_sum9) = + xor_table::generate_interaction_trace(xor_lookup_data9, &xor_lookup_elements.xor9); + let (xor_trace8, xor_claimed_sum8) = + xor_table::generate_interaction_trace(xor_lookup_data8, &xor_lookup_elements.xor8); + let (xor_trace7, xor_claimed_sum7) = + xor_table::generate_interaction_trace(xor_lookup_data7, &xor_lookup_elements.xor7); + let (xor_trace4, xor_claimed_sum4) = + xor_table::generate_interaction_trace(xor_lookup_data4, &xor_lookup_elements.xor4); + + let mut tree_builder = commitment_scheme.tree_builder(); + tree_builder.extend_evals( + chain![ + scheduler_trace, + round_traces.into_iter().flatten(), + xor_trace12, + xor_trace9, + xor_trace8, + xor_trace7, + xor_trace4, + ] + .collect_vec(), + ); + tree_builder.commit(channel); + span.exit(); + + // Constant trace. + let span = span!(Level::INFO, "Constant Trace").entered(); + let mut tree_builder = commitment_scheme.tree_builder(); + tree_builder.extend_evals( + chain![ + [gen_is_first(log_size)], + ROUND_LOG_SPLIT.map(|l| gen_is_first(log_size + l)), + xor_table::generate_constant_trace::<12, 4>(), + xor_table::generate_constant_trace::<9, 2>(), + xor_table::generate_constant_trace::<8, 2>(), + xor_table::generate_constant_trace::<7, 2>(), + xor_table::generate_constant_trace::<4, 0>(), + ] + .collect_vec(), + ); + tree_builder.commit(channel); + span.exit(); + + // Prove constraints. + let scheduler_component = BlakeSchedulerComponent { + log_size, + blake_lookup_elements, + round_lookup_elements: round_lookup_elements.clone(), + claimed_sum: scheduler_claimed_sum, + }; + let round_components = round_claimed_sums + .into_iter() + .zip(ROUND_LOG_SPLIT) + .map(|(claimed_sum, l)| BlakeRoundComponent { + log_size: log_size + l, + xor_lookup_elements: xor_lookup_elements.clone(), + round_lookup_elements: round_lookup_elements.clone(), + claimed_sum, + }) + .collect(); + let xor12 = XorTableComponent::<12, 4> { + lookup_elements: xor_lookup_elements.xor12, + claimed_sum: xor_claimed_sum12, + }; + let xor9 = XorTableComponent::<9, 2> { + lookup_elements: xor_lookup_elements.xor9, + claimed_sum: xor_claimed_sum9, + }; + let xor8 = XorTableComponent::<8, 2> { + lookup_elements: xor_lookup_elements.xor8, + claimed_sum: xor_claimed_sum8, + }; + let xor7 = XorTableComponent::<7, 2> { + lookup_elements: xor_lookup_elements.xor7, + claimed_sum: xor_claimed_sum7, + }; + let xor4 = XorTableComponent::<4, 0> { + lookup_elements: xor_lookup_elements.xor4, + claimed_sum: xor_claimed_sum4, + }; + let air = BlakeAir { + scheduler_component, + round_components, + xor12, + xor9, + xor8, + xor7, + xor4, + }; + let proof = prove::( + &air.component_provers(), + channel, + &InteractionElements::default(), + commitment_scheme, + ) + .unwrap(); + + (air, proof) +} + +#[cfg(test)] +mod tests { + use std::env; + + use crate::core::air::{Air, Components}; + use crate::core::channel::{Blake2sChannel, Channel}; + use crate::core::pcs::CommitmentSchemeVerifier; + use crate::core::prover::verify; + use crate::core::vcs::blake2_hash::Blake2sHash; + use crate::core::vcs::blake2_merkle::Blake2sMerkleHasher; + use crate::core::InteractionElements; + use crate::examples::blake::air::prove_blake; + use crate::examples::blake::round::RoundElements; + use crate::examples::blake::xor_table::XorElements; + + // Note: this test is slow. Only run in release. + #[ignore] + #[test_log::test] + fn test_simd_blake_prove() { + // Note: To see time measurement, run test with + // LOG_N_INSTANCES=16 RUST_LOG_SPAN_EVENTS=enter,close RUST_LOG=info RUSTFLAGS=" + // -C target-cpu=native -C target-feature=+avx512f" cargo test --release + // test_simd_blake_prove -- --nocapture --ignored + + // Get from environment variable: + let log_n_instances = env::var("LOG_N_INSTANCES") + .unwrap_or_else(|_| "6".to_string()) + .parse::() + .unwrap(); + + // Prove. + let (air, proof) = prove_blake::(log_n_instances); + + // Verify. + // TODO: Create Air instance independently. + let channel = &mut Blake2sChannel::new(Blake2sHash::default()); + let commitment_scheme = &mut CommitmentSchemeVerifier::new(); + + // Decommit. + let sizes = Components(air.components()).column_log_sizes(); + + // Trace columns. + commitment_scheme.commit(proof.commitments[0], &sizes[0], channel); + // Draw lookup element. + let blake_lookup_elements = RoundElements::draw(channel); + let round_lookup_elements = RoundElements::draw(channel); + let xor_lookup_elements = XorElements::draw(channel); + assert_eq!( + blake_lookup_elements, + air.scheduler_component.blake_lookup_elements + ); + assert_eq!( + round_lookup_elements, + air.scheduler_component.round_lookup_elements + ); + assert_eq!(xor_lookup_elements, air.xor12.lookup_elements); + + // TODO(spapini): Check claimed sum against first and last instances. + // Interaction columns. + commitment_scheme.commit(proof.commitments[1], &sizes[1], channel); + // Constant columns. + commitment_scheme.commit(proof.commitments[2], &sizes[2], channel); + + verify( + &air.components(), + channel, + &InteractionElements::default(), // Not in use. + commitment_scheme, + proof, + ) + .unwrap(); + } +} diff --git a/crates/prover/src/examples/blake/mod.rs b/crates/prover/src/examples/blake/mod.rs index db7e8599e..6fbe6d81b 100644 --- a/crates/prover/src/examples/blake/mod.rs +++ b/crates/prover/src/examples/blake/mod.rs @@ -1,19 +1,18 @@ //! AIR for blake2s and blake3. //! See -#![allow(unused)] use std::fmt::Debug; use std::ops::{Add, AddAssign, Mul, Sub}; use std::simd::u32x16; use xor_table::{XorAccumulator, XorElements}; -use crate::constraint_framework::logup::LookupElements; use crate::core::backend::simd::m31::PackedBaseField; use crate::core::channel::Channel; use crate::core::fields::m31::BaseField; use crate::core::fields::FieldExpOps; +mod air; mod round; mod scheduler; mod xor_table; @@ -22,7 +21,11 @@ const STATE_SIZE: usize = 16; const MESSAGE_SIZE: usize = 16; const N_FELTS_IN_U32: usize = 2; const N_ROUND_INPUT_FELTS: usize = (STATE_SIZE + STATE_SIZE + MESSAGE_SIZE) * N_FELTS_IN_U32; + +// Parameters for Blake2s. Change these for blake3. const N_ROUNDS: usize = 10; +/// A splitting N_ROUNDS into several powers of 2. +const ROUND_LOG_SPLIT: [u32; 2] = [3, 1]; #[derive(Default)] struct XorAccums { diff --git a/crates/prover/src/examples/blake/round/constraints.rs b/crates/prover/src/examples/blake/round/constraints.rs index ebe3bcaea..8e0d92394 100644 --- a/crates/prover/src/examples/blake/round/constraints.rs +++ b/crates/prover/src/examples/blake/round/constraints.rs @@ -2,7 +2,7 @@ use itertools::{chain, Itertools}; use num_traits::One; use super::{BlakeXorElements, RoundElements}; -use crate::constraint_framework::logup::{LogupAtRow, LookupElements}; +use crate::constraint_framework::logup::LogupAtRow; use crate::constraint_framework::EvalAtRow; use crate::core::fields::m31::BaseField; use crate::examples::blake::{Fu32, STATE_SIZE}; diff --git a/crates/prover/src/examples/blake/round/gen.rs b/crates/prover/src/examples/blake/round/gen.rs index 8ddae7411..ce0966381 100644 --- a/crates/prover/src/examples/blake/round/gen.rs +++ b/crates/prover/src/examples/blake/round/gen.rs @@ -6,7 +6,7 @@ use num_traits::One; use tracing::{span, Level}; use super::{BlakeXorElements, RoundElements}; -use crate::constraint_framework::logup::{LogupTraceGenerator, LookupElements}; +use crate::constraint_framework::logup::LogupTraceGenerator; use crate::core::backend::simd::column::BaseColumn; use crate::core::backend::simd::m31::{PackedBaseField, LOG_N_LANES}; use crate::core::backend::simd::qm31::PackedSecureField; @@ -18,9 +18,7 @@ use crate::core::poly::circle::{CanonicCoset, CircleEvaluation}; use crate::core::poly::BitReversedOrder; use crate::core::ColumnVec; use crate::examples::blake::round::blake_round_info; -use crate::examples::blake::{ - to_felts, XorAccums, MESSAGE_SIZE, N_FELTS_IN_U32, N_ROUND_INPUT_FELTS, STATE_SIZE, -}; +use crate::examples::blake::{to_felts, XorAccums, N_ROUND_INPUT_FELTS, STATE_SIZE}; pub struct BlakeRoundLookupData { /// A vector of (w, [a_col, b_col, c_col]) for each xor lookup. @@ -214,6 +212,7 @@ pub fn generate_trace( ColumnVec>, BlakeRoundLookupData, ) { + let _span = span!(Level::INFO, "Round Generation").entered(); let mut generator = TraceGenerator::new(log_size); for vec_row in 0..(1 << (log_size - LOG_N_LANES)) { diff --git a/crates/prover/src/examples/blake/round/mod.rs b/crates/prover/src/examples/blake/round/mod.rs index 02e4da44d..c5123b5ce 100644 --- a/crates/prover/src/examples/blake/round/mod.rs +++ b/crates/prover/src/examples/blake/round/mod.rs @@ -3,13 +3,12 @@ mod gen; use constraints::BlakeRoundEval; use num_traits::Zero; -pub use r#gen::BlakeRoundInput; +pub use r#gen::{generate_interaction_trace, generate_trace, BlakeRoundInput}; -use super::{BlakeXorElements, N_ROUND_INPUT_FELTS, STATE_SIZE}; +use super::{BlakeXorElements, N_ROUND_INPUT_FELTS}; use crate::constraint_framework::logup::{LogupAtRow, LookupElements}; use crate::constraint_framework::{EvalAtRow, FrameworkComponent, InfoEvaluator}; use crate::core::fields::qm31::SecureField; -use crate::examples::blake::XorAccums; pub fn blake_round_info() -> InfoEvaluator { let component = BlakeRoundComponent { @@ -36,7 +35,7 @@ impl FrameworkComponent for BlakeRoundComponent { fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + 1 } - fn evaluate(&self, mut eval: E) -> E { + fn evaluate(&self, eval: E) -> E { let blake_eval = BlakeRoundEval { eval, xor_lookup_elements: &self.xor_lookup_elements, @@ -54,12 +53,8 @@ mod tests { use itertools::Itertools; use crate::constraint_framework::constant_columns::gen_is_first; - use crate::constraint_framework::logup::LookupElements; use crate::constraint_framework::FrameworkComponent; - use crate::core::backend::simd::SimdBackend; - use crate::core::fields::m31::BaseField; - use crate::core::poly::circle::{CanonicCoset, CircleEvaluation}; - use crate::core::poly::BitReversedOrder; + use crate::core::poly::circle::CanonicCoset; use crate::examples::blake::round::r#gen::{ generate_interaction_trace, generate_trace, BlakeRoundInput, }; @@ -76,7 +71,7 @@ mod tests { let (trace, lookup_data) = generate_trace( LOG_SIZE, &(0..(1 << LOG_SIZE)) - .map(|i| BlakeRoundInput { + .map(|_| BlakeRoundInput { v: std::array::from_fn(|i| Simd::splat(i as u32)), m: std::array::from_fn(|i| Simd::splat((i + 1) as u32)), }) diff --git a/crates/prover/src/examples/blake/scheduler/constraints.rs b/crates/prover/src/examples/blake/scheduler/constraints.rs index 9218f5949..395dac6f8 100644 --- a/crates/prover/src/examples/blake/scheduler/constraints.rs +++ b/crates/prover/src/examples/blake/scheduler/constraints.rs @@ -2,7 +2,7 @@ use itertools::{chain, Itertools}; use num_traits::One; use super::BlakeElements; -use crate::constraint_framework::logup::{LogupAtRow, LookupElements}; +use crate::constraint_framework::logup::LogupAtRow; use crate::constraint_framework::EvalAtRow; use crate::core::vcs::blake2s_ref::SIGMA; use crate::examples::blake::round::RoundElements; diff --git a/crates/prover/src/examples/blake/scheduler/gen.rs b/crates/prover/src/examples/blake/scheduler/gen.rs index 23b15c5d7..3b4437920 100644 --- a/crates/prover/src/examples/blake/scheduler/gen.rs +++ b/crates/prover/src/examples/blake/scheduler/gen.rs @@ -1,4 +1,3 @@ -use std::f32::consts::E; use std::simd::u32x16; use itertools::{chain, Itertools}; @@ -6,9 +5,9 @@ use num_traits::One; use tracing::{span, Level}; use super::{blake_scheduler_info, BlakeElements}; -use crate::constraint_framework::logup::{LogupTraceGenerator, LookupElements}; +use crate::constraint_framework::logup::LogupTraceGenerator; use crate::core::backend::simd::column::BaseColumn; -use crate::core::backend::simd::m31::{PackedBaseField, LOG_N_LANES}; +use crate::core::backend::simd::m31::LOG_N_LANES; use crate::core::backend::simd::qm31::PackedSecureField; use crate::core::backend::simd::{blake2s, SimdBackend}; use crate::core::backend::Column; @@ -51,6 +50,7 @@ pub fn gen_trace( BlakeSchedulerLookupData, Vec, ) { + let _span = span!(Level::INFO, "Scheduler Generation").entered(); let mut lookup_data = BlakeSchedulerLookupData::new(log_size); let mut round_inputs = Vec::with_capacity(inputs.len() * N_ROUNDS); diff --git a/crates/prover/src/examples/blake/scheduler/mod.rs b/crates/prover/src/examples/blake/scheduler/mod.rs index 29df6e712..116053246 100644 --- a/crates/prover/src/examples/blake/scheduler/mod.rs +++ b/crates/prover/src/examples/blake/scheduler/mod.rs @@ -2,15 +2,16 @@ mod constraints; mod gen; use constraints::BlakeSchedulerEval; +pub use gen::{gen_interaction_trace, gen_trace, BlakeInput}; use num_traits::Zero; use super::round::RoundElements; -use super::{N_ROUND_INPUT_FELTS, STATE_SIZE}; +use super::N_ROUND_INPUT_FELTS; use crate::constraint_framework::logup::{LogupAtRow, LookupElements}; use crate::constraint_framework::{EvalAtRow, FrameworkComponent, InfoEvaluator}; use crate::core::fields::qm31::SecureField; -type BlakeElements = LookupElements; +pub type BlakeElements = LookupElements; pub fn blake_scheduler_info() -> InfoEvaluator { let component = BlakeSchedulerComponent { @@ -35,7 +36,7 @@ impl FrameworkComponent for BlakeSchedulerComponent { fn max_constraint_log_degree_bound(&self) -> u32 { self.log_size + 1 } - fn evaluate(&self, mut eval: E) -> E { + fn evaluate(&self, eval: E) -> E { let blake_eval = BlakeSchedulerEval { eval, blake_lookup_elements: &self.blake_lookup_elements, @@ -52,16 +53,11 @@ mod tests { use itertools::Itertools; - use crate::constraint_framework::logup::LookupElements; use crate::constraint_framework::FrameworkComponent; - use crate::core::backend::simd::SimdBackend; - use crate::core::fields::m31::BaseField; - use crate::core::poly::circle::{CanonicCoset, CircleEvaluation}; - use crate::core::poly::BitReversedOrder; + use crate::core::poly::circle::CanonicCoset; use crate::examples::blake::round::RoundElements; use crate::examples::blake::scheduler::r#gen::{gen_interaction_trace, gen_trace, BlakeInput}; use crate::examples::blake::scheduler::{BlakeElements, BlakeSchedulerComponent}; - use crate::examples::blake::XorAccums; #[test] fn test_blake_scheduler() { @@ -72,7 +68,7 @@ mod tests { let (trace, lookup_data, _round_inputs) = gen_trace( LOG_SIZE, &(0..(1 << LOG_SIZE)) - .map(|i| BlakeInput { + .map(|_| BlakeInput { v: std::array::from_fn(|i| Simd::splat(i as u32)), m: std::array::from_fn(|i| Simd::splat((i + 1) as u32)), }) diff --git a/crates/prover/src/examples/blake/xor_table/mod.rs b/crates/prover/src/examples/blake/xor_table/mod.rs index 58267b183..5d2781c13 100644 --- a/crates/prover/src/examples/blake/xor_table/mod.rs +++ b/crates/prover/src/examples/blake/xor_table/mod.rs @@ -17,6 +17,7 @@ use std::simd::u32x16; use constraints::XorTableEval; use itertools::Itertools; +pub use r#gen::{generate_constant_trace, generate_interaction_trace, generate_trace}; use crate::constraint_framework::logup::{LogupAtRow, LookupElements}; use crate::constraint_framework::{EvalAtRow, FrameworkComponent};