openvm_stark_backend/interaction/mod.rs
1use std::fmt::Debug;
2
3use p3_air::AirBuilder;
4use p3_field::{Field, PrimeCharacteristicRing};
5use serde::{Deserialize, Serialize};
6
7use crate::air_builders::symbolic::symbolic_expression::SymbolicExpression;
8
9/// Interaction debugging tools
10pub mod debug;
11
12// Must be a type smaller than u32 to make BusIndex p - 1 unrepresentable.
13pub type BusIndex = u16;
14
15#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
16pub struct Interaction<Expr> {
17 pub message: Vec<Expr>,
18 pub count: Expr,
19 /// The bus index specifying the bus to send the message over. All valid instantiations of
20 /// `BusIndex` are safe.
21 pub bus_index: BusIndex,
22 /// Determines the contribution of each interaction message to a linear constraint on the trace
23 /// heights in the verifier.
24 ///
25 /// For each bus index and trace, `count_weight` values are summed per interaction on that
26 /// bus index and multiplied by the trace height. The total sum over all traces is constrained
27 /// by the verifier to not overflow the field characteristic \( p \).
28 ///
29 /// This is used to impose sufficient conditions for bus constraint soundness and setting a
30 /// proper value depends on the bus and the constraint it imposes.
31 pub count_weight: u32,
32}
33
34pub type SymbolicInteraction<F> = Interaction<SymbolicExpression<F>>;
35
36/// An [AirBuilder] with additional functionality to build special logUp arguments for
37/// communication between AIRs across buses. These arguments use randomness to
38/// add additional trace columns (in the extension field) and constraints to the AIR.
39///
40/// An interactive AIR is a AIR that can specify buses for sending and receiving data
41/// to other AIRs. The original AIR is augmented by virtual columns determined by
42/// the interactions to define a [RAP](crate::rap::Rap).
43pub trait InteractionBuilder: AirBuilder<F: Field, Var: Copy> {
44 /// Stores a new interaction in the builder.
45 ///
46 /// See [Interaction] for more details on `count_weight`.
47 fn push_interaction<E: Into<Self::Expr>>(
48 &mut self,
49 bus_index: BusIndex,
50 fields: impl IntoIterator<Item = E>,
51 count: impl Into<Self::Expr>,
52 count_weight: u32,
53 );
54
55 /// Returns the current number of interactions.
56 fn num_interactions(&self) -> usize;
57
58 /// Returns all interactions stored.
59 fn all_interactions(&self) -> &[Interaction<Self::Expr>];
60
61 // This used to be in Plonky3 but is no longer there. We preserve the
62 // implementation here for downstream callers.
63 fn assert_tern(&mut self, x: impl Into<Self::Expr>) {
64 let x = x.into();
65 self.assert_zero(x.clone() * (x.clone() - Self::Expr::ONE) * (x - Self::Expr::TWO));
66 }
67}
68
69/// A `Lookup` bus is used to establish that one multiset of values (the queries) are subset of
70/// another multiset of values (the keys).
71///
72/// Soundness requires that the total number of queries sent over the bus per message is at most the
73/// field characteristic.
74#[derive(Copy, Clone, Debug, PartialEq, Eq)]
75pub struct LookupBus {
76 pub index: BusIndex,
77}
78
79impl LookupBus {
80 pub const fn new(index: BusIndex) -> Self {
81 Self { index }
82 }
83
84 /// Performs a lookup on the given bus.
85 ///
86 /// This method asserts that `key` is present in the lookup table. The parameter `enabled`
87 /// must be constrained to be boolean, and the lookup constraint is imposed provided `enabled`
88 /// is one.
89 ///
90 /// Caller must constrain that `enabled` is boolean.
91 pub fn lookup_key<AB, E>(
92 &self,
93 builder: &mut AB,
94 query: impl IntoIterator<Item = E>,
95 enabled: impl Into<AB::Expr>,
96 ) where
97 AB: InteractionBuilder,
98 E: Into<AB::Expr>,
99 {
100 // We embed the query multiplicity as {0, 1} in the integers and the lookup table key
101 // multiplicity to be {0, -1, ..., -p + 1}. Setting `count_weight = 1` will ensure that the
102 // total number of lookups is at most p, which is sufficient to establish lookup multiset is
103 // a subset of the key multiset. See Corollary 3.6 in
104 // [docs/Soundess_of_Interactions_via_LogUp.pdf].
105 builder.push_interaction(self.index, query, enabled, 1);
106 }
107
108 /// Adds a key to the lookup table.
109 ///
110 /// The `num_lookups` parameter should equal the number of enabled lookups performed.
111 pub fn add_key_with_lookups<AB, E>(
112 &self,
113 builder: &mut AB,
114 key: impl IntoIterator<Item = E>,
115 num_lookups: impl Into<AB::Expr>,
116 ) where
117 AB: InteractionBuilder,
118 E: Into<AB::Expr>,
119 {
120 // Since we only want a subset constraint, `count_weight` can be zero here. See the comment
121 // in `LookupBus::lookup_key`.
122 builder.push_interaction(self.index, key, -num_lookups.into(), 0);
123 }
124}
125
126/// A `PermutationCheckBus` bus is used to establish that two multi-sets of values are equal.
127///
128/// Soundness requires that both the total number of messages sent and received over the bus per
129/// message is at most the field characteristic.
130#[derive(Copy, Clone, Debug, PartialEq, Eq)]
131pub struct PermutationCheckBus {
132 pub index: BusIndex,
133}
134
135#[derive(Copy, Clone, Debug, PartialEq, Eq)]
136pub enum PermutationInteractionType {
137 Send,
138 Receive,
139}
140
141impl PermutationCheckBus {
142 pub const fn new(index: BusIndex) -> Self {
143 Self { index }
144 }
145
146 /// Send a message.
147 ///
148 /// Caller must constrain `enabled` to be boolean.
149 pub fn send<AB, E>(
150 &self,
151 builder: &mut AB,
152 message: impl IntoIterator<Item = E>,
153 enabled: impl Into<AB::Expr>,
154 ) where
155 AB: InteractionBuilder,
156 E: Into<AB::Expr>,
157 {
158 // We embed the multiplicity `enabled` as an integer {0, 1}.
159 builder.push_interaction(self.index, message, enabled, 1);
160 }
161
162 /// Receive a message.
163 ///
164 /// Caller must constrain `enabled` to be boolean.
165 pub fn receive<AB, E>(
166 &self,
167 builder: &mut AB,
168 message: impl IntoIterator<Item = E>,
169 enabled: impl Into<AB::Expr>,
170 ) where
171 AB: InteractionBuilder,
172 E: Into<AB::Expr>,
173 {
174 // We embed the multiplicity `enabled` as an integer {0, -1}.
175 builder.push_interaction(self.index, message, -enabled.into(), 1);
176 }
177
178 /// Send or receive determined by `interaction_type`.
179 ///
180 /// Caller must constrain `enabled` to be boolean.
181 pub fn send_or_receive<AB, E>(
182 &self,
183 builder: &mut AB,
184 interaction_type: PermutationInteractionType,
185 message: impl IntoIterator<Item = E>,
186 enabled: impl Into<AB::Expr>,
187 ) where
188 AB: InteractionBuilder,
189 E: Into<AB::Expr>,
190 {
191 match interaction_type {
192 PermutationInteractionType::Send => self.send(builder, message, enabled),
193 PermutationInteractionType::Receive => self.receive(builder, message, enabled),
194 }
195 }
196
197 /// Send or receive a message determined by the expression `direction`.
198 ///
199 /// Direction = 1 means send, direction = -1 means receive, and direction = 0 means disabled.
200 ///
201 /// Caller must constrain that direction is in {-1, 0, 1}.
202 pub fn interact<AB, E>(
203 &self,
204 builder: &mut AB,
205 message: impl IntoIterator<Item = E>,
206 direction: impl Into<AB::Expr>,
207 ) where
208 AB: InteractionBuilder,
209 E: Into<AB::Expr>,
210 {
211 // We embed the multiplicity `direction` as an integer {-1, 0, 1}.
212 builder.push_interaction(self.index, message, direction.into(), 1);
213 }
214}
215
216/// Parameters to ensure sufficient soundness of the LogUp part of the protocol.
217#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
218#[repr(C)]
219pub struct LogUpSecurityParameters {
220 /// A bound on the total number of interactions.
221 /// Determines a constraint at keygen that is checked by the verifier.
222 pub max_interaction_count: u32,
223 /// A bound on the base-2 logarithm of the length of the longest interaction. Checked in
224 /// keygen.
225 pub log_max_message_length: u32,
226 /// The number of proof-of-work bits for the LogUp proof-of-work phase.
227 pub pow_bits: usize,
228}
229
230impl LogUpSecurityParameters {
231 pub fn max_message_length(&self) -> usize {
232 2usize
233 .checked_pow(self.log_max_message_length)
234 .expect("max_message_length overflowed usize")
235 }
236}