diff --git a/CHANGELOG.md b/CHANGELOG.md index 50913a1..d0dfba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,17 +12,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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] ### Removed - Remove `default` and `alloc` features [#184] -- Remove `Strategy`, `ScalarStrategy` and `GadgetStrategy` from public API [#243] +- Remove `hades::Strategy`, `hades::ScalarStrategy` and `hades::GadgetStrategy` from public API [#243] ### Added - Add `zk` and `cipher` features [#184] - Add hades permutation here [#240] -- Add internal `permute` and `permute_gadget` functions [#243] +- Add `permute` method to `ScalarStrategy` and `GadgetStrategy` [#243] ## [0.33.0] - 2024-01-03 diff --git a/src/hades.rs b/src/hades.rs index bffb90c..e06d9b2 100644 --- a/src/hades.rs +++ b/src/hades.rs @@ -21,12 +21,12 @@ //! noted in section "Concrete Instantiations Poseidon and Starkad" mod mds_matrix; +mod permutation; mod round_constants; -mod strategies; use mds_matrix::MDS_MATRIX; +use permutation::Permutation; use round_constants::ROUND_CONSTANTS; -use strategies::Strategy; const TOTAL_FULL_ROUNDS: usize = 8; @@ -37,9 +37,9 @@ const CONSTANTS: usize = 960; /// The amount of field elements that fit into the hades permutation container pub const WIDTH: usize = 5; -pub(crate) use strategies::permute; +pub(crate) use permutation::permute; #[cfg(feature = "zk")] -pub(crate) use strategies::permute_gadget; +pub(crate) use permutation::permute_gadget; const fn u64_from_buffer(buf: &[u8; N], i: usize) -> u64 { u64::from_le_bytes([ diff --git a/src/hades/permutation.rs b/src/hades/permutation.rs new file mode 100644 index 0000000..d873932 --- /dev/null +++ b/src/hades/permutation.rs @@ -0,0 +1,190 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +//! This module contains an implementation of the `Hades252` permutation +//! algorithm specifically designed to work outside of Rank 1 Constraint Systems +//! (R1CS) or other custom Constraint Systems such as Add/Mul/Custom plonk +//! gate-circuits. +//! +//! The inputs of the permutation function have to be explicitly over the +//! scalar Field of the bls12_381 curve so over a modulus +//! `p = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`. + +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}; + +/// State for zero-knowledge plonk circuits +#[cfg(feature = "zk")] +mod gadget; +#[cfg(feature = "zk")] +use gadget::GadgetPermutaiton; + +/// State for scalar +mod scalar; +use scalar::ScalarPermutation; + +/// Applies one Hades permutation to the state operating on scalar. +/// +/// 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 other half of the `FULL_ROUNDS`. +/// +/// This structure allows to minimize the number of non-linear ops while +/// mantaining the security. +pub fn permute(state: &mut [BlsScalar; WIDTH]) { + let mut hades = ScalarPermutation::new(); + + hades.perm(state); +} + +/// Applies one Hades permutation on the given state in a plonk circuit. +/// +/// 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 other half of the `FULL_ROUNDS`. +/// +/// This structure allows to minimize the number of non-linear ops while +/// mantaining the security. +#[cfg(feature = "zk")] +pub fn permute_gadget(composer: &mut Composer, state: &mut [Witness; WIDTH]) { + let mut hades = GadgetPermutaiton::new(composer); + + hades.perm(state); +} + +/// 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") + } + + /// Add round keys to the state. + /// + /// This round key addition also known as `ARK` 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; + + /// Computes `input ^ 5 (mod p)` + /// + /// The modulo depends on the input you use. In our case the modulo is done + /// in respect of the scalar field of the bls12_381 curve + /// `p = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`. + fn quintic_s_box(&mut self, value: &mut T); + + /// Multiply the values for MDS matrix with the state during the full rounds + /// application. + fn mul_matrix<'b, I>(&mut self, constants: &mut I, state: &mut [T; WIDTH]) + where + I: Iterator; + + /// 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 + /// 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 + self.quintic_s_box(&mut state[WIDTH - 1]); + + // Multiply this result by the MDS matrix + self.mul_matrix(constants, 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** + /// 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 + state.iter_mut().for_each(|w| self.quintic_s_box(w)); + + // Multiply this result by the MDS matrix + self.mul_matrix(constants, state); + } + + /// Applies one Hades 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 + /// ops). + /// - Applies the other half of the `FULL_ROUNDS`. + /// + /// This structure allows to minimize the number of non-linear ops while + /// mantaining the security. + fn perm(&mut self, data: &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, data); + } + + // Apply R_P partial rounds + for _ in 0..PARTIAL_ROUNDS { + self.apply_partial_round(&mut constants, data); + } + + // Apply R_f full rounds + for _ in 0..TOTAL_FULL_ROUNDS / 2 { + self.apply_full_round(&mut constants, data); + } + } + + /// Return the total rounds count + fn rounds() -> usize { + TOTAL_FULL_ROUNDS + PARTIAL_ROUNDS + } +} diff --git a/src/hades/strategies/gadget.rs b/src/hades/permutation/gadget.rs similarity index 74% rename from src/hades/strategies/gadget.rs rename to src/hades/permutation/gadget.rs index 6abfa96..dbce114 100644 --- a/src/hades/strategies/gadget.rs +++ b/src/hades/permutation/gadget.rs @@ -7,121 +7,124 @@ use dusk_bls12_381::BlsScalar; use dusk_plonk::prelude::*; -use crate::hades::{Strategy, MDS_MATRIX, WIDTH}; +use crate::hades::{Permutation as HadesPermutation, MDS_MATRIX, WIDTH}; -/// Implements a Hades252 strategy for `Witness` as input values. +/// A state for the ['HadesPermutation`] operating on [`Witness`]es. /// Requires a reference to a `ConstraintSystem`. -pub(crate) struct GadgetStrategy<'a> { +pub(crate) struct GadgetPermutaiton<'a> { /// A reference to the constraint system used by the gadgets - cs: &'a mut Composer, - count: usize, + composer: &'a mut Composer, + round: usize, } -impl<'a> GadgetStrategy<'a> { - /// Constructs a new `GadgetStrategy` with the constraint system. - pub fn new(cs: &'a mut Composer) -> Self { - GadgetStrategy { cs, count: 0 } +impl<'a> GadgetPermutaiton<'a> { + /// Constructs a new `GadgetPermutaiton` with the constraint system. + pub fn new(composer: &'a mut Composer) -> Self { + Self { composer, round: 0 } } } -impl AsMut for GadgetStrategy<'_> { +impl AsMut for GadgetPermutaiton<'_> { fn as_mut(&mut self) -> &mut Composer { - self.cs + self.composer } } -impl<'a> Strategy for GadgetStrategy<'a> { - fn add_round_key<'b, I>(&mut self, constants: &mut I, words: &mut [Witness]) - where +impl<'a> HadesPermutation for GadgetPermutaiton<'a> { + fn add_round_key<'b, I>( + &mut self, + constants: &mut I, + state: &mut [Witness; WIDTH], + ) where I: Iterator, { - // Add only for the first round. - // - // The remainder ARC are performed with the constant appended - // to the linear layer - if self.count == 0 { - words.iter_mut().for_each(|w| { + // 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); let constraint = Constraint::new().left(1).a(*w).constant(constant); - *w = self.cs.gate_add(constraint); + *w = self.composer.gate_add(constraint); }); } } fn quintic_s_box(&mut self, value: &mut Witness) { let constraint = Constraint::new().mult(1).a(*value).b(*value); - let v2 = self.cs.gate_mul(constraint); + let v2 = self.composer.gate_mul(constraint); let constraint = Constraint::new().mult(1).a(v2).b(v2); - let v4 = self.cs.gate_mul(constraint); + let v4 = self.composer.gate_mul(constraint); let constraint = Constraint::new().mult(1).a(v4).b(*value); - *value = self.cs.gate_mul(constraint); + *value = self.composer.gate_mul(constraint); } /// Adds a constraint for each matrix coefficient multiplication - fn mul_matrix<'b, I>(&mut self, constants: &mut I, values: &mut [Witness]) - where + fn mul_matrix<'b, I>( + &mut self, + constants: &mut I, + state: &mut [Witness; WIDTH], + ) where I: Iterator, { let mut result = [Composer::ZERO; WIDTH]; - self.count += 1; + self.round += 1; // Implementation optimized for WIDTH = 5 // - // c is the next round constant. - // For the partial round, it is added only for the last element + // 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] * values[j] ) + c + // r[x] = sum_{j=0..WIDTH} ( MDS[x][j] * state[j] ) + c // // q_l = MDS[x][0] // q_r = MDS[x][1] // q_4 = MDS[x][2] - // w_l = values[0] - // w_r = values[1] - // w_4 = values[2] + // w_l = state[0] + // w_r = state[1] + // w_4 = state[2] // r[x] = q_l · w_l + q_r · w_r + q_4 · w_4; // // q_l = MDS[x][3] // q_r = MDS[x][4] // q_4 = 1 - // w_l = values[3] - // w_r = values[4] + // w_l = state[3] + // w_r = state[4] // w_4 = r[x] // r[x] = q_l · w_l + q_r · w_r + q_4 · w_4 + c; for j in 0..WIDTH { - let c = if self.count < Self::rounds() { - Self::next_c(constants) - } else { - BlsScalar::zero() + let c = match self.round < Self::rounds() { + true => Self::next_c(constants), + false => BlsScalar::zero(), }; let constraint = Constraint::new() .left(MDS_MATRIX[j][0]) - .a(values[0]) + .a(state[0]) .right(MDS_MATRIX[j][1]) - .b(values[1]) + .b(state[1]) .fourth(MDS_MATRIX[j][2]) - .d(values[2]); + .d(state[2]); - result[j] = self.cs.gate_add(constraint); + result[j] = self.composer.gate_add(constraint); let constraint = Constraint::new() .left(MDS_MATRIX[j][3]) - .a(values[3]) + .a(state[3]) .right(MDS_MATRIX[j][4]) - .b(values[4]) + .b(state[4]) .fourth(1) .d(result[j]) .constant(c); - result[j] = self.cs.gate_add(constraint); + result[j] = self.composer.gate_add(constraint); } - values.copy_from_slice(&result); + state.copy_from_slice(&result); } } @@ -158,7 +161,7 @@ mod tests { *v = composer.append_witness(*o); }); - // Apply Hades gadget strategy. + // Apply Hades gadget permutation. permute_gadget(composer, &mut i_var); // Copy the result of the permutation into the perm. diff --git a/src/hades/strategies/scalar.rs b/src/hades/permutation/scalar.rs similarity index 65% rename from src/hades/strategies/scalar.rs rename to src/hades/permutation/scalar.rs index 8197eb5..8c2ac5d 100644 --- a/src/hades/strategies/scalar.rs +++ b/src/hades/permutation/scalar.rs @@ -6,28 +6,29 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::{Strategy, MDS_MATRIX, WIDTH}; +use crate::hades::{Permutation as HadesPermutation, MDS_MATRIX, WIDTH}; -/// Implements a Hades252 strategy for `BlsScalar` as input values. +/// State that implements the [`HadesPermutation`] for `BlsScalar` as input +/// values. #[derive(Default)] -pub(crate) struct ScalarStrategy {} +pub(crate) struct ScalarPermutation {} -impl ScalarStrategy { - /// Constructs a new `ScalarStrategy`. +impl ScalarPermutation { + /// Constructs a new `ScalarPermutation`. pub fn new() -> Self { Default::default() } } -impl Strategy for ScalarStrategy { +impl HadesPermutation for ScalarPermutation { fn add_round_key<'b, I>( &mut self, constants: &mut I, - words: &mut [BlsScalar], + state: &mut [BlsScalar; WIDTH], ) where I: Iterator, { - words.iter_mut().for_each(|w| { + state.iter_mut().for_each(|w| { *w += Self::next_c(constants); }); } @@ -39,19 +40,19 @@ impl Strategy for ScalarStrategy { fn mul_matrix<'b, I>( &mut self, _constants: &mut I, - values: &mut [BlsScalar], + state: &mut [BlsScalar; WIDTH], ) where I: Iterator, { let mut result = [BlsScalar::zero(); WIDTH]; - for (j, value) in values.iter().enumerate().take(WIDTH) { + for (j, value) in state.iter().enumerate() { for k in 0..WIDTH { result[k] += MDS_MATRIX[k][j] * value; } } - values.copy_from_slice(&result); + state.copy_from_slice(&result); } } @@ -59,10 +60,7 @@ impl Strategy for ScalarStrategy { mod tests { use super::*; - fn perm(values: &mut [BlsScalar]) { - let mut strategy = ScalarStrategy::new(); - strategy.perm(values); - } + use crate::hades::permute; #[test] fn hades_det() { @@ -70,9 +68,9 @@ mod tests { let mut y = [BlsScalar::from(17u64); WIDTH]; let mut z = [BlsScalar::from(19u64); WIDTH]; - perm(&mut x); - perm(&mut y); - perm(&mut z); + permute(&mut x); + permute(&mut y); + permute(&mut z); assert_eq!(x, y); assert_ne!(x, z); diff --git a/src/hades/strategies.rs b/src/hades/strategies.rs deleted file mode 100644 index 1571c86..0000000 --- a/src/hades/strategies.rs +++ /dev/null @@ -1,185 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// Copyright (c) DUSK NETWORK. All rights reserved. - -//! This module contains an implementation of the `Hades252` -//! strategy algorithm specifically designed to work outside of -//! Rank 1 Constraint Systems (R1CS) or other custom Constraint -//! Systems such as Add/Mul/Custom plonk gate-circuits. -//! -//! The inputs of the permutation function have to be explicitly -//! over the BlsScalar Field of the bls12_381 curve so working over -//! `Fq = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`. - -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}; - -/// Strategy for zero-knowledge plonk circuits -#[cfg(feature = "zk")] -mod gadget; -#[cfg(feature = "zk")] -use gadget::GadgetStrategy; - -/// Strategy for scalars -mod scalar; -use scalar::ScalarStrategy; - -// #[cfg(feature = "zk")] -// pub use gadget::GadgetStrategy; -// pub use scalar::ScalarStrategy; - -/// Perform one Hades permutation on the given state. -pub fn permute(state: &mut [BlsScalar; WIDTH]) { - let mut hades = ScalarStrategy::new(); - - hades.perm(state); -} - -/// Perform one Hades permutation on the given state in a plonk circuit. -#[cfg(feature = "zk")] -pub fn permute_gadget(composer: &mut Composer, state: &mut [Witness; WIDTH]) { - let mut hades = GadgetStrategy::new(composer); - - hades.perm(state); -} - -/// Defines the Hades252 strategy algorithm. -pub(crate) trait Strategy { - /// 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 out of ARK constants") - } - - /// Add round keys to a set of `StrategyInput`. - /// - /// This round key addition also known as `ARK` 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, words: &mut [T]) - where - I: Iterator; - - /// Computes `input ^ 5 (mod Fp)` - /// - /// The modulo depends on the input you use. In our case - /// the modulo is done in respect of the `bls12_381 scalar field` - /// == `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`. - fn quintic_s_box(&mut self, value: &mut T); - - /// Multiply the values for MDS matrix during the - /// full rounds application. - fn mul_matrix<'b, I>(&mut self, constants: &mut I, values: &mut [T]) - where - I: Iterator; - - /// Applies a `Partial Round` also known as a - /// `Partial S-Box layer` to a set of inputs. - /// - /// ### A partial round has 3 steps on every iteration: - /// - /// - Add round keys to each word. Also known as `ARK`. - /// - Apply `quintic S-Box` **just to the last element of - /// the words generated from the first step.** This is also known - /// as a `Sub Words` operation. - /// - Multiplies the output words from the second step by - /// the `MDS_MATRIX`. - /// This is known as the `Mix Layer`. - fn apply_partial_round<'b, I>(&mut self, constants: &mut I, words: &mut [T]) - where - I: Iterator, - { - let last = words.len() - 1; - - // Add round keys to each word - self.add_round_key(constants, words); - - // Then apply quintic s-box - self.quintic_s_box(&mut words[last]); - - // Multiply this result by the MDS matrix - self.mul_matrix(constants, words); - } - - /// Applies a `Full Round` also known as a - /// `Full S-Box layer` to a set of inputs. - /// - /// A full round has 3 steps on every iteration: - /// - /// - Add round keys to each word. Also known as `ARK`. - /// - Apply `quintic S-Box` **to all of the words generated - /// from the first step.** - /// This is also known as a `Sub Words` operation. - /// - Multiplies the output words from the second step by - /// the `MDS_MATRIX`. - /// This is known as the `Mix Layer`. - fn apply_full_round<'a, I>(&mut self, constants: &mut I, words: &mut [T]) - where - I: Iterator, - { - // Add round keys to each word - self.add_round_key(constants, words); - - // Then apply quintic s-box - words.iter_mut().for_each(|w| self.quintic_s_box(w)); - - // Multiply this result by the MDS matrix - self.mul_matrix(constants, words); - } - - /// Applies a `permutation-round` of the `Hades252` strategy. - /// - /// It returns a vec of `WIDTH` outputs as a result which should be - /// a randomly permuted version of the input. - /// - /// In general, the same round function is iterated enough times - /// to make sure that any symmetries and structural properties that - /// might exist in the round function vanish. - /// - /// This `permutation` is a 3-step process that: - /// - /// - Applies twice the half of the `FULL_ROUNDS` - /// (which can be understood as linear ops). - /// - /// - In the middle step it applies the `PARTIAL_ROUDS` - /// (which can be understood as non-linear ops). - /// - /// This structure allows to minimize the number of non-linear - /// ops while mantaining the security. - fn perm(&mut self, data: &mut [T]) { - let mut constants = ROUND_CONSTANTS.iter(); - - // Apply R_f full rounds - for _ in 0..TOTAL_FULL_ROUNDS / 2 { - self.apply_full_round(&mut constants, data); - } - - // Apply R_P partial rounds - for _ in 0..PARTIAL_ROUNDS { - self.apply_partial_round(&mut constants, data); - } - - // Apply R_f full rounds - for _ in 0..TOTAL_FULL_ROUNDS / 2 { - self.apply_full_round(&mut constants, data); - } - } - - /// Return the total rounds count - fn rounds() -> usize { - TOTAL_FULL_ROUNDS + PARTIAL_ROUNDS - } -}