Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crypto/crypto/src/fiat_shamir/default_transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -28,7 +28,7 @@ impl<F: HasDefaultTranscript> Clone for DefaultTranscript<F> {
impl<F> DefaultTranscript<F>
where
F: HasDefaultTranscript,
FieldElement<F>: ByteConversion,
FieldElement<F>: AsBytes,
{
pub fn new(data: &[u8]) -> Self {
let mut res = Self {
Expand All @@ -50,7 +50,7 @@ where
impl<F> Default for DefaultTranscript<F>
where
F: HasDefaultTranscript,
FieldElement<F>: ByteConversion,
FieldElement<F>: AsBytes,
{
fn default() -> Self {
Self::new(&[])
Expand All @@ -60,14 +60,14 @@ where
impl<F> IsTranscript<F> for DefaultTranscript<F>
where
F: HasDefaultTranscript,
FieldElement<F>: ByteConversion,
FieldElement<F>: AsBytes,
{
fn append_bytes(&mut self, new_bytes: &[u8]) {
self.hasher.update(new_bytes);
}

fn append_field_element(&mut self, element: &FieldElement<F>) {
self.append_bytes(&element.to_bytes_be());
element.stream_bytes(&mut |b| self.hasher.update(b));
}

fn state(&self) -> [u8; 32] {
Expand All @@ -94,7 +94,7 @@ where
impl<F, S> IsStarkTranscript<F, S> for DefaultTranscript<F>
where
F: HasDefaultTranscript,
FieldElement<F>: ByteConversion,
FieldElement<F>: AsBytes,
S: IsField + IsSubFieldOf<F>,
{
// nothing to implement: sample_z_ood uses the default body
Expand Down
2 changes: 1 addition & 1 deletion crypto/crypto/src/merkle_tree/backends/field_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ where

fn hash_data(input: &FieldElement<F>) -> [u8; NUM_BYTES] {
let mut hasher = D::new();
hasher.update(input.as_bytes());
input.stream_bytes(&mut |b| hasher.update(b));
hasher.finalize().into()
}

Expand Down
31 changes: 22 additions & 9 deletions crypto/crypto/src/merkle_tree/backends/field_element_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ where

fn hash_data(input: &[FieldElement<F>; 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
Expand Down Expand Up @@ -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<F>]]) -> [u8; NUM_BYTES]
where
F: IsField,
FieldElement<F>: 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<F, D: Digest, const NUM_BYTES: usize> IsMerkleTreeBackend
Expand All @@ -100,13 +119,7 @@ where
type Data = Vec<FieldElement<F>>;

fn hash_data(input: &Vec<FieldElement<F>>) -> [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] {
Expand Down
18 changes: 16 additions & 2 deletions crypto/crypto/src/merkle_tree/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,28 @@ pub struct Proof<T: PartialEq + Eq> {
pub fn verify_merkle_path<B>(
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::<B>(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<B>(
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);
Expand Down
7 changes: 7 additions & 0 deletions crypto/math/src/field/extensions_goldilocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,13 @@ impl AsBytes for FieldElement<Degree3GoldilocksExtensionField> {
fn as_bytes(&self) -> alloc::vec::Vec<u8> {
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 {
Expand Down
5 changes: 5 additions & 0 deletions crypto/math/src/field/goldilocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@ impl AsBytes for FieldElement<GoldilocksField> {
fn as_bytes(&self) -> alloc::vec::Vec<u8> {
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
Expand Down
6 changes: 6 additions & 0 deletions crypto/math/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ pub trait ByteConversion {
pub trait AsBytes {
/// Default serialize without args
fn as_bytes(&self) -> alloc::vec::Vec<u8>;

/// 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")]
Expand Down
36 changes: 20 additions & 16 deletions crypto/stark/src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
},
};
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;
use math::field::element::ArchivedFieldElement;
#[cfg(not(feature = "test_fiat_shamir"))]
use log::error;
Expand Down Expand Up @@ -369,13 +369,15 @@ where
E::BaseType: rkyv::Archive,
Field: IsSubFieldOf<E>,
{
let mut value = evals(&opening.evaluations).to_vec();
value.extend_from_slice(evals(&opening.evaluations_sym));
verify_merkle_path::<BatchedMerkleTreeBackend<E>>(
let leaf_hash = BatchedMerkleTreeBackend::<E>::hash_data_parts(&[
evals(&opening.evaluations),
evals(&opening.evaluations_sym),
]);
verify_merkle_path_from_hash::<BatchedMerkleTreeBackend<E>>(
opening.proof.merkle_path.as_slice(),
root,
iota,
&value,
leaf_hash,
)
}

Expand Down Expand Up @@ -426,6 +428,8 @@ where

/// 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: &ArchivedDeepPolynomialOpening<Field, FieldExtension>,
composition_poly_merkle_root: &Commitment,
Expand All @@ -435,14 +439,10 @@ where
FieldElement<Field>: AsBytes + Sync + Send,
FieldElement<FieldExtension>: AsBytes + Sync + Send,
{
let mut value = evals(&deep_poly_openings.composition_poly.evaluations).to_vec();
value.extend_from_slice(evals(&deep_poly_openings.composition_poly.evaluations_sym));

verify_merkle_path::<BatchedMerkleTreeBackend<FieldExtension>>(
deep_poly_openings.composition_poly.proof.merkle_path.as_slice(),
Self::verify_opening_pair::<FieldExtension>(
&deep_poly_openings.composition_poly,
composition_poly_merkle_root,
*iota,
&value,
)
}

Expand Down Expand Up @@ -485,17 +485,21 @@ where
FieldElement<Field>: AsBytes + Sync + Send,
FieldElement<FieldExtension>: 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::<FieldExtension>::hash_data_parts(&[
core::slice::from_ref(first),
core::slice::from_ref(second),
]);

verify_merkle_path::<BatchedMerkleTreeBackend<FieldExtension>>(
verify_merkle_path_from_hash::<BatchedMerkleTreeBackend<FieldExtension>>(
auth_path_sym,
merkle_root,
iota >> 1,
&evaluations,
leaf_hash,
)
}

Expand Down
Loading