From de34dd91c2afe9d81c2870652e4b7a3cecfd46d6 Mon Sep 17 00:00:00 2001 From: moana Date: Tue, 23 Jan 2024 14:48:53 +0100 Subject: [PATCH] Adjust hades round constants - Decrease amount of statically loaded constants from 960 to 335 - Change the `const ROUND_CONSTANTS` to a 2-dim array - Add compile time warning for constants generation - Remove the hades constants iterator - Rename `add_round_key` to `add_round_constants` to match the paper - Rename internal constant `TOTAL_FULL_ROUNDS` to `FULL_ROUNDS`. Resolves #246 --- CHANGELOG.md | 24 +++++-- assets/HOWTO.md | 14 ++-- assets/arc.bin | Bin 0 -> 10720 bytes assets/ark.bin | Bin 30720 -> 0 bytes src/hades.rs | 7 +- src/hades/permutation.rs | 109 +++++++++++++------------------- src/hades/permutation/gadget.rs | 43 ++++++------- src/hades/permutation/scalar.rs | 41 ++++++------ src/hades/round_constants.rs | 40 +++++++----- 9 files changed, 135 insertions(+), 143 deletions(-) create mode 100644 assets/arc.bin delete mode 100644 assets/ark.bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b31d4..c234020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,27 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.34.0] - 2024-01-24 - ### Changed -- Restructure crate features [#184] - Rename trait `hades::Strategy` to `hades::Permutation` [#243] - Rename struct `hades::ScalarStrategy` to `hades::ScalarPermutation` [#243] - Rename struct `hades::GadgetStrategy` to `hades::GadgetPermutaiton` [#243] +- Reduce the number of `ROUND_CONSTANTS` from 960 to 335 [#246] +- Remove the constants iterator in favor of indexing the constants array directly [#246] +- Change `ROUND_CONSTANTS` into a two-dimensional array [#246] +- Rename `TOTAL_FULL_ROUNDS` to `FULL_ROUNDS` [#246] ### Removed -- Remove `default` and `alloc` features [#184] - Remove `hades::Strategy`, `hades::ScalarStrategy` and `hades::GadgetStrategy` from public API [#243] - Remove `dusk-hades` dependency [#240] ### Added -- Add `zk` and `cipher` features [#184] - Add the code for the hades permutation to crate [#240] - Add internal `permute` and `permute_gadget` functions to `hades` module [#243] +## [0.34.0] - 2024-01-24 + +### Changed + +- Restructure crate features [#184] + +### Removed + +- Remove `default` and `alloc` features [#184] + +### Added + +- Add `zk` and `cipher` features [#184] + ## [0.33.0] - 2024-01-03 ### Changed @@ -436,6 +449,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Variants of sponge for `Scalar` & `Gadget(Variable/LC)`. +[#246]: https://github.com/dusk-network/poseidon252/issues/246 [#243]: https://github.com/dusk-network/poseidon252/issues/243 [#240]: https://github.com/dusk-network/poseidon252/issues/240 [#215]: https://github.com/dusk-network/poseidon252/issues/215 diff --git a/assets/HOWTO.md b/assets/HOWTO.md index 3cabe0c..3a3cdad 100644 --- a/assets/HOWTO.md +++ b/assets/HOWTO.md @@ -6,9 +6,9 @@ # How to generate the assets -The `ark.bin` and `mds.bin` files in this folder are generated using the snippets below: +The `arc.bin` and `mds.bin` files in this folder are generated using the snippets below: -## Filename: ark.bin +## Generate round constants ```rust use dusk_bls12_381::BlsScalar; @@ -16,9 +16,9 @@ use sha2::{Digest, Sha512}; use std::fs; use std::io::Write; -// The amount of constants generated, this needs to be the same number as in -// `dusk_poseidon::hades::CONSTANTS`. -const CONSTANTS: usize = 960; +// The amount of constants generated, this needs to be at least the total number +// of rounds (= 59 + 8) multiplied by the width of the permutaiton array (= 5). +const CONSTANTS: usize = (59 + 8) * 5; fn constants() -> [BlsScalar; CONSTANTS] { let mut cnst = [BlsScalar::zero(); CONSTANTS]; @@ -41,7 +41,7 @@ fn constants() -> [BlsScalar; CONSTANTS] { } fn write_constants() -> std::io::Result<()> { - let filename = "ark.bin"; + let filename = "arc.bin"; let mut buf: Vec = vec![]; constants().iter().for_each(|c| { @@ -56,7 +56,7 @@ fn write_constants() -> std::io::Result<()> { } ``` -## Filename: mds.bin +## Generate mds matrix ```rust use dusk_bls12_381::BlsScalar; diff --git a/assets/arc.bin b/assets/arc.bin new file mode 100644 index 0000000000000000000000000000000000000000..55c576d65f09362a7af02be1040bc37c144129c2 GIT binary patch literal 10720 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 literal 0 HcmV?d00001 diff --git a/assets/ark.bin b/assets/ark.bin deleted file mode 100644 index 043249354529e6feab08ca2603d3e40691de7ca2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30720 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 { - /// Fetch the next round constant from an iterator - fn next_c<'b, I>(constants: &mut I) -> BlsScalar - where - I: Iterator, - { - constants - .next() - .copied() - .expect("Hades252 shouldn't be out of ARK constants") - } + /// Increment the inner rounds counter. + /// + /// This counter is needed to index the `ROUND_CONSTANTS`. + fn increment_round(&mut self); - /// Add round keys to the state. + /// Add round constants to the state. /// - /// This round key addition, also known as `ARK`, is used to reach + /// This constants addition, also known as `ARC`, is used to reach /// `Confusion and Diffusion` properties for the algorithm. /// /// Basically it allows to destroy any connection between the inputs and the /// outputs of the function. - fn add_round_key<'b, I>( - &mut self, - constants: &mut I, - state: &mut [T; WIDTH], - ) where - I: Iterator; + fn add_round_constants(&mut self, state: &mut [T; WIDTH]); /// Computes `input ^ 5 (mod p)` /// @@ -100,60 +89,52 @@ pub(crate) trait Permutation { fn quintic_s_box(&mut self, value: &mut T); /// Multiply the MDS matrix with the state. - fn mul_matrix<'b, I>(&mut self, constants: &mut I, state: &mut [T; WIDTH]) - where - I: Iterator; + fn mul_matrix(&mut self, state: &mut [T; WIDTH]); /// Applies a `Partial Round` also known as a `Partial S-Box layer` to a set /// of inputs. /// /// One partial round consists of 3 steps: - /// - ARK: Add round keys constants to each state element. - /// - Sub State: Apply `quintic S-Box` just to **the last element of the + /// - ARC: Add round constants to the elements of the state. + /// - Sub Words: Apply `quintic S-Box` just to **the last element of the /// state** generated from the first step. /// - Mix Layer: Multiplies the output state from the second step by the /// `MDS_MATRIX`. - fn apply_partial_round<'b, I>( - &mut self, - constants: &mut I, - state: &mut [T; WIDTH], - ) where - I: Iterator, - { - // Add round keys to each state element - self.add_round_key(constants, state); - - // Then apply quintic s-box + fn apply_partial_round(&mut self, state: &mut [T; WIDTH]) { + // Increment the inner rounds counter + self.increment_round(); + + // Add round constants to each state element + self.add_round_constants(state); + + // Then apply quintic s-box to the last element of the state self.quintic_s_box(&mut state[WIDTH - 1]); // Multiply this result by the MDS matrix - self.mul_matrix(constants, state); + self.mul_matrix(state); } /// Applies a `Full Round` also known as a `Full S-Box layer` to a set of /// inputs. /// - /// One full round constists of 3 steps: - /// - ARK: Add round keys to each state element. - /// - Sub State: Apply `quintic S-Box` to **all of the state-elements** + /// One full round consists of 3 steps: + /// - ARC: Add round constants to the elements of the state. + /// - Sub Words: Apply `quintic S-Box` to **all of the state-elements** /// generated from the first step. /// - Mix Layer: Multiplies the output state from the second step by the /// `MDS_MATRIX`. - fn apply_full_round<'a, I>( - &mut self, - constants: &mut I, - state: &mut [T; WIDTH], - ) where - I: Iterator, - { - // Add round keys to each state element - self.add_round_key(constants, state); - - // Then apply quintic s-box + fn apply_full_round(&mut self, state: &mut [T; WIDTH]) { + // Increment the inner rounds counter + self.increment_round(); + + // Add round constants to each state element + self.add_round_constants(state); + + // Then apply quintic s-box to each element of the state state.iter_mut().for_each(|w| self.quintic_s_box(w)); // Multiply this result by the MDS matrix - self.mul_matrix(constants, state); + self.mul_matrix(state); } /// Applies one Hades permutation. @@ -161,33 +142,31 @@ pub(crate) trait Permutation { /// This permutation is a 3-step process that: /// - Applies half of the `FULL_ROUNDS` (which can be understood as linear /// ops). - /// - Applies the `PARTIAL_ROUDS` (which can be understood as non-linear + /// - Applies the `PARTIAL_ROUNDS` (which can be understood as non-linear /// ops). /// - Applies the other half of the `FULL_ROUNDS`. /// /// This structure allows to minimize the number of non-linear ops while - /// mantaining the security. + /// maintaining the security. fn perm(&mut self, state: &mut [T; WIDTH]) { - let mut constants = ROUND_CONSTANTS.iter(); - // Apply R_f full rounds - for _ in 0..TOTAL_FULL_ROUNDS / 2 { - self.apply_full_round(&mut constants, state); + for _ in 0..FULL_ROUNDS / 2 { + self.apply_full_round(state); } // Apply R_P partial rounds for _ in 0..PARTIAL_ROUNDS { - self.apply_partial_round(&mut constants, state); + self.apply_partial_round(state); } // Apply R_f full rounds - for _ in 0..TOTAL_FULL_ROUNDS / 2 { - self.apply_full_round(&mut constants, state); + for _ in 0..FULL_ROUNDS / 2 { + self.apply_full_round(state); } } /// Return the total rounds count fn rounds() -> usize { - TOTAL_FULL_ROUNDS + PARTIAL_ROUNDS + FULL_ROUNDS + PARTIAL_ROUNDS } } diff --git a/src/hades/permutation/gadget.rs b/src/hades/permutation/gadget.rs index dbce114..5a7cc5a 100644 --- a/src/hades/permutation/gadget.rs +++ b/src/hades/permutation/gadget.rs @@ -7,9 +7,11 @@ use dusk_bls12_381::BlsScalar; use dusk_plonk::prelude::*; -use crate::hades::{Permutation as HadesPermutation, MDS_MATRIX, WIDTH}; +use crate::hades::{ + Permutation as HadesPermutation, MDS_MATRIX, ROUND_CONSTANTS, WIDTH, +}; -/// A state for the ['HadesPermutation`] operating on [`Witness`]es. +/// An implementation for the ['HadesPermutation`] operating on [`Witness`]es. /// Requires a reference to a `ConstraintSystem`. pub(crate) struct GadgetPermutaiton<'a> { /// A reference to the constraint system used by the gadgets @@ -31,19 +33,19 @@ impl AsMut for GadgetPermutaiton<'_> { } impl<'a> HadesPermutation for GadgetPermutaiton<'a> { - fn add_round_key<'b, I>( - &mut self, - constants: &mut I, - state: &mut [Witness; WIDTH], - ) where - I: Iterator, - { + fn increment_round(&mut self) { + self.round += 1; + } + + fn add_round_constants(&mut self, state: &mut [Witness; WIDTH]) { // To safe constraints we only add the constants here in the first // round. The remaining constants will be added in the matrix // multiplication. - if self.round == 0 { - state.iter_mut().for_each(|w| { - let constant = Self::next_c(constants); + // Note that the rounds start counting at 1 but the ROUND_CONSTANTS + // start counting at 0. + if self.round == 1 { + state.iter_mut().enumerate().for_each(|(i, w)| { + let constant = ROUND_CONSTANTS[0][i]; let constraint = Constraint::new().left(1).a(*w).constant(constant); @@ -64,22 +66,14 @@ impl<'a> HadesPermutation for GadgetPermutaiton<'a> { } /// Adds a constraint for each matrix coefficient multiplication - fn mul_matrix<'b, I>( - &mut self, - constants: &mut I, - state: &mut [Witness; WIDTH], - ) where - I: Iterator, - { + fn mul_matrix(&mut self, state: &mut [Witness; WIDTH]) { let mut result = [Composer::ZERO; WIDTH]; - self.round += 1; // Implementation optimized for WIDTH = 5 // - // c is the next round's constant and hence zero for the last round. - // // The resulting array `r` will be defined as // r[x] = sum_{j=0..WIDTH} ( MDS[x][j] * state[j] ) + c + // with c being the constant for the next round. // // q_l = MDS[x][0] // q_r = MDS[x][1] @@ -97,8 +91,11 @@ impl<'a> HadesPermutation for GadgetPermutaiton<'a> { // w_4 = r[x] // r[x] = q_l · w_l + q_r · w_r + q_4 · w_4 + c; for j in 0..WIDTH { + // c is the next round's constant and hence zero for the last round. let c = match self.round < Self::rounds() { - true => Self::next_c(constants), + // the rounds start counting at 1, so the constants for the next + // round are stored at the round index (and not at `round + 1`) + true => ROUND_CONSTANTS[self.round][j], false => BlsScalar::zero(), }; diff --git a/src/hades/permutation/scalar.rs b/src/hades/permutation/scalar.rs index 8c2ac5d..023cc54 100644 --- a/src/hades/permutation/scalar.rs +++ b/src/hades/permutation/scalar.rs @@ -6,44 +6,43 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::{Permutation as HadesPermutation, MDS_MATRIX, WIDTH}; +use crate::hades::{ + Permutation as HadesPermutation, MDS_MATRIX, ROUND_CONSTANTS, WIDTH, +}; -/// State that implements the [`HadesPermutation`] for `BlsScalar` as input +/// An implementation of the [`HadesPermutation`] for `BlsScalar` as input /// values. #[derive(Default)] -pub(crate) struct ScalarPermutation {} +pub(crate) struct ScalarPermutation { + round: usize, +} impl ScalarPermutation { /// Constructs a new `ScalarPermutation`. pub fn new() -> Self { - Default::default() + Self { round: 0 } } } impl HadesPermutation for ScalarPermutation { - fn add_round_key<'b, I>( - &mut self, - constants: &mut I, - state: &mut [BlsScalar; WIDTH], - ) where - I: Iterator, - { - state.iter_mut().for_each(|w| { - *w += Self::next_c(constants); - }); + fn increment_round(&mut self) { + self.round += 1; + } + + fn add_round_constants(&mut self, state: &mut [BlsScalar; WIDTH]) { + state + .iter_mut() + .enumerate() + // the rounds start counting at 1, so the respective round constants + // are stored at index `round - 1` + .for_each(|(i, s)| *s += ROUND_CONSTANTS[self.round - 1][i]); } fn quintic_s_box(&mut self, value: &mut BlsScalar) { *value = value.square().square() * *value; } - fn mul_matrix<'b, I>( - &mut self, - _constants: &mut I, - state: &mut [BlsScalar; WIDTH], - ) where - I: Iterator, - { + fn mul_matrix(&mut self, state: &mut [BlsScalar; WIDTH]) { let mut result = [BlsScalar::zero(); WIDTH]; for (j, value) in state.iter().enumerate() { diff --git a/src/hades/round_constants.rs b/src/hades/round_constants.rs index cba27c8..b167baa 100644 --- a/src/hades/round_constants.rs +++ b/src/hades/round_constants.rs @@ -4,8 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -//! This module is designed to load the 960 constants used as `round_constants` -//! from `ark.bin`. +//! This module is designed to load the constants used as `ROUND_CONSTANTS` +//! from `assets/arc.bin`. //! //! The constants were originally computed using: //! https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py @@ -13,30 +13,36 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::CONSTANTS; +use crate::hades::{FULL_ROUNDS, PARTIAL_ROUNDS, WIDTH}; -/// `ROUND_CONSTANTS` constists on a static reference -/// that points to the pre-loaded 960 Fq constants. +const ROUNDS: usize = FULL_ROUNDS + PARTIAL_ROUNDS; + +/// `ROUND_CONSTANTS` consists on a static reference that points to the +/// pre-loaded 335 constant scalar of the bls12_381 curve. /// -/// This 960 `BlsScalar` constants are loaded from `ark.bin` -/// where all of the `BlsScalar`s are represented in buf. +/// These 335 `BlsScalar` constants are loaded from `assets/arc.bin`. /// -/// This round constants have been taken from: -/// https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py -/// and then mapped onto `Fq` in the Ristretto scalar field. -pub const ROUND_CONSTANTS: [BlsScalar; CONSTANTS] = { - let bytes = include_bytes!("../../assets/ark.bin"); - let mut cnst = [BlsScalar::zero(); CONSTANTS]; +/// Check `assets/HOWTO.md` to see how we generated the constants. +pub const ROUND_CONSTANTS: [[BlsScalar; WIDTH]; ROUNDS] = { + let bytes = include_bytes!("../../assets/arc.bin"); + + // make sure that there are enough bytes for (WIDTH * ROUNDS) BlsScalar + // stored under 'assets/arc.bin' + if bytes.len() < WIDTH * ROUNDS * 32 { + panic!("There are not enough round constants stored in 'assets/arc.bin', have a look at the HOWTO to generate enough constants."); + } + + let mut cnst = [[BlsScalar::zero(); WIDTH]; ROUNDS]; let mut i = 0; let mut j = 0; - while i < bytes.len() { + while i < WIDTH * ROUNDS * 32 { let a = super::u64_from_buffer(bytes, i); let b = super::u64_from_buffer(bytes, i + 8); let c = super::u64_from_buffer(bytes, i + 16); let d = super::u64_from_buffer(bytes, i + 24); - cnst[j] = BlsScalar::from_raw([a, b, c, d]); + cnst[j / WIDTH][j % WIDTH] = BlsScalar::from_raw([a, b, c, d]); j += 1; i += 32; @@ -53,8 +59,8 @@ mod test { fn test_round_constants() { // Check each element is non-zero let zero = BlsScalar::zero(); - let has_zero = ROUND_CONSTANTS.iter().any(|&x| x == zero); - for ctant in ROUND_CONSTANTS.iter() { + let has_zero = ROUND_CONSTANTS.iter().flatten().any(|&x| x == zero); + for ctant in ROUND_CONSTANTS.iter().flatten() { let bytes = ctant.to_bytes(); assert!(&BlsScalar::from_bytes(&bytes).unwrap() == ctant); }