Skip to main content

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 {}