Skip to content

Commit

Permalink
Merge pull request #244 from dusk-network/mocello/243_refactor_hades
Browse files Browse the repository at this point in the history
Refactor hades and only expose `WIDTH`
  • Loading branch information
moCello authored Jan 25, 2024
2 parents 0e347a3 + 1caa47a commit 6d91b47
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 277 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +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 `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.33.0] - 2024-01-03

Expand Down Expand Up @@ -429,6 +436,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Variants of sponge for `Scalar` & `Gadget(Variable/LC)`.

<!-- ISSUES -->
[#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
Expand Down
24 changes: 10 additions & 14 deletions src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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::*;

Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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]);

Expand Down
13 changes: 7 additions & 6 deletions src/hades.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@
//! - 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;

const TOTAL_FULL_ROUNDS: usize = 8;
Expand All @@ -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 permutation::permute;
#[cfg(feature = "zk")]
pub use strategies::GadgetStrategy;
pub use strategies::{ScalarStrategy, Strategy};
pub(crate) use permutation::permute_gadget;

const fn u64_from_buffer<const N: usize>(buf: &[u8; N], i: usize) -> u64 {
u64::from_le_bytes([
Expand Down
193 changes: 193 additions & 0 deletions src/hades/permutation.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
/// Fetch the next round constant from an iterator
fn next_c<'b, I>(constants: &mut I) -> BlsScalar
where
I: Iterator<Item = &'b BlsScalar>,
{
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<Item = &'b BlsScalar>;

/// 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<Item = &'b BlsScalar>;

/// 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<Item = &'b BlsScalar>,
{
// 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<Item = &'a BlsScalar>,
{
// 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
}
}
Loading

0 comments on commit 6d91b47

Please sign in to comment.