From f58afb4d5d0d4186af45b203bff2c54d8a823fdb Mon Sep 17 00:00:00 2001 From: moana Date: Mon, 22 Jan 2024 13:28:52 +0100 Subject: [PATCH 1/2] Hide everything except `WIDTH` from hades --- CHANGELOG.md | 5 +++++ src/cipher.rs | 24 ++++++++++-------------- src/hades.rs | 5 +++-- src/hades/strategies.rs | 29 +++++++++++++++++++++++++---- src/hades/strategies/gadget.rs | 19 ++++++------------- src/hades/strategies/scalar.rs | 2 +- src/perm_uses.rs | 4 ++-- src/sponge.rs | 13 ++++++------- src/sponge/merkle.rs | 10 ++++------ 9 files changed, 62 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5466a9..50913a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Remove `default` and `alloc` features [#184] +- Remove `Strategy`, `ScalarStrategy` and `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] ## [0.33.0] - 2024-01-03 @@ -429,6 +432,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Variants of sponge for `Scalar` & `Gadget(Variable/LC)`. +[#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 [#212]: https://github.com/dusk-network/poseidon252/issues/212 [#206]: https://github.com/dusk-network/poseidon252/issues/206 diff --git a/src/cipher.rs b/src/cipher.rs index da1b5f9..e8a1627 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -90,7 +90,7 @@ use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; use dusk_jubjub::JubJubAffine; -use crate::hades::{ScalarStrategy, Strategy, WIDTH}; +use crate::hades::{permute, WIDTH}; #[cfg(feature = "rkyv-impl")] use bytecheck::CheckBytes; @@ -199,12 +199,10 @@ impl PoseidonCipher { nonce: &BlsScalar, ) -> Self { let zero = BlsScalar::zero(); - let mut strategy = ScalarStrategy::new(); - let mut cipher = [zero; CIPHER_SIZE]; let mut state = PoseidonCipher::initial_state(secret, *nonce); - strategy.perm(&mut state); + permute(&mut state); (0..MESSAGE_CAPACITY).for_each(|i| { state[i + 1] += if i < message.len() { @@ -216,7 +214,7 @@ impl PoseidonCipher { cipher[i] = state[i + 1]; }); - strategy.perm(&mut state); + permute(&mut state); cipher[MESSAGE_CAPACITY] = state[1]; PoseidonCipher::new(cipher) @@ -231,19 +229,17 @@ impl PoseidonCipher { nonce: &BlsScalar, ) -> Option<[BlsScalar; MESSAGE_CAPACITY]> { let zero = BlsScalar::zero(); - let mut strategy = ScalarStrategy::new(); - let mut message = [zero; MESSAGE_CAPACITY]; let mut state = PoseidonCipher::initial_state(secret, *nonce); - strategy.perm(&mut state); + permute(&mut state); (0..MESSAGE_CAPACITY).for_each(|i| { message[i] = self.cipher[i] - state[i + 1]; state[i + 1] = self.cipher[i]; }); - strategy.perm(&mut state); + permute(&mut state); if self.cipher[MESSAGE_CAPACITY] != state[1] { return None; @@ -256,7 +252,7 @@ impl PoseidonCipher { #[cfg(feature = "zk")] mod zk { use super::PoseidonCipher; - use crate::hades::{GadgetStrategy, WIDTH}; + use crate::hades::{permute_gadget, WIDTH}; use dusk_plonk::prelude::*; @@ -302,7 +298,7 @@ mod zk { let mut state = PoseidonCipher::initial_state_circuit(composer, ks0, ks1, nonce); - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); (0..PoseidonCipher::capacity()).for_each(|i| { let x = if i < message.len() { @@ -319,7 +315,7 @@ mod zk { cipher[i] = state[i + 1]; }); - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); cipher[PoseidonCipher::capacity()] = state[1]; cipher @@ -342,7 +338,7 @@ mod zk { let mut state = PoseidonCipher::initial_state_circuit(composer, ks0, ks1, nonce); - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); (0..PoseidonCipher::capacity()).for_each(|i| { let constraint = Constraint::new() @@ -356,7 +352,7 @@ mod zk { state[i + 1] = cipher[i]; }); - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); composer.assert_equal(cipher[PoseidonCipher::capacity()], state[1]); diff --git a/src/hades.rs b/src/hades.rs index f10b75b..bffb90c 100644 --- a/src/hades.rs +++ b/src/hades.rs @@ -26,6 +26,7 @@ mod strategies; use mds_matrix::MDS_MATRIX; use round_constants::ROUND_CONSTANTS; +use strategies::Strategy; const TOTAL_FULL_ROUNDS: usize = 8; @@ -36,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; #[cfg(feature = "zk")] -pub use strategies::GadgetStrategy; -pub use strategies::{ScalarStrategy, Strategy}; +pub(crate) use strategies::permute_gadget; const fn u64_from_buffer(buf: &[u8; N], i: usize) -> u64 { u64::from_le_bytes([ diff --git a/src/hades/strategies.rs b/src/hades/strategies.rs index b913374..1571c86 100644 --- a/src/hades/strategies.rs +++ b/src/hades/strategies.rs @@ -15,21 +15,42 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::{PARTIAL_ROUNDS, ROUND_CONSTANTS, TOTAL_FULL_ROUNDS}; +#[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 use gadget::GadgetStrategy; -pub use scalar::ScalarStrategy; +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 trait Strategy { +pub(crate) trait Strategy { /// Fetch the next round constant from an iterator fn next_c<'b, I>(constants: &mut I) -> BlsScalar where diff --git a/src/hades/strategies/gadget.rs b/src/hades/strategies/gadget.rs index 8cd91e7..6abfa96 100644 --- a/src/hades/strategies/gadget.rs +++ b/src/hades/strategies/gadget.rs @@ -11,7 +11,7 @@ use crate::hades::{Strategy, MDS_MATRIX, WIDTH}; /// Implements a Hades252 strategy for `Witness` as input values. /// Requires a reference to a `ConstraintSystem`. -pub struct GadgetStrategy<'a> { +pub(crate) struct GadgetStrategy<'a> { /// A reference to the constraint system used by the gadgets cs: &'a mut Composer, count: usize, @@ -22,13 +22,6 @@ impl<'a> GadgetStrategy<'a> { pub fn new(cs: &'a mut Composer) -> Self { GadgetStrategy { cs, count: 0 } } - - /// Perform the hades permutation on a plonk circuit - pub fn gadget(composer: &'a mut Composer, x: &mut [Witness]) { - let mut strategy = GadgetStrategy::new(composer); - - strategy.perm(x); - } } impl AsMut for GadgetStrategy<'_> { @@ -136,7 +129,7 @@ impl<'a> Strategy for GadgetStrategy<'a> { mod tests { use super::*; - use crate::hades::ScalarStrategy; + use crate::hades::{permute, permute_gadget}; use core::result::Result; use ff::Field; @@ -166,7 +159,7 @@ mod tests { }); // Apply Hades gadget strategy. - GadgetStrategy::gadget(composer, &mut i_var); + permute_gadget(composer, &mut i_var); // Copy the result of the permutation into the perm. perm.copy_from_slice(&i_var); @@ -193,7 +186,7 @@ mod tests { let mut output = [BlsScalar::zero(); WIDTH]; output.copy_from_slice(&input); - ScalarStrategy::new().perm(&mut output); + permute(&mut output); (input, output) } @@ -235,7 +228,7 @@ mod tests { // Prepare input & output let i = [BlsScalar::from(5000u64); WIDTH]; let mut o = [BlsScalar::from(5000u64); WIDTH]; - ScalarStrategy::new().perm(&mut o); + permute(&mut o); let circuit = TestCircuit { i, o }; let mut rng = StdRng::seed_from_u64(0xbeef); @@ -262,7 +255,7 @@ mod tests { i[1] = x_scalar; let mut o = [BlsScalar::from(31u64); WIDTH]; - ScalarStrategy::new().perm(&mut o); + permute(&mut o); let circuit = TestCircuit { i, o }; let mut rng = StdRng::seed_from_u64(0xbeef); diff --git a/src/hades/strategies/scalar.rs b/src/hades/strategies/scalar.rs index 21b3280..8197eb5 100644 --- a/src/hades/strategies/scalar.rs +++ b/src/hades/strategies/scalar.rs @@ -10,7 +10,7 @@ use crate::hades::{Strategy, MDS_MATRIX, WIDTH}; /// Implements a Hades252 strategy for `BlsScalar` as input values. #[derive(Default)] -pub struct ScalarStrategy {} +pub(crate) struct ScalarStrategy {} impl ScalarStrategy { /// Constructs a new `ScalarStrategy`. diff --git a/src/perm_uses.rs b/src/perm_uses.rs index 46ffaf0..47a4aa9 100644 --- a/src/perm_uses.rs +++ b/src/perm_uses.rs @@ -8,7 +8,7 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::{ScalarStrategy, Strategy, WIDTH}; +use crate::hades::{permute, WIDTH}; /// Takes in one BlsScalar and outputs 2. /// This function is fixed. @@ -23,7 +23,7 @@ pub fn two_outputs(message: BlsScalar) -> [BlsScalar; 2] { // Since we do a fixed_length hash, `words` is always the size of `WIDTH`. // Therefore, we can simply do the permutation and return the desired // results. - ScalarStrategy::new().perm(&mut words); + permute(&mut words); [words[1], words[2]] } diff --git a/src/sponge.rs b/src/sponge.rs index 6d8a665..b33a7fa 100644 --- a/src/sponge.rs +++ b/src/sponge.rs @@ -11,7 +11,7 @@ pub mod truncated; use dusk_bls12_381::BlsScalar; -use crate::hades::{ScalarStrategy, Strategy, WIDTH}; +use crate::hades::{permute, WIDTH}; #[cfg(feature = "zk")] pub use zk::gadget; @@ -32,7 +32,6 @@ pub use zk::gadget; /// value. The padding values will be zeroes. To avoid collision, the padding /// will imply one additional permutation in case `len` is a multiple of `r`. pub fn hash(messages: &[BlsScalar]) -> BlsScalar { - let mut h = ScalarStrategy::new(); let mut state = [BlsScalar::zero(); WIDTH]; // If exists an `m` such as `m · (WIDTH - 1) == l`, then the last iteration @@ -66,12 +65,12 @@ pub fn hash(messages: &[BlsScalar]) -> BlsScalar { // append `1`, then there must be an extra permutation // for the padding } else if i == last_iteration { - h.perm(&mut state); + permute(&mut state); state[1] += BlsScalar::one(); } - h.perm(&mut state); + permute(&mut state); }); state[1] @@ -79,7 +78,7 @@ pub fn hash(messages: &[BlsScalar]) -> BlsScalar { #[cfg(feature = "zk")] mod zk { - use crate::hades::{GadgetStrategy, WIDTH}; + use crate::hades::{permute_gadget, WIDTH}; use dusk_plonk::prelude::{Composer, Constraint, Witness}; /// Mirror the implementation of [`hash`] inside of a PLONK circuit. @@ -121,7 +120,7 @@ mod zk { state[chunk.len() + 1] = composer.gate_add(constraint); } else if i == last_iteration { - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); let constraint = Constraint::new().left(1).a(state[1]).constant(1); @@ -129,7 +128,7 @@ mod zk { state[1] = composer.gate_add(constraint); } - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); }); state[1] diff --git a/src/sponge/merkle.rs b/src/sponge/merkle.rs index fe8eba3..005c34b 100644 --- a/src/sponge/merkle.rs +++ b/src/sponge/merkle.rs @@ -9,7 +9,7 @@ use dusk_bls12_381::BlsScalar; -use crate::hades::{ScalarStrategy, Strategy, WIDTH}; +use crate::hades::{permute, WIDTH}; #[cfg(feature = "zk")] pub use zk::gadget; @@ -47,8 +47,6 @@ fn tag() -> u64 { /// /// If `A` is not dividable by `r`, the padding values will be zeroes. pub fn hash(messages: &[BlsScalar; A]) -> BlsScalar { - let mut h = ScalarStrategy::new(); - // initialize the state with zeros and set the first element to the tag let mut state = [BlsScalar::zero(); WIDTH]; state[0] = BlsScalar::from(tag::()); @@ -59,7 +57,7 @@ pub fn hash(messages: &[BlsScalar; A]) -> BlsScalar { state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { *s += c; }); - h.perm(&mut state); + permute(&mut state); }); state[1] @@ -71,7 +69,7 @@ mod zk { use dusk_plonk::prelude::*; - use crate::hades::{GadgetStrategy, WIDTH}; + use crate::hades::{permute_gadget, WIDTH}; /// Mirror the implementation of merkle [`hash`] inside of a PLONK circuit. /// @@ -94,7 +92,7 @@ mod zk { let constraint = Constraint::new().left(1).a(*s).right(1).b(*c); *s = composer.gate_add(constraint); }); - GadgetStrategy::gadget(composer, &mut state); + permute_gadget(composer, &mut state); }); state[1] From 1caa47a66a530b18f8fd2ca1ce01710be0765d7c Mon Sep 17 00:00:00 2001 From: moana Date: Tue, 23 Jan 2024 14:48:53 +0100 Subject: [PATCH 2/2] Rename strategy to permutation This commit also improves the comments in the hades module. --- CHANGELOG.md | 10 +- src/hades.rs | 14 +- src/hades/permutation.rs | 193 ++++++++++++++++++ .../{strategies => permutation}/gadget.rs | 99 ++++----- .../{strategies => permutation}/scalar.rs | 34 ++- src/hades/strategies.rs | 185 ----------------- 6 files changed, 274 insertions(+), 261 deletions(-) create mode 100644 src/hades/permutation.rs rename src/hades/{strategies => permutation}/gadget.rs (74%) rename src/hades/{strategies => permutation}/scalar.rs (65%) delete mode 100644 src/hades/strategies.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 50913a1..45b31d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,17 +12,21 @@ 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] +- Remove `dusk-hades` dependency [#240] ### Added - Add `zk` and `cipher` features [#184] -- Add hades permutation here [#240] -- Add internal `permute` and `permute_gadget` functions [#243] +- Add the code for the hades permutation to crate [#240] +- Add internal `permute` and `permute_gadget` functions to `hades` module [#243] ## [0.33.0] - 2024-01-03 diff --git a/src/hades.rs b/src/hades.rs index bffb90c..cea4f39 100644 --- a/src/hades.rs +++ b/src/hades.rs @@ -13,20 +13,20 @@ //! - Permutation `WIDTH` is 5 field elements //! - 8 full rounds: 4 full rounds at the beginning and 4 full rounds at the //! end, and each full round has `WIDTH` quintic S-Boxes. -//! - 59 partial rounds: each partial round has a quintic S-Box and `WIDTH - 1` -//! identity functions. +//! - 59 partial rounds: each partial round has `WIDTH - 1` identity function +//! and one quintic S-Box. //! - 960 round constants -//! - Round constants for the full rounds are generated using [this algorithm](https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py) +//! - Round constants 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" 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..e93ce20 --- /dev/null +++ b/src/hades/permutation.rs @@ -0,0 +1,193 @@ +// 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 the scalar-field of +/// the bls12_381 elliptic curve. +/// +/// 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(crate) 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(crate) 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 MDS matrix with the state. + 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, 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); + } + + // Apply R_P partial rounds + for _ in 0..PARTIAL_ROUNDS { + self.apply_partial_round(&mut constants, state); + } + + // Apply R_f full rounds + for _ in 0..TOTAL_FULL_ROUNDS / 2 { + self.apply_full_round(&mut constants, state); + } + } + + /// 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 - } -}