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