openvm_stark_backend/interaction/
mod.rs

1use std::{fmt::Debug, sync::Arc};
2
3use p3_air::AirBuilder;
4use p3_challenger::CanObserve;
5use p3_field::Field;
6use p3_matrix::dense::RowMajorMatrix;
7use p3_util::log2_ceil_usize;
8use serde::{de::DeserializeOwned, Deserialize, Serialize};
9
10use crate::{
11    air_builders::symbolic::{symbolic_expression::SymbolicExpression, SymbolicConstraints},
12    interaction::fri_log_up::{STARK_LU_NUM_CHALLENGES, STARK_LU_NUM_EXPOSED_VALUES},
13    prover::types::PairView,
14};
15
16/// Interaction debugging tools
17pub mod debug;
18pub mod fri_log_up;
19pub mod rap;
20pub mod trace;
21mod utils;
22
23// Must be a type smaller than u32 to make BusIndex p - 1 unrepresentable.
24pub type BusIndex = u16;
25
26#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
27pub struct Interaction<Expr> {
28    pub message: Vec<Expr>,
29    pub count: Expr,
30    /// The bus index specifying the bus to send the message over. All valid instantiations of
31    /// `BusIndex` are safe.
32    pub bus_index: BusIndex,
33    /// Determines the contribution of each interaction message to a linear constraint on the trace
34    /// heights in the verifier.
35    ///
36    /// For each bus index and trace, `count_weight` values are summed per interaction on that
37    /// bus index and multiplied by the trace height. The total sum over all traces is constrained
38    /// by the verifier to not overflow the field characteristic \( p \).
39    ///
40    /// This is used to impose sufficient conditions for bus constraint soundness and setting a
41    /// proper value depends on the bus and the constraint it imposes.
42    pub count_weight: u32,
43}
44
45pub type SymbolicInteraction<F> = Interaction<SymbolicExpression<F>>;
46
47/// An [AirBuilder] with additional functionality to build special logUp arguments for
48/// communication between AIRs across buses. These arguments use randomness to
49/// add additional trace columns (in the extension field) and constraints to the AIR.
50///
51/// An interactive AIR is a AIR that can specify buses for sending and receiving data
52/// to other AIRs. The original AIR is augmented by virtual columns determined by
53/// the interactions to define a [RAP](crate::rap::Rap).
54pub trait InteractionBuilder: AirBuilder {
55    /// Stores a new interaction in the builder.
56    ///
57    /// See [Interaction] for more details on `count_weight`.
58    fn push_interaction<E: Into<Self::Expr>>(
59        &mut self,
60        bus_index: BusIndex,
61        fields: impl IntoIterator<Item = E>,
62        count: impl Into<Self::Expr>,
63        count_weight: u32,
64    );
65
66    /// Returns the current number of interactions.
67    fn num_interactions(&self) -> usize;
68
69    /// Returns all interactions stored.
70    fn all_interactions(&self) -> &[Interaction<Self::Expr>];
71}
72
73/// A `Lookup` bus is used to establish that one multiset of values (the queries) are subset of
74/// another multiset of values (the keys).
75///
76/// Soundness requires that the total number of queries sent over the bus per message is at most the
77/// field characteristic.
78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
79pub struct LookupBus {
80    pub index: BusIndex,
81}
82
83impl LookupBus {
84    pub const fn new(index: BusIndex) -> Self {
85        Self { index }
86    }
87
88    /// Performs a lookup on the given bus.
89    ///
90    /// This method asserts that `key` is present in the lookup table. The parameter `enabled`
91    /// must be constrained to be boolean, and the lookup constraint is imposed provided `enabled`
92    /// is one.
93    ///
94    /// Caller must constrain that `enabled` is boolean.
95    pub fn lookup_key<AB, E>(
96        &self,
97        builder: &mut AB,
98        query: impl IntoIterator<Item = E>,
99        enabled: impl Into<AB::Expr>,
100    ) where
101        AB: InteractionBuilder,
102        E: Into<AB::Expr>,
103    {
104        // We embed the query multiplicity as {0, 1} in the integers and the lookup table key
105        // multiplicity to be {0, -1, ..., -p + 1}. Setting `count_weight = 1` will ensure that the
106        // total number of lookups is at most p, which is sufficient to establish lookup multiset is
107        // a subset of the key multiset. See Corollary 3.6 in
108        // [docs/Soundess_of_Interactions_via_LogUp.pdf].
109        builder.push_interaction(self.index, query, enabled, 1);
110    }
111
112    /// Adds a key to the lookup table.
113    ///
114    /// The `num_lookups` parameter should equal the number of enabled lookups performed.
115    pub fn add_key_with_lookups<AB, E>(
116        &self,
117        builder: &mut AB,
118        key: impl IntoIterator<Item = E>,
119        num_lookups: impl Into<AB::Expr>,
120    ) where
121        AB: InteractionBuilder,
122        E: Into<AB::Expr>,
123    {
124        // Since we only want a subset constraint, `count_weight` can be zero here. See the comment
125        // in `LookupBus::lookup_key`.
126        builder.push_interaction(self.index, key, -num_lookups.into(), 0);
127    }
128}
129
130/// A `PermutationCheckBus` bus is used to establish that two multi-sets of values are equal.
131///
132/// Soundness requires that both the total number of messages sent and received over the bus per
133/// message is at most the field characteristic.
134#[derive(Copy, Clone, Debug, PartialEq, Eq)]
135pub struct PermutationCheckBus {
136    pub index: BusIndex,
137}
138
139#[derive(Copy, Clone, Debug, PartialEq, Eq)]
140pub enum PermutationInteractionType {
141    Send,
142    Receive,
143}
144
145impl PermutationCheckBus {
146    pub const fn new(index: BusIndex) -> Self {
147        Self { index }
148    }
149
150    /// Send a message.
151    ///
152    /// Caller must constrain `enabled` to be boolean.
153    pub fn send<AB, E>(
154        &self,
155        builder: &mut AB,
156        message: impl IntoIterator<Item = E>,
157        enabled: impl Into<AB::Expr>,
158    ) where
159        AB: InteractionBuilder,
160        E: Into<AB::Expr>,
161    {
162        // We embed the multiplicity `enabled` as an integer {0, 1}.
163        builder.push_interaction(self.index, message, enabled, 1);
164    }
165
166    /// Receive a message.
167    ///
168    /// Caller must constrain `enabled` to be boolean.
169    pub fn receive<AB, E>(
170        &self,
171        builder: &mut AB,
172        message: impl IntoIterator<Item = E>,
173        enabled: impl Into<AB::Expr>,
174    ) where
175        AB: InteractionBuilder,
176        E: Into<AB::Expr>,
177    {
178        // We embed the multiplicity `enabled` as an integer {0, -1}.
179        builder.push_interaction(self.index, message, -enabled.into(), 1);
180    }
181
182    /// Send or receive determined by `interaction_type`.
183    ///
184    /// Caller must constrain `enabled` to be boolean.
185    pub fn send_or_receive<AB, E>(
186        &self,
187        builder: &mut AB,
188        interaction_type: PermutationInteractionType,
189        message: impl IntoIterator<Item = E>,
190        enabled: impl Into<AB::Expr>,
191    ) where
192        AB: InteractionBuilder,
193        E: Into<AB::Expr>,
194    {
195        match interaction_type {
196            PermutationInteractionType::Send => self.send(builder, message, enabled),
197            PermutationInteractionType::Receive => self.receive(builder, message, enabled),
198        }
199    }
200
201    /// Send or receive a message determined by the expression `direction`.
202    ///
203    /// Direction = 1 means send, direction = -1 means receive, and direction = 0 means disabled.
204    ///
205    /// Caller must constrain that direction is in {-1, 0, 1}.
206    pub fn interact<AB, E>(
207        &self,
208        builder: &mut AB,
209        message: impl IntoIterator<Item = E>,
210        direction: impl Into<AB::Expr>,
211    ) where
212        AB: InteractionBuilder,
213        E: Into<AB::Expr>,
214    {
215        // We embed the multiplicity `direction` as an integer {-1, 0, 1}.
216        builder.push_interaction(self.index, message, direction.into(), 1);
217    }
218}
219
220pub struct RapPhaseProverData<Challenge> {
221    /// Challenges from the challenger in this phase that determine RAP constraints and exposed
222    /// values.
223    pub challenges: Vec<Challenge>,
224
225    /// After challenge trace per air computed as a function of `challenges`.
226    pub after_challenge_trace_per_air: Vec<Option<RowMajorMatrix<Challenge>>>,
227
228    /// Public values of the phase that are functions of `challenges`.
229    pub exposed_values_per_air: Vec<Option<Vec<Challenge>>>,
230}
231
232#[derive(Default)]
233pub struct RapPhaseVerifierData<Challenge> {
234    /// Challenges from the challenger in this phase that determine RAP constraints and exposed
235    /// values.
236    pub challenges_per_phase: Vec<Vec<Challenge>>,
237}
238
239#[derive(Debug)]
240pub struct RapPhaseShape {
241    pub num_challenges: usize,
242
243    pub num_exposed_values: usize,
244
245    /// Any additional rotations to open at in the permutation PCS round.
246    ///
247    /// Specifies that each `i` in `extra_opening_rots` should be opened at
248    /// `zeta * g^i` (in addition to `zeta` and `zeta * g`).
249    pub extra_opening_rots: Vec<usize>,
250}
251
252/// Supported challenge phases in a RAP.
253#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
254#[repr(u8)]
255pub enum RapPhaseSeqKind {
256    /// Up to one phase with prover/verifier given by [[fri_log_up::FriLogUpPhase]] and
257    /// constraints given by [[fri_log_up::eval_fri_log_up_phase]].
258    FriLogUp,
259}
260
261impl RapPhaseSeqKind {
262    pub fn shape(&self) -> Vec<RapPhaseShape> {
263        match self {
264            RapPhaseSeqKind::FriLogUp => vec![RapPhaseShape {
265                num_challenges: STARK_LU_NUM_CHALLENGES,
266                num_exposed_values: STARK_LU_NUM_EXPOSED_VALUES,
267                extra_opening_rots: vec![],
268            }],
269        }
270    }
271}
272
273/// Defines a particular protocol for the "after challenge" phase in a RAP.
274///
275/// A [RapPhaseSeq] is defined by the proving and verifying methods implemented in this trait,
276/// as well as via some "eval" method that is determined by `RapPhaseId`.
277pub trait RapPhaseSeq<F, Challenge, Challenger> {
278    type PartialProof: Clone + Serialize + DeserializeOwned;
279    /// Preprocessed data necessary for the RAP partial proving
280    type PartialProvingKey: Clone + Serialize + DeserializeOwned;
281    type Error: Debug;
282
283    const ID: RapPhaseSeqKind;
284
285    fn log_up_security_params(&self) -> &LogUpSecurityParameters;
286
287    /// The protocol parameters for the challenge phases may depend on the AIR constraints.
288    fn generate_pk_per_air(
289        &self,
290        symbolic_constraints_per_air: &[SymbolicConstraints<F>],
291        max_constraint_degree: usize,
292    ) -> Vec<Self::PartialProvingKey>;
293
294    /// Partially prove the challenge phases,
295    ///
296    /// Samples challenges, generates after challenge traces and exposed values, and proves any
297    /// extra-STARK part of the protocol.
298    ///
299    /// "Partial" refers to the fact that some STARK parts of the protocol---namely, the constraints
300    /// on the after challenge traces returned in `RapPhaseProverData`---are handled external to
301    /// this function.
302    fn partially_prove(
303        &self,
304        challenger: &mut Challenger,
305        constraints_per_air: &[&SymbolicConstraints<F>],
306        params_per_air: &[&Self::PartialProvingKey],
307        trace_view_per_air: Vec<PairTraceView<F>>,
308    ) -> Option<(Self::PartialProof, RapPhaseProverData<Challenge>)>;
309
310    /// Partially verifies the challenge phases.
311    ///
312    /// Assumes the shape of `exposed_values_per_air_per_phase` is verified externally.
313    ///
314    /// An implementation of this function must sample challenges for the challenge phases and then
315    /// observe the exposed values and commitment.
316    fn partially_verify<Commitment: Clone>(
317        &self,
318        challenger: &mut Challenger,
319        partial_proof: Option<&Self::PartialProof>,
320        exposed_values_per_air_per_phase: &[Vec<Vec<Challenge>>],
321        commitments_per_phase: &[Commitment],
322        // per commitment, per matrix, per rotation, per column
323        after_challenge_opened_values: &[Vec<Vec<Vec<Challenge>>>],
324    ) -> (RapPhaseVerifierData<Challenge>, Result<(), Self::Error>)
325    where
326        Challenger: CanObserve<Commitment>;
327}
328
329type PairTraceView<'a, F> = PairView<Arc<RowMajorMatrix<F>>, F>;
330
331/// Parameters to ensure sufficient soundness of the LogUp part of the protocol.
332#[derive(Clone, Debug, Serialize, Deserialize)]
333#[repr(C)]
334pub struct LogUpSecurityParameters {
335    /// A bound on the total number of interactions.
336    /// Determines a constraint at keygen that is checked by the verifier.
337    pub max_interaction_count: u32,
338    /// A bound on the base-2 logarithm of the length of the longest interaction. Checked in
339    /// keygen.
340    pub log_max_message_length: u32,
341    /// The number of proof-of-work bits for the LogUp proof-of-work phase.
342    pub log_up_pow_bits: usize,
343}
344
345impl LogUpSecurityParameters {
346    /// The number of conjectured bits of security.
347    pub fn conjectured_bits_of_security<F: Field>(&self) -> u32 {
348        // See Section 4 of [docs/Soundness_of_Interactions_via_LogUp.pdf].
349        let log_order = u32::try_from(F::order().bits() - 1).unwrap();
350        log_order
351            - log2_ceil_usize(2 * self.max_interaction_count as usize) as u32  // multiply by two to account for the poles as well
352            - self.log_max_message_length
353            + u32::try_from(self.log_up_pow_bits).unwrap()
354    }
355    pub fn max_message_length(&self) -> usize {
356        2usize
357            .checked_pow(self.log_max_message_length)
358            .expect("max_message_length overflowed usize")
359    }
360}