Skip to main content

binius_iop/basefold/
compiler.rs

1// Copyright 2026 The Binius Developers
2
3//! BaseFold compiler for IOP verifiers.
4
5use std::borrow::BorrowMut;
6
7use binius_field::BinaryField;
8use binius_hash::HashSuite;
9use binius_transcript::{VerifierTranscript, fiat_shamir::Challenger};
10use binius_utils::{DeserializeBytes, FixedSizeSerializeBytes};
11use digest::Output;
12
13use crate::{
14	basefold::channel::BaseFoldVerifierChannel,
15	channel::OracleSpec,
16	fri::{AritySelectionStrategy, FRIParams},
17	merkle_channel::{MerkleIPVerifierChannel, VerifierMerkleTranscriptChannel},
18	merkle_tree::BinaryMerkleTreeScheme,
19};
20
21/// A compiler that creates BaseFold ZK verifier channels with precomputed parameters.
22///
23/// This compiler builds a single combined FRI over all oracles. ZK oracles configure FRI
24/// parameters for zero-knowledge mode (`log_msg_len + 1` as the message length and
25/// `log_batch_size = 1`); non-ZK oracles take a flexible batch size with no mask.
26#[derive(Clone)]
27pub struct BaseFoldVerifierCompiler<F>
28where
29	F: BinaryField,
30{
31	oracle_specs: Vec<OracleSpec>,
32	fri_params: FRIParams<F>,
33}
34
35impl<F> BaseFoldVerifierCompiler<F>
36where
37	F: BinaryField,
38{
39	/// Creates a new compiler with precomputed combined FRI parameters.
40	///
41	/// The `merkle_scheme` is consulted only for proof-size estimation while choosing the FRI
42	/// parameters; it is not stored. Each oracle's batch size is derived from its ZK flag: a ZK
43	/// oracle fixes `log_batch_size = 1` (message ‖ equal-length mask), a non-ZK oracle takes a
44	/// flexible batch size. Requires at least one oracle spec.
45	pub fn new<H, Strategy>(
46		merkle_scheme: &BinaryMerkleTreeScheme<F, H>,
47		oracle_specs: Vec<OracleSpec>,
48		log_inv_rate: usize,
49		n_test_queries: usize,
50		_arity_strategy: &Strategy,
51	) -> Self
52	where
53		H: HashSuite,
54		Strategy: AritySelectionStrategy,
55	{
56		assert!(
57			!oracle_specs.is_empty(),
58			"BaseFoldVerifierCompiler requires at least one oracle spec"
59		);
60
61		// The single combined FRI parameters over all oracles. `optimal_for_batch` chooses the fold
62		// arities to minimize proof size, so `_arity_strategy` is not consulted here. It derives
63		// each oracle's batch size from its ZK flag: ZK oracles fix `log_batch_size = 1` (message
64		// ‖ mask), non-ZK oracles take a flexible batch size.
65		let (fri_params, _) = FRIParams::optimal_for_batch(
66			merkle_scheme,
67			&oracle_specs,
68			log_inv_rate,
69			n_test_queries,
70		);
71
72		Self {
73			oracle_specs,
74			fri_params,
75		}
76	}
77
78	/// Returns a reference to the oracle specifications.
79	pub fn oracle_specs(&self) -> &[OracleSpec] {
80		&self.oracle_specs
81	}
82
83	/// Returns a reference to the precomputed combined FRI parameters.
84	pub const fn fri_params(&self) -> &FRIParams<F> {
85		&self.fri_params
86	}
87
88	/// The dimension of the largest evaluation domain the combined FRI parameters need.
89	///
90	/// A prover builds its NTT domain context from this. The basis is not communicated because
91	/// [`ReedSolomonCode`](binius_math::reed_solomon::ReedSolomonCode) fixes it: the Gao-Mateer
92	/// basis of this dimension.
93	pub fn max_log_domain_size(&self) -> usize {
94		self.fri_params.rs_code().log_len()
95	}
96
97	/// Creates a ZK verifier channel over the given Merkle channel.
98	///
99	/// The returned channel drives all prover interaction through `channel`, opening oracles with
100	/// this compiler's oracle specs and combined FRI parameters. The caller constructs the Merkle
101	/// channel, so it decides how commitments are received and verified.
102	pub fn create_channel<Channel>(
103		&self,
104		channel: Channel,
105	) -> BaseFoldVerifierChannel<'_, F, Channel>
106	where
107		Channel: MerkleIPVerifierChannel<F, Elem: From<F> + 'static>,
108	{
109		BaseFoldVerifierChannel::new(channel, &self.oracle_specs, &self.fri_params)
110	}
111
112	/// Creates a ZK verifier channel over a transcript, for the common case.
113	///
114	/// The transcript may be owned or mutably borrowed.
115	/// It is wrapped in a [`VerifierMerkleTranscriptChannel`] for the given hash suite.
116	/// That channel is then passed to [`Self::create_channel`].
117	pub fn create_channel_from_transcript<H, Challenger_, T>(
118		&self,
119		transcript: T,
120	) -> BaseFoldVerifierChannel<'_, F, VerifierMerkleTranscriptChannel<T, Challenger_, F, H>>
121	where
122		F: FixedSizeSerializeBytes,
123		H: HashSuite,
124		Challenger_: Challenger,
125		T: BorrowMut<VerifierTranscript<Challenger_>>,
126		Output<H::LeafHash>: DeserializeBytes,
127	{
128		self.create_channel(VerifierMerkleTranscriptChannel::new(transcript))
129	}
130}