Skip to content

Commit 62d62a5

Browse files
authored
refactor: remove merge_with_int() from hashers (#894)
1 parent 3785088 commit 62d62a5

File tree

12 files changed

+17
-184
lines changed

12 files changed

+17
-184
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 0.24.0 (TBD)
22

3+
- [BREAKING] Removed `AlgebraicSponge::merge_with_int()` method ([#894](https://github.com/0xMiden/crypto/pull/894)).
4+
35
## 0.23.0 (2026-03-11)
46

57
- Replaced `Subtree` internal storage with bitmask layout ([#784](https://github.com/0xMiden/crypto/pull/784)).

miden-crypto/benches/common/config.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,6 @@ pub const MERGE_INPUT_SIZES: &[usize] = &[
5454
512, // 512 bytes
5555
];
5656

57-
/// Integer sizes for merge_with_int tests
58-
pub const MERGE_INT_SIZES: &[usize] = &[
59-
1, // Single byte integer
60-
2, // 16-bit integer
61-
4, // 32-bit integer
62-
8, // 64-bit integer
63-
];
64-
6557
// === Field Operations Configuration ===
6658
/// Field element counts for batch operations
6759
pub const FIELD_BATCH_SIZES: &[usize] = &[

miden-crypto/benches/common/macros.rs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -258,34 +258,6 @@ macro_rules! benchmark_hash_merge_domain {
258258
};
259259
}
260260

261-
// Creates a benchmark for hash merge with int operations
262-
#[macro_export]
263-
macro_rules! benchmark_hash_merge_with_int {
264-
($func_name:ident, $hasher_name:literal, $sizes:expr, $int_sizes:expr, $closure:expr) => {
265-
fn $func_name(c: &mut Criterion) {
266-
let mut group = c.benchmark_group(concat!("hash-", $hasher_name, "-merge-int"));
267-
group.sample_size(10);
268-
269-
for size_ref in $sizes {
270-
let size = *size_ref;
271-
for int_size_ref in $int_sizes {
272-
let int_size = *int_size_ref;
273-
group.bench_with_input(
274-
criterion::BenchmarkId::new("merge_with_int", format!("{size}_{int_size}")),
275-
&(size, int_size),
276-
|b: &mut criterion::Bencher, param_ref: &(usize, usize)| {
277-
let (size_param, int_size_param) = *param_ref;
278-
$closure(b, (size_param, int_size_param))
279-
},
280-
);
281-
}
282-
}
283-
284-
group.finish();
285-
}
286-
};
287-
}
288-
289261
// Creates a benchmark for hash merge many operations
290262
#[macro_export]
291263
macro_rules! benchmark_hash_merge_many {

miden-crypto/src/hash/algebraic_sponge/mod.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -192,29 +192,6 @@ pub(crate) trait AlgebraicSponge {
192192
Self::hash_elements(elements)
193193
}
194194

195-
/// Returns hash(`seed` || `value`). This method is intended for use in PRNG and PoW contexts.
196-
fn merge_with_int(seed: Word, value: u64) -> Word {
197-
// initialize the state as follows:
198-
// - seed is copied into the first 4 elements of the rate portion of the state.
199-
// - if the value fits into a single field element, copy it into the fifth rate element and
200-
// set the first capacity element to 5.
201-
// - if the value doesn't fit into a single field element, split it into two field elements,
202-
// copy them into rate elements 5 and 6 and set the first capacity element to 6.
203-
let mut state = [ZERO; STATE_WIDTH];
204-
state[RATE0_RANGE].copy_from_slice(seed.as_elements());
205-
state[RATE1_RANGE.start] = Felt::new(value);
206-
if value < Felt::ORDER {
207-
state[CAPACITY_RANGE.start] = Felt::from_u8(5_u8);
208-
} else {
209-
state[RATE1_RANGE.start + 1] = Felt::new(value / Felt::ORDER);
210-
state[CAPACITY_RANGE.start] = Felt::from_u8(6_u8);
211-
}
212-
213-
// apply the permutation and return the digest portion of the rate
214-
Self::apply_permutation(&mut state);
215-
Word::new(state[DIGEST_RANGE].try_into().unwrap())
216-
}
217-
218195
// DOMAIN IDENTIFIER HASHING
219196
// --------------------------------------------------------------------------------------------
220197

miden-crypto/src/hash/algebraic_sponge/poseidon2/mod.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,11 @@ mod test;
3535
/// and it can be serialized into 32 bytes (256 bits).
3636
///
3737
/// ## Hash output consistency
38-
/// Functions [hash_elements()](Poseidon2::hash_elements), [merge()](Poseidon2::merge), and
39-
/// [merge_with_int()](Poseidon2::merge_with_int) are internally consistent. That is, computing
40-
/// a hash for the same set of elements using these functions will always produce the same
41-
/// result. For example, merging two digests using [merge()](Poseidon2::merge) will produce the
42-
/// same result as hashing 8 elements which make up these digests using
43-
/// [hash_elements()](Poseidon2::hash_elements) function.
38+
/// Functions [hash_elements()](Poseidon2::hash_elements), and [merge()](Poseidon2::merge), are
39+
/// internally consistent. That is, computing a hash for the same set of elements using these
40+
/// functions will always produce the same result. For example, merging two digests using
41+
/// [merge()](Poseidon2::merge) will produce the same result as hashing 8 elements which make up
42+
/// these digests using [hash_elements()](Poseidon2::hash_elements) function.
4443
///
4544
/// However, [hash()](Poseidon2::hash) function is not consistent with functions mentioned above.
4645
/// For example, if we take two field elements, serialize them to bytes and hash them using
@@ -164,12 +163,6 @@ impl Poseidon2 {
164163
<Self as AlgebraicSponge>::merge_many(values)
165164
}
166165

167-
/// Returns a hash of a digest and a u64 value.
168-
#[inline(always)]
169-
pub fn merge_with_int(seed: Word, value: u64) -> Word {
170-
<Self as AlgebraicSponge>::merge_with_int(seed, value)
171-
}
172-
173166
/// Returns a hash of two digests and a domain identifier.
174167
#[inline(always)]
175168
pub fn merge_in_domain(values: &[Word; 2], domain: Felt) -> Word {

miden-crypto/src/hash/algebraic_sponge/rescue/rpo/mod.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,10 @@ mod tests;
2929
/// and it can be serialized into 32 bytes (256 bits).
3030
///
3131
/// ## Hash output consistency
32-
/// Functions [hash_elements()](Rpo256::hash_elements), [merge()](Rpo256::merge), and
33-
/// [merge_with_int()](Rpo256::merge_with_int) are internally consistent. That is, computing
34-
/// a hash for the same set of elements using these functions will always produce the same
35-
/// result. For example, merging two digests using [merge()](Rpo256::merge) will produce the
36-
/// same result as hashing 8 elements which make up these digests using
32+
/// Functions [hash_elements()](Rpo256::hash_elements), and [merge()](Rpo256::merge), are internally
33+
/// consistent. That is, computing a hash for the same set of elements using these functions will
34+
/// always produce the same result. For example, merging two digests using [merge()](Rpo256::merge)
35+
/// will produce the same result as hashing 8 elements which make up these digests using
3736
/// [hash_elements()](Rpo256::hash_elements) function.
3837
///
3938
/// However, [hash()](Rpo256::hash) function is not consistent with functions mentioned above.
@@ -148,12 +147,6 @@ impl Rpo256 {
148147
<Self as AlgebraicSponge>::merge_many(values)
149148
}
150149

151-
/// Returns a hash of a digest and a u64 value.
152-
#[inline(always)]
153-
pub fn merge_with_int(seed: Word, value: u64) -> Word {
154-
<Self as AlgebraicSponge>::merge_with_int(seed, value)
155-
}
156-
157150
/// Returns a hash of two digests and a domain identifier.
158151
#[inline(always)]
159152
pub fn merge_in_domain(values: &[Word; 2], domain: Felt) -> Word {

miden-crypto/src/hash/algebraic_sponge/rescue/rpo/tests.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ const ALPHA: u64 = 7;
1414
const INV_ALPHA: u64 = 10540996611094048183;
1515
use crate::{
1616
ONE, Word, ZERO,
17-
hash::algebraic_sponge::{
18-
AlgebraicSponge, BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_RANGE, RATE_WIDTH,
19-
},
17+
hash::algebraic_sponge::{BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_RANGE, RATE_WIDTH},
2018
rand::test_utils::rand_value,
2119
};
2220

@@ -87,33 +85,6 @@ fn merge_vs_merge_in_domain() {
8785
assert_ne!(merge_result, merge_in_domain_result);
8886
}
8987

90-
#[test]
91-
fn hash_elements_vs_merge_with_int() {
92-
let tmp = [Felt::new(rand_value()); 4];
93-
let seed = Word::new(tmp);
94-
95-
// ----- value fits into a field element ------------------------------------------------------
96-
let val: Felt = Felt::new(rand_value());
97-
let m_result = <Rpo256 as AlgebraicSponge>::merge_with_int(seed, val.as_canonical_u64());
98-
99-
let mut elements = seed.as_elements().to_vec();
100-
elements.push(val);
101-
let h_result = Rpo256::hash_elements(&elements);
102-
103-
assert_eq!(m_result, h_result);
104-
105-
// ----- value does not fit into a field element ----------------------------------------------
106-
let val = Felt::ORDER + 2;
107-
let m_result = <Rpo256 as AlgebraicSponge>::merge_with_int(seed, val);
108-
109-
let mut elements = seed.as_elements().to_vec();
110-
elements.push(Felt::new(val));
111-
elements.push(ONE);
112-
let h_result = Rpo256::hash_elements(&elements);
113-
114-
assert_eq!(m_result, h_result);
115-
}
116-
11788
#[test]
11889
fn hash_padding() {
11990
// adding a zero bytes at the end of a byte string should result in a different hash

miden-crypto/src/hash/algebraic_sponge/rescue/rpx/mod.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,10 @@ mod tests;
3232
/// and it can be serialized into 32 bytes (256 bits).
3333
///
3434
/// ## Hash output consistency
35-
/// Functions [hash_elements()](Rpx256::hash_elements), [merge()](Rpx256::merge), and
36-
/// [merge_with_int()](Rpx256::merge_with_int) are internally consistent. That is, computing
37-
/// a hash for the same set of elements using these functions will always produce the same
38-
/// result. For example, merging two digests using [merge()](Rpx256::merge) will produce the
39-
/// same result as hashing 8 elements which make up these digests using
35+
/// Functions [hash_elements()](Rpx256::hash_elements), and [merge()](Rpx256::merge), are internally
36+
/// consistent. That is, computing a hash for the same set of elements using these functions will
37+
/// always produce the same result. For example, merging two digests using [merge()](Rpx256::merge)
38+
/// will produce the same result as hashing 8 elements which make up these digests using
4039
/// [hash_elements()](Rpx256::hash_elements) function.
4140
///
4241
/// However, [hash()](Rpx256::hash) function is not consistent with functions mentioned above.
@@ -149,12 +148,6 @@ impl Rpx256 {
149148
<Self as AlgebraicSponge>::merge_many(values)
150149
}
151150

152-
/// Returns a hash of a digest and a u64 value.
153-
#[inline(always)]
154-
pub fn merge_with_int(seed: Word, value: u64) -> Word {
155-
<Self as AlgebraicSponge>::merge_with_int(seed, value)
156-
}
157-
158151
/// Returns a hash of two digests and a domain identifier.
159152
#[inline(always)]
160153
pub fn merge_in_domain(values: &[Word; 2], domain: Felt) -> Word {

miden-crypto/src/hash/algebraic_sponge/rescue/rpx/tests.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use alloc::{collections::BTreeSet, vec::Vec};
44
use proptest::prelude::*;
55

66
use super::{Felt, Rpx256};
7-
use crate::{
8-
ONE, Word, ZERO, hash::algebraic_sponge::AlgebraicSponge, rand::test_utils::rand_value,
9-
};
7+
use crate::{ONE, Word, ZERO, rand::test_utils::rand_value};
108

119
// The number of iterations to run the `ext_round_matches_reference_many` test.
1210
#[cfg(all(
@@ -59,33 +57,6 @@ fn merge_vs_merge_in_domain() {
5957
assert_ne!(merge_result, merge_in_domain_result);
6058
}
6159

62-
#[test]
63-
fn hash_elements_vs_merge_with_int() {
64-
let tmp = [Felt::new(rand_value()); 4];
65-
let seed = Word::new(tmp);
66-
67-
// ----- value fits into a field element ------------------------------------------------------
68-
let val: Felt = Felt::new(rand_value());
69-
let m_result = <Rpx256 as AlgebraicSponge>::merge_with_int(seed, val.as_canonical_u64());
70-
71-
let mut elements = seed.as_elements().to_vec();
72-
elements.push(val);
73-
let h_result = Rpx256::hash_elements(&elements);
74-
75-
assert_eq!(m_result, h_result);
76-
77-
// ----- value does not fit into a field element ----------------------------------------------
78-
let val = Felt::ORDER + 2;
79-
let m_result = <Rpx256 as AlgebraicSponge>::merge_with_int(seed, val);
80-
81-
let mut elements = seed.as_elements().to_vec();
82-
elements.push(Felt::new(val));
83-
elements.push(ONE);
84-
let h_result = Rpx256::hash_elements(&elements);
85-
86-
assert_eq!(m_result, h_result);
87-
}
88-
8960
#[test]
9061
fn hash_padding() {
9162
// adding a zero bytes at the end of a byte string should result in a different hash

miden-crypto/src/hash/blake/mod.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ impl Blake3_256 {
4848
Digest::new(blake3::hash(bytes).into())
4949
}
5050

51-
// Note: merge/merge_many/merge_with_int methods were previously trait delegations
52-
// (<Self as Hasher>::merge). They're now direct implementations as part of removing
53-
// the Winterfell Hasher trait dependency. These are public API used in benchmarks.
5451
pub fn merge(values: &[Digest256; 2]) -> Digest256 {
5552
Self::hash(Digest::digests_as_bytes(values))
5653
}
@@ -59,13 +56,6 @@ impl Blake3_256 {
5956
Digest::new(blake3::hash(Digest::digests_as_bytes(values)).into())
6057
}
6158

62-
pub fn merge_with_int(seed: Digest256, value: u64) -> Digest256 {
63-
let mut hasher = blake3::Hasher::new();
64-
hasher.update(seed.as_bytes());
65-
hasher.update(&value.to_le_bytes());
66-
Digest::new(hasher.finalize().into())
67-
}
68-
6959
/// Returns a hash of the provided field elements.
7060
#[inline(always)]
7161
pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Digest256 {
@@ -116,13 +106,6 @@ impl Blake3_192 {
116106
Self::hash(Digest::digests_as_bytes(values))
117107
}
118108

119-
pub fn merge_with_int(seed: Digest192, value: u64) -> Digest192 {
120-
let mut hasher = blake3::Hasher::new();
121-
hasher.update(seed.as_bytes());
122-
hasher.update(&value.to_le_bytes());
123-
Digest::new(shrink_array(hasher.finalize().into()))
124-
}
125-
126109
/// Returns a hash of the provided field elements.
127110
#[inline(always)]
128111
pub fn hash_elements<E: BasedVectorSpace<Felt>>(elements: &[E]) -> Digest192 {

0 commit comments

Comments
 (0)