diff --git a/src/hash/rescue/rpo/mod.rs b/src/hash/rescue/rpo/mod.rs index fa6cfc20..d591a21c 100644 --- a/src/hash/rescue/rpo/mod.rs +++ b/src/hash/rescue/rpo/mod.rs @@ -1,11 +1,10 @@ -use core::ops::Range; - use super::{ add_constants, add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, apply_sbox, Digest, ElementHasher, Felt, FieldElement, Hasher, StarkField, ARK1, ARK2, BINARY_CHUNK_SIZE, CAPACITY_RANGE, DIGEST_BYTES, DIGEST_RANGE, DIGEST_SIZE, INPUT1_RANGE, INPUT2_RANGE, MDS, NUM_ROUNDS, RATE_RANGE, RATE_WIDTH, STATE_WIDTH, ZERO, }; +use core::ops::Range; mod digest; pub use digest::{RpoDigest, RpoDigestError}; @@ -86,16 +85,16 @@ impl Hasher for Rpo256 { // every time the rate range is filled, a permutation is performed. if the final value of // `rate_pos` is not zero, then the chunks count wasn't enough to fill the state range, // and an additional permutation must be performed. - let mut current_element = 0_usize; + let mut current_chunk_idx = 0_usize; // handle the case of an empty `bytes` - let last_element = if num_field_elem == 0 { - current_element + let last_chunk_idx = if num_field_elem == 0 { + current_chunk_idx } else { num_field_elem - 1 }; let rate_pos = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |rate_pos, chunk| { // copy the chunk into the buffer - if current_element != last_element { + if current_chunk_idx != last_chunk_idx { buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk); } else { // on the last iteration, we pad `buf` with a 1 followed by as many 0's as are @@ -104,7 +103,7 @@ impl Hasher for Rpo256 { buf[..chunk.len()].copy_from_slice(chunk); buf[chunk.len()] = 1; } - current_element += 1; + current_chunk_idx += 1; // set the current rate element to the input. since we take at most 7 bytes, we are // guaranteed that the inputs data will fit into a single field element. diff --git a/src/hash/rescue/rpo/tests.rs b/src/hash/rescue/rpo/tests.rs index 159fc65b..92d0b46e 100644 --- a/src/hash/rescue/rpo/tests.rs +++ b/src/hash/rescue/rpo/tests.rs @@ -5,7 +5,10 @@ use super::{ super::{apply_inv_sbox, apply_sbox, ALPHA, INV_ALPHA}, Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, STATE_WIDTH, ZERO, }; -use crate::{Word, ONE}; +use crate::{ + hash::rescue::{BINARY_CHUNK_SIZE, CAPACITY_RANGE, RATE_WIDTH}, + Word, ONE, +}; use alloc::{collections::BTreeSet, vec::Vec}; #[test] @@ -125,6 +128,27 @@ fn hash_padding() { assert_ne!(r1, r2); } +#[test] +fn hash_padding_no_extra_permutation_call() { + use crate::hash::rescue::DIGEST_RANGE; + + // Implementation + let num_bytes = BINARY_CHUNK_SIZE * RATE_WIDTH; + let mut buffer = vec![0_u8; num_bytes]; + *buffer.last_mut().unwrap() = 97; + let r1 = Rpo256::hash(&buffer); + + // Expected + let final_chunk = [0_u8, 0, 0, 0, 0, 0, 97, 1]; + let mut state = [ZERO; STATE_WIDTH]; + // padding when hashing bytes + state[CAPACITY_RANGE.start] = Felt::from(RATE_WIDTH as u8); + *state.last_mut().unwrap() = Felt::new(u64::from_le_bytes(final_chunk)); + Rpo256::apply_permutation(&mut state); + + assert_eq!(&r1[0..4], &state[DIGEST_RANGE]); +} + #[test] fn hash_elements_padding() { let e1 = [Felt::new(rand_value()); 2]; diff --git a/src/hash/rescue/rpx/mod.rs b/src/hash/rescue/rpx/mod.rs index 8ffda3b9..a2af9ef9 100644 --- a/src/hash/rescue/rpx/mod.rs +++ b/src/hash/rescue/rpx/mod.rs @@ -91,16 +91,16 @@ impl Hasher for Rpx256 { // every time the rate range is filled, a permutation is performed. if the final value of // `rate_pos` is not zero, then the chunks count wasn't enough to fill the state range, // and an additional permutation must be performed. - let mut current_element = 0_usize; + let mut current_chunk_idx = 0_usize; // handle the case of an empty `bytes` - let last_element = if num_field_elem == 0 { - current_element + let last_chunk_idx = if num_field_elem == 0 { + current_chunk_idx } else { num_field_elem - 1 }; let rate_pos = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |rate_pos, chunk| { // copy the chunk into the buffer - if current_element != last_element { + if current_chunk_idx != last_chunk_idx { buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk); } else { // on the last iteration, we pad `buf` with a 1 followed by as many 0's as are @@ -109,7 +109,7 @@ impl Hasher for Rpx256 { buf[..chunk.len()].copy_from_slice(chunk); buf[chunk.len()] = 1; } - current_element += 1; + current_chunk_idx += 1; // set the current rate element to the input. since we take at most 7 bytes, we are // guaranteed that the inputs data will fit into a single field element.