diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 71dd4e1fe..5b0de361e 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -784,17 +784,16 @@ fn cmd_verify_continuation( return ExitCode::FAILURE; } }; - let bundle: prover::continuation::ContinuationProof = match rkyv::from_bytes::< - prover::continuation::ContinuationProof, - rkyv::rancor::Error, - >(&proof_bytes) - { - Ok(p) => p, - Err(e) => { - eprintln!("Failed to deserialize proof: {}", e); - return ExitCode::FAILURE; - } - }; + let bundle: prover::continuation::ContinuationProof = + match rkyv::from_bytes::( + &proof_bytes, + ) { + Ok(p) => p, + Err(e) => { + eprintln!("Failed to deserialize proof: {}", e); + return ExitCode::FAILURE; + } + }; let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) { Ok(opts) => opts, diff --git a/crypto/crypto/src/fiat_shamir/default_transcript.rs b/crypto/crypto/src/fiat_shamir/default_transcript.rs index 7c3c0bf99..60922aec2 100644 --- a/crypto/crypto/src/fiat_shamir/default_transcript.rs +++ b/crypto/crypto/src/fiat_shamir/default_transcript.rs @@ -6,7 +6,7 @@ use math::{ element::FieldElement, traits::{HasDefaultTranscript, IsField, IsSubFieldOf}, }, - traits::ByteConversion, + traits::AsBytes, }; use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; use sha3::{Digest, Keccak256}; @@ -28,7 +28,7 @@ impl Clone for DefaultTranscript { impl DefaultTranscript where F: HasDefaultTranscript, - FieldElement: ByteConversion, + FieldElement: AsBytes, { pub fn new(data: &[u8]) -> Self { let mut res = Self { @@ -50,7 +50,7 @@ where impl Default for DefaultTranscript where F: HasDefaultTranscript, - FieldElement: ByteConversion, + FieldElement: AsBytes, { fn default() -> Self { Self::new(&[]) @@ -60,14 +60,14 @@ where impl IsTranscript for DefaultTranscript where F: HasDefaultTranscript, - FieldElement: ByteConversion, + FieldElement: AsBytes, { fn append_bytes(&mut self, new_bytes: &[u8]) { self.hasher.update(new_bytes); } fn append_field_element(&mut self, element: &FieldElement) { - self.append_bytes(&element.to_bytes_be()); + element.stream_bytes(&mut |b| self.hasher.update(b)); } fn state(&self) -> [u8; 32] { @@ -94,7 +94,7 @@ where impl IsStarkTranscript for DefaultTranscript where F: HasDefaultTranscript, - FieldElement: ByteConversion, + FieldElement: AsBytes, S: IsField + IsSubFieldOf, { // nothing to implement: sample_z_ood uses the default body diff --git a/crypto/crypto/src/merkle_tree/backends/field_element.rs b/crypto/crypto/src/merkle_tree/backends/field_element.rs index d5d5c32d7..e8f106f5a 100644 --- a/crypto/crypto/src/merkle_tree/backends/field_element.rs +++ b/crypto/crypto/src/merkle_tree/backends/field_element.rs @@ -34,7 +34,7 @@ where fn hash_data(input: &FieldElement) -> [u8; NUM_BYTES] { let mut hasher = D::new(); - hasher.update(input.as_bytes()); + input.stream_bytes(&mut |b| hasher.update(b)); hasher.finalize().into() } diff --git a/crypto/crypto/src/merkle_tree/backends/field_element_vector.rs b/crypto/crypto/src/merkle_tree/backends/field_element_vector.rs index 25ba807c6..870eb5c17 100644 --- a/crypto/crypto/src/merkle_tree/backends/field_element_vector.rs +++ b/crypto/crypto/src/merkle_tree/backends/field_element_vector.rs @@ -39,8 +39,8 @@ where fn hash_data(input: &[FieldElement; 2]) -> [u8; NUM_BYTES] { let mut hasher = D::new(); - hasher.update(input[0].as_bytes()); - hasher.update(input[1].as_bytes()); + input[0].stream_bytes(&mut |b| hasher.update(b)); + input[1].stream_bytes(&mut |b| hasher.update(b)); let mut result_hash = [0_u8; NUM_BYTES]; result_hash.copy_from_slice(&hasher.finalize()); result_hash @@ -86,6 +86,25 @@ where result.copy_from_slice(&hasher.finalize()); result } + + /// Hashes the concatenation of `parts` (each a slice of field elements) as a + /// single leaf, without allocating a buffer to concatenate them first. Byte- + /// identical to hashing `parts.concat()` via [`Self::hash_data`]. + pub fn hash_data_parts(parts: &[&[FieldElement]]) -> [u8; NUM_BYTES] + where + F: IsField, + FieldElement: AsBytes, + { + let mut hasher = D::new(); + for part in parts { + for element in part.iter() { + element.stream_bytes(&mut |b| hasher.update(b)); + } + } + let mut result = [0u8; NUM_BYTES]; + result.copy_from_slice(&hasher.finalize()); + result + } } impl IsMerkleTreeBackend @@ -100,13 +119,7 @@ where type Data = Vec>; fn hash_data(input: &Vec>) -> [u8; NUM_BYTES] { - let mut hasher = D::new(); - for element in input.iter() { - hasher.update(element.as_bytes()); - } - let mut result_hash = [0_u8; NUM_BYTES]; - result_hash.copy_from_slice(&hasher.finalize()); - result_hash + Self::hash_data_parts(&[input.as_slice()]) } fn hash_new_parent(left: &[u8; NUM_BYTES], right: &[u8; NUM_BYTES]) -> [u8; NUM_BYTES] { diff --git a/crypto/crypto/src/merkle_tree/proof.rs b/crypto/crypto/src/merkle_tree/proof.rs index 2bbcfb3c5..518412ec8 100644 --- a/crypto/crypto/src/merkle_tree/proof.rs +++ b/crypto/crypto/src/merkle_tree/proof.rs @@ -30,14 +30,28 @@ pub struct Proof { pub fn verify_merkle_path( merkle_path: &[B::Node], root_hash: &B::Node, - mut index: usize, + index: usize, value: &B::Data, ) -> bool where B: IsMerkleTreeBackend, { - let mut hashed_value = B::hash_data(value); + verify_merkle_path_from_hash::(merkle_path, root_hash, index, B::hash_data(value)) +} +/// Same check as [`verify_merkle_path`], starting from an already-computed leaf +/// hash. Lets callers that can hash their leaf data more efficiently than the +/// backend's `hash_data` (e.g. hashing several slices without concatenating +/// them first) still walk the shared path-verification logic. +pub fn verify_merkle_path_from_hash( + merkle_path: &[B::Node], + root_hash: &B::Node, + mut index: usize, + mut hashed_value: B::Node, +) -> bool +where + B: IsMerkleTreeBackend, +{ for sibling_node in merkle_path.iter() { if index.is_multiple_of(2) { hashed_value = B::hash_new_parent(&hashed_value, sibling_node); diff --git a/crypto/math/src/field/extensions_goldilocks.rs b/crypto/math/src/field/extensions_goldilocks.rs index d6bac98df..63a9992ed 100644 --- a/crypto/math/src/field/extensions_goldilocks.rs +++ b/crypto/math/src/field/extensions_goldilocks.rs @@ -554,6 +554,13 @@ impl AsBytes for FieldElement { fn as_bytes(&self) -> alloc::vec::Vec { self.to_bytes_be() } + + #[inline(always)] + fn stream_bytes(&self, sink: &mut dyn FnMut(&[u8])) { + let mut buf = [0u8; 24]; + crate::traits::ByteConversion::write_bytes_be(self, &mut buf); + sink(&buf); + } } impl HasDefaultTranscript for Degree3GoldilocksExtensionField { diff --git a/crypto/math/src/field/goldilocks.rs b/crypto/math/src/field/goldilocks.rs index 082d57325..1d60ee5b2 100644 --- a/crypto/math/src/field/goldilocks.rs +++ b/crypto/math/src/field/goldilocks.rs @@ -488,6 +488,11 @@ impl AsBytes for FieldElement { fn as_bytes(&self) -> alloc::vec::Vec { ByteConversion::to_bytes_be(self) } + + #[inline(always)] + fn stream_bytes(&self, sink: &mut dyn FnMut(&[u8])) { + sink(&self.canonical_u64().to_be_bytes()); + } } // Implement IsPrimeField for the native Goldilocks diff --git a/crypto/math/src/traits.rs b/crypto/math/src/traits.rs index 0e902c6ff..e16b5bfb1 100644 --- a/crypto/math/src/traits.rs +++ b/crypto/math/src/traits.rs @@ -39,6 +39,12 @@ pub trait ByteConversion { pub trait AsBytes { /// Default serialize without args fn as_bytes(&self) -> alloc::vec::Vec; + + /// Streams the byte representation to `sink` without heap-allocating a `Vec`. + /// Default falls back to `as_bytes`; override for zero-allocation hashing/transcript hot paths. + fn stream_bytes(&self, sink: &mut dyn FnMut(&[u8])) { + sink(&self.as_bytes()); + } } #[cfg(feature = "alloc")] diff --git a/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs b/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs index 823c2cef5..a1f90f197 100644 --- a/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs +++ b/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs @@ -16,7 +16,15 @@ use math::{ traits::AsBytes, }; use std::marker::PhantomData; -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct PublicInputs where diff --git a/crypto/stark/src/examples/fibonacci_multi_column.rs b/crypto/stark/src/examples/fibonacci_multi_column.rs index 127836ca9..64c9f57c2 100644 --- a/crypto/stark/src/examples/fibonacci_multi_column.rs +++ b/crypto/stark/src/examples/fibonacci_multi_column.rs @@ -20,7 +20,15 @@ use math::field::{ /// Public inputs for the multi-column Fibonacci AIR. /// Contains the initial values (first two elements) for each column. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct FibonacciMultiColumnPublicInputs { /// Initial values for each column: (a0, a1) pairs diff --git a/crypto/stark/src/examples/fibonacci_rap.rs b/crypto/stark/src/examples/fibonacci_rap.rs index d715f9a9d..c00ffdac8 100644 --- a/crypto/stark/src/examples/fibonacci_rap.rs +++ b/crypto/stark/src/examples/fibonacci_rap.rs @@ -71,7 +71,15 @@ where phantom: PhantomData, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct FibonacciRAPPublicInputs where diff --git a/crypto/stark/src/examples/quadratic_air.rs b/crypto/stark/src/examples/quadratic_air.rs index bf0b3374e..08354ac59 100644 --- a/crypto/stark/src/examples/quadratic_air.rs +++ b/crypto/stark/src/examples/quadratic_air.rs @@ -50,7 +50,15 @@ where phantom: PhantomData, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct QuadraticPublicInputs where diff --git a/crypto/stark/src/examples/read_only_memory.rs b/crypto/stark/src/examples/read_only_memory.rs index 935eb3e59..ee07ee7e7 100644 --- a/crypto/stark/src/examples/read_only_memory.rs +++ b/crypto/stark/src/examples/read_only_memory.rs @@ -82,7 +82,15 @@ where phantom: PhantomData, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct ReadOnlyPublicInputs where diff --git a/crypto/stark/src/examples/read_only_memory_logup.rs b/crypto/stark/src/examples/read_only_memory_logup.rs index a1fe964af..9068e7276 100644 --- a/crypto/stark/src/examples/read_only_memory_logup.rs +++ b/crypto/stark/src/examples/read_only_memory_logup.rs @@ -99,7 +99,15 @@ where phantom: PhantomData<(F, E)>, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct LogReadOnlyPublicInputs where diff --git a/crypto/stark/src/examples/simple_addition.rs b/crypto/stark/src/examples/simple_addition.rs index 433496422..df2e6d8c0 100644 --- a/crypto/stark/src/examples/simple_addition.rs +++ b/crypto/stark/src/examples/simple_addition.rs @@ -54,7 +54,15 @@ where phantom: PhantomData, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct SimpleAdditionPublicInputs where diff --git a/crypto/stark/src/examples/simple_fibonacci.rs b/crypto/stark/src/examples/simple_fibonacci.rs index 917d0be55..4df8bcd28 100644 --- a/crypto/stark/src/examples/simple_fibonacci.rs +++ b/crypto/stark/src/examples/simple_fibonacci.rs @@ -50,7 +50,15 @@ where phantom: PhantomData, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[derive( + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, +)] #[serde(bound = "FieldElement: serde::Serialize + serde::de::DeserializeOwned")] pub struct FibonacciPublicInputs where diff --git a/crypto/stark/src/fri/fri_decommit.rs b/crypto/stark/src/fri/fri_decommit.rs index 252483c1a..0c1c24112 100644 --- a/crypto/stark/src/fri/fri_decommit.rs +++ b/crypto/stark/src/fri/fri_decommit.rs @@ -5,7 +5,13 @@ use math::field::traits::IsField; use crate::config::Commitment; #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "")] pub struct FriDecommitment { diff --git a/crypto/stark/src/lookup.rs b/crypto/stark/src/lookup.rs index 2f017404e..abd3218b8 100644 --- a/crypto/stark/src/lookup.rs +++ b/crypto/stark/src/lookup.rs @@ -1499,7 +1499,13 @@ impl BusInteraction { /// For the circular constraint, `table_contribution / N` is the per-row offset /// that makes the accumulated column wrap to zero at row N-1. #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "")] pub struct BusPublicInputs diff --git a/crypto/stark/src/proof/options.rs b/crypto/stark/src/proof/options.rs index 277f5d9f3..15e2c8909 100644 --- a/crypto/stark/src/proof/options.rs +++ b/crypto/stark/src/proof/options.rs @@ -41,7 +41,13 @@ impl fmt::Display for ProofOptionsError { /// - `fri_final_poly_log_degree`: log2 degree bound at which FRI terminates folding #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive( - Clone, Debug, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Clone, + Debug, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] pub struct ProofOptions { pub blowup_factor: u8, diff --git a/crypto/stark/src/proof/stark.rs b/crypto/stark/src/proof/stark.rs index 471c9f74d..f04370ea5 100644 --- a/crypto/stark/src/proof/stark.rs +++ b/crypto/stark/src/proof/stark.rs @@ -9,7 +9,13 @@ use crate::{ }; #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "")] /// Opening of a bit-reversed, row-paired commitment at one FRI query. @@ -25,7 +31,13 @@ pub struct PolynomialOpenings { } #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "")] pub struct DeepPolynomialOpening, E: IsField> { @@ -40,7 +52,13 @@ pub struct DeepPolynomialOpening, E: IsField> { pub type DeepPolynomialOpenings = Vec>; #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "PI: serde::Serialize + serde::de::DeserializeOwned")] pub struct StarkProof, E: IsField, PI> { @@ -85,7 +103,13 @@ pub struct StarkProof, E: IsField, PI> { /// Used for multi-table proving where tables are linked via bus (LogUp). /// Returned by `Prover::multi_prove` and verified by `Verifier::multi_verify`. #[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize + Debug, + Clone, + serde::Serialize, + serde::Deserialize, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize, )] #[serde(bound = "PI: serde::Serialize + serde::de::DeserializeOwned")] pub struct MultiProof, E: IsField, PI> { diff --git a/crypto/stark/src/proof/view.rs b/crypto/stark/src/proof/view.rs index ba39a8afc..84c530e19 100644 --- a/crypto/stark/src/proof/view.rs +++ b/crypto/stark/src/proof/view.rs @@ -8,8 +8,8 @@ //! with no serialization and no logic duplication. use crate::config::Commitment; -use crate::fri::fri_decommit::{ArchivedFriDecommitment, FriDecommitment}; use crate::frame::Frame; +use crate::fri::fri_decommit::{ArchivedFriDecommitment, FriDecommitment}; use crate::proof::stark::{ ArchivedDeepPolynomialOpening, ArchivedPolynomialOpenings, ArchivedStarkProof, DeepPolynomialOpening, PolynomialOpenings, StarkProof, @@ -138,7 +138,10 @@ where pub fn aux_trace_polys(&self) -> Option> { match self { - Self::Owned(p) => p.aux_trace_polys.as_ref().map(PolynomialOpeningsView::Owned), + Self::Owned(p) => p + .aux_trace_polys + .as_ref() + .map(PolynomialOpeningsView::Owned), Self::Archived(p) => p .aux_trace_polys .as_ref() @@ -407,7 +410,10 @@ where /// out (it's a single field element, not worth a dedicated view type). pub fn bus_table_contribution(&self) -> Option> { match self { - Self::Owned(p) => p.bus_public_inputs.as_ref().map(|b| b.table_contribution.clone()), + Self::Owned(p) => p + .bus_public_inputs + .as_ref() + .map(|b| b.table_contribution.clone()), Self::Archived(p) => p .bus_public_inputs .as_ref() @@ -430,7 +436,9 @@ where { match self { Self::Owned(p) => Some(p.public_inputs.clone()), - Self::Archived(p) => rkyv::deserialize::(&p.public_inputs).ok(), + Self::Archived(p) => { + rkyv::deserialize::(&p.public_inputs).ok() + } } } } diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 510325738..30a60e080 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -5,20 +5,18 @@ use super::{ proof::stark::StarkProof, traits::{AIR, TransitionEvaluationContext}, }; +pub use crate::proof::view::PiDeserializer; use crate::{ config::Commitment, domain::new_verifier_domain, - lookup::{ - BusPublicInputs, LOGUP_CHALLENGE_ALPHA, LOGUP_NUM_CHALLENGES, compute_alpha_powers, - }, + lookup::{BusPublicInputs, LOGUP_CHALLENGE_ALPHA, LOGUP_NUM_CHALLENGES, compute_alpha_powers}, proof::stark::{ArchivedStarkProof, MultiProof}, proof::view::{ DeepPolynomialOpeningView, FriDecommitmentView, PolynomialOpeningsView, StarkProofView, }, }; -pub use crate::proof::view::PiDeserializer; use crypto::fiat_shamir::is_transcript::IsStarkTranscript; -use crypto::merkle_tree::proof::verify_merkle_path; +use crypto::merkle_tree::proof::verify_merkle_path_from_hash; #[cfg(not(feature = "test_fiat_shamir"))] use log::error; #[cfg(feature = "debug-checks")] @@ -31,10 +29,10 @@ use math::{ }, traits::AsBytes, }; +use std::collections::HashMap; use std::marker::PhantomData; #[cfg(feature = "instruments")] use std::time::Instant; -use std::collections::HashMap; /// A default STARK verifier implementing `IsStarkVerifier`. pub struct Verifier< @@ -138,7 +136,9 @@ pub trait IsStarkVerifier< let trace_length = proof.trace_length(); // Owned `BusPublicInputs` (just the table contribution L — one field // element) reconstructed for the AIR boundary call. - let bus_public_inputs = proof.bus_table_contribution().map(BusPublicInputs::from_contribution); + let bus_public_inputs = proof + .bus_table_contribution() + .map(BusPublicInputs::from_contribution); let boundary_constraints = air.boundary_constraints( public_inputs, &challenges.rap_challenges, @@ -413,9 +413,16 @@ pub trait IsStarkVerifier< E::BaseType: rkyv::Archive, Field: IsSubFieldOf, { - let mut value = opening.evaluations().to_vec(); - value.extend_from_slice(opening.evaluations_sym()); - verify_merkle_path::>(opening.merkle_path(), root, iota, &value) + let leaf_hash = BatchedMerkleTreeBackend::::hash_data_parts(&[ + &opening.evaluations(), + &opening.evaluations_sym(), + ]); + verify_merkle_path_from_hash::>( + opening.merkle_path(), + root, + iota, + leaf_hash, + ) } /// Verify opening Open(tā±¼(D_LDE), šœ) and Open(tā±¼(D_LDE), -šœ) for all trace polynomials tā±¼, @@ -465,6 +472,8 @@ pub trait IsStarkVerifier< /// Verify opening Open(Hįµ¢(D_LDE), šœ) and Open(Hįµ¢(D_LDE), -šœ) for all parts Hįµ¢of the composition /// polynomial, where šœ and -šœ are the elements corresponding to the index challenge `iota`. + /// The composition-poly opening has the identical row-pair leaf layout as + /// `verify_opening_pair`, so it's the same check with `E = FieldExtension`. fn verify_composition_poly_opening( deep_poly_openings: DeepPolynomialOpeningView<'_, Field, FieldExtension>, composition_poly_merkle_root: &Commitment, @@ -474,15 +483,10 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let composition_poly = deep_poly_openings.composition_poly(); - let mut value = composition_poly.evaluations().to_vec(); - value.extend_from_slice(composition_poly.evaluations_sym()); - - verify_merkle_path::>( - composition_poly.merkle_path(), - composition_poly_merkle_root, + Self::verify_opening_pair::( + deep_poly_openings.composition_poly(), + &composition_poly_merkle_root, *iota, - &value, ) } @@ -524,17 +528,21 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let evaluations = if iota % 2 == 1 { - vec![evaluation_sym.clone(), evaluation.clone()] + let (first, second) = if iota % 2 == 1 { + (evaluation_sym, evaluation) } else { - vec![evaluation.clone(), evaluation_sym.clone()] + (evaluation, evaluation_sym) }; + let leaf_hash = BatchedMerkleTreeBackend::::hash_data_parts(&[ + core::slice::from_ref(first), + core::slice::from_ref(second), + ]); - verify_merkle_path::>( + verify_merkle_path_from_hash::>( auth_path_sym, merkle_root, iota >> 1, - &evaluations, + leaf_hash, ) } @@ -828,8 +836,11 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let views: Vec> = - multi_proof.proofs.iter().map(StarkProofView::Owned).collect(); + let views: Vec> = multi_proof + .proofs + .iter() + .map(StarkProofView::Owned) + .collect(); Self::multi_verify_views(airs, &views, transcript, expected_bus_balance) } @@ -1100,7 +1111,9 @@ pub trait IsStarkVerifier< // <<<< Receive challenge: š›½ let beta = transcript.sample_field_element(); let trace_length = proof.trace_length(); - let bus_public_inputs = proof.bus_table_contribution().map(BusPublicInputs::from_contribution); + let bus_public_inputs = proof + .bus_table_contribution() + .map(BusPublicInputs::from_contribution); let num_boundary_constraints = air .boundary_constraints( public_inputs, diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 27e7e1c79..06dcb7ed3 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -301,9 +301,8 @@ pub fn verify_recursion_blob<'a>( // precisely so the archive lands aligned at // `PRIVATE_INPUT_START + 4 + PREFIX_LEN`), so the in-place doubleword // loads do not trap. - let archive_bytes = recursion_archive_bytes(blob).ok_or_else(|| { - Error::Execution(String::from("recursion blob: bad magic or version")) - })?; + let archive_bytes = recursion_archive_bytes(blob) + .ok_or_else(|| Error::Execution(String::from("recursion blob: bad magic or version")))?; // A host caller's buffer carries no alignment guarantee (`Vec` is // align-1) — in-place access there would be UB. Fall back to one aligned @@ -337,9 +336,11 @@ pub fn verify_recursion_blob<'a>( RkyvError, >(&archived.vm_proof.runtime_page_ranges) .map_err(|e| Error::Execution(format!("rkyv deserialize page ranges failed: {e}")))?; - let page_commitments: Vec<(u64, Commitment)> = - rkyv::deserialize::, RkyvError>(&archived.page_commitments) - .map_err(|e| Error::Execution(format!("rkyv deserialize page commitments failed: {e}")))?; + let page_commitments: Vec<(u64, Commitment)> = rkyv::deserialize::< + Vec<(u64, Commitment)>, + RkyvError, + >(&archived.page_commitments) + .map_err(|e| Error::Execution(format!("rkyv deserialize page commitments failed: {e}")))?; let num_private_input_pages = archived.vm_proof.num_private_input_pages.to_native() as usize; // Bytes read straight from the archived buffer (zero-copy). let inner_elf: &[u8] = archived.inner_elf.as_slice(); @@ -1255,8 +1256,12 @@ pub fn verify_with_options( decode_commitment: Option, page_commitments: Option<&[(u64, Commitment)]>, ) -> Result { - let views: Vec> = - vm_proof.proof.proofs.iter().map(StarkProofView::Owned).collect(); + let views: Vec> = vm_proof + .proof + .proofs + .iter() + .map(StarkProofView::Owned) + .collect(); verify_proof_parts( &views,