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}