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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [BREAKING] Refactored BLAKE3 to use `Digest<N>` struct, added `Digest192` type alias ([#811](https://github.com/0xMiden/crypto/pull/811)).
- [BREAKING] Removed `hashbrown` dependency and `hashmaps` feature; `Map`/`Set` type aliases are now tied to the `std` feature ([#813](https://github.com/0xMiden/crypto/pull/813)).
- [BREAKING] Renamed `NodeIndex::value()` to `NodeIndex::position()`, `NodeIndex::is_value_odd()` to `NodeIndex::is_position_odd()`, and `LeafIndex::value()` to `LeafIndex::position()` ([#814](https://github.com/0xMiden/crypto/pull/814)).
- Added `hash_iter()` to `Rpo256`, `Rpx256`, and `Poseidon2` for hashing field elements from iterators without allocation ([#823](https://github.com/0xMiden/crypto/pull/823)).

## 0.22.2 (2026-02-01)

Expand Down
44 changes: 44 additions & 0 deletions miden-crypto/src/hash/algebraic_sponge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,50 @@ pub(crate) trait AlgebraicSponge {
Word::new(state[DIGEST_RANGE].try_into().unwrap())
}

/// Returns a hash of field elements provided via an iterator.
///
/// This is functionally equivalent to [hash_elements()](Self::hash_elements) but avoids
/// requiring a contiguous slice, which can eliminate intermediate allocations when the
/// elements are produced lazily or come from multiple sources.
///
/// The iterator must implement [ExactSizeIterator] because the total element count is needed
/// upfront for domain separation.
fn hash_iter<I>(iter: I) -> Word
where
I: IntoIterator<Item = Felt>,
I::IntoIter: ExactSizeIterator,
{
let iter = iter.into_iter();
let total_len = iter.len();

if total_len == 0 {
return Word::default();
}

let mut state = [ZERO; STATE_WIDTH];
state[CAPACITY_RANGE.start] = Felt::from_u8((total_len % RATE_WIDTH) as u8);

let mut i = 0;
for felt in iter {
state[RATE_RANGE.start + i] = felt;
i += 1;
if i == RATE_WIDTH {
Self::apply_permutation(&mut state);
i = 0;
}
}

if i > 0 {
while i != RATE_WIDTH {
state[RATE_RANGE.start + i] = ZERO;
i += 1;
}
Self::apply_permutation(&mut state);
}

Word::new(state[DIGEST_RANGE].try_into().unwrap())
}

/// Returns a hash of the provided sequence of bytes.
fn hash(bytes: &[u8]) -> Word {
// initialize the state with zeroes
Expand Down
13 changes: 13 additions & 0 deletions miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,19 @@ impl Poseidon2 {
<Self as AlgebraicSponge>::hash_elements(elements)
}

/// Returns a hash of field elements provided via an iterator.
///
/// This is functionally equivalent to [hash_elements()](Self::hash_elements) but avoids
/// requiring a contiguous slice, which can eliminate intermediate allocations.
#[inline(always)]
pub fn hash_iter<I>(iter: I) -> Word
where
I: IntoIterator<Item = Felt>,
I::IntoIter: ExactSizeIterator,
{
<Self as AlgebraicSponge>::hash_iter(iter)
}

/// Returns a hash of two digests. This method is intended for use in construction of
/// Merkle trees and verification of Merkle paths.
#[inline(always)]
Expand Down
13 changes: 13 additions & 0 deletions miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ impl Rpo256 {
<Self as AlgebraicSponge>::hash_elements(elements)
}

/// Returns a hash of field elements provided via an iterator.
///
/// This is functionally equivalent to [hash_elements()](Self::hash_elements) but avoids
/// requiring a contiguous slice, which can eliminate intermediate allocations.
#[inline(always)]
pub fn hash_iter<I>(iter: I) -> Word
where
I: IntoIterator<Item = Felt>,
I::IntoIter: ExactSizeIterator,
{
<Self as AlgebraicSponge>::hash_iter(iter)
}

/// Returns a hash of two digests. This method is intended for use in construction of
/// Merkle trees and verification of Merkle paths.
#[inline(always)]
Expand Down
32 changes: 32 additions & 0 deletions miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,38 @@ fn hash_empty_bytes() {
assert_eq!(zero_digest, h_result);
}

#[test]
fn hash_iter_vs_hash_elements() {
// empty input
let empty: Vec<Felt> = vec![];
assert_eq!(Rpo256::hash_iter(empty.iter().copied()), Rpo256::hash_elements(&empty));

// single element
let single = [Felt::new(42)];
assert_eq!(Rpo256::hash_iter(single.iter().copied()), Rpo256::hash_elements(&single));

// partial rate (less than 8 elements)
let partial: Vec<Felt> = (0..5).map(Felt::new).collect();
assert_eq!(Rpo256::hash_iter(partial.iter().copied()), Rpo256::hash_elements(&partial));

// exact rate (exactly 8 elements)
let exact_rate: Vec<Felt> = (0..8).map(Felt::new).collect();
assert_eq!(
Rpo256::hash_iter(exact_rate.iter().copied()),
Rpo256::hash_elements(&exact_rate)
);

// multiple rates (more than 8 elements)
let multi: Vec<Felt> = (0..19).map(Felt::new).collect();
assert_eq!(Rpo256::hash_iter(multi.iter().copied()), Rpo256::hash_elements(&multi));

// verify against known test vectors
for (i, expected) in EXPECTED.iter().enumerate() {
let elements: Vec<Felt> = (0..=i).map(|j| Felt::new(j as u64)).collect();
assert_eq!(Rpo256::hash_iter(elements.iter().copied()), *expected);
}
}

#[test]
fn hash_test_vectors() {
let elements = [
Expand Down
13 changes: 13 additions & 0 deletions miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,19 @@ impl Rpx256 {
<Self as AlgebraicSponge>::hash_elements(elements)
}

/// Returns a hash of field elements provided via an iterator.
///
/// This is functionally equivalent to [hash_elements()](Self::hash_elements) but avoids
/// requiring a contiguous slice, which can eliminate intermediate allocations.
#[inline(always)]
pub fn hash_iter<I>(iter: I) -> Word
where
I: IntoIterator<Item = Felt>,
I::IntoIter: ExactSizeIterator,
{
<Self as AlgebraicSponge>::hash_iter(iter)
}

/// Returns a hash of two digests. This method is intended for use in construction of
/// Merkle trees and verification of Merkle paths.
#[inline(always)]
Expand Down