Browse Source

New padding rule for RPX (#236)

* feat: new padding rule for RPX
* fix: documentation on security
km/mkdocs-impl
Al-Kindi-0 1 year ago
committed by Bobbin Threadbare
parent
commit
c9ab3beccc
2 changed files with 28 additions and 48 deletions
  1. +2
    -8
      src/hash/rescue/rpo/mod.rs
  2. +26
    -40
      src/hash/rescue/rpx/mod.rs

+ 2
- 8
src/hash/rescue/rpo/mod.rs

@ -27,7 +27,7 @@ mod tests;
/// * Number of founds: 7. /// * Number of founds: 7.
/// * S-Box degree: 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). /// and it can be serialized into 32 bytes (256 bits).
/// ///
/// ## Hash output consistency /// ## Hash output consistency
@ -55,13 +55,7 @@ mod tests;
pub struct Rpo256(); pub struct Rpo256();
impl Hasher for 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; const COLLISION_RESISTANCE: u32 = 128;
type Digest = RpoDigest; type Digest = RpoDigest;

+ 26
- 40
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, 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, apply_mds, apply_sbox, CubeExtension, Digest, ElementHasher, Felt, FieldElement, Hasher,
StarkField, ARK1, ARK2, BINARY_CHUNK_SIZE, CAPACITY_RANGE, DIGEST_BYTES, DIGEST_RANGE, 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}; use core::{convert::TryInto, ops::Range};
@ -30,7 +30,7 @@ pub type CubicExtElement = CubeExtension;
/// - (M): `apply_mds` → `add_constants`. /// - (M): `apply_mds` → `add_constants`.
/// * Permutation: (FB) (E) (FB) (E) (FB) (E) (M). /// * 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). /// and it can be serialized into 32 bytes (256 bits).
/// ///
/// ## Hash output consistency /// ## Hash output consistency
@ -58,13 +58,7 @@ pub type CubicExtElement = CubeExtension;
pub struct Rpx256(); pub struct Rpx256();
impl Hasher for 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; const COLLISION_RESISTANCE: u32 = 128;
type Digest = RpxDigest; type Digest = RpxDigest;
@ -73,14 +67,16 @@ impl Hasher for Rpx256 {
// initialize the state with zeroes // initialize the state with zeroes
let mut state = [ZERO; STATE_WIDTH]; 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. // initialize a buffer to receive the little-endian elements.
let mut buf = [0_u8; 8]; 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| { 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 // 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`. // 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 { if chunk.len() == BINARY_CHUNK_SIZE {
buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk); buf[..BINARY_CHUNK_SIZE].copy_from_slice(chunk);
} else { } 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 // 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 // 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 // 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 { if i != 0 {
state[RATE_RANGE.start + i..RATE_RANGE.end].fill(ZERO); state[RATE_RANGE.start + i..RATE_RANGE.end].fill(ZERO);
state[RATE_RANGE.start + i] = ONE;
Self::apply_permutation(&mut state); Self::apply_permutation(&mut state);
} }
@ -148,25 +144,20 @@ impl Hasher for Rpx256 {
fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest { fn merge_with_int(seed: Self::Digest, value: u64) -> Self::Digest {
// initialize the state as follows: // initialize the state as follows:
// - seed is copied into the first 4 elements of the rate portion of the state. // - 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 // - 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]; let mut state = [ZERO; STATE_WIDTH];
state[INPUT1_RANGE].copy_from_slice(seed.as_elements()); state[INPUT1_RANGE].copy_from_slice(seed.as_elements());
state[INPUT2_RANGE.start] = Felt::new(value); state[INPUT2_RANGE.start] = Felt::new(value);
if value < Felt::MODULUS { if value < Felt::MODULUS {
state[INPUT2_RANGE.start + 1] = ONE;
state[CAPACITY_RANGE.start] = Felt::from(5_u8);
} else { } else {
state[INPUT2_RANGE.start + 1] = Felt::new(value / Felt::MODULUS); 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 // apply the RPX permutation and return the first four elements of the state
Self::apply_permutation(&mut state); Self::apply_permutation(&mut state);
RpxDigest::new(state[DIGEST_RANGE].try_into().unwrap()) RpxDigest::new(state[DIGEST_RANGE].try_into().unwrap())
@ -181,11 +172,9 @@ impl ElementHasher for Rpx256 {
let elements = E::slice_as_base_elements(elements); let elements = E::slice_as_base_elements(elements);
// initialize state to all zeros, except for the first element of the capacity part, which // 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]; 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 // 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 // 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 // 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 // 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 { if i > 0 {
state[RATE_RANGE.start + i] = ONE;
i += 1;
while i != RATE_WIDTH { while i != RATE_WIDTH {
state[RATE_RANGE.start + i] = ZERO; state[RATE_RANGE.start + i] = ZERO;
i += 1; i += 1;
@ -354,7 +340,7 @@ impl Rpx256 {
add_constants(state, &ARK1[round]); 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)] #[inline(always)]
pub fn exp7(x: CubeExtension<Felt>) -> CubeExtension<Felt> { pub fn exp7(x: CubeExtension<Felt>) -> CubeExtension<Felt> {
let x2 = x.square(); let x2 = x.square();

Loading…
Cancel
Save