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}