Skip to main content

binius_iop_prover/fri/
query.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use std::ops::Deref;
5
6use binius_field::{Field, PackedField};
7use binius_math::FieldBuffer;
8use tracing::instrument;
9
10use crate::merkle_channel::MerkleIPProverChannel;
11
12/// The prover counterpart of a `ProxTestOracle` (verifier side), producing the per-oracle query
13/// openings.
14pub trait ProxTestOracleProver<F: Field> {
15	/// The Merkle commitment handle for the committed oracle.
16	type Commitment;
17
18	/// Sends the per-oracle batched query openings: the oracle's optimal Merkle layer once,
19	/// followed by each queried coset's values and Merkle opening proof.
20	fn open_queries<Channel>(&self, indices: &[Channel::Word], channel: &mut Channel)
21	where
22		Channel: MerkleIPProverChannel<F, Commitment = Self::Commitment>;
23}
24
25/// The [`ProxTestOracleProver`] for a [Brakedown]-style interleaved code proximity check.
26///
27/// [Brakedown]: <https://dl.acm.org/doi/10.1007/978-3-031-38545-2_7>
28pub struct BrakedownOracleProver<P, C, Data = Vec<P>>
29where
30	P: PackedField,
31	Data: Deref<Target = [P]>,
32{
33	codeword: FieldBuffer<P, Data>,
34	commitment: C,
35	/// log2 the lift factor (oracle padding). The committed codeword is virtually duplicated
36	/// `2^log_lift` times to reach the common first-round length; a query at global index `k`
37	/// opens the committed codeword at `k >> log_lift`. Zero when no lifting is needed.
38	log_lift: usize,
39}
40
41impl<P, C, Data> BrakedownOracleProver<P, C, Data>
42where
43	P: PackedField,
44	Data: Deref<Target = [P]>,
45{
46	/// Constructs a new oracle prover wrapping a committed interleaved codeword.
47	///
48	/// `log_lift` is the oracle-padding lift factor (the committed codeword is virtually duplicated
49	/// `2^log_lift` times to reach the common first-round length); pass `0` when no lifting is
50	/// needed.
51	pub const fn new(codeword: FieldBuffer<P, Data>, commitment: C, log_lift: usize) -> Self {
52		Self {
53			codeword,
54			commitment,
55			log_lift,
56		}
57	}
58}
59
60impl<F, P, C, Data> ProxTestOracleProver<F> for BrakedownOracleProver<P, C, Data>
61where
62	F: Field,
63	P: PackedField<Scalar = F>,
64	Data: Deref<Target = [P]>,
65{
66	type Commitment = C;
67
68	fn open_queries<Channel>(&self, indices: &[Channel::Word], channel: &mut Channel)
69	where
70		Channel: MerkleIPProverChannel<F, Commitment = Self::Commitment>,
71	{
72		// When the oracle is lifted (`log_lift > 0`), each global query index is translated to the
73		// committed codeword by dropping its low `log_lift` bits (the duplicated copies), mirroring
74		// the verifier.
75		let lifted_indices = indices
76			.iter()
77			.map(|index| index.clone() >> self.log_lift as u32)
78			.collect::<Vec<_>>();
79		channel.send_openings(&self.commitment, self.codeword.as_view(), &lifted_indices);
80	}
81}
82
83/// A [`ProxTestOracleProver`] bundling several separately committed [`BrakedownOracleProver`]s.
84///
85/// The bundled oracles all wrap interleaved codewords of the same length that are batched into a
86/// single folded codeword during the first FRI fold. Their query openings are written sequentially,
87/// one oracle's full decommitment after another, so the verifier reads each committed oracle's
88/// advice in turn.
89pub struct BatchBrakedownOracleProver<P, C, Data = Vec<P>>
90where
91	P: PackedField,
92	Data: Deref<Target = [P]>,
93{
94	oracles: Vec<BrakedownOracleProver<P, C, Data>>,
95}
96
97impl<P, C, Data> BatchBrakedownOracleProver<P, C, Data>
98where
99	P: PackedField,
100	Data: Deref<Target = [P]>,
101{
102	/// Constructs a batch oracle prover from the per-commitment oracle provers.
103	pub const fn new(oracles: Vec<BrakedownOracleProver<P, C, Data>>) -> Self {
104		Self { oracles }
105	}
106}
107
108impl<F, P, C, Data> ProxTestOracleProver<F> for BatchBrakedownOracleProver<P, C, Data>
109where
110	F: Field,
111	P: PackedField<Scalar = F>,
112	Data: Deref<Target = [P]>,
113{
114	type Commitment = C;
115
116	fn open_queries<Channel>(&self, indices: &[Channel::Word], channel: &mut Channel)
117	where
118		Channel: MerkleIPProverChannel<F, Commitment = Self::Commitment>,
119	{
120		for oracle in &self.oracles {
121			oracle.open_queries(indices, channel);
122		}
123	}
124}
125
126/// The [`ProxTestOracleProver`] for a FRI-style code proximity check.
127pub struct FRIOracleProver<F, C>
128where
129	F: Field,
130{
131	codeword: FieldBuffer<F>,
132	commitment: C,
133	/// The base-2 log of the size of each coset opened from the committed oracle: the arity of
134	/// the fold that consumes this codeword, which is also the commitment's leaf size.
135	coset_log_size: usize,
136}
137
138impl<F, C> FRIOracleProver<F, C>
139where
140	F: Field,
141{
142	/// Constructs a new oracle prover wrapping a committed fold-round codeword.
143	///
144	/// `coset_log_size` is the base-2 log of the coset size of the fold that consumes this
145	/// codeword, which must equal the commitment's leaf size.
146	pub const fn new(codeword: FieldBuffer<F>, commitment: C, coset_log_size: usize) -> Self {
147		Self {
148			codeword,
149			commitment,
150			coset_log_size,
151		}
152	}
153
154	/// The base-2 log of the size of each coset opened from the committed oracle.
155	const fn coset_log_size(&self) -> usize {
156		self.coset_log_size
157	}
158}
159
160impl<F, C> ProxTestOracleProver<F> for FRIOracleProver<F, C>
161where
162	F: Field,
163{
164	type Commitment = C;
165
166	fn open_queries<Channel>(&self, indices: &[Channel::Word], channel: &mut Channel)
167	where
168		Channel: MerkleIPProverChannel<F, Commitment = Self::Commitment>,
169	{
170		channel.send_openings(&self.commitment, self.codeword.as_view(), indices);
171	}
172}
173
174/// A prover for the FRI query phase.
175///
176/// This is a composition of [`ProxTestOracleProver`]s mirroring the verifier's `FRIQueryVerifier`:
177/// a [`BatchBrakedownOracleProver`] for the codeword's interleaved reduction, then one
178/// [`FRIOracleProver`] per fold arity for the subsequent reductions.
179pub struct FRIQueryProver<F, P, C, Data = Vec<P>>
180where
181	F: Field,
182	P: PackedField<Scalar = F>,
183	Data: Deref<Target = [P]>,
184{
185	codeword_oracle: BatchBrakedownOracleProver<P, C, Data>,
186	fri_oracles: Vec<FRIOracleProver<F, C>>,
187}
188
189impl<F, P, C, Data> FRIQueryProver<F, P, C, Data>
190where
191	F: Field,
192	P: PackedField<Scalar = F>,
193	Data: Deref<Target = [P]>,
194{
195	/// Constructs a query prover from the per-oracle provers built during the fold phase.
196	///
197	/// `codeword_oracle` is the [`BatchBrakedownOracleProver`] for the originally committed
198	/// interleaved codeword(s), and `fri_oracles` holds one [`FRIOracleProver`] per fold arity. The
199	/// terminal codeword is sent in full and therefore is not represented here.
200	pub const fn new(
201		codeword_oracle: BatchBrakedownOracleProver<P, C, Data>,
202		fri_oracles: Vec<FRIOracleProver<F, C>>,
203	) -> Self {
204		Self {
205			codeword_oracle,
206			fri_oracles,
207		}
208	}
209
210	/// Number of oracles sent during the fold rounds.
211	pub const fn n_oracles(&self) -> usize {
212		1 + self.fri_oracles.len()
213	}
214
215	/// Proves the FRI challenge queries, batched per oracle.
216	///
217	/// For each committed oracle (the codeword first, then each fold-round oracle excluding the
218	/// terminal codeword) this sends the oracle's optimal Merkle layer once, followed by the coset
219	/// opening for each query index. This per-oracle batched layout matches the verifier, which
220	/// receives each oracle's layer and then all of its query openings together.
221	///
222	/// ## Arguments
223	///
224	/// * `indices` - the sampled query indices into the original codeword domain
225	#[instrument(skip_all, name = "fri::FRIQueryProver::prove_queries", level = "debug")]
226	pub fn prove_queries<Channel>(&self, indices: &[Channel::Word], channel: &mut Channel)
227	where
228		Channel: MerkleIPProverChannel<F, Commitment = C>,
229	{
230		self.codeword_oracle.open_queries(indices, channel);
231
232		// Each subsequent oracle indexes the previous virtual oracle, so shift the query indices
233		// right by the round's arity before opening it.
234		let mut indices = indices.to_vec();
235		for fri_oracle in &self.fri_oracles {
236			indices = indices
237				.into_iter()
238				.map(|index| index >> fri_oracle.coset_log_size() as u32)
239				.collect();
240			fri_oracle.open_queries(&indices, channel);
241		}
242	}
243}