p3_lookup/types.rs
1//! Core lookup types and traits.
2//!
3//! These types define the data structures for lookup arguments in STARKs.
4//! They were previously in `p3-air` but live here to keep the `Air` trait
5//! free of lookup concerns.
6
7use alloc::string::String;
8use alloc::vec::Vec;
9use core::ops::Neg;
10
11use p3_air::{Air, PermutationAirBuilder, SymbolicExpression};
12use p3_field::Field;
13use serde::{Deserialize, Serialize};
14
15/// Defines errors that can occur during lookup verification.
16#[derive(Debug)]
17pub enum LookupError {
18 /// Error indicating that the global cumulative sum is incorrect.
19 GlobalCumulativeMismatch(Option<String>),
20}
21
22/// Specifies whether a lookup is local to an AIR or part of a global interaction.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Kind {
25 /// A lookup where all entries are contained within a single AIR.
26 Local,
27 /// A lookup that spans multiple AIRs, identified by a unique interaction name.
28 ///
29 /// The interaction name is used to identify all elements that are part of the same interaction.
30 Global(String),
31}
32
33/// Indicates the direction of data flow in a global lookup.
34#[derive(Clone, Copy)]
35pub enum Direction {
36 /// Indicates that elements are being sent (contributed) to the lookup.
37 Send,
38 /// Indicates that elements are being received (removed) from the lookup.
39 Receive,
40}
41
42impl Direction {
43 /// Helper method to compute the signed multiplicity based on the direction.
44 pub fn multiplicity<T: Neg<Output = T>>(&self, mult: T) -> T {
45 match self {
46 Self::Send => -mult,
47 Self::Receive => mult,
48 }
49 }
50}
51
52/// Data required for global lookup arguments in a multi-STARK proof.
53#[derive(Debug, Default, Clone, Serialize, Deserialize)]
54pub struct LookupData<F> {
55 /// Name of the global lookup interaction.
56 pub name: String,
57 /// Index of the auxiliary column (if there are multiple auxiliary columns, this is the first one)
58 pub aux_idx: usize,
59 /// Expected cumulated value for a global lookup argument.
60 pub expected_cumulated: F,
61}
62
63/// A type alias for a lookup input tuple.
64///
65/// The tuple contains:
66/// - a vector of symbolic expressions representing the elements involved in the lookup,
67/// - a symbolic expression representing the multiplicity of the lookup,
68/// - a direction indicating whether the elements are being sent or received.
69///
70/// # Example
71/// ```ignored
72/// use p3_lookup::{LookupInput, Direction};
73/// use p3_air::SymbolicExpression;
74///
75/// let lookup_input: LookupInput<SymbolicExpression<F>> = (
76/// vec![SymbolicExpression::Constant(F::ONE)],
77/// SymbolicExpression::Constant(F::ONE),
78/// Direction::Send
79/// );
80/// ```
81pub type LookupInput<F> = (Vec<SymbolicExpression<F>>, SymbolicExpression<F>, Direction);
82
83/// A structure that holds the lookup data necessary to generate lookup contexts.
84/// It is shared between the prover and the verifier.
85#[derive(Clone, Debug)]
86pub struct Lookup<F: Field> {
87 /// Type of lookup: local or global
88 pub kind: Kind,
89 /// Elements being read (consumed from the table). Each `Vec<SymbolicExpression<F>>`
90 /// actually represents a tuple of elements that are bundled together to make one lookup.
91 pub element_exprs: Vec<Vec<SymbolicExpression<F>>>,
92 /// Multiplicities for the elements.
93 /// Note that Lagrange selectors may not be normalized, and so cannot be used as proper
94 /// filters in the multiplicities.
95 pub multiplicities_exprs: Vec<SymbolicExpression<F>>,
96 /// The column index in the permutation trace for this lookup's running sum
97 pub columns: Vec<usize>,
98}
99
100impl<F: Field> Lookup<F> {
101 /// Creates a new lookup with the specified column.
102 ///
103 /// # Arguments
104 /// * `elements` - Elements from either the main execution trace or a lookup table.
105 /// * `multiplicities` - How many times each `element` should appear
106 /// * `column` - The column index in the permutation trace for this lookup
107 pub const fn new(
108 kind: Kind,
109 element_exprs: Vec<Vec<SymbolicExpression<F>>>,
110 multiplicities_exprs: Vec<SymbolicExpression<F>>,
111 columns: Vec<usize>,
112 ) -> Self {
113 Self {
114 kind,
115 element_exprs,
116 multiplicities_exprs,
117 columns,
118 }
119 }
120}
121
122/// Trait for evaluating lookup constraints.
123pub trait LookupEvaluator {
124 /// Returns the number of auxiliary columns needed by this lookup protocol.
125 ///
126 /// For example:
127 /// - LogUp needs 1 column (running sum)
128 fn num_aux_cols(&self) -> usize;
129
130 /// Returns the number of challenges for each lookup argument.
131 ///
132 /// For example, for LogUp, this is 2:
133 /// - one challenge for combining the lookup tuples,
134 /// - one challenge for the running sum.
135 fn num_challenges(&self) -> usize;
136
137 /// Evaluates a local lookup argument based on the provided context.
138 ///
139 /// For example, in LogUp:
140 /// - this checks that the running sum is updated correctly.
141 /// - it checks that the final value of the running sum is 0.
142 fn eval_local_lookup<AB>(&self, builder: &mut AB, context: &Lookup<AB::F>)
143 where
144 AB: PermutationAirBuilder;
145
146 /// Evaluates a global lookup update based on the provided context, and the expected cumulated value.
147 /// This evaluation is carried out at the AIR level. We still need to check that the permutation argument holds
148 /// over all AIRs involved in the interaction.
149 ///
150 /// For example, in LogUp:
151 /// - this checks that the running sum is updated correctly.
152 /// - it checks that the local final value of the running sum is equal to the value provided by the prover.
153 fn eval_global_update<AB>(
154 &self,
155 builder: &mut AB,
156 context: &Lookup<AB::F>,
157 expected_cumulated: AB::ExprEF,
158 ) where
159 AB: PermutationAirBuilder;
160
161 /// Evaluates the lookup constraints for all provided contexts.
162 ///
163 /// For each context:
164 /// - if it is a local lookup, evaluates it with `eval_local_lookup`.
165 /// - if it is a global lookup, evaluates it with `eval_global_update`, reading the expected
166 /// cumulated value from the builder's `permutation_values()`.
167 fn eval_lookups<AB>(&self, builder: &mut AB, contexts: &[Lookup<AB::F>])
168 where
169 AB: PermutationAirBuilder,
170 {
171 let mut pv_idx = 0;
172 for context in contexts.iter() {
173 match &context.kind {
174 Kind::Local => {
175 self.eval_local_lookup(builder, context);
176 }
177 Kind::Global(_) => {
178 let expected = builder.permutation_values()[pv_idx].clone();
179 pv_idx += 1;
180 self.eval_global_update(builder, context, expected.into());
181 }
182 }
183 }
184 assert_eq!(
185 pv_idx,
186 builder.permutation_values().len(),
187 "permutation values count mismatch"
188 );
189 }
190}
191
192/// Extension trait for AIRs that use lookups.
193///
194/// This decouples lookup definition from the core [`Air`] trait, so AIRs
195/// that don't use lookups don't need to know about lookup types at all.
196pub trait LookupAir<F: Field> {
197 /// Allocate auxiliary columns for a new lookup and return their indices.
198 ///
199 /// Default implementation returns an empty vector, indicating no lookup columns.
200 fn add_lookup_columns(&mut self) -> Vec<usize> {
201 Vec::new()
202 }
203
204 /// Return all lookups registered by this AIR.
205 ///
206 /// Default implementation returns an empty vector, indicating no lookups.
207 fn get_lookups(&mut self) -> Vec<Lookup<F>> {
208 Vec::new()
209 }
210
211 /// Register a lookup to be used in this AIR.
212 ///
213 /// This is a convenience method that constructs a [`Lookup`] from inputs
214 /// and allocates auxiliary columns via [`add_lookup_columns`](Self::add_lookup_columns).
215 fn register_lookup(&mut self, kind: Kind, lookup_inputs: &[LookupInput<F>]) -> Lookup<F> {
216 let (element_exprs, multiplicities_exprs) = lookup_inputs
217 .iter()
218 .map(|(elems, mult, dir)| {
219 let multiplicity = dir.multiplicity(mult.clone());
220 (elems.clone(), multiplicity)
221 })
222 .unzip();
223
224 Lookup {
225 kind,
226 element_exprs,
227 multiplicities_exprs,
228 columns: self.add_lookup_columns(),
229 }
230 }
231}
232
233/// Extension of [`Air`] that adds lookup constraint evaluation.
234///
235/// This trait is blanket-implemented for every type that implements [`Air`],
236/// so any AIR automatically supports `eval_with_lookups`. It lives in
237/// `p3-lookup` (rather than `p3-air`) to keep the core `Air` trait free of
238/// lookup concerns.
239pub trait AirWithLookups<AB: PermutationAirBuilder>: Air<AB> {
240 /// Evaluate both AIR constraints and lookup constraints.
241 ///
242 /// First evaluates the core AIR constraints via [`Air::eval`], then
243 /// evaluates any lookup constraints via the provided [`LookupEvaluator`].
244 ///
245 /// For AIRs without lookups, pass an empty `lookups` slice and the
246 /// evaluator will be skipped entirely.
247 fn eval_with_lookups(
248 &self,
249 builder: &mut AB,
250 lookups: &[Lookup<AB::F>],
251 lookup_evaluator: &impl LookupEvaluator,
252 ) {
253 self.eval(builder);
254
255 if !lookups.is_empty() {
256 lookup_evaluator.eval_lookups(builder, lookups);
257 }
258 }
259}
260
261impl<AB: PermutationAirBuilder, A: Air<AB>> AirWithLookups<AB> for A {}