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 0000000..0432493 Binary files /dev/null and b/assets/ark.bin differ diff --git a/assets/mds.bin b/assets/mds.bin new file mode 100644 index 0000000..2d26cff Binary files /dev/null and b/assets/mds.bin differ 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]