openvm_stark_backend/interaction/
mod.rs

1use std::fmt::Debug;
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 [docs/Soundess_of_Interactions_via_LogUp.pdf].
108        builder.push_interaction(self.index, query, enabled, 1);
109    }
110
111    /// Adds a key to the lookup table.
112    ///
113    /// The `num_lookups` parameter should equal the number of enabled lookups performed.
114    pub fn add_key_with_lookups<AB, E>(
115        &self,
116        builder: &mut AB,
117        key: impl IntoIterator<Item = E>,
118        num_lookups: impl Into<AB::Expr>,
119    ) where
120        AB: InteractionBuilder,
121        E: Into<AB::Expr>,
122    {
123        // Since we only want a subset constraint, `count_weight` can be zero here. See the comment
124        // in `LookupBus::lookup_key`.
125        builder.push_interaction(self.index, key, -num_lookups.into(), 0);
126    }
127}
128
129/// A `PermutationCheckBus` bus is used to establish that two multi-sets of values are equal.
130///
131/// Soundness requires that both the total number of messages sent and received over the bus per
132/// message is at most the field characteristic.
133#[derive(Copy, Clone, Debug, PartialEq, Eq)]
134pub struct PermutationCheckBus {
135    pub index: BusIndex,
136}
137
138#[derive(Copy, Clone, Debug, PartialEq, Eq)]
139pub enum PermutationInteractionType {
140    Send,
141    Receive,
142}
143
144impl PermutationCheckBus {
145    pub const fn new(index: BusIndex) -> Self {
146        Self { index }
147    }
148
149    /// Send a message.
150    ///
151    /// Caller must constrain `enabled` to be boolean.
152    pub fn send<AB, E>(
153        &self,
154        builder: &mut AB,
155        message: impl IntoIterator<Item = E>,
156        enabled: impl Into<AB::Expr>,
157    ) where
158        AB: InteractionBuilder,
159        E: Into<AB::Expr>,
160    {
161        // We embed the multiplicity `enabled` as an integer {0, 1}.
162        builder.push_interaction(self.index, message, enabled, 1);
163    }
164
165    /// Receive a message.
166    ///
167    /// Caller must constrain `enabled` to be boolean.
168    pub fn receive<AB, E>(
169        &self,
170        builder: &mut AB,
171        message: impl IntoIterator<Item = E>,
172        enabled: impl Into<AB::Expr>,
173    ) where
174        AB: InteractionBuilder,
175        E: Into<AB::Expr>,
176    {
177        // We embed the multiplicity `enabled` as an integer {0, -1}.
178        builder.push_interaction(self.index, message, -enabled.into(), 1);
179    }
180
181    /// Send or receive determined by `interaction_type`.
182    ///
183    /// Caller must constrain `enabled` to be boolean.
184    pub fn send_or_receive<AB, E>(
185        &self,
186        builder: &mut AB,
187        interaction_type: PermutationInteractionType,
188        message: impl IntoIterator<Item = E>,
189        enabled: impl Into<AB::Expr>,
190    ) where
191        AB: InteractionBuilder,
192        E: Into<AB::Expr>,
193    {
194        match interaction_type {
195            PermutationInteractionType::Send => self.send(builder, message, enabled),
196            PermutationInteractionType::Receive => self.receive(builder, message, enabled),
197        }
198    }
199
200    /// Send or receive a message determined by the expression `direction`.
201    ///
202    /// Direction = 1 means send, direction = -1 means receive, and direction = 0 means disabled.
203    ///
204    /// Caller must constrain that direction is in {-1, 0, 1}.
205    pub fn interact<AB, E>(
206        &self,
207        builder: &mut AB,
208        message: impl IntoIterator<Item = E>,
209        direction: impl Into<AB::Expr>,
210    ) where
211        AB: InteractionBuilder,
212        E: Into<AB::Expr>,
213    {
214        // We embed the multiplicity `direction` as an integer {-1, 0, 1}.
215        builder.push_interaction(self.index, message, direction.into(), 1);
216    }
217}
218
219pub struct RapPhaseProverData<Challenge> {
220    /// Challenges from the challenger in this phase that determine RAP constraints and exposed values.
221    pub challenges: Vec<Challenge>,
222
223    /// After challenge trace per air computed as a function of `challenges`.
224    pub after_challenge_trace_per_air: Vec<Option<RowMajorMatrix<Challenge>>>,
225
226    /// Public values of the phase that are functions of `challenges`.
227    pub exposed_values_per_air: Vec<Option<Vec<Challenge>>>,
228}
229
230#[derive(Default)]
231pub struct RapPhaseVerifierData<Challenge> {
232    /// Challenges from the challenger in this phase that determine RAP constraints and exposed values.
233    pub challenges_per_phase: Vec<Vec<Challenge>>,
234}
235
236#[derive(Debug)]
237pub struct RapPhaseShape {
238    pub num_challenges: usize,
239
240    pub num_exposed_values: usize,
241
242    /// Any additional rotations to open at in the permutation PCS round.
243    ///
244    /// Specifies that each `i` in `extra_opening_rots` should be opened at
245    /// `zeta * g^i` (in addition to `zeta` and `zeta * g`).
246    pub extra_opening_rots: Vec<usize>,
247}
248
249/// Supported challenge phases in a RAP.
250#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
251#[repr(u8)]
252pub enum RapPhaseSeqKind {
253    // GkrLogUp,
254    /// Up to one phase with prover/verifier given by [[fri_log_up::FriLogUpPhase]] and
255    /// constraints given by [[fri_log_up::eval_fri_log_up_phase]].
256    FriLogUp,
257}
258
259impl RapPhaseSeqKind {
260    pub fn shape(&self) -> Vec<RapPhaseShape> {
261        match self {
262            RapPhaseSeqKind::FriLogUp => vec![RapPhaseShape {
263                num_challenges: STARK_LU_NUM_CHALLENGES,
264                num_exposed_values: STARK_LU_NUM_EXPOSED_VALUES,
265                extra_opening_rots: vec![],
266            }],
267        }
268    }
269}
270
271/// Defines a particular protocol for the "after challenge" phase in a RAP.
272///
273/// A [RapPhaseSeq] is defined by the proving and verifying methods implemented in this trait,
274/// as well as via some "eval" method that is determined by `RapPhaseId`.
275pub trait RapPhaseSeq<F, Challenge, Challenger> {
276    type PartialProof: Clone + Serialize + DeserializeOwned;
277    /// Preprocessed data necessary for the RAP partial proving
278    type PartialProvingKey: Clone + Serialize + DeserializeOwned;
279    type Error: Debug;
280
281    const ID: RapPhaseSeqKind;
282
283    fn log_up_security_params(&self) -> &LogUpSecurityParameters;
284
285    /// The protocol parameters for the challenge phases may depend on the AIR constraints.
286    fn generate_pk_per_air(
287        &self,
288        symbolic_constraints_per_air: &[SymbolicConstraints<F>],
289        max_constraint_degree: usize,
290    ) -> Vec<Self::PartialProvingKey>;
291
292    /// Partially prove the challenge phases,
293    ///
294    /// Samples challenges, generates after challenge traces and exposed values, and proves any
295    /// extra-STARK part of the protocol.
296    ///
297    /// "Partial" refers to the fact that some STARK parts of the protocol---namely, the constraints
298    /// on the after challenge traces returned in `RapPhaseProverData`---are handled external to
299    /// this function.
300    fn partially_prove(
301        &self,
302        challenger: &mut Challenger,
303        constraints_per_air: &[&SymbolicConstraints<F>],
304        params_per_air: &[&Self::PartialProvingKey],
305        trace_view_per_air: &[PairTraceView<F>],
306    ) -> Option<(Self::PartialProof, RapPhaseProverData<Challenge>)>;
307
308    /// Partially verifies the challenge phases.
309    ///
310    /// Assumes the shape of `exposed_values_per_air_per_phase` is verified externally.
311    ///
312    /// An implementation of this function must sample challenges for the challenge phases and then
313    /// observe the exposed values and commitment.
314    fn partially_verify<Commitment: Clone>(
315        &self,
316        challenger: &mut Challenger,
317        partial_proof: Option<&Self::PartialProof>,
318        exposed_values_per_air_per_phase: &[Vec<Vec<Challenge>>],
319        commitments_per_phase: &[Commitment],
320        // per commitment, per matrix, per rotation, per column
321        after_challenge_opened_values: &[Vec<Vec<Vec<Challenge>>>],
322    ) -> (RapPhaseVerifierData<Challenge>, Result<(), Self::Error>)
323    where
324        Challenger: CanObserve<Commitment>;
325}
326
327type PairTraceView<'a, F> = PairView<&'a RowMajorMatrix<F>, F>;
328
329/// Parameters to ensure sufficient soundness of the LogUp part of the protocol.
330#[derive(Clone, Debug, Serialize, Deserialize)]
331#[repr(C)]
332pub struct LogUpSecurityParameters {
333    /// A bound on the total number of interactions.
334    /// Determines a constraint at keygen that is checked by the verifier.
335    pub max_interaction_count: u32,
336    /// A bound on the base-2 logarithm of the length of the longest interaction. Checked in keygen.
337    pub log_max_message_length: u32,
338    /// The number of proof-of-work bits for the LogUp proof-of-work phase.
339    pub log_up_pow_bits: usize,
340}
341
342impl LogUpSecurityParameters {
343    /// The number of conjectured bits of security.
344    pub fn conjectured_bits_of_security<F: Field>(&self) -> u32 {
345        // See Section 4 of [docs/Soundness_of_Interactions_via_LogUp.pdf].
346        let log_order = u32::try_from(F::order().bits() - 1).unwrap();
347        log_order
348            - log2_ceil_usize(self.max_interaction_count as usize) as u32
349            - self.log_max_message_length
350            + u32::try_from(self.log_up_pow_bits).unwrap()
351    }
352    pub fn max_message_length(&self) -> usize {
353        2usize
354            .checked_pow(self.log_max_message_length)
355            .expect("max_message_length overflowed usize")
356    }
357}