diff --git a/src/hash/rescue/rpo/mod.rs b/src/hash/rescue/rpo/mod.rs index 24a9f681..864f5689 100644 --- a/src/hash/rescue/rpo/mod.rs +++ b/src/hash/rescue/rpo/mod.rs @@ -27,7 +27,7 @@ mod tests; /// * Number of founds: 7. /// * S-Box degree: 7. /// -/// The above parameters target 128-bit security level. The digest consists of four field elements +/// The above parameters target a 128-bit security level. The digest consists of four field elements /// and it can be serialized into 32 bytes (256 bits). /// /// ## Hash output consistency @@ -55,13 +55,7 @@ mod tests; pub struct Rpo256(); impl Hasher for Rpo256 { - /// Rpo256 collision resistance is the same as the security level, that is 128-bits. - /// - /// #### Collision resistance - /// - /// However, our setup of the capacity registers might drop it to 126. - /// - /// Related issue: [#69](https://github.com/0xPolygonMiden/crypto/issues/69) + /// Rpo256 collision resistance is 128-bits. const COLLISION_RESISTANCE: u32 = 128; type Digest = RpoDigest; diff --git a/src/hash/rescue/rpx/mod.rs b/src/hash/rescue/rpx/mod.rs index 5458c072..6b947997 100644 --- a/src/hash/rescue/rpx/mod.rs +++ b/src/hash/rescue/rpx/mod.rs @@ -2,8 +2,8 @@ use super::{ add_constants, add_constants_and_apply_inv_sbox, add_constants_and_apply_sbox, apply_inv_sbox, apply_mds, apply_sbox, CubeExtension, 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, ONE, RATE_RANGE, RATE_WIDTH, - STATE_WIDTH, ZERO, + DIGEST_SIZE, INPUT1_RANGE, INPUT2_RANGE, MDS, NUM_ROUNDS, RATE_RANGE, RATE_WIDTH, STATE_WIDTH, + ZERO, }; use core::{convert::TryInto, ops::Range}; @@ -30,7 +30,7 @@ pub type CubicExtElement = CubeExtension; /// - (M): `apply_mds` → `add_constants`. /// * Permutation: (FB) (E) (FB) (E) (FB) (E) (M). /// -/// The above parameters target 128-bit security level. The digest consists of four field elements +/// The above parameters target a 128-bit security level. The digest consists of four field elements /// and it can be serialized into 32 bytes (256 bits). /// /// ## Hash output consistency @@ -58,13 +58,7 @@ pub type CubicExtElement = CubeExtension; pub struct Rpx256(); impl Hasher for Rpx256 { - /// Rpx256 collision resistance is the same as the security level, that is 128-bits. - /// - /// #### Collision resistance - /// - /// However, our setup of the capacity registers might drop it to 126. - /// - /// Related issue: [#69](https://github.com/0xPolygonMiden/crypto/issues/69) + /// Rpx256 collision resistance is 128-bits. const COLLISION_RESISTANCE: u32 = 128; type Digest = RpxDigest; @@ -73,14 +67,16 @@ impl Hasher for Rpx256 { // initialize the state with zeroes let mut state = [ZERO; STATE_WIDTH]; - // set the capacity (first element) to a flag on whether or not the input length is evenly - // divided by the rate. this will prevent collisions between padded and non-padded inputs, - // and will rule out the need to perform an extra permutation in case of evenly divided - // inputs. - let is_rate_multiple = bytes.len() % RATE_WIDTH == 0; - if !is_rate_multiple { - state[CAPACITY_RANGE.start] = ONE; - } + // determine the number of field elements needed to encode `bytes` when each field element + // represents at most 7 bytes. + let num_field_elem = bytes.len().div_ceil(BINARY_CHUNK_SIZE); + + // set the first capacity element to `RATE_WIDTH + (num_field_elem % RATE_WIDTH)`. We do + // this to achieve: + // 1. Domain separating hashing of `[u8]` from hashing of `[Felt]`. + // 2. Avoiding collisions at the `[Felt]` representation of the encoded bytes. + state[CAPACITY_RANGE.start] = + Felt::from((RATE_WIDTH + (num_field_elem % RATE_WIDTH)) as u8); // initialize a buffer to receive the little-endian elements. let mut buf = [0_u8; 8]; @@ -94,7 +90,7 @@ impl Hasher for Rpx256 { let i = bytes.chunks(BINARY_CHUNK_SIZE).fold(0, |i, chunk| { // the last element of the iteration may or may not be a full chunk. if it's not, then // we need to pad the remainder bytes of the chunk with zeroes, separated by a `1`. - // this will avoid collisions. + // this will avoid collisions at the bytes level. if chunk.len() == BINARY_CHUNK_SIZE { buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk); } else { @@ -120,10 +116,10 @@ impl Hasher for Rpx256 { // if we absorbed some elements but didn't apply a permutation to them (would happen when // the number of elements is not a multiple of RATE_WIDTH), apply the RPX permutation. we // don't need to apply any extra padding because the first capacity element contains a - // flag indicating whether the input is evenly divisible by the rate. + // flag indicating the number of field elements constituting the last block when the latter + // is not divisible by `RATE_WIDTH`. if i != 0 { state[RATE_RANGE.start + i..RATE_RANGE.end].fill(ZERO); - state[RATE_RANGE.start + i] = ONE; Self::apply_permutation(&mut state); } @@ -148,25 +144,20 @@ impl Hasher for Rpx256 { fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { // initialize the state as follows: // - seed is copied into the first 4 elements of the rate portion of the state. - // - if the value fits into a single field element, copy it into the fifth rate element - // and set the sixth rate element to 1. + // - if the value fits into a single field element, copy it into the fifth rate element and + // set the first capacity element to 5. // - if the value doesn't fit into a single field element, split it into two field - // elements, copy them into rate elements 5 and 6, and set the seventh rate element - // to 1. - // - set the first capacity element to 1 + // elements, copy them into rate elements 5 and 6 and set the first capacity element to 6. let mut state = [ZERO; STATE_WIDTH]; state[INPUT1_RANGE].copy_from_slice(seed.as_elements()); state[INPUT2_RANGE.start] = Felt::new(value); if value < Felt::MODULUS { - state[INPUT2_RANGE.start + 1] = ONE; + state[CAPACITY_RANGE.start] = Felt::from(5_u8); } else { state[INPUT2_RANGE.start + 1] = Felt::new(value / Felt::MODULUS); - state[INPUT2_RANGE.start + 2] = ONE; + state[CAPACITY_RANGE.start] = Felt::from(6_u8); } - // common padding for both cases - state[CAPACITY_RANGE.start] = ONE; - // apply the RPX permutation and return the first four elements of the state Self::apply_permutation(&mut state); RpxDigest::new(state[DIGEST_RANGE].try_into().unwrap()) @@ -181,11 +172,9 @@ impl ElementHasher for Rpx256 { let elements = E::slice_as_base_elements(elements); // initialize state to all zeros, except for the first element of the capacity part, which - // is set to 1 if the number of elements is not a multiple of RATE_WIDTH. + // is set to `elements.len() % RATE_WIDTH`. let mut state = [ZERO; STATE_WIDTH]; - if elements.len() % RATE_WIDTH != 0 { - state[CAPACITY_RANGE.start] = ONE; - } + state[CAPACITY_RANGE.start] = Self::BaseField::from((elements.len() % RATE_WIDTH) as u8); // absorb elements into the state one by one until the rate portion of the state is filled // up; then apply the Rescue permutation and start absorbing again; repeat until all @@ -202,11 +191,8 @@ impl ElementHasher for Rpx256 { // if we absorbed some elements but didn't apply a permutation to them (would happen when // the number of elements is not a multiple of RATE_WIDTH), apply the RPX permutation after - // padding by appending a 1 followed by as many 0 as necessary to make the input length a - // multiple of the RATE_WIDTH. + // padding by as many 0 as necessary to make the input length a multiple of the RATE_WIDTH. if i > 0 { - state[RATE_RANGE.start + i] = ONE; - i += 1; while i != RATE_WIDTH { state[RATE_RANGE.start + i] = ZERO; i += 1; @@ -354,7 +340,7 @@ impl Rpx256 { add_constants(state, &ARK1[round]); } - /// Computes an exponentiation to the power 7 in cubic extension field + /// Computes an exponentiation to the power 7 in cubic extension field. #[inline(always)] pub fn exp7(x: CubeExtension) -> CubeExtension { let x2 = x.square();