Skip to content
Merged
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 @@ -8,7 +8,7 @@ use math::{
element::FieldElement,
traits::{HasDefaultTranscript, IsField, IsSubFieldOf},
},
traits::ByteConversion,
traits::AsBytes,
};
use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};

Expand All @@ -29,7 +29,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 @@ -51,7 +51,7 @@ where
impl<F> Default for DefaultTranscript<F>
where
F: HasDefaultTranscript,
FieldElement<F>: ByteConversion,
FieldElement<F>: AsBytes,
{
fn default() -> Self {
Self::new(&[])
Expand All @@ -61,14 +61,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 @@ -95,7 +95,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
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 @@ -102,7 +102,7 @@ where
pub fn hash_data_from_slices(a: &[FieldElement<F>], b: &[FieldElement<F>]) -> [u8; NUM_BYTES] {
let mut hasher = D::new();
for element in a.iter().chain(b.iter()) {
hasher.update(element.as_bytes());
element.stream_bytes(&mut |bytes| hasher.update(bytes));
}
let mut result_hash = [0_u8; NUM_BYTES];
result_hash.copy_from_slice(&hasher.finalize());
Expand Down
11 changes: 11 additions & 0 deletions crypto/math/src/field/extensions_goldilocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,17 @@ impl AsBytes for FieldElement<Degree3GoldilocksExtensionField> {
fn as_bytes(&self) -> alloc::vec::Vec<u8> {
self.to_bytes_be()
}

// One sink call over a stack buffer instead of three (one per limb): each
// sink call lands as its own `Digest::update` on the guest, and dyn dispatch
// here is fully devirtualized by the #[inline(always)] chain, so call count
// — not indirection — is the cost being cut.
#[inline(always)]
fn stream_bytes(&self, sink: &mut dyn FnMut(&[u8])) {
let mut buf = [0u8; 24];
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
Loading