Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor hades and only expose WIDTH #244

Merged
merged 2 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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