1#![warn(rustdoc::missing_crate_level_docs)]
30
31pub mod config;
32pub mod constraint_system;
33pub mod wiring;
34pub mod wrapper;
35
36use std::{marker::PhantomData, rc::Rc};
37
38use binius_field::{BinaryField, Field, field::FieldOps};
39use binius_hash::HashSuite;
40use binius_iop::{
41 basefold,
42 basefold::compiler::BaseFoldVerifierCompiler,
43 channel::{
44 IOPVerifierChannel, OracleSpec,
45 oracle_setup::{DummyElem, OracleSetupChannel},
46 },
47 fri::{self, MinProofSizeStrategy},
48 merkle_tree::BinaryMerkleTreeScheme,
49};
50use binius_ip::{channel::IPVerifierChannel, mlecheck, sumcheck};
51use binius_math::{multilinear::eq::eq_ind_partial_eval_scalars, univariate::evaluate_univariate};
52use binius_spartan_frontend::constraint_system::{ConstraintSystem, WitnessSegment};
53use binius_transcript::{VerifierTranscript, fiat_shamir::Challenger};
54use binius_utils::{DeserializeBytes, checked_arithmetics::checked_log_2};
55use digest::Output;
56
57use crate::constraint_system::{BlindingInfo, ConstraintSystemPadded};
58
59pub const SECURITY_BITS: usize = 96;
60
61#[derive(Debug, Clone)]
63pub struct MulcheckOutput<F> {
64 pub a_eval: F,
66 pub b_eval: F,
68 pub c_eval: F,
70 pub mask_eval: F,
72 pub r_x: Vec<F>,
74}
75
76#[derive(Debug, Clone)]
82pub struct IOPVerifier<F: Field> {
83 constraint_system: ConstraintSystemPadded<F>,
84}
85
86#[derive(Clone)]
91pub struct Verifier<F, H>
92where
93 F: BinaryField,
94 H: HashSuite,
95{
96 iop_verifier: IOPVerifier<F>,
97 basefold_compiler: BaseFoldVerifierCompiler<F>,
99 _hash_marker: PhantomData<H>,
101}
102
103impl<F: Field> IOPVerifier<F> {
104 pub const fn new(constraint_system: ConstraintSystemPadded<F>) -> Self {
106 Self { constraint_system }
107 }
108
109 pub const fn constraint_system(&self) -> &ConstraintSystemPadded<F> {
110 &self.constraint_system
111 }
112
113 pub fn oracle_specs(&self) -> Vec<OracleSpec>
123 where
124 F: BinaryField,
125 {
126 let cs = &self.constraint_system;
127 let mut channel = OracleSetupChannel::new(true);
128 <OracleSetupChannel as IOPVerifierChannel<F>>::recv_oracle(
132 &mut channel,
133 cs.log_precommit() as usize,
134 true,
135 )
136 .expect("OracleSetupChannel::recv_oracle is infallible");
137 let public = vec![DummyElem::<F>::default(); 1 << cs.log_public()];
138 let _ = self.verify((), &public, &mut channel);
141 channel.into_oracle_specs()
142 }
143
144 pub fn verify<Channel>(
160 &self,
161 precommit_oracle: Channel::Oracle,
162 public: &[Channel::Elem],
163 channel: &mut Channel,
164 ) -> Result<(), Error>
165 where
166 F: BinaryField,
167 Channel: IOPVerifierChannel<F>,
168 {
169 let cs = &self.constraint_system;
170
171 if public.len() != 1 << cs.log_public() {
173 return Err(Error::IncorrectPublicInputLength {
174 expected: 1 << cs.log_public(),
175 actual: public.len(),
176 });
177 }
178
179 let private_oracle = channel.recv_oracle(cs.log_private() as usize, true)?;
184 let (m_n, m_d) = cs.mask_dims();
185 let mask_oracle = channel.recv_oracle(m_n + m_d, true)?;
186
187 let MulcheckOutput {
189 a_eval,
190 b_eval,
191 c_eval,
192 mask_eval,
193 r_x,
194 } = verify_mulcheck(cs, channel)?;
195
196 let lambda = channel.sample();
198
199 let batched_sum = evaluate_univariate(&[a_eval, b_eval, c_eval], &lambda);
201
202 let r_x_tensor: Rc<[Channel::Elem]> = eq_ind_partial_eval_scalars(&r_x).into();
207
208 let public_eval =
210 wiring::evaluate_wiring_mle_public(cs.mul_constraints(), public, &lambda, &r_x_tensor);
211
212 let precommit_claim = channel.recv_one()?;
214
215 let private_claim = batched_sum - public_eval - precommit_claim.clone();
216
217 let mul_constraints: Rc<[_]> = cs.mul_constraints().into();
221 let precommit_transparent = wiring::eval_transparent(
222 mul_constraints.clone(),
223 WitnessSegment::Precommit,
224 r_x_tensor.clone(),
225 lambda.clone(),
226 );
227 let private_transparent =
228 wiring::eval_transparent(mul_constraints, WitnessSegment::Private, r_x_tensor, lambda);
229 let mask_transparent = mask_transparent(cs, &r_x);
230
231 channel.verify_oracle_relation(precommit_oracle, precommit_transparent, precommit_claim)?;
233 channel.verify_oracle_relation(private_oracle, private_transparent, private_claim)?;
234 channel.verify_oracle_relation(mask_oracle, mask_transparent, mask_eval)?;
235
236 Ok(())
237 }
238}
239
240impl<F, H> Verifier<F, H>
241where
242 F: BinaryField,
243 H: HashSuite,
244 Output<H::LeafHash>: DeserializeBytes,
245{
246 pub fn setup(
250 constraint_system: ConstraintSystem<F>,
251 log_inv_rate: usize,
252 ) -> Result<Self, Error> {
253 let n_test_queries = fri::calculate_n_test_queries(SECURITY_BITS, log_inv_rate);
255 let blinding_info = BlindingInfo::for_fri_queries(n_test_queries);
256 let constraint_system = ConstraintSystemPadded::new(constraint_system, blinding_info);
257
258 let iop_verifier = IOPVerifier::new(constraint_system);
259 let oracle_specs = iop_verifier.oracle_specs();
260
261 let merkle_scheme = BinaryMerkleTreeScheme::<F, H>::new();
262
263 let basefold_compiler = BaseFoldVerifierCompiler::new(
265 &merkle_scheme,
266 oracle_specs,
267 log_inv_rate,
268 n_test_queries,
269 &MinProofSizeStrategy,
270 );
271
272 Ok(Self {
273 iop_verifier,
274 basefold_compiler,
275 _hash_marker: PhantomData,
276 })
277 }
278
279 pub const fn iop_verifier(&self) -> &IOPVerifier<F> {
281 &self.iop_verifier
282 }
283
284 pub const fn constraint_system(&self) -> &ConstraintSystemPadded<F> {
285 self.iop_verifier.constraint_system()
286 }
287
288 pub const fn iop_compiler(&self) -> &BaseFoldVerifierCompiler<F> {
290 &self.basefold_compiler
291 }
292
293 pub fn verify<Challenger_: Challenger>(
304 &self,
305 public: &[F],
306 transcript: &mut VerifierTranscript<Challenger_>,
307 ) -> Result<(), Error> {
308 transcript.observe().write_slice(public);
310
311 let mut channel = self
314 .basefold_compiler
315 .create_channel_from_transcript::<H, Challenger_, _>(transcript);
316 let precommit_oracle =
317 channel.recv_oracle(self.constraint_system().log_precommit() as usize, true)?;
318 self.iop_verifier
319 .verify(precommit_oracle, public, &mut channel)?;
320 channel.finish()?;
321 Ok(())
322 }
323}
324
325fn verify_mulcheck<F, C>(
326 cs: &ConstraintSystemPadded<F>,
327 channel: &mut C,
328) -> Result<MulcheckOutput<C::Elem>, Error>
329where
330 F: BinaryField,
331 C: IPVerifierChannel<F>,
332{
333 let log_mul_constraints = checked_log_2(cs.mul_constraints().len());
334
335 let r_mulcheck = channel.sample_many(log_mul_constraints);
337
338 let mlecheck::VerifyZKOutput {
340 eval,
341 mask_eval,
342 challenges: mut r_x,
343 } = mlecheck::verify_zk(&r_mulcheck, 2, C::Elem::zero(), channel)?;
344
345 r_x.reverse();
347
348 let [a_eval, b_eval, c_eval] = channel.recv_array()?;
350
351 channel.assert_zero(a_eval.clone() * b_eval.clone() - c_eval.clone() - eval)?;
352
353 Ok(MulcheckOutput {
354 a_eval,
355 b_eval,
356 c_eval,
357 mask_eval,
358 r_x,
359 })
360}
361
362fn mask_transparent<F: Field, E: FieldOps + 'static>(
364 cs: &ConstraintSystemPadded<F>,
365 r_x: &[E],
366) -> binius_iop::channel::TransparentEvalFn<E> {
367 let (_m_n, m_d) = cs.mask_dims();
368 let n_vars = r_x.len();
369 let mask_degree = 2; let r_x = r_x.to_vec();
371
372 Box::new(move |point: &[E]| {
373 let (query_k, query_j) = point.split_at(m_d);
375
376 mlecheck::libra_eval(&r_x, query_j, query_k, n_vars, mask_degree)
377 })
378}
379
380#[derive(Debug, thiserror::Error)]
381pub enum Error {
382 #[error("FRI error: {0}")]
383 FRI(#[from] fri::Error),
384 #[error("Sumcheck error: {0}")]
385 Sumcheck(#[from] sumcheck::Error),
386 #[error("BaseFold error: {0}")]
387 BaseFold(#[from] basefold::Error),
388 #[error("Transcript error: {0}")]
389 Transcript(#[from] binius_transcript::Error),
390 #[error("IOP channel error: {0}")]
391 IOPChannel(#[from] binius_iop::channel::Error),
392 #[error("IP channel error: {0}")]
393 IPChannel(#[from] binius_ip::channel::Error),
394 #[error("incorrect public inputs length: expected {expected}, got {actual}")]
395 IncorrectPublicInputLength { expected: usize, actual: usize },
396 #[error("incorrect reduction output of the multiplication check")]
397 IncorrectMulCheckEvaluation,
398}