From 118405aee37af20b1a9cf7c8f613898c5d20eff4 Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 22:45:36 -0500 Subject: [PATCH 1/7] refactor: eliminate copy in `Assignment::get` --- src/frontend/gadgets/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/frontend/gadgets/mod.rs b/src/frontend/gadgets/mod.rs index 5ae75c45..ebb6ab84 100644 --- a/src/frontend/gadgets/mod.rs +++ b/src/frontend/gadgets/mod.rs @@ -18,9 +18,6 @@ pub trait Assignment { impl Assignment for Option { fn get(&self) -> Result<&T, SynthesisError> { - match *self { - Some(ref v) => Ok(v), - None => Err(SynthesisError::AssignmentMissing), - } + self.as_ref().ok_or(SynthesisError::AssignmentMissing) } } From 010288cd00bb1c3e655a92c6c5f92fcaa22323d9 Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 22:37:59 -0500 Subject: [PATCH 2/7] refactor: remove dead code in `f_to_nat` --- src/gadgets/nonnative/util.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/gadgets/nonnative/util.rs b/src/gadgets/nonnative/util.rs index e0243566..bda12483 100644 --- a/src/gadgets/nonnative/util.rs +++ b/src/gadgets/nonnative/util.rs @@ -2,13 +2,9 @@ use super::{BitAccess, OptionExt}; use crate::frontend::{ num::AllocatedNum, ConstraintSystem, LinearCombination, SynthesisError, Variable, }; -use byteorder::WriteBytesExt; use ff::PrimeField; use num_bigint::{BigInt, Sign}; -use std::{ - convert::From, - io::{self, Write}, -}; +use std::convert::From; #[derive(Clone)] /// A representation of a bit @@ -232,18 +228,8 @@ impl From> for Num { } } -fn write_be(f: &F, mut writer: W) -> io::Result<()> { - for digit in f.to_repr().as_ref().iter().rev() { - writer.write_u8(*digit)?; - } - - Ok(()) -} - /// Convert a field element to a natural number pub fn f_to_nat(f: &Scalar) -> BigInt { - let mut s = Vec::new(); - write_be(f, &mut s).unwrap(); // f.to_repr().write_be(&mut s).unwrap(); BigInt::from_bytes_le(Sign::Plus, f.to_repr().as_ref()) } From e660235ed464c65be251a273da5bd5471fb4ea85 Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 22:41:40 -0500 Subject: [PATCH 3/7] refactor: use `digest::XofReader::read` directly in `impl_traits_no_dlog_ext` macro `std::io::Read::read_exact` was used before, which indirectly is a single call to `digest::XofReader::read` in this scenario --- src/provider/bn256_grumpkin.rs | 1 - src/provider/pasta.rs | 1 - src/provider/secp_secq.rs | 1 - src/provider/traits.rs | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index ccc90911..53684d3f 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -21,7 +21,6 @@ use num_integer::Integer; use num_traits::{Num, ToPrimitive}; use rayon::prelude::*; use sha3::Shake256; -use std::io::Read; /// Re-exports that give access to the standard aliases used in the code base, for bn256 pub mod bn256 { diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 76d0f07d..fcc3981a 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -19,7 +19,6 @@ use num_integer::Integer; use num_traits::{Num, ToPrimitive}; use rayon::prelude::*; use sha3::Shake256; -use std::io::Read; /// Re-exports that give access to the standard aliases used in the code base, for pallas pub mod pallas { diff --git a/src/provider/secp_secq.rs b/src/provider/secp_secq.rs index 7887d2f1..d3ec21b4 100644 --- a/src/provider/secp_secq.rs +++ b/src/provider/secp_secq.rs @@ -20,7 +20,6 @@ use num_integer::Integer; use num_traits::{Num, ToPrimitive}; use rayon::prelude::*; use sha3::Shake256; -use std::io::Read; /// Re-exports that give access to the standard aliases used in the code base, for secp pub mod secp256k1 { diff --git a/src/provider/traits.rs b/src/provider/traits.rs index f346c490..b88c435f 100644 --- a/src/provider/traits.rs +++ b/src/provider/traits.rs @@ -168,7 +168,7 @@ macro_rules! impl_traits_no_dlog_ext { let mut uniform_bytes_vec = Vec::new(); for _ in 0..n { let mut uniform_bytes = [0u8; 32]; - reader.read_exact(&mut uniform_bytes).unwrap(); + digest::XofReader::read(&mut reader, &mut uniform_bytes); uniform_bytes_vec.push(uniform_bytes); } let gens_proj: Vec<$name_curve> = (0..n) From 739ce3a4e6f5fb0aea2fa0b68d922eb0a7c636b6 Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 23:00:56 -0500 Subject: [PATCH 4/7] refactor: remove de-facto unused `SynthesisError::IoError` variant --- src/frontend/constraint_system.rs | 7 ++----- src/gadgets/nonnative/bignat.rs | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/frontend/constraint_system.rs b/src/frontend/constraint_system.rs index 08036d13..e3ce0820 100644 --- a/src/frontend/constraint_system.rs +++ b/src/frontend/constraint_system.rs @@ -1,4 +1,4 @@ -use std::{io, marker::PhantomData}; +use std::marker::PhantomData; use ff::PrimeField; @@ -16,7 +16,7 @@ pub trait Circuit { /// This is an error that could occur during circuit synthesis contexts, /// such as CRS generation, proving or verification. #[allow(clippy::upper_case_acronyms)] -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, Clone)] pub enum SynthesisError { /// During synthesis, we lacked knowledge of a variable assignment. #[error("an assignment for a variable could not be computed")] @@ -33,9 +33,6 @@ pub enum SynthesisError { /// During proof generation, we encountered an identity in the CRS #[error("encountered an identity element in the CRS")] UnexpectedIdentity, - /// During proof generation, we encountered an I/O error with the CRS - #[error("encountered an I/O error: {0}")] - IoError(#[from] io::Error), /// During verification, our verifying key was malformed. #[error("malformed verifying key")] MalformedVerifyingKey, diff --git a/src/gadgets/nonnative/bignat.rs b/src/gadgets/nonnative/bignat.rs index 0f9b67df..183eb106 100644 --- a/src/gadgets/nonnative/bignat.rs +++ b/src/gadgets/nonnative/bignat.rs @@ -143,8 +143,7 @@ impl BigNat { } Ok(vs[limb_i]) } - // Hack b/c SynthesisError and io::Error don't implement Clone - Err(ref e) => Err(SynthesisError::from(std::io::Error::other(format!("{e}")))), + Err(ref e) => Err(e.clone()), }, ) .map(|v| LinearCombination::zero() + v) @@ -192,8 +191,7 @@ impl BigNat { limb_values.push(vs[limb_i]); Ok(vs[limb_i]) } - // Hack b/c SynthesisError and io::Error don't implement Clone - Err(ref e) => Err(SynthesisError::from(std::io::Error::other(format!("{e}")))), + Err(ref e) => Err(e.clone()), }, ) .map(|v| LinearCombination::zero() + v) From 72e677bb83aa747aeb8cb58e784636d7e14cd58b Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 22:58:09 -0500 Subject: [PATCH 5/7] refactor: use `bincode`'s `Writer` instead of `io::Write`. Also, uses `bincode`s `EncodeError` instead of `io::Error`. In practice, no error is ever returned. --- src/digest.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/digest.rs b/src/digest.rs index e063449a..7bda1f5f 100644 --- a/src/digest.rs +++ b/src/digest.rs @@ -1,13 +1,14 @@ use crate::constants::NUM_HASH_BITS; +use bincode::{enc::write::Writer, error::EncodeError}; use ff::PrimeField; use serde::Serialize; use sha3::{Digest, Sha3_256}; -use std::{io, marker::PhantomData}; +use std::marker::PhantomData; /// Trait for components with potentially discrete digests to be included in their container's digest. pub trait Digestible { /// Write the byte representation of Self in a byte buffer - fn write_bytes(&self, byte_sink: &mut W) -> Result<(), io::Error>; + fn write_bytes(&self, byte_sink: &mut W) -> Result<(), EncodeError>; } /// Marker trait to be implemented for types that implement `Digestible` and `Serialize`. @@ -15,14 +16,12 @@ pub trait Digestible { pub trait SimpleDigestible: Serialize {} impl Digestible for T { - fn write_bytes(&self, byte_sink: &mut W) -> Result<(), io::Error> { + fn write_bytes(&self, byte_sink: &mut W) -> Result<(), EncodeError> { let config = bincode::config::legacy() .with_little_endian() .with_fixed_int_encoding(); // Note: bincode recursively length-prefixes every field! - bincode::serde::encode_into_std_write(self, byte_sink, config) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - Ok(()) + bincode::serde::encode_into_writer(self, byte_sink, config) } } @@ -64,13 +63,17 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> { } /// Compute the digest of a `Digestible` instance. - pub fn digest(&self) -> Result { - let mut hasher = Self::hasher(); - self - .inner - .write_bytes(&mut hasher) - .expect("Serialization error"); - let bytes: [u8; 32] = hasher.finalize().into(); + pub fn digest(&self) -> Result { + struct Hasher(Sha3_256); + impl Writer for Hasher { + fn write(&mut self, bytes: &[u8]) -> Result<(), EncodeError> { + self.0.update(bytes); + Ok(()) + } + } + let mut hasher = Hasher(Self::hasher()); + self.inner.write_bytes(&mut hasher)?; + let bytes: [u8; 32] = hasher.0.finalize().into(); Ok(Self::map_to_field(&bytes)) } } From 144f2dea9eb7308e428f37f73dfc082a2f0e0e4e Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 23:30:09 -0500 Subject: [PATCH 6/7] feat: gate `setup` file io behind new `io` feature that is a default --- Cargo.toml | 3 ++- src/provider/hyperkzg.rs | 11 +++++------ src/provider/mod.rs | 2 ++ src/provider/pedersen.rs | 10 ++++++---- src/traits/commitment.rs | 8 ++++---- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e88bb7f0..6bd61e98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ name = "sumcheckeq" harness = false [features] -default = ["halo2curves/asm"] +default = ["halo2curves/asm", "io"] flamegraph = ["pprof2/flamegraph", "pprof2/criterion"] experimental = [] +io = [] diff --git a/src/provider/hyperkzg.rs b/src/provider/hyperkzg.rs index f9cb11a9..635c3bec 100644 --- a/src/provider/hyperkzg.rs +++ b/src/provider/hyperkzg.rs @@ -6,15 +6,12 @@ //! (2) HyperKZG is specialized to use KZG as the univariate commitment scheme, so it includes several optimizations (both during the transformation of multilinear-to-univariate claims //! and within the KZG commitment scheme implementation itself). #![allow(non_snake_case)] +#[cfg(feature = "io")] +use crate::provider::{ptau::PtauFileError, read_ptau, write_ptau}; use crate::{ errors::NovaError, gadgets::utils::to_bignat_repr, - provider::{ - ptau::PtauFileError, - read_ptau, - traits::{DlogGroup, DlogGroupExt, PairingGroup}, - write_ptau, - }, + provider::traits::{DlogGroup, DlogGroupExt, PairingGroup}, traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, evaluation::EvaluationEngineTrait, @@ -140,6 +137,7 @@ where E::GE: PairingGroup, { /// Save keys + #[cfg(feature = "io")] pub fn save_to( &self, mut writer: &mut (impl std::io::Write + std::io::Seek), @@ -541,6 +539,7 @@ where } } + #[cfg(feature = "io")] fn load_setup( reader: &mut (impl std::io::Read + std::io::Seek), label: &'static [u8], diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 34764532..96fc3193 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -14,6 +14,7 @@ pub mod secp_secq; pub(crate) mod blitzar; pub(crate) mod keccak; pub(crate) mod pedersen; +#[cfg(feature = "io")] pub(crate) mod ptau; pub(crate) mod traits; @@ -31,6 +32,7 @@ use crate::{ }, traits::Engine, }; +#[cfg(feature = "io")] pub use ptau::{check_sanity_of_ptau_file, read_ptau, write_ptau}; use serde::{Deserialize, Serialize}; diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index da6aad4e..328b84d6 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -1,11 +1,10 @@ //! This module provides an implementation of a commitment engine +#[cfg(feature = "io")] +use crate::provider::ptau::{read_points, write_points, PtauFileError}; use crate::{ errors::NovaError, gadgets::utils::to_bignat_repr, - provider::{ - ptau::{read_points, write_points, PtauFileError}, - traits::{DlogGroup, DlogGroupExt}, - }, + provider::traits::{DlogGroup, DlogGroupExt}, traits::{ commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, AbsorbInRO2Trait, AbsorbInROTrait, Engine, ROTrait, TranscriptReprTrait, @@ -22,6 +21,7 @@ use num_traits::ToPrimitive; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +#[cfg(feature = "io")] const KEY_FILE_HEAD: [u8; 12] = *b"PEDERSEN_KEY"; /// A type that holds commitment generators @@ -192,6 +192,7 @@ impl CommitmentKey where E::GE: DlogGroup, { + #[cfg(feature = "io")] pub fn save_to(&self, writer: &mut impl std::io::Write) -> Result<(), PtauFileError> { writer.write_all(&KEY_FILE_HEAD)?; let mut points = Vec::with_capacity(self.ck.len() + 1); @@ -278,6 +279,7 @@ where } } + #[cfg(feature = "io")] fn load_setup( reader: &mut (impl std::io::Read + std::io::Seek), _label: &'static [u8], diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index 003caf7e..61a53c80 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -1,9 +1,8 @@ //! This module defines a collection of traits that define the behavior of a commitment engine //! We require the commitment engine to provide a commitment to vectors with a single group element -use crate::{ - provider::ptau::PtauFileError, - traits::{AbsorbInRO2Trait, AbsorbInROTrait, Engine, TranscriptReprTrait}, -}; +#[cfg(feature = "io")] +use crate::provider::ptau::PtauFileError; +use crate::traits::{AbsorbInRO2Trait, AbsorbInROTrait, Engine, TranscriptReprTrait}; use core::{ fmt::Debug, ops::{Add, Mul, MulAssign, Range}, @@ -61,6 +60,7 @@ pub trait CommitmentEngineTrait: Clone + Send + Sync { type Commitment: CommitmentTrait; /// Load keys + #[cfg(feature = "io")] fn load_setup( reader: &mut (impl std::io::Read + std::io::Seek), label: &'static [u8], From e72926a355ef16ac073404a1638bf5aa3682c58a Mon Sep 17 00:00:00 2001 From: Jay White Date: Thu, 18 Dec 2025 23:47:02 -0500 Subject: [PATCH 7/7] chore: disallow `(e)println!` in production code --- src/frontend/constraint_system.rs | 4 ++-- src/frontend/gadgets/boolean.rs | 4 +++- src/frontend/util_cs/mod.rs | 1 + src/gadgets/nonnative/bignat.rs | 15 ++++++++------- src/lib.rs | 1 + src/neutron/relation.rs | 4 +--- src/spartan/polys/univariate.rs | 9 --------- 7 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/frontend/constraint_system.rs b/src/frontend/constraint_system.rs index e3ce0820..f8030f3a 100644 --- a/src/frontend/constraint_system.rs +++ b/src/frontend/constraint_system.rs @@ -25,8 +25,8 @@ pub enum SynthesisError { #[error("division by zero")] DivisionByZero, /// During synthesis, we constructed an unsatisfiable constraint system. - #[error("unsatisfiable constraint system")] - Unsatisfiable, + #[error("unsatisfiable constraint system: {0}")] + Unsatisfiable(String), /// During synthesis, our polynomials ended up being too high of degree #[error("polynomial degree is too large")] PolynomialDegreeTooLarge, diff --git a/src/frontend/gadgets/boolean.rs b/src/frontend/gadgets/boolean.rs index a99fa264..33e5ff06 100644 --- a/src/frontend/gadgets/boolean.rs +++ b/src/frontend/gadgets/boolean.rs @@ -350,7 +350,9 @@ impl Boolean { if a == b { Ok(()) } else { - Err(SynthesisError::Unsatisfiable) + Err(SynthesisError::Unsatisfiable( + "Booleans are not equal".to_string(), + )) } } (&Boolean::Constant(true), a) | (a, &Boolean::Constant(true)) => { diff --git a/src/frontend/util_cs/mod.rs b/src/frontend/util_cs/mod.rs index cb968a89..7c0cd5fa 100644 --- a/src/frontend/util_cs/mod.rs +++ b/src/frontend/util_cs/mod.rs @@ -1,4 +1,5 @@ //! The `util_cs` module provides a set of utilities for working with constraint system +#[cfg(test)] pub mod test_cs; pub mod witness_cs; diff --git a/src/gadgets/nonnative/bignat.rs b/src/gadgets/nonnative/bignat.rs index 183eb106..9408151b 100644 --- a/src/gadgets/nonnative/bignat.rs +++ b/src/gadgets/nonnative/bignat.rs @@ -52,8 +52,9 @@ pub fn nat_to_limbs( .collect(), ) } else { - eprintln!("nat {nat} does not fit in {n_limbs} limbs of width {limb_width}"); - Err(SynthesisError::Unsatisfiable) + Err(SynthesisError::Unsatisfiable(format!( + "nat {nat} does not fit in {n_limbs} limbs of width {limb_width}" + ))) } } @@ -132,8 +133,9 @@ impl BigNat { || match values_cell { Ok(ref vs) => { if vs.len() != n_limbs { - eprintln!("Values do not match stated limb count"); - return Err(SynthesisError::Unsatisfiable); + return Err(SynthesisError::Unsatisfiable( + "Values do not match stated limb count".to_string(), + )); } if value.is_none() { value = Some(limbs_to_nat::(vs.iter(), limb_width)); @@ -315,11 +317,10 @@ impl BigNat { if self.params.limb_width == other.params.limb_width { Ok(self.params.limb_width) } else { - eprintln!( + Err(SynthesisError::Unsatisfiable(format!( "Limb widths {}, {}, do not agree at {}", self.params.limb_width, other.params.limb_width, location - ); - Err(SynthesisError::Unsatisfiable) + ))) } } diff --git a/src/lib.rs b/src/lib.rs index 43c83664..f87626b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ )] #![allow(non_snake_case)] #![forbid(unsafe_code)] +#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::print_stderr))] // main APIs exposed by this library pub mod nova; diff --git a/src/neutron/relation.rs b/src/neutron/relation.rs index 3dce56cf..b1e02b5f 100644 --- a/src/neutron/relation.rs +++ b/src/neutron/relation.rs @@ -97,10 +97,8 @@ impl Structure { .reduce(|| E::Scalar::ZERO, |acc, x| acc + x); if sum != U.T { - println!("sum: {:?}", sum); - println!("U.T: {:?}", U.T); return Err(NovaError::UnSat { - reason: "sum != U.T".to_string(), + reason: format!("sum != U.T\n sum: {:?}\n U.T: {:?}", sum, U.T), }); } diff --git a/src/spartan/polys/univariate.rs b/src/spartan/polys/univariate.rs index ce9c1226..1cd3f6ee 100644 --- a/src/spartan/polys/univariate.rs +++ b/src/spartan/polys/univariate.rs @@ -157,15 +157,6 @@ pub fn gaussian_elimination(matrix: &mut [Vec]) -> Vec { eliminate(matrix, i); } - // Disable cargo clippy warnings about needless range loops. - // Checking the diagonal like this is simpler than any alternative. - #[allow(clippy::needless_range_loop)] - for i in 0..size { - if matrix[i][i] == F::ZERO { - println!("Infinitely many solutions"); - } - } - let mut result: Vec = vec![F::ZERO; size]; for i in 0..size { result[i] = div_f(matrix[i][size], matrix[i][i]);