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 0000000..55c576d Binary files /dev/null and b/assets/arc.bin differ diff --git a/assets/ark.bin b/assets/ark.bin deleted file mode 100644 index 0432493..0000000 Binary files a/assets/ark.bin and /dev/null differ diff --git a/src/hades.rs b/src/hades.rs index cea4f39..a5c45c2 100644 --- a/src/hades.rs +++ b/src/hades.rs @@ -15,8 +15,7 @@ //! end, and each full round has `WIDTH` quintic S-Boxes. //! - 59 partial rounds: each partial round has `WIDTH - 1` identity function //! and one quintic S-Box. -//! - 960 round constants -//! - Round constants are generated using [this algorithm](https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py) +//! - 335 round constants which are generated using [this algorithm](https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py) //! - The MDS matrix is a cauchy matrix, the method used to generate it, is //! noted in section "Concrete Instantiations Poseidon and Starkad" @@ -28,12 +27,10 @@ use mds_matrix::MDS_MATRIX; use permutation::Permutation; use round_constants::ROUND_CONSTANTS; -const TOTAL_FULL_ROUNDS: usize = 8; +const FULL_ROUNDS: usize = 8; const PARTIAL_ROUNDS: usize = 59; -const CONSTANTS: usize = 960; - /// The amount of field elements that fit into the hades permutation container pub const WIDTH: usize = 5; diff --git a/src/hades/permutation.rs b/src/hades/permutation.rs index e93ce20..5021940 100644 --- a/src/hades/permutation.rs +++ b/src/hades/permutation.rs @@ -18,7 +18,7 @@ use dusk_bls12_381::BlsScalar; #[cfg(feature = "zk")] use dusk_plonk::prelude::{Composer, Witness}; -use crate::hades::{PARTIAL_ROUNDS, ROUND_CONSTANTS, TOTAL_FULL_ROUNDS, WIDTH}; +use crate::hades::{FULL_ROUNDS, PARTIAL_ROUNDS, WIDTH}; /// State for zero-knowledge plonk circuits #[cfg(feature = "zk")] @@ -35,11 +35,11 @@ use scalar::ScalarPermutation; /// /// 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 ops). +/// - 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. pub(crate) fn permute(state: &mut [BlsScalar; WIDTH]) { let mut hades = ScalarPermutation::new(); @@ -50,11 +50,11 @@ pub(crate) fn permute(state: &mut [BlsScalar; WIDTH]) { /// /// 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 ops). +/// - 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. #[cfg(feature = "zk")] pub(crate) fn permute_gadget( composer: &mut Composer, @@ -67,30 +67,19 @@ pub(crate) fn permute_gadget( /// Defines the Hades252 permutation algorithm. pub(crate) trait Permutation { - /// 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); }