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}