From 2eeb49c61590c51470ed90740b23a7c5e7a693c6 Mon Sep 17 00:00:00 2001 From: moana Date: Wed, 17 Jan 2024 17:44:54 +0100 Subject: [PATCH] Move hades permutation into poseidon lib Resolves #240 --- Cargo.toml | 2 - README.md | 5 +- assets/HOWTO.md | 115 ++++++++++++++ assets/ark.bin | Bin 0 -> 30720 bytes assets/mds.bin | Bin 0 -> 800 bytes benches/sponge.rs | 2 +- src/cipher.rs | 9 +- src/hades.rs | 54 +++++++ src/hades/mds_matrix.rs | 39 +++++ src/hades/round_constants.rs | 63 ++++++++ src/hades/strategies.rs | 164 +++++++++++++++++++ src/hades/strategies/gadget.rs | 278 +++++++++++++++++++++++++++++++++ src/hades/strategies/scalar.rs | 80 ++++++++++ src/lib.rs | 3 + src/perm_uses.rs | 11 +- src/sponge.rs | 7 +- src/sponge/merkle.rs | 10 +- tests/cipher.rs | 2 +- 18 files changed, 819 insertions(+), 25 deletions(-) create mode 100644 assets/HOWTO.md create mode 100644 assets/ark.bin create mode 100644 assets/mds.bin create mode 100644 src/hades.rs create mode 100644 src/hades/mds_matrix.rs create mode 100644 src/hades/round_constants.rs create mode 100644 src/hades/strategies.rs create mode 100644 src/hades/strategies/gadget.rs create mode 100644 src/hades/strategies/scalar.rs diff --git a/Cargo.toml b/Cargo.toml index d30c27a..6603c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ license = "MPL-2.0" dusk-bls12_381 = { version = "0.13", default-features = false } dusk-jubjub = { version = "0.14", default-features = false } dusk-bytes = "0.1" -dusk-hades = "0.24" dusk-plonk = { version = "0.19", default-features = false, features = ["alloc"], optional = true } rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } @@ -26,7 +25,6 @@ ff = { version = "0.13", default-features = false } [features] zk = [ "dusk-plonk", - "dusk-hades/plonk" ] merkle = [] cipher = [] diff --git a/README.md b/README.md index 76a0318..0c99b25 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,7 @@ Reference implementation for the Poseidon Hashing algorithm. This repository has been created so there's a unique library that holds the tools & functions required to perform Poseidon Hashes. -This hashes heavily rely on the Hades permutation, which is one of the key parts that Poseidon needs in order -to work. -This library uses the reference implementation of [Dusk-Hades](https://github.com/dusk-network/hades252) which has been -designed & build by the [Dusk-Network team](https://dusk.network/). +These hashes heavily rely on the Hades design for its inner permutation. **The library provides the two hashing techniques of Poseidon:** diff --git a/assets/HOWTO.md b/assets/HOWTO.md new file mode 100644 index 0000000..3cabe0c --- /dev/null +++ b/assets/HOWTO.md @@ -0,0 +1,115 @@ + + + + + + +# How to generate the assets + +The `ark.bin` and `mds.bin` files in this folder are generated using the snippets below: + +## Filename: ark.bin + +```rust +use dusk_bls12_381::BlsScalar; +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; + +fn constants() -> [BlsScalar; CONSTANTS] { + let mut cnst = [BlsScalar::zero(); CONSTANTS]; + let mut p = BlsScalar::one(); + let mut bytes = b"poseidon-for-plonk".to_vec(); + + cnst.iter_mut().for_each(|c| { + let mut hasher = Sha512::new(); + hasher.update(bytes.as_slice()); + bytes = hasher.finalize().to_vec(); + + let mut v = [0x00u8; 64]; + v.copy_from_slice(&bytes[0..64]); + + *c = BlsScalar::from_bytes_wide(&v) + p; + p = *c; + }); + + cnst +} + +fn write_constants() -> std::io::Result<()> { + let filename = "ark.bin"; + let mut buf: Vec = vec![]; + + constants().iter().for_each(|c| { + c.internal_repr() + .iter() + .for_each(|r| buf.extend_from_slice(&(*r).to_le_bytes())); + }); + + let mut file = fs::File::create(filename)?; + file.write_all(&buf)?; + Ok(()) +} +``` + +## Filename: mds.bin + +```rust +use dusk_bls12_381::BlsScalar; +use std::fs; +use std::io::Write; + +// The width of the permutation container, this needs to be the same number as +// in `dusk_poseidon::hades::WIDTH`. +const WIDTH: usize = 5; + +fn mds() -> [[BlsScalar; WIDTH]; WIDTH] { + let mut matrix = [[BlsScalar::zero(); WIDTH]; WIDTH]; + let mut xs = [BlsScalar::zero(); WIDTH]; + let mut ys = [BlsScalar::zero(); WIDTH]; + + // Generate x and y values deterministically for the cauchy matrix, where + // `x[i] != y[i]` to allow the values to be inverted and there are no + // duplicates in the x vector or y vector, so that the determinant is always + // non-zero. + // [a b] + // [c d] + // det(M) = (ad - bc) ; if a == b and c == d => det(M) = 0 + // For an MDS matrix, every possible mxm submatrix, must have det(M) != 0 + (0..WIDTH).for_each(|i| { + xs[i] = BlsScalar::from(i as u64); + ys[i] = BlsScalar::from((i + WIDTH) as u64); + }); + + let mut m = 0; + (0..WIDTH).for_each(|i| { + (0..WIDTH).for_each(|j| { + matrix[m][j] = (xs[i] + ys[j]).invert().unwrap(); + }); + m += 1; + }); + + matrix +} + +fn write_mds() -> std::io::Result<()> { + let filename = "mds.bin"; + let mut buf: Vec = vec![]; + + mds().iter().for_each(|row| { + row.iter().for_each(|c| { + c.internal_repr() + .iter() + .for_each(|r| buf.extend_from_slice(&(*r).to_le_bytes())); + }); + }); + + let mut file = fs::File::create(filename)?; + file.write_all(&buf)?; + Ok(()) +} +``` diff --git a/assets/ark.bin b/assets/ark.bin new file mode 100644 index 0000000000000000000000000000000000000000..043249354529e6feab08ca2603d3e40691de7ca2 GIT binary patch literal 30720 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 literal 0 HcmV?d00001 diff --git a/benches/sponge.rs b/benches/sponge.rs index 1fd9d4b..448898f 100644 --- a/benches/sponge.rs +++ b/benches/sponge.rs @@ -5,8 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use dusk_hades::WIDTH; use dusk_plonk::prelude::*; +use dusk_poseidon::hades::WIDTH; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; diff --git a/src/cipher.rs b/src/cipher.rs index 964efcd..da1b5f9 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -88,9 +88,10 @@ use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; -use dusk_hades::{ScalarStrategy, Strategy}; use dusk_jubjub::JubJubAffine; +use crate::hades::{ScalarStrategy, Strategy, WIDTH}; + #[cfg(feature = "rkyv-impl")] use bytecheck::CheckBytes; #[cfg(feature = "rkyv-impl")] @@ -169,7 +170,7 @@ impl PoseidonCipher { pub fn initial_state( secret: &JubJubAffine, nonce: BlsScalar, - ) -> [BlsScalar; dusk_hades::WIDTH] { + ) -> [BlsScalar; WIDTH] { [ // Domain - Maximum plaintext length of the elements of Fq, as // defined in the paper @@ -255,7 +256,7 @@ impl PoseidonCipher { #[cfg(feature = "zk")] mod zk { use super::PoseidonCipher; - use dusk_hades::GadgetStrategy; + use crate::hades::{GadgetStrategy, WIDTH}; use dusk_plonk::prelude::*; @@ -267,7 +268,7 @@ mod zk { ks0: Witness, ks1: Witness, nonce: Witness, - ) -> [Witness; dusk_hades::WIDTH] { + ) -> [Witness; WIDTH] { let domain = BlsScalar::from_raw([0x100000000u64, 0, 0, 0]); let domain = composer.append_constant(domain); diff --git a/src/hades.rs b/src/hades.rs new file mode 100644 index 0000000..f10b75b --- /dev/null +++ b/src/hades.rs @@ -0,0 +1,54 @@ +// 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. + +//! Implementation of [Hades252](https://eprint.iacr.org/2019/458.pdf) +//! permutation algorithm over the Bls12-381 Scalar field. +//! +//! ## Parameters +//! +//! - `p = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` +//! - 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. +//! - 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) +//! - 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 round_constants; +mod strategies; + +use mds_matrix::MDS_MATRIX; +use round_constants::ROUND_CONSTANTS; + +const TOTAL_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; + +#[cfg(feature = "zk")] +pub use strategies::GadgetStrategy; +pub use strategies::{ScalarStrategy, Strategy}; + +const fn u64_from_buffer(buf: &[u8; N], i: usize) -> u64 { + u64::from_le_bytes([ + buf[i], + buf[i + 1], + buf[i + 2], + buf[i + 3], + buf[i + 4], + buf[i + 5], + buf[i + 6], + buf[i + 7], + ]) +} diff --git a/src/hades/mds_matrix.rs b/src/hades/mds_matrix.rs new file mode 100644 index 0000000..a0b9955 --- /dev/null +++ b/src/hades/mds_matrix.rs @@ -0,0 +1,39 @@ +// 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. + +use dusk_bls12_381::BlsScalar; + +use crate::hades::WIDTH; + +/// Represents a `static reference` to the +/// `Maximum Distance Separable Matrix -> MDS_MATRIX` +/// of `(WIDTH x WIDTH)`. +/// +/// This matrix is loaded from the `mds.bin` file where +/// is pre-computed and represented in bytes. +pub const MDS_MATRIX: [[BlsScalar; WIDTH]; WIDTH] = { + let bytes = include_bytes!("../../assets/mds.bin"); + let mut mds = [[BlsScalar::zero(); WIDTH]; WIDTH]; + let mut k = 0; + let mut i = 0; + + while i < WIDTH { + let mut j = 0; + while j < WIDTH { + let a = super::u64_from_buffer(bytes, k); + let b = super::u64_from_buffer(bytes, k + 8); + let c = super::u64_from_buffer(bytes, k + 16); + let d = super::u64_from_buffer(bytes, k + 24); + k += 32; + + mds[i][j] = BlsScalar::from_raw([a, b, c, d]); + j += 1; + } + i += 1; + } + + mds +}; diff --git a/src/hades/round_constants.rs b/src/hades/round_constants.rs new file mode 100644 index 0000000..cba27c8 --- /dev/null +++ b/src/hades/round_constants.rs @@ -0,0 +1,63 @@ +// 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 is designed to load the 960 constants used as `round_constants` +//! from `ark.bin`. +//! +//! The constants were originally computed using: +//! https://extgit.iaik.tugraz.at/krypto/hadesmimc/blob/master/code/calc_round_numbers.py +//! and then mapped onto `BlsScalar` in the Bls12_381 scalar field. + +use dusk_bls12_381::BlsScalar; + +use crate::hades::CONSTANTS; + +/// `ROUND_CONSTANTS` constists on a static reference +/// that points to the pre-loaded 960 Fq constants. +/// +/// This 960 `BlsScalar` constants are loaded from `ark.bin` +/// where all of the `BlsScalar`s are represented in buf. +/// +/// 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]; + + let mut i = 0; + let mut j = 0; + while i < bytes.len() { + 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]); + j += 1; + + i += 32; + } + + cnst +}; + +#[cfg(test)] +mod test { + use super::*; + + #[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 bytes = ctant.to_bytes(); + assert!(&BlsScalar::from_bytes(&bytes).unwrap() == ctant); + } + assert!(!has_zero); + } +} diff --git a/src/hades/strategies.rs b/src/hades/strategies.rs new file mode 100644 index 0000000..b913374 --- /dev/null +++ b/src/hades/strategies.rs @@ -0,0 +1,164 @@ +// 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; + +use crate::hades::{PARTIAL_ROUNDS, ROUND_CONSTANTS, TOTAL_FULL_ROUNDS}; + +/// Strategy for zero-knowledge plonk circuits +#[cfg(feature = "zk")] +mod gadget; + +/// Strategy for scalars +mod scalar; + +#[cfg(feature = "zk")] +pub use gadget::GadgetStrategy; +pub use scalar::ScalarStrategy; + +/// Defines the Hades252 strategy algorithm. +pub 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 + } +} diff --git a/src/hades/strategies/gadget.rs b/src/hades/strategies/gadget.rs new file mode 100644 index 0000000..8cd91e7 --- /dev/null +++ b/src/hades/strategies/gadget.rs @@ -0,0 +1,278 @@ +// 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. + +use dusk_bls12_381::BlsScalar; +use dusk_plonk::prelude::*; + +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> { + /// A reference to the constraint system used by the gadgets + cs: &'a mut Composer, + count: 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 } + } + + /// 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<'_> { + fn as_mut(&mut self) -> &mut Composer { + self.cs + } +} + +impl<'a> Strategy for GadgetStrategy<'a> { + fn add_round_key<'b, I>(&mut self, constants: &mut I, words: &mut [Witness]) + 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| { + let constant = Self::next_c(constants); + let constraint = + Constraint::new().left(1).a(*w).constant(constant); + + *w = self.cs.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 constraint = Constraint::new().mult(1).a(v2).b(v2); + let v4 = self.cs.gate_mul(constraint); + + let constraint = Constraint::new().mult(1).a(v4).b(*value); + *value = self.cs.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 + I: Iterator, + { + let mut result = [Composer::ZERO; WIDTH]; + self.count += 1; + + // Implementation optimized for WIDTH = 5 + // + // c is the next round constant. + // For the partial round, it is added only for the last element + // + // The resulting array `r` will be defined as + // r[x] = sum j 0..WIDTH ( MDS[x][j] * values[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] + // 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_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 constraint = Constraint::new() + .left(MDS_MATRIX[j][0]) + .a(values[0]) + .right(MDS_MATRIX[j][1]) + .b(values[1]) + .fourth(MDS_MATRIX[j][2]) + .d(values[2]); + + result[j] = self.cs.gate_add(constraint); + + let constraint = Constraint::new() + .left(MDS_MATRIX[j][3]) + .a(values[3]) + .right(MDS_MATRIX[j][4]) + .b(values[4]) + .fourth(1) + .d(result[j]) + .constant(c); + + result[j] = self.cs.gate_add(constraint); + } + + values.copy_from_slice(&result); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::hades::ScalarStrategy; + + use core::result::Result; + use ff::Field; + use rand::rngs::StdRng; + use rand::SeedableRng; + + #[derive(Default)] + struct TestCircuit { + i: [BlsScalar; WIDTH], + o: [BlsScalar; WIDTH], + } + + impl Circuit for TestCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { + let zero = Composer::ZERO; + + let mut perm: [Witness; WIDTH] = [zero; WIDTH]; + + let mut i_var: [Witness; WIDTH] = [zero; WIDTH]; + self.i.iter().zip(i_var.iter_mut()).for_each(|(i, v)| { + *v = composer.append_witness(*i); + }); + + let mut o_var: [Witness; WIDTH] = [zero; WIDTH]; + self.o.iter().zip(o_var.iter_mut()).for_each(|(o, v)| { + *v = composer.append_witness(*o); + }); + + // Apply Hades gadget strategy. + GadgetStrategy::gadget(composer, &mut i_var); + + // Copy the result of the permutation into the perm. + perm.copy_from_slice(&i_var); + + // Check that the Gadget perm results = BlsScalar perm results + i_var.iter().zip(o_var.iter()).for_each(|(p, o)| { + composer.assert_equal(*p, *o); + }); + + Ok(()) + } + } + + /// Generate a random input and perform a permutation + fn hades() -> ([BlsScalar; WIDTH], [BlsScalar; WIDTH]) { + let mut input = [BlsScalar::zero(); WIDTH]; + + let mut rng = StdRng::seed_from_u64(0xbeef); + + input + .iter_mut() + .for_each(|s| *s = BlsScalar::random(&mut rng)); + + let mut output = [BlsScalar::zero(); WIDTH]; + + output.copy_from_slice(&input); + ScalarStrategy::new().perm(&mut output); + + (input, output) + } + + /// Setup the test circuit prover and verifier + fn setup() -> Result<(Prover, Verifier), Error> { + const CAPACITY: usize = 1 << 10; + + let mut rng = StdRng::seed_from_u64(0xbeef); + + let pp = PublicParameters::setup(CAPACITY, &mut rng)?; + let label = b"hades_gadget_tester"; + + Compiler::compile::(&pp, label) + } + + #[test] + fn preimage() -> Result<(), Error> { + let (prover, verifier) = setup()?; + + let (i, o) = hades(); + + let circuit = TestCircuit { i, o }; + let mut rng = StdRng::seed_from_u64(0xbeef); + + // Proving + let (proof, public_inputs) = prover.prove(&mut rng, &circuit)?; + + // Verifying + verifier.verify(&proof, &public_inputs)?; + + Ok(()) + } + + #[test] + fn preimage_constant() -> Result<(), Error> { + let (prover, verifier) = setup()?; + + // Prepare input & output + let i = [BlsScalar::from(5000u64); WIDTH]; + let mut o = [BlsScalar::from(5000u64); WIDTH]; + ScalarStrategy::new().perm(&mut o); + + let circuit = TestCircuit { i, o }; + let mut rng = StdRng::seed_from_u64(0xbeef); + + // Proving + let (proof, public_inputs) = prover.prove(&mut rng, &circuit)?; + + // Verifying + verifier.verify(&proof, &public_inputs)?; + + Ok(()) + } + + #[test] + fn preimage_fails() -> Result<(), Error> { + let (prover, _) = setup()?; + + // Generate [31, 0, 0, 0, 0] as real input to the perm but build the + // proof with [31, 31, 31, 31, 31]. This should fail on verification + // since the Proof contains incorrect statements. + let x_scalar = BlsScalar::from(31u64); + + let mut i = [BlsScalar::zero(); WIDTH]; + i[1] = x_scalar; + + let mut o = [BlsScalar::from(31u64); WIDTH]; + ScalarStrategy::new().perm(&mut o); + + let circuit = TestCircuit { i, o }; + let mut rng = StdRng::seed_from_u64(0xbeef); + + // Proving should fail + assert!( + prover.prove(&mut rng, &circuit).is_err(), + "proving should fail since the circuit is invalid" + ); + + Ok(()) + } +} diff --git a/src/hades/strategies/scalar.rs b/src/hades/strategies/scalar.rs new file mode 100644 index 0000000..21b3280 --- /dev/null +++ b/src/hades/strategies/scalar.rs @@ -0,0 +1,80 @@ +// 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. + +use dusk_bls12_381::BlsScalar; + +use crate::hades::{Strategy, MDS_MATRIX, WIDTH}; + +/// Implements a Hades252 strategy for `BlsScalar` as input values. +#[derive(Default)] +pub struct ScalarStrategy {} + +impl ScalarStrategy { + /// Constructs a new `ScalarStrategy`. + pub fn new() -> Self { + Default::default() + } +} + +impl Strategy for ScalarStrategy { + fn add_round_key<'b, I>( + &mut self, + constants: &mut I, + words: &mut [BlsScalar], + ) where + I: Iterator, + { + words.iter_mut().for_each(|w| { + *w += Self::next_c(constants); + }); + } + + fn quintic_s_box(&mut self, value: &mut BlsScalar) { + *value = value.square().square() * *value; + } + + fn mul_matrix<'b, I>( + &mut self, + _constants: &mut I, + values: &mut [BlsScalar], + ) where + I: Iterator, + { + let mut result = [BlsScalar::zero(); WIDTH]; + + for (j, value) in values.iter().enumerate().take(WIDTH) { + for k in 0..WIDTH { + result[k] += MDS_MATRIX[k][j] * value; + } + } + + values.copy_from_slice(&result); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn perm(values: &mut [BlsScalar]) { + let mut strategy = ScalarStrategy::new(); + strategy.perm(values); + } + + #[test] + fn hades_det() { + let mut x = [BlsScalar::from(17u64); WIDTH]; + let mut y = [BlsScalar::from(17u64); WIDTH]; + let mut z = [BlsScalar::from(19u64); WIDTH]; + + perm(&mut x); + perm(&mut y); + perm(&mut z); + + assert_eq!(x, y); + assert_ne!(x, z); + } +} diff --git a/src/lib.rs b/src/lib.rs index 34f6bbc..04de6b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub mod perm_uses; /// Implementation for the Poseidon Sponge hash function pub mod sponge; + +/// Implementation of the Poseidon permutation based on the Hades strategy +pub mod hades; diff --git a/src/perm_uses.rs b/src/perm_uses.rs index 1d98555..46ffaf0 100644 --- a/src/perm_uses.rs +++ b/src/perm_uses.rs @@ -7,21 +7,22 @@ //! The `pad` module implements the padding algorithm on the Poseidon hash. use dusk_bls12_381::BlsScalar; -use dusk_hades::{ScalarStrategy, Strategy}; + +use crate::hades::{ScalarStrategy, Strategy, WIDTH}; /// Takes in one BlsScalar and outputs 2. /// This function is fixed. pub fn two_outputs(message: BlsScalar) -> [BlsScalar; 2] { const CAPACITY: BlsScalar = BlsScalar::from_raw([0, 1, 0, 0]); - let mut words = [BlsScalar::zero(); dusk_hades::WIDTH]; + let mut words = [BlsScalar::zero(); WIDTH]; words[0] = CAPACITY; words[1] = message; - // 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. + // 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); [words[1], words[2]] diff --git a/src/sponge.rs b/src/sponge.rs index 69a26ed..6d8a665 100644 --- a/src/sponge.rs +++ b/src/sponge.rs @@ -10,7 +10,8 @@ pub mod merkle; pub mod truncated; use dusk_bls12_381::BlsScalar; -use dusk_hades::{ScalarStrategy, Strategy, WIDTH}; + +use crate::hades::{ScalarStrategy, Strategy, WIDTH}; #[cfg(feature = "zk")] pub use zk::gadget; @@ -78,9 +79,7 @@ pub fn hash(messages: &[BlsScalar]) -> BlsScalar { #[cfg(feature = "zk")] mod zk { - use super::WIDTH; - - use dusk_hades::GadgetStrategy; + use crate::hades::{GadgetStrategy, WIDTH}; use dusk_plonk::prelude::{Composer, Constraint, Witness}; /// Mirror the implementation of [`hash`] inside of a PLONK circuit. diff --git a/src/sponge/merkle.rs b/src/sponge/merkle.rs index 59ab48b..fe8eba3 100644 --- a/src/sponge/merkle.rs +++ b/src/sponge/merkle.rs @@ -8,7 +8,8 @@ //! length is constant and the output is always exactly one scalar. use dusk_bls12_381::BlsScalar; -use dusk_hades::{ScalarStrategy, Strategy, WIDTH}; + +use crate::hades::{ScalarStrategy, Strategy, WIDTH}; #[cfg(feature = "zk")] pub use zk::gadget; @@ -35,7 +36,7 @@ fn tag() -> u64 { /// /// As per the paper definition, the capacity `c` is 1, the rate `r` is `4`, /// which makes the permutation container exactly `5` elements long -/// (= `dusk_hades::WIDTH`). +/// (= `crate::hades::WIDTH`). /// /// The capacity element is the first scalar of the state and is set to the tag /// which is calculated based on the arity of the tree and appended to the @@ -66,11 +67,12 @@ pub fn hash(messages: &[BlsScalar; A]) -> BlsScalar { #[cfg(feature = "zk")] mod zk { - use super::{tag, WIDTH}; + use super::tag; - use dusk_hades::GadgetStrategy; use dusk_plonk::prelude::*; + use crate::hades::{GadgetStrategy, WIDTH}; + /// Mirror the implementation of merkle [`hash`] inside of a PLONK circuit. /// /// The tag is dependent of the arity `A` as described in [`hash`] and diff --git a/tests/cipher.rs b/tests/cipher.rs index d62ecc7..777de27 100644 --- a/tests/cipher.rs +++ b/tests/cipher.rs @@ -50,7 +50,7 @@ fn sanity() { // The hades permutation cannot be performed if the cipher is bigger than // hades width - assert!(dusk_hades::WIDTH >= PoseidonCipher::cipher_size()); + assert!(dusk_poseidon::hades::WIDTH >= PoseidonCipher::cipher_size()); } #[test]