From 7e1464ccb5a38c7fe82509c40769ac707ca01e6c Mon Sep 17 00:00:00 2001 From: yash Date: Tue, 3 Mar 2026 23:36:08 +0530 Subject: [PATCH 1/5] rebase main --- Cargo.toml | 6 +- provekit/common/Cargo.toml | 1 + provekit/common/src/file/bin.rs | 50 ++++- provekit/common/src/file/mod.rs | 82 +++++++- provekit/common/src/hash_config.rs | 176 ++++++++++++++++++ provekit/common/src/lib.rs | 18 +- provekit/common/src/noir_proof_scheme.rs | 24 ++- provekit/common/src/prover.rs | 2 +- provekit/common/src/skyscraper/pow.rs | 3 +- provekit/common/src/skyscraper/whir.rs | 11 +- provekit/common/src/transcript_sponge.rs | 99 ++++++++++ provekit/common/src/verifier.rs | 4 +- provekit/common/src/whir_r1cs.rs | 10 +- provekit/prover/Cargo.toml | 2 + provekit/prover/src/lib.rs | 4 +- provekit/r1cs-compiler/Cargo.toml | 1 + .../r1cs-compiler/src/noir_proof_scheme.rs | 3 +- provekit/r1cs-compiler/src/whir_r1cs.rs | 34 ++-- provekit/verifier/src/lib.rs | 7 +- provekit/verifier/src/whir_r1cs.rs | 8 +- tooling/cli/src/cmd/prepare.rs | 9 +- tooling/provekit-bench/Cargo.toml | 8 +- tooling/provekit-bench/benches/bench.rs | 8 +- 23 files changed, 516 insertions(+), 54 deletions(-) create mode 100644 provekit/common/src/hash_config.rs create mode 100644 provekit/common/src/transcript_sponge.rs diff --git a/Cargo.toml b/Cargo.toml index d07e3413f..2e1cea09e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,7 +125,9 @@ ruint = { version = "1.12.3", features = ["num-traits", "rand"] } seq-macro = "0.3.6" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -sha2 = "0.10.9" +sha2 = { version = "0.10.9", features = ["asm"] } +sha3 = "0.11.0-rc.3" +blake3 = "1.5.6" test-case = "3.3.1" tikv-jemallocator = "0.6" toml = "0.8.8" @@ -158,7 +160,7 @@ noirc_driver = { git = "https://github.com/noir-lang/noir", rev = "v1.0.0-beta.1 ark-bn254 = { version = "0.5.0", default-features = false, features = [ "scalar_field", ] } -ark-crypto-primitives = { version = "0.5", features = ["merkle_tree"] } +ark-crypto-primitives = { version = "0.5", features = ["merkle_tree", "parallel"] } ark-ff = { version = "0.5", features = ["asm", "std"] } ark-poly = "0.5" ark-serialize = "0.5" diff --git a/provekit/common/Cargo.toml b/provekit/common/Cargo.toml index 38d26f7ce..3acdbefc3 100644 --- a/provekit/common/Cargo.toml +++ b/provekit/common/Cargo.toml @@ -35,6 +35,7 @@ bytes.workspace = true hex.workspace = true itertools.workspace = true postcard.workspace = true +rand.workspace = true rand08.workspace = true rayon.workspace = true ruint.workspace = true diff --git a/provekit/common/src/file/bin.rs b/provekit/common/src/file/bin.rs index 82e061d79..09cb66350 100644 --- a/provekit/common/src/file/bin.rs +++ b/provekit/common/src/file/bin.rs @@ -1,6 +1,6 @@ use { super::BufExt as _, - crate::utils::human, + crate::{utils::human, HashConfig}, anyhow::{ensure, Context as _, Result}, bytes::{Buf, BufMut as _, Bytes, BytesMut}, serde::{Deserialize, Serialize}, @@ -12,8 +12,13 @@ use { tracing::{info, instrument}, }; -const HEADER_SIZE: usize = 20; +/// Header layout: MAGIC(8) + FORMAT(8) + MAJOR(2) + MINOR(2) + HASH_CONFIG(1) = +/// 21 bytes +const HEADER_SIZE: usize = 21; const MAGIC_BYTES: &[u8] = b"\xDC\xDFOZkp\x01\x00"; +/// Byte offset where hash config is stored: MAGIC(8) + FORMAT(8) + MAJOR(2) + +/// MINOR(2) = 20 +const HASH_CONFIG_OFFSET: usize = 20; /// Zstd magic number: `28 B5 2F FD`. const ZSTD_MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd]; @@ -36,6 +41,7 @@ pub fn write_bin( format: [u8; 8], (major, minor): (u16, u16), compression: Compression, + hash_config: Option, ) -> Result<()> { let postcard_data = postcard::to_allocvec(value).context("while encoding to postcard")?; let uncompressed = postcard_data.len(); @@ -57,11 +63,14 @@ pub fn write_bin( let mut file = File::create(path).context("while creating output file")?; + // Write header: MAGIC(8) + FORMAT(8) + MAJOR(2) + MINOR(2) + HASH_CONFIG(1) let mut header = BytesMut::with_capacity(HEADER_SIZE); header.put(MAGIC_BYTES); header.put(&format[..]); header.put_u16_le(major); header.put_u16_le(minor); + header.put_u8(hash_config.map(|c| c.to_byte()).unwrap_or(0xff)); + file.write_all(&header).context("while writing header")?; file.write_all(&compressed_data) @@ -84,6 +93,40 @@ pub fn write_bin( Ok(()) } +/// Read just the hash_config from the file header (byte 20). +#[instrument(fields(size = path.metadata().map(|m| m.len()).ok()))] +pub fn read_hash_config( + path: &Path, + format: [u8; 8], + (major, minor): (u16, u16), +) -> Result { + let mut file = File::open(path).context("while opening input file")?; + + // Read header + let mut buffer = [0; HEADER_SIZE]; + file.read_exact(&mut buffer) + .context("while reading header")?; + let mut header = Bytes::from_owner(buffer); + + ensure!( + header.get_bytes::<8>() == MAGIC_BYTES, + "Invalid magic bytes" + ); + ensure!(header.get_bytes::<8>() == format, "Invalid format"); + + let file_major = header.get_u16_le(); + let file_minor = header.get_u16_le(); + + ensure!(file_major == major, "Incompatible format major version"); + ensure!(file_minor >= minor, "Incompatible format minor version"); + + // Read hash_config at HASH_CONFIG_OFFSET (byte 20) + debug_assert_eq!(header.remaining(), HEADER_SIZE - HASH_CONFIG_OFFSET); + let hash_config_byte = header.get_u8(); + HashConfig::from_byte(hash_config_byte) + .with_context(|| format!("Invalid hash config byte: 0x{:02X}", hash_config_byte)) +} + /// Read a compressed binary file, auto-detecting zstd or XZ compression. #[instrument(fields(size = path.metadata().map(|m| m.len()).ok()))] pub fn read_bin Deserialize<'a>>( @@ -111,6 +154,9 @@ pub fn read_bin Deserialize<'a>>( "Incompatible format minor version" ); + // Skip hash_config byte (can be read separately via read_hash_config if needed) + let _hash_config_byte = header.get_u8(); + let uncompressed = decompress_stream(&mut file)?; postcard::from_bytes(&uncompressed).context("while decoding from postcard") diff --git a/provekit/common/src/file/mod.rs b/provekit/common/src/file/mod.rs index 2107e3487..f2f9995f0 100644 --- a/provekit/common/src/file/mod.rs +++ b/provekit/common/src/file/mod.rs @@ -5,12 +5,12 @@ mod json; use { self::{ - bin::{read_bin, write_bin, Compression}, + bin::{read_bin, read_hash_config as read_hash_config_bin, write_bin, Compression}, buf_ext::BufExt, counting_writer::CountingWriter, json::{read_json, write_json}, }, - crate::{NoirProof, NoirProofScheme, Prover, Verifier}, + crate::{HashConfig, NoirProof, NoirProofScheme, Prover, Verifier}, anyhow::Result, serde::{Deserialize, Serialize}, std::{ffi::OsStr, path::Path}, @@ -25,6 +25,45 @@ pub trait FileFormat: Serialize + for<'a> Deserialize<'a> { const COMPRESSION: Compression; } +/// Helper trait to optionally extract hash config. +pub(crate) trait MaybeHashAware { + fn maybe_hash_config(&self) -> Option; +} + +/// Impl for Prover (has hash config). +impl MaybeHashAware for Prover { + fn maybe_hash_config(&self) -> Option { + match self { + Prover::Noir(p) => Some(p.hash_config), + Prover::Mavros(p) => Some(p.hash_config), + } + } +} + +/// Impl for Verifier (has hash config). +impl MaybeHashAware for Verifier { + fn maybe_hash_config(&self) -> Option { + Some(self.hash_config) + } +} + +/// Impl for NoirProof (no hash config). +impl MaybeHashAware for NoirProof { + fn maybe_hash_config(&self) -> Option { + None + } +} + +/// Impl for NoirProofScheme (has hash config). +impl MaybeHashAware for NoirProofScheme { + fn maybe_hash_config(&self) -> Option { + match self { + NoirProofScheme::Noir(d) => Some(d.hash_config), + NoirProofScheme::Mavros(d) => Some(d.hash_config), + } + } +} + impl FileFormat for NoirProofScheme { const FORMAT: [u8; 8] = *b"NrProScm"; const EXTENSION: &'static str = "nps"; @@ -54,12 +93,13 @@ impl FileFormat for NoirProof { } /// Write a file with format determined from extension. +#[allow(private_bounds)] #[instrument(skip(value))] -pub fn write(value: &T, path: &Path) -> Result<()> { +pub fn write(value: &T, path: &Path) -> Result<()> { match path.extension().and_then(OsStr::to_str) { Some("json") => write_json(value, path), Some(ext) if ext == T::EXTENSION => { - write_bin(value, path, T::FORMAT, T::VERSION, T::COMPRESSION) + write_bin_with_hash_config(value, path, T::FORMAT, T::VERSION, T::COMPRESSION) } _ => Err(anyhow::anyhow!( "Unsupported file extension, please specify .{} or .json", @@ -68,6 +108,19 @@ pub fn write(value: &T, path: &Path) -> Result<()> { } } +/// Helper to write binary files with hash_config if T implements +/// MaybeHashAware. +fn write_bin_with_hash_config( + value: &T, + path: &Path, + format: [u8; 8], + version: (u16, u16), + compression: Compression, +) -> Result<()> { + let hash_config = value.maybe_hash_config(); + write_bin(value, path, format, version, compression, hash_config) +} + /// Read a file with format determined from extension. #[instrument()] pub fn read(path: &Path) -> Result { @@ -80,3 +133,24 @@ pub fn read(path: &Path) -> Result { )), } } + +/// Read just the hash configuration from a file. +#[instrument()] +pub fn read_hash_config(path: &Path) -> Result { + match path.extension().and_then(OsStr::to_str) { + Some("json") => { + // For JSON, parse and extract hash_config field (required) + let json_str = std::fs::read_to_string(path)?; + let value: serde_json::Value = serde_json::from_str(&json_str)?; + value + .get("hash_config") + .and_then(|v| serde_json::from_value(v.clone()).ok()) + .ok_or_else(|| anyhow::anyhow!("Missing hash_config field in JSON file")) + } + Some(ext) if ext == T::EXTENSION => read_hash_config_bin(path, T::FORMAT, T::VERSION), + _ => Err(anyhow::anyhow!( + "Unsupported file extension, please specify .{} or .json", + T::EXTENSION + )), + } +} diff --git a/provekit/common/src/hash_config.rs b/provekit/common/src/hash_config.rs new file mode 100644 index 000000000..4fec80eb8 --- /dev/null +++ b/provekit/common/src/hash_config.rs @@ -0,0 +1,176 @@ +/// Runtime hash configuration selection for ProveKit. +/// +/// This module provides runtime selection of hash algorithms. The selected +/// hash is used for Merkle tree commitments (via WHIR's `EngineId`) and +/// the Fiat-Shamir transcript sponge (via [`crate::TranscriptSponge`]). +use { + serde::{Deserialize, Serialize}, + std::fmt, +}; + +/// Hash algorithm configuration that can be selected at runtime. +/// +/// Each variant uses the same hash algorithm for: +/// - **Merkle tree commitments**: Binds polynomial data +/// - **Fiat-Shamir transcript**: Interactive proof made non-interactive +/// - **Proof of Work**: Optional computational puzzle +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum HashConfig { + #[serde(alias = "sky")] + Skyscraper, + + #[serde(alias = "sha", alias = "sha-256")] + Sha256, + + #[serde(alias = "keccak-256", alias = "shake")] + Keccak, + + #[serde(alias = "blake-3", alias = "b3")] + Blake3, +} + +impl HashConfig { + /// Returns the canonical name of this hash configuration. + pub fn name(&self) -> &'static str { + match self { + Self::Skyscraper => "skyscraper", + Self::Sha256 => "sha256", + Self::Keccak => "keccak", + Self::Blake3 => "blake3", + } + } + + /// Returns the WHIR 2.0 engine ID for this hash configuration. + pub fn engine_id(&self) -> whir::engines::EngineId { + match self { + Self::Skyscraper => crate::skyscraper::SKYSCRAPER, + Self::Sha256 => whir::hash::SHA2, + Self::Keccak => whir::hash::KECCAK, + Self::Blake3 => whir::hash::BLAKE3, + } + } + + /// Converts hash configuration to a single byte for binary file headers. + pub fn to_byte(&self) -> u8 { + match self { + Self::Skyscraper => 0, + Self::Sha256 => 1, + Self::Keccak => 2, + Self::Blake3 => 3, + } + } + + /// Converts a byte from binary file header to hash configuration. + pub fn from_byte(byte: u8) -> Option { + match byte { + 0 => Some(Self::Skyscraper), + 1 => Some(Self::Sha256), + 2 => Some(Self::Keccak), + 3 => Some(Self::Blake3), + _ => None, + } + } + + /// Parses a hash configuration from a string. + pub fn parse(s: &str) -> Option { + let lower = s.to_lowercase(); + match lower.as_str() { + "skyscraper" | "sky" => Some(Self::Skyscraper), + "sha256" | "sha" | "sha-256" => Some(Self::Sha256), + "keccak" | "keccak-256" | "shake" => Some(Self::Keccak), + "blake3" | "blake-3" | "b3" => Some(Self::Blake3), + _ => None, + } + } +} + +impl Default for HashConfig { + fn default() -> Self { + Self::Skyscraper + } +} + +impl fmt::Display for HashConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name()) + } +} + +impl std::str::FromStr for HashConfig { + type Err = String; + + fn from_str(s: &str) -> Result { + Self::parse(s).ok_or_else(|| { + format!( + "Invalid hash configuration: '{}'. Valid options: skyscraper, sha256, keccak, \ + blake3", + s + ) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// All known variants. If a new variant is added to `HashConfig`, this + /// list must be updated — causing the exhaustiveness tests below to fail + /// until `from_byte` / `to_byte` are also updated. + const ALL_VARIANTS: &[HashConfig] = &[ + HashConfig::Skyscraper, + HashConfig::Sha256, + HashConfig::Keccak, + HashConfig::Blake3, + ]; + + #[test] + fn from_byte_roundtrips_with_to_byte() { + for &variant in ALL_VARIANTS { + let byte = variant.to_byte(); + let recovered = HashConfig::from_byte(byte) + .unwrap_or_else(|| panic!("from_byte({byte}) returned None for {variant:?}")); + assert_eq!(variant, recovered, "roundtrip failed for {variant:?}"); + } + } + + #[test] + fn from_byte_returns_none_for_invalid() { + // One past the last valid byte, and a large value. + let first_invalid = ALL_VARIANTS.len() as u8; + assert!( + HashConfig::from_byte(first_invalid).is_none(), + "from_byte({first_invalid}) should be None" + ); + assert!( + HashConfig::from_byte(u8::MAX).is_none(), + "from_byte(255) should be None" + ); + } + + #[test] + fn to_byte_values_are_contiguous_from_zero() { + let mut bytes: Vec = ALL_VARIANTS.iter().map(|v| v.to_byte()).collect(); + bytes.sort(); + let expected: Vec = (0..ALL_VARIANTS.len() as u8).collect(); + assert_eq!(bytes, expected, "to_byte values should be 0..N contiguous"); + } + + #[test] + fn from_byte_covers_all_variants() { + // Collect every Some value from from_byte over the full u8 range. + let recovered: Vec = (0..=u8::MAX).filter_map(HashConfig::from_byte).collect(); + for &variant in ALL_VARIANTS { + assert!( + recovered.contains(&variant), + "{variant:?} is not reachable via from_byte" + ); + } + assert_eq!( + recovered.len(), + ALL_VARIANTS.len(), + "from_byte maps to more variants than ALL_VARIANTS lists" + ); + } +} diff --git a/provekit/common/src/lib.rs b/provekit/common/src/lib.rs index b92d1936b..b1ed6ac50 100644 --- a/provekit/common/src/lib.rs +++ b/provekit/common/src/lib.rs @@ -1,4 +1,5 @@ pub mod file; +pub mod hash_config; mod interner; mod noir_proof_scheme; pub mod prefix_covector; @@ -6,6 +7,7 @@ mod prover; mod r1cs; pub mod skyscraper; pub mod sparse_matrix; +mod transcript_sponge; pub mod utils; mod verifier; mod whir_r1cs; @@ -22,17 +24,12 @@ pub use { prefix_covector::{OffsetCovector, PrefixCovector}, prover::{MavrosProver, NoirProver, Prover}, r1cs::R1CS, + transcript_sponge::TranscriptSponge, verifier::Verifier, - whir_r1cs::{ - WhirConfig, WhirDomainSeparator, WhirProof, WhirProverState, WhirR1CSProof, WhirR1CSScheme, - WhirZkConfig, - }, + whir_r1cs::{WhirConfig, WhirR1CSProof, WhirR1CSScheme, WhirZkConfig}, witness::PublicInputs, }; -/// SHA-256 based transcript sponge for Fiat-Shamir. -pub type TranscriptSponge = spongefish::instantiations::SHA256; - /// Register provekit's custom implementations in whir's global registries. /// /// Must be called once before any prove/verify operations. @@ -41,12 +38,13 @@ pub fn register_ntt() { use std::sync::{Arc, Once}; static INIT: Once = Once::new(); INIT.call_once(|| { + // Register NTT for polynomial operations let ntt: Arc> = Arc::new(whir::algebra::ntt::ArkNtt::::default()); whir::algebra::ntt::NTT.insert(ntt); - let skyscraper: Arc = - Arc::new(skyscraper::SkyscraperHashEngine); - whir::hash::ENGINES.register(skyscraper); + // Register Skyscraper (ProveKit-specific); WHIR's built-in engines + // (SHA2, Keccak, Blake3, etc.) are pre-registered via whir::hash::ENGINES. + whir::hash::ENGINES.register(Arc::new(skyscraper::SkyscraperHashEngine)); }); } diff --git a/provekit/common/src/noir_proof_scheme.rs b/provekit/common/src/noir_proof_scheme.rs index f8b675a14..fe3d197c3 100644 --- a/provekit/common/src/noir_proof_scheme.rs +++ b/provekit/common/src/noir_proof_scheme.rs @@ -2,7 +2,7 @@ use { crate::{ whir_r1cs::{WhirR1CSProof, WhirR1CSScheme}, witness::{NoirWitnessGenerator, SplitWitnessBuilders}, - NoirElement, PublicInputs, R1CS, + HashConfig, NoirElement, PublicInputs, R1CS, }, acir::circuit::Program, mavros_vm::{ConstraintsLayout, WitnessLayout}, @@ -17,6 +17,28 @@ pub struct NoirSchemeData { pub split_witness_builders: SplitWitnessBuilders, pub witness_generator: NoirWitnessGenerator, pub whir_for_witness: WhirR1CSScheme, + pub hash_config: HashConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MavrosSchemeData { + #[serde(with = "crate::utils::serde_jsonify")] + pub abi: Abi, + pub num_public_inputs: usize, + pub r1cs: R1CS, + pub whir_for_witness: WhirR1CSScheme, + pub witgen_binary: Vec, + pub ad_binary: Vec, + pub constraints_layout: ConstraintsLayout, + pub witness_layout: WitnessLayout, + #[serde(default)] + pub hash_config: HashConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum NoirProofScheme { + Noir(NoirSchemeData), + Mavros(MavrosSchemeData), } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/provekit/common/src/prover.rs b/provekit/common/src/prover.rs index 0feaf68fa..22cf55068 100644 --- a/provekit/common/src/prover.rs +++ b/provekit/common/src/prover.rs @@ -3,7 +3,7 @@ use { noir_proof_scheme::NoirProofScheme, whir_r1cs::WhirR1CSScheme, witness::{NoirWitnessGenerator, SplitWitnessBuilders}, - NoirElement, R1CS, + HashConfig, NoirElement, R1CS, }, acir::circuit::Program, mavros_vm::{ConstraintsLayout, WitnessLayout}, diff --git a/provekit/common/src/skyscraper/pow.rs b/provekit/common/src/skyscraper/pow.rs index 7c0678c5a..48e21d6e1 100644 --- a/provekit/common/src/skyscraper/pow.rs +++ b/provekit/common/src/skyscraper/pow.rs @@ -4,7 +4,8 @@ use { zerocopy::transmute, }; -#[derive(Clone, Copy)] +/// Skyscraper proof of work +#[derive(Clone, Copy, Debug, PartialEq)] pub struct SkyscraperPoW { challenge: [u8; 32], bits: f64, diff --git a/provekit/common/src/skyscraper/whir.rs b/provekit/common/src/skyscraper/whir.rs index 6ee863cf8..0095cc877 100644 --- a/provekit/common/src/skyscraper/whir.rs +++ b/provekit/common/src/skyscraper/whir.rs @@ -19,6 +19,10 @@ pub const SKYSCRAPER: EngineId = EngineId::new([ 0x77, 0xb5, 0x82, 0xb0, 0xb2, 0xdd, 0x42, 0x1c, 0x66, 0x19, 0x13, 0xe6, 0xa5, 0x63, 0xf8, 0xa1, ]); +// ============================================================================ +// WHIR 2.0 HashEngine Implementation +// ============================================================================ + #[derive(Clone, Copy, Debug)] pub struct SkyscraperHashEngine; @@ -66,8 +70,7 @@ impl HashEngine for SkyscraperHashEngine { } // Leaf hashing: left-fold 32-byte chunks, batched across messages - // for SIMD throughput. Equivalent to main's SkyscraperCRH::evaluate: - // elements.reduce(compress) + // for SIMD throughput (equivalent to elements.reduce(compress)). // Processes in fixed-size groups to avoid heap allocation. const GROUP: usize = 4 * skyscraper::WIDTH_LCM; // fits in 3 KiB on stack let chunks_per_msg = size / 32; @@ -97,6 +100,10 @@ impl HashEngine for SkyscraperHashEngine { } } +// ============================================================================ +// Tests +// ============================================================================ + #[cfg(test)] mod tests { use {super::*, zerocopy::IntoBytes}; diff --git a/provekit/common/src/transcript_sponge.rs b/provekit/common/src/transcript_sponge.rs new file mode 100644 index 000000000..5af0ce322 --- /dev/null +++ b/provekit/common/src/transcript_sponge.rs @@ -0,0 +1,99 @@ +//! Runtime-selectable Fiat-Shamir transcript sponge. +//! +//! Instead of making every function generic over a sponge type parameter, +//! we use a single enum that delegates to the concrete sponge at runtime. +//! The branch cost is negligible — the sponge is called O(log n) times +//! per proof for Fiat-Shamir challenges, not in a tight inner loop. + +use { + crate::{hash_config::HashConfig, skyscraper::SkyscraperSponge}, + spongefish::{instantiations, DuplexSpongeInterface}, +}; + +/// Fiat-Shamir transcript sponge, selected at runtime by [`HashConfig`]. +/// +/// Wraps one of the four supported sponge implementations and delegates +/// all [`DuplexSpongeInterface`] calls to the active variant. +#[derive(Clone)] +pub enum TranscriptSponge { + Sha256(instantiations::SHA256), + Blake3(instantiations::Blake3), + Keccak(instantiations::Keccak), + Skyscraper(SkyscraperSponge), +} + +impl TranscriptSponge { + /// Create a sponge matching the given hash configuration. + pub fn from_config(config: HashConfig) -> Self { + match config { + HashConfig::Sha256 => Self::Sha256(Default::default()), + HashConfig::Blake3 => Self::Blake3(Default::default()), + HashConfig::Keccak => Self::Keccak(Default::default()), + HashConfig::Skyscraper => Self::Skyscraper(Default::default()), + } + } +} + +impl Default for TranscriptSponge { + fn default() -> Self { + Self::from_config(HashConfig::default()) + } +} + +impl DuplexSpongeInterface for TranscriptSponge { + type U = u8; + + fn absorb(&mut self, input: &[u8]) -> &mut Self { + match self { + Self::Sha256(s) => { + s.absorb(input); + } + Self::Blake3(s) => { + s.absorb(input); + } + Self::Keccak(s) => { + s.absorb(input); + } + Self::Skyscraper(s) => { + s.absorb(input); + } + } + self + } + + fn squeeze(&mut self, output: &mut [u8]) -> &mut Self { + match self { + Self::Sha256(s) => { + s.squeeze(output); + } + Self::Blake3(s) => { + s.squeeze(output); + } + Self::Keccak(s) => { + s.squeeze(output); + } + Self::Skyscraper(s) => { + s.squeeze(output); + } + } + self + } + + fn ratchet(&mut self) -> &mut Self { + match self { + Self::Sha256(s) => { + s.ratchet(); + } + Self::Blake3(s) => { + s.ratchet(); + } + Self::Keccak(s) => { + s.ratchet(); + } + Self::Skyscraper(s) => { + s.ratchet(); + } + } + self + } +} diff --git a/provekit/common/src/verifier.rs b/provekit/common/src/verifier.rs index 624b486f9..49ce85f76 100644 --- a/provekit/common/src/verifier.rs +++ b/provekit/common/src/verifier.rs @@ -1,6 +1,7 @@ use { crate::{ - noir_proof_scheme::NoirProofScheme, utils::serde_jsonify, whir_r1cs::WhirR1CSScheme, R1CS, + noir_proof_scheme::NoirProofScheme, utils::serde_jsonify, whir_r1cs::WhirR1CSScheme, + HashConfig, R1CS, }, noirc_abi::Abi, serde::{Deserialize, Serialize}, @@ -8,6 +9,7 @@ use { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Verifier { + pub hash_config: HashConfig, pub r1cs: R1CS, pub whir_for_witness: Option, #[serde(with = "serde_jsonify")] diff --git a/provekit/common/src/whir_r1cs.rs b/provekit/common/src/whir_r1cs.rs index 24241d964..e36713a5c 100644 --- a/provekit/common/src/whir_r1cs.rs +++ b/provekit/common/src/whir_r1cs.rs @@ -1,4 +1,6 @@ #[cfg(debug_assertions)] +use std::fmt::Debug; +#[cfg(debug_assertions)] use whir::transcript::Interaction; use { crate::{utils::serde_hex, FieldElement}, @@ -14,13 +16,7 @@ pub type WhirConfig = GenericWhirConfig>; pub type WhirZkConfig = GenericWhirZkConfig; /// Type alias for the whir domain separator used in provekit's outer protocol. -pub type WhirDomainSeparator = transcript::DomainSeparator<'static, ()>; - -/// Type alias for the whir prover transcript state. -pub type WhirProverState = transcript::ProverState; - -/// Type alias for the whir proof. -pub type WhirProof = transcript::Proof; +type WhirDomainSeparator = transcript::DomainSeparator<'static, ()>; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct WhirR1CSScheme { diff --git a/provekit/prover/Cargo.toml b/provekit/prover/Cargo.toml index 9a6007b88..a4390623d 100644 --- a/provekit/prover/Cargo.toml +++ b/provekit/prover/Cargo.toml @@ -24,9 +24,11 @@ noir_artifact_cli.workspace = true noirc_abi.workspace = true # Cryptography and proof systems +ark-crypto-primitives.workspace = true ark-ff.workspace = true ark-std.workspace = true spongefish.workspace = true +spongefish-pow.workspace = true whir.workspace = true # 3rd party diff --git a/provekit/prover/src/lib.rs b/provekit/prover/src/lib.rs index 44cd0ca07..b8cf9ed69 100644 --- a/provekit/prover/src/lib.rs +++ b/provekit/prover/src/lib.rs @@ -81,12 +81,12 @@ impl Prove for NoirProver { let num_witnesses = compressed_r1cs.num_witnesses(); let num_constraints = compressed_r1cs.num_constraints(); - // Set up transcript + // Set up transcript with sponge selected by hash_config. let ds = self .whir_for_witness .create_domain_separator() .instance(&Empty); - let mut merlin = ProverState::new(&ds, TranscriptSponge::default()); + let mut merlin = ProverState::new(&ds, TranscriptSponge::from_config(self.hash_config)); let mut witness: Vec> = vec![None; num_witnesses]; diff --git a/provekit/r1cs-compiler/Cargo.toml b/provekit/r1cs-compiler/Cargo.toml index d7e64f516..6d64aca1c 100644 --- a/provekit/r1cs-compiler/Cargo.toml +++ b/provekit/r1cs-compiler/Cargo.toml @@ -23,6 +23,7 @@ noirc_abi.workspace = true noirc_artifacts.workspace = true # Cryptography and proof systems +ark-crypto-primitives.workspace = true ark-ff.workspace = true ark-std.workspace = true whir.workspace = true diff --git a/provekit/r1cs-compiler/src/noir_proof_scheme.rs b/provekit/r1cs-compiler/src/noir_proof_scheme.rs index 654a2aab6..b8b36e84f 100644 --- a/provekit/r1cs-compiler/src/noir_proof_scheme.rs +++ b/provekit/r1cs-compiler/src/noir_proof_scheme.rs @@ -25,7 +25,7 @@ impl NoirCompiler { let file = File::open(path).context("while opening Noir program")?; let program = serde_json::from_reader(file).context("while reading Noir program")?; - Self::from_program(program) + Self::from_program(program, hash_config) } #[instrument(skip_all)] @@ -82,6 +82,7 @@ impl NoirCompiler { split_witness_builders.w1_size, num_challenges, has_public_inputs, + hash_config.engine_id(), ); Ok(NoirProofScheme::Noir(NoirSchemeData { diff --git a/provekit/r1cs-compiler/src/whir_r1cs.rs b/provekit/r1cs-compiler/src/whir_r1cs.rs index 64b16b7a9..e9ade1076 100644 --- a/provekit/r1cs-compiler/src/whir_r1cs.rs +++ b/provekit/r1cs-compiler/src/whir_r1cs.rs @@ -1,7 +1,7 @@ use { mavros_artifacts::R1CS as MavrosR1CS, provekit_common::{utils::next_power_of_two, WhirR1CSScheme, WhirZkConfig, R1CS}, - whir::parameters::ProtocolParameters, + whir::{engines::EngineId, parameters::ProtocolParameters}, }; const MIN_WHIR_NUM_VARIABLES: usize = 13; @@ -13,6 +13,7 @@ pub trait WhirR1CSSchemeBuilder { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self; fn new_from_mavros_r1cs( @@ -40,6 +41,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self { let total_witnesses = r1cs.num_witnesses(); assert!( @@ -66,12 +68,16 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { m_0, a_num_terms: next_power_of_two(r1cs.a().iter().count()), num_challenges, - whir_witness: Self::new_whir_zk_config_for_size(m_raw, 1), + whir_witness: Self::new_whir_zk_config_for_size(m_raw, 1, hash_id), has_public_inputs, } } - fn new_whir_zk_config_for_size(num_variables: usize, num_polynomials: usize) -> WhirZkConfig { + fn new_whir_zk_config_for_size( + num_variables: usize, + num_polynomials: usize, + hash_id: EngineId, + ) -> WhirZkConfig { let nv = num_variables.max(MIN_WHIR_NUM_VARIABLES); // Parameters tuned for 128-bit security under the Johnson bound (the old @@ -80,14 +86,14 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { // security budget toward algebraic hardness (118 bits) with light PoW per // round, which is faster than the default ~18-bit grinding. let whir_params = ProtocolParameters { - unique_decoding: false, - security_level: 128, - pow_bits: 10, + unique_decoding: false, + security_level: 128, + pow_bits: 10, initial_folding_factor: 3, - folding_factor: 3, - starting_log_inv_rate: 2, - batch_size: 1, - hash_id: whir::hash::SHA2, + folding_factor: 3, + starting_log_inv_rate: 2, + batch_size: 1, + hash_id, }; WhirZkConfig::new(1 << nv, &whir_params, num_polynomials) } @@ -149,7 +155,7 @@ mod tests { #[test] fn verify_security_level() { - let config = WhirR1CSScheme::new_whir_zk_config_for_size(20, 1); + let config = WhirR1CSScheme::new_whir_zk_config_for_size(20, 1, whir::hash::SHA2); let sec_blinded = config .blinded_commitment .security_level(config.blinded_commitment.initial_committer.num_vectors, 1); @@ -168,7 +174,11 @@ mod tests { #[test] fn verify_security_level_min_variables() { - let config = WhirR1CSScheme::new_whir_zk_config_for_size(MIN_WHIR_NUM_VARIABLES, 1); + let config = WhirR1CSScheme::new_whir_zk_config_for_size( + MIN_WHIR_NUM_VARIABLES, + 1, + whir::hash::SHA2, + ); let sec_blinded = config .blinded_commitment .security_level(config.blinded_commitment.initial_committer.num_vectors, 1); diff --git a/provekit/verifier/src/lib.rs b/provekit/verifier/src/lib.rs index 1c3461fa8..328ef141d 100644 --- a/provekit/verifier/src/lib.rs +++ b/provekit/verifier/src/lib.rs @@ -19,7 +19,12 @@ impl Verify for Verifier { self.whir_for_witness .take() .context("Verifier has already been consumed; cannot verify twice")? - .verify(&proof.whir_r1cs_proof, &proof.public_inputs, &self.r1cs)?; + .verify( + &proof.whir_r1cs_proof, + &proof.public_inputs, + &self.r1cs, + self.hash_config, + )?; Ok(()) } diff --git a/provekit/verifier/src/whir_r1cs.rs b/provekit/verifier/src/whir_r1cs.rs index d3a148b9a..59288172b 100644 --- a/provekit/verifier/src/whir_r1cs.rs +++ b/provekit/verifier/src/whir_r1cs.rs @@ -8,7 +8,8 @@ use { utils::sumcheck::{ calculate_eq, eval_cubic_poly, multiply_transposed_by_eq_alpha, transpose_r1cs_matrices, }, - FieldElement, PublicInputs, TranscriptSponge, WhirR1CSProof, WhirR1CSScheme, R1CS, + FieldElement, HashConfig, PublicInputs, TranscriptSponge, WhirR1CSProof, WhirR1CSScheme, + R1CS, }, tracing::instrument, whir::{ @@ -30,6 +31,7 @@ pub trait WhirR1CSVerifier { proof: &WhirR1CSProof, public_inputs: &PublicInputs, r1cs: &R1CS, + hash_config: HashConfig, ) -> Result<()>; } @@ -40,6 +42,7 @@ impl WhirR1CSVerifier for WhirR1CSScheme { proof: &WhirR1CSProof, public_inputs: &PublicInputs, r1cs: &R1CS, + hash_config: HashConfig, ) -> Result<()> { let ds = self.create_domain_separator().instance(&Empty); let whir_proof = Proof { @@ -48,7 +51,8 @@ impl WhirR1CSVerifier for WhirR1CSScheme { #[cfg(debug_assertions)] pattern: proof.pattern.clone(), }; - let mut arthur = VerifierState::new(&ds, &whir_proof, TranscriptSponge::default()); + let mut arthur = + VerifierState::new(&ds, &whir_proof, TranscriptSponge::from_config(hash_config)); let commitment_1 = self .whir_witness diff --git a/tooling/cli/src/cmd/prepare.rs b/tooling/cli/src/cmd/prepare.rs index f4e0dbe38..9434404de 100644 --- a/tooling/cli/src/cmd/prepare.rs +++ b/tooling/cli/src/cmd/prepare.rs @@ -60,6 +60,11 @@ pub struct Args { default = "PathBuf::from(\"noir_proof_scheme.pkv\")" )] pkv_path: PathBuf, + + /// hash algorithm for Merkle commitments (skyscraper, sha256, keccak, + /// blake3) + #[argh(option, long = "hash", default = "String::from(\"skyscraper\")")] + hash: String, } impl Command for Args { @@ -82,9 +87,9 @@ impl Command for Args { &Prover::from_noir_proof_scheme(scheme.clone()), &self.pkp_path, ) - .context("while writing Noir proof scheme")?; + .context("while writing Provekit Prover")?; write(&Verifier::from_noir_proof_scheme(scheme), &self.pkv_path) - .context("while writing Noir proof scheme")?; + .context("while writing Provekit Verifier")?; Ok(()) } } diff --git a/tooling/provekit-bench/Cargo.toml b/tooling/provekit-bench/Cargo.toml index f43de8106..3b1993b2a 100644 --- a/tooling/provekit-bench/Cargo.toml +++ b/tooling/provekit-bench/Cargo.toml @@ -19,15 +19,21 @@ provekit-verifier.workspace = true nargo.workspace = true nargo_cli.workspace = true nargo_toml.workspace = true -noirc_driver.workspace = true +noir_artifact_cli.workspace = true noirc_artifacts.workspace = true +noirc_abi.workspace = true +noirc_driver.workspace = true # 3rd party anyhow.workspace = true +ark-ff.workspace = true +ark-std.workspace = true divan.workspace = true +rand.workspace = true serde.workspace = true test-case.workspace = true toml.workspace = true +whir.workspace = true [lints] workspace = true diff --git a/tooling/provekit-bench/benches/bench.rs b/tooling/provekit-bench/benches/bench.rs index ce7034533..c9bd1fa95 100644 --- a/tooling/provekit-bench/benches/bench.rs +++ b/tooling/provekit-bench/benches/bench.rs @@ -60,10 +60,14 @@ fn prove_poseidon_1000_with_io(bencher: Bencher) { fn verify_poseidon_1000(bencher: Bencher) { let crate_dir: &Path = "../../noir-examples/poseidon-rounds".as_ref(); let proof_verifier_path = crate_dir.join("noir-provekit-verifier.pkv"); - let mut verifier: Verifier = read(&proof_verifier_path).unwrap(); let proof_path = crate_dir.join("noir-proof.np"); let proof: NoirProof = read(&proof_path).unwrap(); - bencher.bench_local(|| black_box(&mut verifier).verify(black_box(&proof))); + + bencher.bench_local(|| { + // Read a fresh verifier for each iteration (verify consumes internal state) + let mut verifier: Verifier = read(&proof_verifier_path).unwrap(); + black_box(&mut verifier).verify(black_box(&proof)) + }); } fn main() { From e64ac1413ef127b2eeb3bd63b93dd500d8db4b5e Mon Sep 17 00:00:00 2001 From: yash Date: Tue, 3 Mar 2026 23:42:27 +0530 Subject: [PATCH 2/5] minor version update --- provekit/common/src/file/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provekit/common/src/file/mod.rs b/provekit/common/src/file/mod.rs index f2f9995f0..06f6f34f9 100644 --- a/provekit/common/src/file/mod.rs +++ b/provekit/common/src/file/mod.rs @@ -67,28 +67,28 @@ impl MaybeHashAware for NoirProofScheme { impl FileFormat for NoirProofScheme { const FORMAT: [u8; 8] = *b"NrProScm"; const EXTENSION: &'static str = "nps"; - const VERSION: (u16, u16) = (1, 1); + const VERSION: (u16, u16) = (1, 2); const COMPRESSION: Compression = Compression::Zstd; } impl FileFormat for Prover { const FORMAT: [u8; 8] = *b"PrvKitPr"; const EXTENSION: &'static str = "pkp"; - const VERSION: (u16, u16) = (1, 1); + const VERSION: (u16, u16) = (1, 2); const COMPRESSION: Compression = Compression::Xz; } impl FileFormat for Verifier { const FORMAT: [u8; 8] = *b"PrvKitVr"; const EXTENSION: &'static str = "pkv"; - const VERSION: (u16, u16) = (1, 2); + const VERSION: (u16, u16) = (1, 3); const COMPRESSION: Compression = Compression::Zstd; } impl FileFormat for NoirProof { const FORMAT: [u8; 8] = *b"NPSProof"; const EXTENSION: &'static str = "np"; - const VERSION: (u16, u16) = (1, 0); + const VERSION: (u16, u16) = (1, 1); const COMPRESSION: Compression = Compression::Zstd; } From 4114a3a827a9b804e297de3ab5e158b228263619 Mon Sep 17 00:00:00 2001 From: yash Date: Wed, 4 Mar 2026 20:34:09 +0530 Subject: [PATCH 3/5] fix : resolve rebase conflicts --- provekit/common/src/lib.rs | 1 + provekit/common/src/noir_proof_scheme.rs | 19 ------------------- provekit/common/src/prover.rs | 5 +++++ provekit/common/src/transcript_sponge.rs | 2 +- provekit/common/src/verifier.rs | 2 ++ provekit/prover/src/lib.rs | 2 +- .../r1cs-compiler/src/noir_proof_scheme.rs | 17 ++++++++++++++--- provekit/r1cs-compiler/src/whir_r1cs.rs | 13 +++++++++++-- tooling/cli/src/cmd/prepare.rs | 8 +++++--- tooling/provekit-bench/tests/compiler.rs | 6 +++++- 10 files changed, 45 insertions(+), 30 deletions(-) diff --git a/provekit/common/src/lib.rs b/provekit/common/src/lib.rs index b1ed6ac50..7d13aa2f1 100644 --- a/provekit/common/src/lib.rs +++ b/provekit/common/src/lib.rs @@ -20,6 +20,7 @@ use crate::{ pub use { acir::FieldElement as NoirElement, ark_bn254::Fr as FieldElement, + hash_config::HashConfig, noir_proof_scheme::{MavrosSchemeData, NoirProof, NoirProofScheme, NoirSchemeData}, prefix_covector::{OffsetCovector, PrefixCovector}, prover::{MavrosProver, NoirProver, Prover}, diff --git a/provekit/common/src/noir_proof_scheme.rs b/provekit/common/src/noir_proof_scheme.rs index fe3d197c3..87082e9cf 100644 --- a/provekit/common/src/noir_proof_scheme.rs +++ b/provekit/common/src/noir_proof_scheme.rs @@ -41,25 +41,6 @@ pub enum NoirProofScheme { Mavros(MavrosSchemeData), } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MavrosSchemeData { - #[serde(with = "crate::utils::serde_jsonify")] - pub abi: Abi, - pub num_public_inputs: usize, - pub r1cs: R1CS, - pub whir_for_witness: WhirR1CSScheme, - pub witgen_binary: Vec, - pub ad_binary: Vec, - pub constraints_layout: ConstraintsLayout, - pub witness_layout: WitnessLayout, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum NoirProofScheme { - Noir(NoirSchemeData), - Mavros(MavrosSchemeData), -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct NoirProof { pub public_inputs: PublicInputs, diff --git a/provekit/common/src/prover.rs b/provekit/common/src/prover.rs index 22cf55068..2c5670f89 100644 --- a/provekit/common/src/prover.rs +++ b/provekit/common/src/prover.rs @@ -13,6 +13,7 @@ use { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NoirProver { + pub hash_config: HashConfig, pub program: Program, pub r1cs: R1CS, pub split_witness_builders: SplitWitnessBuilders, @@ -30,6 +31,8 @@ pub struct MavrosProver { pub ad_binary: Vec, pub constraints_layout: ConstraintsLayout, pub witness_layout: WitnessLayout, + #[serde(default)] + pub hash_config: HashConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -42,6 +45,7 @@ impl Prover { pub fn from_noir_proof_scheme(scheme: NoirProofScheme) -> Self { match scheme { NoirProofScheme::Noir(d) => Prover::Noir(NoirProver { + hash_config: d.hash_config, program: d.program, r1cs: d.r1cs, split_witness_builders: d.split_witness_builders, @@ -56,6 +60,7 @@ impl Prover { ad_binary: d.ad_binary, constraints_layout: d.constraints_layout, witness_layout: d.witness_layout, + hash_config: d.hash_config, }), } } diff --git a/provekit/common/src/transcript_sponge.rs b/provekit/common/src/transcript_sponge.rs index 5af0ce322..d59fa72ab 100644 --- a/provekit/common/src/transcript_sponge.rs +++ b/provekit/common/src/transcript_sponge.rs @@ -6,7 +6,7 @@ //! per proof for Fiat-Shamir challenges, not in a tight inner loop. use { - crate::{hash_config::HashConfig, skyscraper::SkyscraperSponge}, + crate::{skyscraper::SkyscraperSponge, HashConfig}, spongefish::{instantiations, DuplexSpongeInterface}, }; diff --git a/provekit/common/src/verifier.rs b/provekit/common/src/verifier.rs index 49ce85f76..ad55d72e1 100644 --- a/provekit/common/src/verifier.rs +++ b/provekit/common/src/verifier.rs @@ -23,11 +23,13 @@ impl Verifier { r1cs: d.r1cs, whir_for_witness: Some(d.whir_for_witness), abi: d.witness_generator.abi.clone(), + hash_config: d.hash_config, }, NoirProofScheme::Mavros(d) => Self { r1cs: d.r1cs, whir_for_witness: Some(d.whir_for_witness), abi: d.abi.clone(), + hash_config: d.hash_config, }, } } diff --git a/provekit/prover/src/lib.rs b/provekit/prover/src/lib.rs index b8cf9ed69..85586ac1f 100644 --- a/provekit/prover/src/lib.rs +++ b/provekit/prover/src/lib.rs @@ -232,7 +232,7 @@ impl Prove for MavrosProver { .whir_for_witness .create_domain_separator() .instance(&Empty); - let mut merlin = ProverState::new(&ds, TranscriptSponge::default()); + let mut merlin = ProverState::new(&ds, TranscriptSponge::from_config(self.hash_config)); let commitment_1 = self .whir_for_witness diff --git a/provekit/r1cs-compiler/src/noir_proof_scheme.rs b/provekit/r1cs-compiler/src/noir_proof_scheme.rs index b8b36e84f..e281bbd1c 100644 --- a/provekit/r1cs-compiler/src/noir_proof_scheme.rs +++ b/provekit/r1cs-compiler/src/noir_proof_scheme.rs @@ -21,7 +21,10 @@ pub struct NoirCompiler; impl NoirCompiler { #[instrument(fields(size = path.as_ref().metadata().map(|m| m.len()).ok()))] - pub fn from_file(path: impl AsRef + std::fmt::Debug) -> Result { + pub fn from_file( + path: impl AsRef + std::fmt::Debug, + hash_config: provekit_common::HashConfig, + ) -> Result { let file = File::open(path).context("while opening Noir program")?; let program = serde_json::from_reader(file).context("while reading Noir program")?; @@ -29,7 +32,10 @@ impl NoirCompiler { } #[instrument(skip_all)] - pub fn from_program(program: ProgramArtifact) -> Result { + pub fn from_program( + program: ProgramArtifact, + hash_config: provekit_common::HashConfig, + ) -> Result { info!("Program noir version: {}", program.noir_version); info!("Program entry point: fn main{};", PrintAbi(&program.abi)); ensure!( @@ -91,6 +97,7 @@ impl NoirCompiler { split_witness_builders, witness_generator, whir_for_witness, + hash_config, })) } } @@ -109,6 +116,7 @@ impl MavrosCompiler { pub fn compile( basic_path: impl AsRef + std::fmt::Debug, r1cs_path: impl AsRef + std::fmt::Debug, + hash_config: provekit_common::HashConfig, ) -> Result { info!("Reading basic artifacts from {:?}", basic_path); let basic_file = File::open(&basic_path).context("while opening basic artifacts")?; @@ -146,6 +154,7 @@ impl MavrosCompiler { mavros_r1cs.witness_layout.pre_commitment_size(), mavros_r1cs.witness_layout.challenges_size, num_public_inputs > 0, + hash_config.engine_id(), ); let r1cs = convert_mavros_r1cs_to_provekit(&mavros_r1cs); @@ -159,6 +168,7 @@ impl MavrosCompiler { r1cs, constraints_layout: mavros_r1cs.constraints_layout, witness_layout: mavros_r1cs.witness_layout, + hash_config, })) } } @@ -193,7 +203,8 @@ mod tests { #[test] fn test_noir_proof_scheme_serde() { let path = PathBuf::from("../../tooling/provekit-bench/benches/poseidon_rounds.json"); - let proof_scheme = NoirCompiler::from_file(path).unwrap(); + let proof_scheme = + NoirCompiler::from_file(path, provekit_common::HashConfig::default()).unwrap(); if let NoirProofScheme::Noir(d) = &proof_scheme { test_serde(&d.r1cs); diff --git a/provekit/r1cs-compiler/src/whir_r1cs.rs b/provekit/r1cs-compiler/src/whir_r1cs.rs index e9ade1076..0b18b19f5 100644 --- a/provekit/r1cs-compiler/src/whir_r1cs.rs +++ b/provekit/r1cs-compiler/src/whir_r1cs.rs @@ -21,6 +21,7 @@ pub trait WhirR1CSSchemeBuilder { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self; fn new_from_dimensions( @@ -30,9 +31,14 @@ pub trait WhirR1CSSchemeBuilder { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self; - fn new_whir_zk_config_for_size(num_variables: usize, num_polynomials: usize) -> WhirZkConfig; + fn new_whir_zk_config_for_size( + num_variables: usize, + num_polynomials: usize, + hash_id: EngineId, + ) -> WhirZkConfig; } impl WhirR1CSSchemeBuilder for WhirR1CSScheme { @@ -103,6 +109,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self { let num_witnesses = r1cs.witness_layout.size(); let num_constraints = r1cs.constraints.len(); @@ -115,6 +122,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { w1_size, num_challenges, has_public_inputs, + hash_id, ) } @@ -125,6 +133,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { w1_size: usize, num_challenges: usize, has_public_inputs: bool, + hash_id: EngineId, ) -> Self { let m_raw = next_power_of_two(num_witnesses); let m0_raw = next_power_of_two(num_constraints); @@ -141,7 +150,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { m, m_0, a_num_terms: next_power_of_two(a_num_entries), - whir_witness: Self::new_whir_zk_config_for_size(m, 1), + whir_witness: Self::new_whir_zk_config_for_size(m, 1, hash_id), w1_size, num_challenges, has_public_inputs, diff --git a/tooling/cli/src/cmd/prepare.rs b/tooling/cli/src/cmd/prepare.rs index 9434404de..dfe08cdd8 100644 --- a/tooling/cli/src/cmd/prepare.rs +++ b/tooling/cli/src/cmd/prepare.rs @@ -1,8 +1,9 @@ use { super::Command, + std::str::FromStr, anyhow::{Context, Result}, argh::FromArgs, - provekit_common::{file::write, Prover, Verifier}, + provekit_common::{file::write, HashConfig, Prover, Verifier}, provekit_r1cs_compiler::{MavrosCompiler, NoirCompiler}, std::path::PathBuf, tracing::instrument, @@ -70,15 +71,16 @@ pub struct Args { impl Command for Args { #[instrument(skip_all)] fn run(&self) -> Result<()> { + let hash_config = HashConfig::from_str(&self.hash).map_err(|e| anyhow::anyhow!("{}", e))?; let scheme = match self.compiler { - Compiler::Noir => NoirCompiler::from_file(&self.program_path) + Compiler::Noir => NoirCompiler::from_file(&self.program_path, hash_config) .context("while compiling Noir program")?, Compiler::Mavros => { let r1cs_path = self .r1cs_path .as_ref() .context("--r1cs is required when using the mavros compiler")?; - MavrosCompiler::compile(&self.program_path, r1cs_path) + MavrosCompiler::compile(&self.program_path, r1cs_path, hash_config) .context("while compiling with Mavros")? } }; diff --git a/tooling/provekit-bench/tests/compiler.rs b/tooling/provekit-bench/tests/compiler.rs index 3b74c8e99..ef61641be 100644 --- a/tooling/provekit-bench/tests/compiler.rs +++ b/tooling/provekit-bench/tests/compiler.rs @@ -38,7 +38,11 @@ fn test_noir_compiler(test_case_path: impl AsRef) { let circuit_path = test_case_path.join(format!("target/{package_name}.json")); let witness_file_path = test_case_path.join("Prover.toml"); - let schema = NoirCompiler::from_file(&circuit_path).expect("Reading proof scheme"); + let schema = NoirCompiler::from_file( + &circuit_path, + provekit_common::HashConfig::default(), + ) + .expect("Reading proof scheme"); let prover = Prover::from_noir_proof_scheme(schema.clone()); let mut verifier = Verifier::from_noir_proof_scheme(schema.clone()); From d9a3f5cf231f2f2d7cbd39fa2a1fbcf07b9295e5 Mon Sep 17 00:00:00 2001 From: yash Date: Wed, 4 Mar 2026 20:34:41 +0530 Subject: [PATCH 4/5] cargo fmt --- tooling/cli/src/cmd/prepare.rs | 3 +-- tooling/provekit-bench/tests/compiler.rs | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tooling/cli/src/cmd/prepare.rs b/tooling/cli/src/cmd/prepare.rs index dfe08cdd8..a491e69bc 100644 --- a/tooling/cli/src/cmd/prepare.rs +++ b/tooling/cli/src/cmd/prepare.rs @@ -1,11 +1,10 @@ use { super::Command, - std::str::FromStr, anyhow::{Context, Result}, argh::FromArgs, provekit_common::{file::write, HashConfig, Prover, Verifier}, provekit_r1cs_compiler::{MavrosCompiler, NoirCompiler}, - std::path::PathBuf, + std::{path::PathBuf, str::FromStr}, tracing::instrument, }; diff --git a/tooling/provekit-bench/tests/compiler.rs b/tooling/provekit-bench/tests/compiler.rs index ef61641be..4746ffe04 100644 --- a/tooling/provekit-bench/tests/compiler.rs +++ b/tooling/provekit-bench/tests/compiler.rs @@ -38,11 +38,8 @@ fn test_noir_compiler(test_case_path: impl AsRef) { let circuit_path = test_case_path.join(format!("target/{package_name}.json")); let witness_file_path = test_case_path.join("Prover.toml"); - let schema = NoirCompiler::from_file( - &circuit_path, - provekit_common::HashConfig::default(), - ) - .expect("Reading proof scheme"); + let schema = NoirCompiler::from_file(&circuit_path, provekit_common::HashConfig::default()) + .expect("Reading proof scheme"); let prover = Prover::from_noir_proof_scheme(schema.clone()); let mut verifier = Verifier::from_noir_proof_scheme(schema.clone()); From 46da48fdf9ff12f71a8f768efebd197987af43c5 Mon Sep 17 00:00:00 2001 From: yash Date: Thu, 5 Mar 2026 14:38:12 +0530 Subject: [PATCH 5/5] address PR review comments --- provekit/common/src/noir_proof_scheme.rs | 1 - provekit/common/src/prover.rs | 1 - provekit/common/src/transcript_sponge.rs | 12 ++++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/provekit/common/src/noir_proof_scheme.rs b/provekit/common/src/noir_proof_scheme.rs index 87082e9cf..ee52e9599 100644 --- a/provekit/common/src/noir_proof_scheme.rs +++ b/provekit/common/src/noir_proof_scheme.rs @@ -31,7 +31,6 @@ pub struct MavrosSchemeData { pub ad_binary: Vec, pub constraints_layout: ConstraintsLayout, pub witness_layout: WitnessLayout, - #[serde(default)] pub hash_config: HashConfig, } diff --git a/provekit/common/src/prover.rs b/provekit/common/src/prover.rs index 2c5670f89..974850607 100644 --- a/provekit/common/src/prover.rs +++ b/provekit/common/src/prover.rs @@ -31,7 +31,6 @@ pub struct MavrosProver { pub ad_binary: Vec, pub constraints_layout: ConstraintsLayout, pub witness_layout: WitnessLayout, - #[serde(default)] pub hash_config: HashConfig, } diff --git a/provekit/common/src/transcript_sponge.rs b/provekit/common/src/transcript_sponge.rs index d59fa72ab..28132f2b6 100644 --- a/provekit/common/src/transcript_sponge.rs +++ b/provekit/common/src/transcript_sponge.rs @@ -8,6 +8,7 @@ use { crate::{skyscraper::SkyscraperSponge, HashConfig}, spongefish::{instantiations, DuplexSpongeInterface}, + std::fmt, }; /// Fiat-Shamir transcript sponge, selected at runtime by [`HashConfig`]. @@ -22,6 +23,17 @@ pub enum TranscriptSponge { Skyscraper(SkyscraperSponge), } +impl fmt::Debug for TranscriptSponge { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Sha256(_) => f.debug_tuple("Sha256").finish(), + Self::Blake3(_) => f.debug_tuple("Blake3").finish(), + Self::Keccak(_) => f.debug_tuple("Keccak").finish(), + Self::Skyscraper(_) => f.debug_tuple("Skyscraper").finish(), + } + } +} + impl TranscriptSponge { /// Create a sponge matching the given hash configuration. pub fn from_config(config: HashConfig) -> Self {