Skip to main content

binius_iop/channel/
mod.rs

1// Copyright 2026 The Binius Developers
2
3//! Channel abstraction for interactive oracle protocol (IOP) verifiers.
4
5pub mod grinding;
6pub mod merge;
7pub mod naive;
8pub mod oracle_setup;
9pub mod size_tracking;
10
11use std::iter;
12
13use binius_field::Field;
14use binius_ip::channel::IPVerifierChannel;
15use binius_utils::checked_arithmetics::log2_ceil_usize;
16
17use crate::basefold;
18
19/// Error type for IOP verifier channel operations.
20#[derive(Debug, thiserror::Error)]
21pub enum Error {
22	#[error("proof is empty")]
23	ProofEmpty,
24	#[error("BaseFold verification failed: {0}")]
25	BaseFold(#[from] basefold::Error),
26	#[error("IP channel error: {0}")]
27	IPChannel(#[from] binius_ip::channel::Error),
28	#[error("sumcheck error: {0}")]
29	Sumcheck(#[from] binius_ip::sumcheck::Error),
30	#[error("Merkle channel error: {0}")]
31	Merkle(#[from] crate::merkle_channel::Error),
32}
33
34/// Specification for an oracle to be committed in the IOP.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct OracleSpec {
37	/// Log2 of the message length (number of field elements).
38	pub log_msg_len: usize,
39	/// Whether the oracle is committed with zero-knowledge (hiding) masking.
40	///
41	/// ZK oracles interleave the message with a fresh mask and are folded by a shared masking
42	/// challenge γ in the batched BaseFold opening; non-ZK oracles are committed without a mask.
43	pub is_zk: bool,
44}
45
46impl OracleSpec {
47	/// A non-ZK (unmasked) oracle of the given message length.
48	pub const fn new(log_msg_len: usize) -> Self {
49		Self {
50			log_msg_len,
51			is_zk: false,
52		}
53	}
54
55	/// A ZK (masked, hiding) oracle of the given message length.
56	pub const fn new_zk(log_msg_len: usize) -> Self {
57		Self {
58			log_msg_len,
59			is_zk: true,
60		}
61	}
62}
63
64/// The length of the one oracle a round's oracles are committed as.
65///
66/// The oracles lie end to end, so this is the smallest power of two covering their total.
67fn merged_log_msg_len(log_msg_lens: impl IntoIterator<Item = usize>) -> usize {
68	let total_len: usize = log_msg_lens.into_iter().map(|n| 1usize << n).sum();
69	log2_ceil_usize(total_len)
70}
71
72/// Every oracle an IOP commits, grouped into the rounds they are committed in.
73///
74/// A round is the run of oracles sent between two challenge samples.
75///
76/// A challenge can only be derived once the commitments before it are absorbed.
77///
78/// So a round closes the moment its challenge is drawn, and takes no further members.
79///
80/// ```text
81/// recv, recv, sample, recv, sample, recv, recv, recv
82/// \__________/        \__/          \______________/
83///    round 0         round 1           round 2
84/// ```
85///
86/// A flat spec list cannot say where those boundaries fall.
87///
88/// A caller that commits a whole round as one oracle needs them.
89#[derive(Debug, Default, Clone, PartialEq, Eq)]
90pub struct OracleSchedule {
91	/// Every oracle, in arrival order, across all rounds.
92	specs: Vec<OracleSpec>,
93
94	/// The exclusive end of each closed round, as an index into `specs`.
95	///
96	/// Round `r` spans `specs[ends[r - 1]..ends[r]]`, and round `0` starts at `0`.
97	///
98	/// Anything past the last entry is in the round still open.
99	ends: Vec<usize>,
100}
101
102impl OracleSchedule {
103	/// Creates an empty schedule.
104	pub const fn new() -> Self {
105		Self {
106			specs: Vec::new(),
107			ends: Vec::new(),
108		}
109	}
110
111	/// Appends one oracle to the round currently open.
112	pub fn push(&mut self, spec: OracleSpec) {
113		self.specs.push(spec);
114	}
115
116	/// Closes the round currently open, so later oracles start a new one.
117	///
118	/// Does nothing when no oracle has arrived since the last close.
119	///
120	/// Calling it wherever a challenge could be drawn is therefore always safe.
121	pub fn end_round(&mut self) {
122		let open_start = self.ends.last().copied().unwrap_or(0);
123		if self.specs.len() > open_start {
124			self.ends.push(self.specs.len());
125		}
126	}
127
128	/// Every oracle in the schedule, in arrival order, with round boundaries dropped.
129	pub fn specs(&self) -> &[OracleSpec] {
130		&self.specs
131	}
132
133	/// Consumes the schedule and returns every oracle, with round boundaries dropped.
134	pub fn into_specs(self) -> Vec<OracleSpec> {
135		self.specs
136	}
137
138	/// The number of closed rounds.
139	pub const fn n_rounds(&self) -> usize {
140		self.ends.len()
141	}
142
143	/// The oracles of each closed round, in commit order.
144	pub fn rounds(&self) -> impl Iterator<Item = &[OracleSpec]> {
145		let starts = iter::once(0).chain(self.ends.iter().copied());
146		iter::zip(starts, self.ends.iter().copied()).map(|(start, end)| &self.specs[start..end])
147	}
148
149	/// One spec per round: the oracle that round's oracles are committed as.
150	///
151	/// This is the coarser list an underlying channel is configured with.
152	///
153	/// A round is masked as a whole, so its oracle is zero-knowledge if any member is.
154	pub fn merged_specs(&self) -> Vec<OracleSpec> {
155		self.rounds()
156			.map(|round| OracleSpec {
157				log_msg_len: merged_log_msg_len(round.iter().map(|spec| spec.log_msg_len)),
158				is_zk: round.iter().any(|spec| spec.is_zk),
159			})
160			.collect()
161	}
162}
163
164/// A boxed closure that evaluates a transparent MLE at a given point.
165///
166/// The closure receives the challenge point sampled during the opening and returns the evaluation
167/// of the transparent polynomial's MLE there. It is `'static` and owns every value it reads,
168/// sharing large data via `Rc`/`Arc`, so a channel that defers the opening can store it and
169/// evaluate it later.
170pub type TransparentEvalFn<Elem> = Box<dyn Fn(&[Elem]) -> Elem + 'static>;
171
172/// Channel for IOP verifiers that extends the IP verifier channel with oracle operations.
173///
174/// In an IOP, the verifier can:
175/// 1. Receive field elements from the prover via `recv_*` methods (inherited)
176/// 2. Sample random challenges via `sample` (inherited)
177/// 3. Receive oracle commitments from the prover
178/// 4. Query oracles at specific positions and verify opening proofs
179///
180/// # Contract
181///
182/// The caller must call `recv_oracle()` exactly `remaining_oracle_specs().len()` times before
183/// calling `verify_oracle_relation()`. The oracles must be received in order and match their
184/// specifications.
185pub trait IOPVerifierChannel<F: Field>: IPVerifierChannel<F, Elem: 'static> {
186	type Oracle: Clone;
187
188	/// Returns the specifications for the remaining oracles to be received.
189	///
190	/// This slice shrinks as oracles are received via `recv_oracle()`.
191	fn remaining_oracle_specs(&self) -> &[OracleSpec];
192
193	/// Receives an oracle commitment from the prover.
194	///
195	/// The caller describes the oracle being received: `log_msg_len` is the log2 of the message
196	/// length, and `is_witness_dependent` is whether the oracle's contents depend on the witness.
197	/// These let a channel record the oracle's [`OracleSpec`] rather than requiring the specs to be
198	/// supplied up front. The resulting oracle is zero-knowledge iff the channel is configured for
199	/// ZK *and* the oracle is witness-dependent — a non-witness-dependent oracle (e.g. a
200	/// pre-indexed commitment to the wiring matrix for succinctness, a planned feature) is never
201	/// masked.
202	fn recv_oracle(
203		&mut self,
204		log_msg_len: usize,
205		is_witness_dependent: bool,
206	) -> Result<Self::Oracle, Error>;
207
208	/// Queues one oracle linear relation to be opened.
209	///
210	/// Implementations may either verify the relation immediately, or queue it and defer the
211	/// actual opening (masking + sumcheck + FRI) to `finish()`. Either way, the relation asserts
212	/// that `<oracle_poly, transparent> = claim`. An oracle may carry any number of relations.
213	///
214	/// # Preconditions
215	///
216	/// * `oracle` must be a valid handle returned by `recv_oracle()`.
217	fn verify_oracle_relation(
218		&mut self,
219		oracle: Self::Oracle,
220		transparent: TransparentEvalFn<Self::Elem>,
221		claim: Self::Elem,
222	) -> Result<(), Error>;
223}