Skip to main content

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}