Skip to main content

binius_iop/channel/
naive.rs

1// Copyright 2026 The Binius Developers
2
3//! Naive [`IOPVerifierChannel`] for testing: reads full polynomials instead of verifying FRI.
4
5use binius_field::Field;
6use binius_ip::channel::IPVerifierChannel;
7use binius_math::{
8	FieldBuffer, inner_product::inner_product_buffers, multilinear::evaluate::evaluate_inplace,
9};
10use binius_transcript::{
11	VerifierTranscript,
12	fiat_shamir::{CanSample, Challenger},
13};
14
15use crate::channel::{Error, IOPVerifierChannel, OracleSpec, TransparentEvalFn};
16
17/// Oracle handle returned by [`NaiveVerifierChannel::recv_oracle`].
18#[derive(Debug, Clone, Copy)]
19pub struct NaiveOracle {
20	index: usize,
21}
22
23/// A naive verifier channel that reads full polynomial data from the transcript.
24///
25/// This channel wraps a [`VerifierTranscript`] and provides oracle operations by reading
26/// the entire polynomial coefficients from the transcript. This is useful for testing
27/// protocols without the complexity of FRI/BaseFold.
28///
29/// # Type Parameters
30///
31/// - `F`: The field type
32/// - `Challenger_`: The Fiat-Shamir challenger
33pub struct NaiveVerifierChannel<'a, F, Challenger_>
34where
35	F: Field,
36	Challenger_: Challenger,
37{
38	/// Verifier transcript for Fiat-Shamir (borrowed).
39	transcript: &'a mut VerifierTranscript<Challenger_>,
40	/// Oracle specifications (borrowed).
41	oracle_specs: &'a [OracleSpec],
42	/// Stored polynomial buffers read from the transcript.
43	/// For ZK oracles, this stores the combined polynomial (witness || mask).
44	/// For non-ZK oracles, this stores the full polynomial.
45	stored_polynomials: Vec<FieldBuffer<F>>,
46	/// Next oracle index.
47	next_oracle_index: usize,
48}
49
50impl<'a, F, Challenger_> NaiveVerifierChannel<'a, F, Challenger_>
51where
52	F: Field,
53	Challenger_: Challenger,
54{
55	/// Creates a new naive verifier channel.
56	///
57	/// # Arguments
58	///
59	/// * `transcript` - The verifier transcript for Fiat-Shamir (borrowed mutably)
60	/// * `oracle_specs` - Specifications for each oracle to be received (borrowed)
61	pub const fn new(
62		transcript: &'a mut VerifierTranscript<Challenger_>,
63		oracle_specs: &'a [OracleSpec],
64	) -> Self {
65		Self {
66			transcript,
67			oracle_specs,
68			stored_polynomials: Vec::new(),
69			next_oracle_index: 0,
70		}
71	}
72
73	/// Returns a reference to the underlying transcript.
74	pub const fn transcript(&self) -> &VerifierTranscript<Challenger_> {
75		self.transcript
76	}
77
78	/// Consumes the channel, asserting all oracle specs have been consumed.
79	pub fn finish(self) {
80		let n_remaining = self.oracle_specs.len() - self.next_oracle_index;
81		assert!(n_remaining == 0, "finish called but {n_remaining} oracle specs remaining",);
82	}
83}
84
85impl<F, Challenger_> IPVerifierChannel<F> for NaiveVerifierChannel<'_, F, Challenger_>
86where
87	F: Field,
88	Challenger_: Challenger,
89{
90	type Elem = F;
91
92	fn recv_one(&mut self) -> Result<F, binius_ip::channel::Error> {
93		self.transcript
94			.message()
95			.read_scalar()
96			.map_err(|_| binius_ip::channel::Error::ProofEmpty)
97	}
98
99	fn recv_many(&mut self, n: usize) -> Result<Vec<F>, binius_ip::channel::Error> {
100		self.transcript
101			.message()
102			.read_scalar_slice(n)
103			.map_err(|_| binius_ip::channel::Error::ProofEmpty)
104	}
105
106	fn recv_array<const N: usize>(&mut self) -> Result<[F; N], binius_ip::channel::Error> {
107		self.transcript
108			.message()
109			.read()
110			.map_err(|_| binius_ip::channel::Error::ProofEmpty)
111	}
112
113	fn sample(&mut self) -> F {
114		CanSample::sample(&mut self.transcript)
115	}
116
117	fn observe_one(&mut self, val: F) -> F {
118		self.transcript.observe().write_scalar(val);
119		val
120	}
121
122	fn observe_many(&mut self, vals: &[F]) -> Vec<F> {
123		self.transcript.observe().write_scalar_slice(vals);
124		vals.to_vec()
125	}
126
127	fn assert_zero(&mut self, val: F) -> Result<(), binius_ip::channel::Error> {
128		if val == F::ZERO {
129			Ok(())
130		} else {
131			Err(binius_ip::channel::Error::InvalidAssert)
132		}
133	}
134}
135
136impl<F, Challenger_> IOPVerifierChannel<F> for NaiveVerifierChannel<'_, F, Challenger_>
137where
138	F: Field,
139	Challenger_: Challenger,
140{
141	type Oracle = NaiveOracle;
142
143	fn remaining_oracle_specs(&self) -> &[OracleSpec] {
144		&self.oracle_specs[self.next_oracle_index..]
145	}
146
147	fn recv_oracle(
148		&mut self,
149		log_msg_len: usize,
150		_is_witness_dependent: bool,
151	) -> Result<Self::Oracle, Error> {
152		assert!(
153			!self.remaining_oracle_specs().is_empty(),
154			"recv_oracle called but no remaining oracle specs"
155		);
156
157		let index = self.next_oracle_index;
158		debug_assert_eq!(log_msg_len, self.oracle_specs[index].log_msg_len);
159
160		let buffer_len = 1 << log_msg_len;
161
162		// Read all polynomial coefficients from the transcript
163		let values = self
164			.transcript
165			.message()
166			.read_scalar_slice(buffer_len)
167			.map_err(|_| Error::ProofEmpty)?;
168
169		self.stored_polynomials
170			.push(FieldBuffer::from_values(&values));
171		self.next_oracle_index += 1;
172
173		Ok(NaiveOracle { index })
174	}
175
176	fn verify_oracle_relation(
177		&mut self,
178		oracle: Self::Oracle,
179		transparent: TransparentEvalFn<F>,
180		claim: F,
181	) -> Result<(), Error> {
182		let index = oracle.index;
183		assert!(index < self.stored_polynomials.len(), "oracle index {index} out of bounds");
184
185		// Extract spec data before mutable borrow of transcript
186		let log_msg_len = self.oracle_specs[index].log_msg_len;
187
188		// Read the transparent polynomial from the transcript (prover wrote it in
189		// prove_oracle_relation)
190		let transparent_len = 1 << log_msg_len;
191		let transparent_values = self
192			.transcript
193			.message()
194			.read_scalar_slice(transparent_len)
195			.map_err(|_| Error::ProofEmpty)?;
196		let transparent_poly = FieldBuffer::from_values(&transparent_values);
197
198		// Verify the inner product claim directly
199		let stored_poly = &self.stored_polynomials[index];
200		let witness_poly = stored_poly.as_view();
201		let actual_inner_product: F = inner_product_buffers(&witness_poly, &transparent_poly);
202
203		assert_eq!(
204			actual_inner_product, claim,
205			"NaiveVerifierChannel: inner product verification failed"
206		);
207
208		// Sample evaluation point challenges (same as prover sampled)
209		let point: Vec<F> = CanSample::sample_vec(&mut self.transcript, log_msg_len);
210
211		// Evaluate the transparent closure at the challenge point
212		let transparent_eval = transparent(&point);
213
214		// Verify the transparent evaluation matches using assert_zero
215		self.assert_zero(transparent_eval - evaluate_inplace(transparent_poly, &point))?;
216
217		Ok(())
218	}
219}