diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 226c858..f5239eb 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -26,12 +26,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo bench --features=cipher,zk --no-run - check_merkle: - name: Check merkle compiles without zk - uses: dusk-network/.github/.github/workflows/run-tests.yml@main - with: - test_flags: --features=merkle --no-run - check_cipher: name: Check cipher compiles without zk uses: dusk-network/.github/.github/workflows/run-tests.yml@main @@ -42,4 +36,4 @@ jobs: name: Tests all uses: dusk-network/.github/.github/workflows/run-tests.yml@main with: - test_flags: --features=zk,cipher,merkle,rkyv-impl,size_32 + test_flags: --features=zk,cipher,rkyv-impl,size_32 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef54ff..52d3865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `Hash` struct [#202] + +### Changed + +- Refactor code with the introduction of SAFE framework [#248] + +### Removed + +- Remove `perm_uses` module as it is obsolete with the introduction of SAFE [#248] +- Remove `merkle` feature with the introduction of SAFE [#248] + ## [0.35.0] - 2024-02-28 ### Changed - Rename trait `hades::Strategy` to `hades::Permutation` [#243] - Rename struct `hades::ScalarStrategy` to `hades::ScalarPermutation` [#243] -- Rename struct `hades::GadgetStrategy` to `hades::GadgetPermutaiton` [#243] +- Rename struct `hades::GadgetStrategy` to `hades::GadgetPermutation` [#243] - Reduce the number of `ROUND_CONSTANTS` from 960 to 335 [#246] - Remove the constants iterator in favor of indexing the constants array directly [#246] - Change `ROUND_CONSTANTS` into a two-dimensional array [#246] @@ -28,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add the code for the hades permutation to crate [#240] - Add internal `permute` and `permute_gadget` functions to `hades` module [#243] +- Add SAFE dependency [#248] ## [0.34.0] - 2024-01-24 @@ -451,6 +465,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Variants of sponge for `Scalar` & `Gadget(Variable/LC)`. +[#248]: https://github.com/dusk-network/poseidon252/issues/248 [#246]: https://github.com/dusk-network/poseidon252/issues/246 [#243]: https://github.com/dusk-network/poseidon252/issues/243 [#240]: https://github.com/dusk-network/poseidon252/issues/240 @@ -458,6 +473,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#212]: https://github.com/dusk-network/poseidon252/issues/212 [#206]: https://github.com/dusk-network/poseidon252/issues/206 [#203]: https://github.com/dusk-network/poseidon252/issues/203 +[#202]: https://github.com/dusk-network/poseidon252/issues/202 [#200]: https://github.com/dusk-network/poseidon252/issues/200 [#198]: https://github.com/dusk-network/poseidon252/issues/198 [#197]: https://github.com/dusk-network/Poseidon252/issues/197 diff --git a/Cargo.toml b/Cargo.toml index e3d28c2..844f18c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ dusk-bls12_381 = { version = "0.13", default-features = false } dusk-jubjub = { version = "0.14", default-features = false } dusk-bytes = "0.1" dusk-plonk = { version = "0.19", default-features = false, features = ["alloc"], optional = true } +dusk-safe = "0.1" rkyv = { version = "0.7", optional = true, default-features = false } bytecheck = { version = "0.6", optional = true, default-features = false } @@ -21,12 +22,12 @@ bytecheck = { version = "0.6", optional = true, default-features = false } criterion = "0.3" rand = { version = "0.8", default-features = false, features = ["getrandom", "std_rng"] } ff = { version = "0.13", default-features = false } +once_cell = "1" [features] zk = [ "dusk-plonk", ] -merkle = [] cipher = [] size_16 = ["rkyv/size_16"] size_32 = ["rkyv/size_32"] @@ -58,7 +59,7 @@ incremental = false codegen-units = 1 [[bench]] -name = "sponge" +name = "hash" harness = false required-features = ["zk"] diff --git a/README.md b/README.md index 0c99b25..2dd5727 100644 --- a/README.md +++ b/README.md @@ -6,42 +6,49 @@ Reference implementation for the Poseidon Hashing algorithm. -#### Reference - +Reference: [Starkad and Poseidon: New Hash Functions for Zero Knowledge Proof Systems](https://eprint.iacr.org/2019/458.pdf) -This repository has been created so there's a unique library that holds the tools & functions -required to perform Poseidon Hashes. +This repository has been created so there's a unique library that holds the tools & functions required to perform Poseidon Hashes on field elements of the bls12-381 elliptic curve. -These hashes heavily rely on the Hades design for its inner permutation. +The hash uses the Hades design for its inner permutation and the [SAFE](https://eprint.iacr.org/2023/522.pdf) framework for contstructing the sponge. -**The library provides the two hashing techniques of Poseidon:** +The library provides the two hashing techniques of Poseidon: +- The 'normal' hashing functionalities operating on `BlsScalar`. +- The 'gadget' hashing functionalities that build a circuit which outputs the hash. -## Sponge Hash +## Example -The `Sponge` technique in Poseidon allows to hash an unlimited amount of data -into a single `Scalar`. -The sponge hash technique requires a padding to be applied before the data can -be hashed. +```rust +use rand::rngs::StdRng; +use rand::SeedableRng; -This is done to avoid hash collisions as stated in the paper of the Poseidon Hash -algorithm. See: . -The inputs of the `sponge_hash` are always `Scalar` or need to be capable of being represented -as it. +use dusk_poseidon::{Domain, Hash}; +use dusk_bls12_381::BlsScalar; +use ff::Field; -The module provides two sponge hash implementations: +// generate random input +let mut rng = StdRng::seed_from_u64(0xbeef); +let mut input = [BlsScalar::zero(); 42]; +for scalar in input.iter_mut() { + *scalar = BlsScalar::random(&mut rng); +} -- Sponge hash using `Scalar` as backend. Which hashes the inputted `Scalar`s and returns a single - `Scalar`. +// digest the input all at once +let hash = Hash::digest(Domain::Other, &input); -- Sponge hash gadget using `dusk_plonk::Witness` as a backend. This technique is used/required - when you want to proof pre-images of unconstrained data inside Zero-Knowledge PLONK circuits. +// update the input gradually +let mut hasher = Hash::new(Domain::Other); +hasher.update(&input[..3]); +hasher.update(&input[3..]); +assert_eq!(hash, hasher.finalize()); -## Documentation +// create a hash used for merkle tree hashing with arity = 4 +let merkle_hash = Hash::digest(Domain::Merkle4, &input[..4]); -This crate contains info about all the functions that the library provides as well as the -documentation regarding the data structures that it exports. To check it, please feel free to go to -the [documentation page](https://dusk-network.github.io/Poseidon252/poseidon252/index.html) +// which is different when another domain is used +assert_ne!(merkle_hash, Hash::digest(Domain::Other, &input[..4])); +``` ## Benchmarks @@ -49,19 +56,10 @@ There are benchmarks for `sponge` and `cipher` in their native form, operating o To run all benchmarks on your machine, run ```shell -cargo bench +cargo bench --features=zk,cipher ``` in the repository. -To run a specific benchmark, run -```shell -cargo bench --bench -``` -where you replace `` with the benchmark name. For example to run the benchmarks for the poseidon cipher encription from the file 'benches/cipher_encrypt.rs', you would need to run -```shell -cargo bench --benches cipher_encrypt -``` - ## Licensing This code is licensed under Mozilla Public License Version 2.0 (MPL-2.0). Please see [LICENSE](https://github.com/dusk-network/plonk/blob/master/LICENSE) for further info. diff --git a/assets/HOWTO.md b/assets/HOWTO.md index 3a3cdad..3719a87 100644 --- a/assets/HOWTO.md +++ b/assets/HOWTO.md @@ -17,7 +17,7 @@ use std::fs; use std::io::Write; // The amount of constants generated, this needs to be at least the total number -// of rounds (= 59 + 8) multiplied by the width of the permutaiton array (= 5). +// of rounds (= 59 + 8) multiplied by the width of the permutation array (= 5). const CONSTANTS: usize = (59 + 8) * 5; fn constants() -> [BlsScalar; CONSTANTS] { diff --git a/benches/cipher_decrypt.rs b/benches/cipher_decrypt.rs index 33a4619..eb7fa38 100644 --- a/benches/cipher_decrypt.rs +++ b/benches/cipher_decrypt.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_poseidon::cipher::{self, PoseidonCipher}; +use dusk_poseidon::{decrypt_gadget, PoseidonCipher}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use dusk_jubjub::GENERATOR; @@ -59,7 +59,7 @@ impl Circuit for CipherDecrypt { *cipher_witness = composer.append_witness(*cipher_scalar); }); - cipher::decrypt(composer, &shared, nonce, &cipher_circuit); + decrypt_gadget(composer, &shared, nonce, &cipher_circuit); Ok(()) } diff --git a/benches/cipher_encrypt.rs b/benches/cipher_encrypt.rs index 1213664..8141a5a 100644 --- a/benches/cipher_encrypt.rs +++ b/benches/cipher_encrypt.rs @@ -4,7 +4,7 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use dusk_poseidon::cipher::{self, PoseidonCipher}; +use dusk_poseidon::{encrypt_gadget, PoseidonCipher}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use dusk_jubjub::GENERATOR; @@ -56,7 +56,7 @@ impl Circuit for CipherEncrypt { *message_witness = composer.append_witness(*message_scalar); }); - cipher::encrypt(composer, &shared, nonce, &message_circuit); + encrypt_gadget(composer, &shared, nonce, &message_circuit); Ok(()) } diff --git a/benches/sponge.rs b/benches/hash.rs similarity index 73% rename from benches/sponge.rs rename to benches/hash.rs index 448898f..7afe097 100644 --- a/benches/sponge.rs +++ b/benches/hash.rs @@ -6,7 +6,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use dusk_plonk::prelude::*; -use dusk_poseidon::hades::WIDTH; +use dusk_poseidon::{Domain, Hash, HashGadget, HADES_WIDTH}; use ff::Field; use rand::rngs::StdRng; use rand::SeedableRng; @@ -15,18 +15,22 @@ const CAPACITY: usize = 11; #[derive(Default)] struct SpongeCircuit { - message: [BlsScalar; WIDTH], + message: [BlsScalar; HADES_WIDTH - 1], + output: BlsScalar, } impl SpongeCircuit { - pub fn new(message: [BlsScalar; WIDTH]) -> Self { - SpongeCircuit { message } + pub fn new( + message: [BlsScalar; HADES_WIDTH - 1], + output: BlsScalar, + ) -> Self { + SpongeCircuit { message, output } } } impl Circuit for SpongeCircuit { fn circuit(&self, composer: &mut Composer) -> Result<(), Error> { - let mut w_message = [Composer::ZERO; WIDTH]; + let mut w_message = [Composer::ZERO; HADES_WIDTH - 1]; w_message .iter_mut() .zip(self.message) @@ -34,7 +38,10 @@ impl Circuit for SpongeCircuit { *witness = composer.append_witness(scalar); }); - dusk_poseidon::sponge::gadget(composer, &w_message); + let output_witness = + HashGadget::digest(Domain::Merkle4, composer, &w_message) + .expect("creating the hash should not fail"); + composer.assert_equal_constant(output_witness[0], 0, Some(self.output)); Ok(()) } @@ -49,20 +56,20 @@ fn bench_sponge(c: &mut Criterion) { let (prover, verifier) = Compiler::compile::(&pp, label) .expect("Circuit should compile successfully"); let mut proof = Proof::default(); - let public_inputs = Vec::new(); let message = [ BlsScalar::random(&mut rng), BlsScalar::random(&mut rng), BlsScalar::random(&mut rng), BlsScalar::random(&mut rng), - BlsScalar::random(&mut rng), ]; - let circuit = SpongeCircuit::new(message); + let public_inputs = Hash::digest(Domain::Merkle4, &message) + .expect("creating the hash should not fail"); + let circuit = SpongeCircuit::new(message, public_inputs[0]); // Benchmark sponge native c.bench_function("sponge native", |b| { b.iter(|| { - dusk_poseidon::sponge::hash(black_box(&circuit.message)); + let _ = Hash::digest(Domain::Merkle4, black_box(&circuit.message)); }) }); diff --git a/src/cipher.rs b/src/cipher.rs index e8a1627..ff8b287 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -24,7 +24,7 @@ //! use core::ops::Mul; //! use dusk_bls12_381::BlsScalar; //! use dusk_jubjub::{dhke, JubJubExtended, JubJubScalar, GENERATOR}; -//! use dusk_poseidon::cipher::PoseidonCipher; +//! use dusk_poseidon::PoseidonCipher; //! use rand::rngs::OsRng; //! use ff::Field; //! @@ -89,17 +89,15 @@ use dusk_bls12_381::BlsScalar; use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable}; use dusk_jubjub::JubJubAffine; +use dusk_safe::Safe; -use crate::hades::{permute, WIDTH}; +use crate::hades::{ScalarPermutation, WIDTH}; #[cfg(feature = "rkyv-impl")] use bytecheck::CheckBytes; #[cfg(feature = "rkyv-impl")] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(feature = "zk")] -pub use zk::{decrypt, encrypt}; - const MESSAGE_CAPACITY: usize = 2; const CIPHER_SIZE: usize = MESSAGE_CAPACITY + 1; const CIPHER_BYTES_SIZE: usize = CIPHER_SIZE * BlsScalar::SIZE; @@ -202,7 +200,7 @@ impl PoseidonCipher { let mut cipher = [zero; CIPHER_SIZE]; let mut state = PoseidonCipher::initial_state(secret, *nonce); - permute(&mut state); + ScalarPermutation::new().permute(&mut state); (0..MESSAGE_CAPACITY).for_each(|i| { state[i + 1] += if i < message.len() { @@ -214,7 +212,7 @@ impl PoseidonCipher { cipher[i] = state[i + 1]; }); - permute(&mut state); + ScalarPermutation::new().permute(&mut state); cipher[MESSAGE_CAPACITY] = state[1]; PoseidonCipher::new(cipher) @@ -232,14 +230,14 @@ impl PoseidonCipher { let mut message = [zero; MESSAGE_CAPACITY]; let mut state = PoseidonCipher::initial_state(secret, *nonce); - permute(&mut state); + ScalarPermutation::new().permute(&mut state); (0..MESSAGE_CAPACITY).for_each(|i| { message[i] = self.cipher[i] - state[i + 1]; state[i + 1] = self.cipher[i]; }); - permute(&mut state); + ScalarPermutation::new().permute(&mut state); if self.cipher[MESSAGE_CAPACITY] != state[1] { return None; @@ -250,11 +248,12 @@ impl PoseidonCipher { } #[cfg(feature = "zk")] -mod zk { +pub mod zk { use super::PoseidonCipher; - use crate::hades::{permute_gadget, WIDTH}; + use crate::hades::{GadgetPermutation, WIDTH}; use dusk_plonk::prelude::*; + use dusk_safe::Safe; impl PoseidonCipher { /// Returns the initial state of the encryption within a composer @@ -298,7 +297,7 @@ mod zk { let mut state = PoseidonCipher::initial_state_circuit(composer, ks0, ks1, nonce); - permute_gadget(composer, &mut state); + GadgetPermutation::new(composer).permute(&mut state); (0..PoseidonCipher::capacity()).for_each(|i| { let x = if i < message.len() { @@ -315,7 +314,7 @@ mod zk { cipher[i] = state[i + 1]; }); - permute_gadget(composer, &mut state); + GadgetPermutation::new(composer).permute(&mut state); cipher[PoseidonCipher::capacity()] = state[1]; cipher @@ -338,7 +337,7 @@ mod zk { let mut state = PoseidonCipher::initial_state_circuit(composer, ks0, ks1, nonce); - permute_gadget(composer, &mut state); + GadgetPermutation::new(composer).permute(&mut state); (0..PoseidonCipher::capacity()).for_each(|i| { let constraint = Constraint::new() @@ -352,7 +351,7 @@ mod zk { state[i + 1] = cipher[i]; }); - permute_gadget(composer, &mut state); + GadgetPermutation::new(composer).permute(&mut state); composer.assert_equal(cipher[PoseidonCipher::capacity()], state[1]); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..fe78480 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,31 @@ +// 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_safe::Error as SafeError; + +/// Defines all possible error variants for SAFE +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Error { + /// A call to during the lifetime of the [`safe::Sponge`] that doesn't fit + /// the io-pattern. + IOPatternViolation, + + /// An invalid io-pattern. + InvalidIOPattern, + + /// The input doesn't yield enough input elements. + TooFewInputElements, +} + +impl From for Error { + fn from(safe_error: SafeError) -> Self { + match safe_error { + SafeError::IOPatternViolation => Self::IOPatternViolation, + SafeError::InvalidIOPattern => Self::InvalidIOPattern, + SafeError::TooFewInputElements => Self::TooFewInputElements, + } + } +} diff --git a/src/hades.rs b/src/hades.rs index a5c45c2..4e5e0d0 100644 --- a/src/hades.rs +++ b/src/hades.rs @@ -10,7 +10,7 @@ //! ## Parameters //! //! - `p = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` -//! - Permutation `WIDTH` is 5 field elements +//! - Permutation container `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 `WIDTH - 1` identity function @@ -24,7 +24,6 @@ mod permutation; mod round_constants; use mds_matrix::MDS_MATRIX; -use permutation::Permutation; use round_constants::ROUND_CONSTANTS; const FULL_ROUNDS: usize = 8; @@ -34,9 +33,9 @@ const PARTIAL_ROUNDS: usize = 59; /// 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(crate) use permutation::permute_gadget; +pub(crate) use permutation::gadget::GadgetPermutation; +pub(crate) use permutation::scalar::ScalarPermutation; const fn u64_from_buffer(buf: &[u8; N], i: usize) -> u64 { u64::from_le_bytes([ @@ -50,3 +49,117 @@ const fn u64_from_buffer(buf: &[u8; N], i: usize) -> u64 { buf[i + 7], ]) } + +// Test the sponge with an internal hades permutation against some predefined +// input and output values. The sponge is initialized with the capacity element +// being zero and the padding is one `BlsScalar::one()`. +#[cfg(test)] +mod tests { + extern crate std; + use std::format; + use std::vec; + + use dusk_bls12_381::BlsScalar; + use dusk_bytes::ParseHexStr; + use dusk_safe::{Call, Safe, Sponge}; + + use crate::hades::{ScalarPermutation, WIDTH}; + + #[derive(Default, Debug, Clone, Copy, PartialEq)] + struct Test(); + + impl Safe for Test { + // apply hades permutation + fn permute(&mut self, state: &mut [BlsScalar; WIDTH]) { + ScalarPermutation::new().permute(state); + } + + // the test in- and outputs have been created with the capacity element + // (tag) being zero + fn tag(&mut self, input: &[u8]) -> BlsScalar { + let _ = input; + BlsScalar::zero() + } + + fn add(&mut self, right: &BlsScalar, left: &BlsScalar) -> BlsScalar { + right + left + } + } + + impl Test { + pub fn new() -> Self { + Self() + } + } + + const TEST_INPUTS: [&str; 10] = [ + "bb67ed265bf1db490ded2e1ede55c0d14c55521509dc73f9c354e98ab76c9625", + "7e74220084d75e10c89e9435d47bb5b8075991b2e29be3b84421dac3b1ee6007", + "5ce5481a4d78cca03498f72761da1b9f1d2aa8fb300be39f0e4fe2534f9d4308", + "b1e710e3c4a8c35154b0ce4e4f4af6f498ebd79f8e7cdf3150372c7501be250b", + "33c9e2025f86b5d82149f1ab8e20a168fc3d99d09b48cbce0286db8752cc3306", + "e98206bfdce791e4e5144079b997d4fc25006194b35655f0e48490b26e24ea35", + "86d2a95cc552de8d5bb20bd4a407fee5ffdc314e93dfe6b2dc792bc71fd8cc2d", + "4edd8307ce28a8c70963d20a7bc28df1e1720bbbc93878a18bd07fad7d51fa15", + "eabc7a296704a68aa01f95adc85f6dd758b175745336d8fc795a17984024b21e", + "cfc108673c93df305e31c283b9c767b7097ae4e174a223e0c24b15a67b701a3a", + ]; + + fn create_poseidon_hash(input: &[BlsScalar]) -> BlsScalar { + let iopattern = + vec![Call::Absorb(input.len()), Call::Absorb(1), Call::Squeeze(1)]; + + let domain_sep = 0; + let mut sponge = Sponge::start(Test::new(), iopattern, domain_sep) + .expect("IO pattern should be valid"); + // absorb given input + sponge + .absorb(input.len(), input) + .expect("Absorbtion of the input should work fine"); + // absorb padding of one BlsScalar::one() + sponge + .absorb(1, &[BlsScalar::one()]) + .expect("Absorbtion of padding should work fine"); + sponge.squeeze(1).expect("Squeezing should work fine"); + let output = sponge.finish().expect("Finish should work fine"); + output[0] + } + + #[test] + fn poseidon_hash() { + let test_inputs: vec::Vec = TEST_INPUTS + .iter() + .map(|input| BlsScalar::from_hex_str(input).unwrap()) + .collect(); + + assert_eq!( + "0x2885ca6d908b34ca83f2177d78283c25d8c5c7230877025bc8d558b8a94e6fe3", + format!("{:?}", create_poseidon_hash(&test_inputs[..3])) + ); + + assert_eq!( + "0x55f7f755570a884cb1430bf5cba11fff94430bea63f3c108e6070dc86532ea75", + format!("{:?}", create_poseidon_hash(&test_inputs[..4])) + ); + + assert_eq!( + "0x4288f8b92a9a8bc20f60aac68a318b3287ddb0a663cef5015bff0e98a0063153", + format!("{:?}", create_poseidon_hash(&test_inputs[..5])) + ); + + assert_eq!( + "0x31673ed327fa548518084e7332c29d4c96ad314cee79d2c447ff4f686458811a", + format!("{:?}", create_poseidon_hash(&test_inputs[..6])) + ); + + assert_eq!( + "0x5aa2df13cf6f910c19e5516c9222df7039d119472534e6488081df57d036b9a8", + format!("{:?}", create_poseidon_hash(&test_inputs[..8])) + ); + + assert_eq!( + "0x0be3889073101db27cd27006256daedda56368a4dad19e6fc810041a23342998", + format!("{:?}", create_poseidon_hash(&test_inputs[..10])) + ); + } +} diff --git a/src/hades/permutation.rs b/src/hades/permutation.rs index 5021940..c03c6a7 100644 --- a/src/hades/permutation.rs +++ b/src/hades/permutation.rs @@ -13,40 +13,16 @@ //! 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::{FULL_ROUNDS, PARTIAL_ROUNDS, WIDTH}; -/// State for zero-knowledge plonk circuits -#[cfg(feature = "zk")] -mod gadget; +/// Hades permutation struct operating in a plonk-circuit. #[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_ROUNDS` (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 -/// maintaining the security. -pub(crate) fn permute(state: &mut [BlsScalar; WIDTH]) { - let mut hades = ScalarPermutation::new(); +pub(crate) mod gadget; - hades.perm(state); -} +/// Hades permutation struct operating on [`BlsScalar`]. +pub(crate) mod scalar; -/// Applies one Hades permutation on the given state in a plonk circuit. +/// Defines the Hades252 permutation algorithm. /// /// This permutation is a 3-step process that: /// - Applies half of the `FULL_ROUNDS` (which can be understood as linear ops). @@ -55,22 +31,8 @@ pub(crate) fn permute(state: &mut [BlsScalar; WIDTH]) { /// /// This structure allows to minimize the number of non-linear ops while /// maintaining 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 { - /// Increment the inner rounds counter. - /// - /// This counter is needed to index the `ROUND_CONSTANTS`. - fn increment_round(&mut self); +pub(crate) trait Hades { + const ROUNDS: usize = FULL_ROUNDS + PARTIAL_ROUNDS; /// Add round constants to the state. /// @@ -79,7 +41,7 @@ pub(crate) trait Permutation { /// /// Basically it allows to destroy any connection between the inputs and the /// outputs of the function. - fn add_round_constants(&mut self, state: &mut [T; WIDTH]); + fn add_round_constants(&mut self, round: usize, state: &mut [T; WIDTH]); /// Computes `input ^ 5 (mod p)` /// @@ -89,7 +51,7 @@ pub(crate) trait Permutation { fn quintic_s_box(&mut self, value: &mut T); /// Multiply the MDS matrix with the state. - fn mul_matrix(&mut self, state: &mut [T; WIDTH]); + fn mul_matrix(&mut self, round: usize, state: &mut [T; WIDTH]); /// Applies a `Partial Round` also known as a `Partial S-Box layer` to a set /// of inputs. @@ -100,18 +62,15 @@ pub(crate) trait Permutation { /// state** generated from the first step. /// - Mix Layer: Multiplies the output state from the second step by the /// `MDS_MATRIX`. - fn apply_partial_round(&mut self, state: &mut [T; WIDTH]) { - // Increment the inner rounds counter - self.increment_round(); - + fn apply_partial_round(&mut self, round: usize, state: &mut [T; WIDTH]) { // Add round constants to each state element - self.add_round_constants(state); + self.add_round_constants(round, state); // Then apply quintic s-box to the last element of the state self.quintic_s_box(&mut state[WIDTH - 1]); // Multiply this result by the MDS matrix - self.mul_matrix(state); + self.mul_matrix(round, state); } /// Applies a `Full Round` also known as a `Full S-Box layer` to a set of @@ -123,18 +82,15 @@ pub(crate) trait Permutation { /// generated from the first step. /// - Mix Layer: Multiplies the output state from the second step by the /// `MDS_MATRIX`. - fn apply_full_round(&mut self, state: &mut [T; WIDTH]) { - // Increment the inner rounds counter - self.increment_round(); - + fn apply_full_round(&mut self, round: usize, state: &mut [T; WIDTH]) { // Add round constants to each state element - self.add_round_constants(state); + self.add_round_constants(round, state); // Then apply quintic s-box to each element of the state state.iter_mut().for_each(|w| self.quintic_s_box(w)); // Multiply this result by the MDS matrix - self.mul_matrix(state); + self.mul_matrix(round, state); } /// Applies one Hades permutation. @@ -150,23 +106,21 @@ pub(crate) trait Permutation { /// maintaining the security. fn perm(&mut self, state: &mut [T; WIDTH]) { // Apply R_f full rounds - for _ in 0..FULL_ROUNDS / 2 { - self.apply_full_round(state); + for round in 0..FULL_ROUNDS / 2 { + self.apply_full_round(round, state); } // Apply R_P partial rounds - for _ in 0..PARTIAL_ROUNDS { - self.apply_partial_round(state); + for round in 0..PARTIAL_ROUNDS { + self.apply_partial_round(round + FULL_ROUNDS / 2, state); } // Apply R_f full rounds - for _ in 0..FULL_ROUNDS / 2 { - self.apply_full_round(state); + for round in 0..FULL_ROUNDS / 2 { + self.apply_full_round( + round + FULL_ROUNDS / 2 + PARTIAL_ROUNDS, + state, + ); } } - - /// Return the total rounds count - fn rounds() -> usize { - FULL_ROUNDS + PARTIAL_ROUNDS - } } diff --git a/src/hades/permutation/gadget.rs b/src/hades/permutation/gadget.rs index 5a7cc5a..4ce9f33 100644 --- a/src/hades/permutation/gadget.rs +++ b/src/hades/permutation/gadget.rs @@ -6,44 +6,52 @@ use dusk_bls12_381::BlsScalar; use dusk_plonk::prelude::*; +use dusk_safe::Safe; -use crate::hades::{ - Permutation as HadesPermutation, MDS_MATRIX, ROUND_CONSTANTS, WIDTH, -}; +use crate::hades::{MDS_MATRIX, ROUND_CONSTANTS, WIDTH}; -/// An implementation for the ['HadesPermutation`] operating on [`Witness`]es. +use super::Hades; + +/// An implementation for the [`Hades`]permutation operating on [`Witness`]es. /// Requires a reference to a `ConstraintSystem`. -pub(crate) struct GadgetPermutaiton<'a> { +pub(crate) struct GadgetPermutation<'a> { /// A reference to the constraint system used by the gadgets composer: &'a mut Composer, - round: usize, } -impl<'a> GadgetPermutaiton<'a> { - /// Constructs a new `GadgetPermutaiton` with the constraint system. +impl<'a> GadgetPermutation<'a> { + /// Constructs a new `GadgetPermutation` with the constraint system. pub fn new(composer: &'a mut Composer) -> Self { - Self { composer, round: 0 } + Self { composer } } } -impl AsMut for GadgetPermutaiton<'_> { - fn as_mut(&mut self) -> &mut Composer { - self.composer +impl<'a> Safe for GadgetPermutation<'a> { + fn permute(&mut self, state: &mut [Witness; WIDTH]) { + self.perm(state); } -} -impl<'a> HadesPermutation for GadgetPermutaiton<'a> { - fn increment_round(&mut self) { - self.round += 1; + fn tag(&mut self, input: &[u8]) -> Witness { + let tag = BlsScalar::hash_to_scalar(input.as_ref()); + self.composer.append_witness(tag) } - fn add_round_constants(&mut self, state: &mut [Witness; WIDTH]) { + fn add(&mut self, right: &Witness, left: &Witness) -> Witness { + let constraint = Constraint::new().left(1).a(*left).right(1).b(*right); + self.composer.gate_add(constraint) + } +} + +impl<'a> Hades for GadgetPermutation<'a> { + fn add_round_constants( + &mut self, + round: usize, + state: &mut [Witness; WIDTH], + ) { // To safe constraints we only add the constants here in the first // round. The remaining constants will be added in the matrix // multiplication. - // Note that the rounds start counting at 1 but the ROUND_CONSTANTS - // start counting at 0. - if self.round == 1 { + if round == 0 { state.iter_mut().enumerate().for_each(|(i, w)| { let constant = ROUND_CONSTANTS[0][i]; let constraint = @@ -66,7 +74,7 @@ impl<'a> HadesPermutation for GadgetPermutaiton<'a> { } /// Adds a constraint for each matrix coefficient multiplication - fn mul_matrix(&mut self, state: &mut [Witness; WIDTH]) { + fn mul_matrix(&mut self, round: usize, state: &mut [Witness; WIDTH]) { let mut result = [Composer::ZERO; WIDTH]; // Implementation optimized for WIDTH = 5 @@ -92,10 +100,8 @@ impl<'a> HadesPermutation for GadgetPermutaiton<'a> { // r[x] = q_l · w_l + q_r · w_r + q_4 · w_4 + c; for j in 0..WIDTH { // c is the next round's constant and hence zero for the last round. - let c = match self.round < Self::rounds() { - // the rounds start counting at 1, so the constants for the next - // round are stored at the round index (and not at `round + 1`) - true => ROUND_CONSTANTS[self.round][j], + let c = match round + 1 < Self::ROUNDS { + true => ROUND_CONSTANTS[round + 1][j], false => BlsScalar::zero(), }; @@ -129,7 +135,7 @@ impl<'a> HadesPermutation for GadgetPermutaiton<'a> { mod tests { use super::*; - use crate::hades::{permute, permute_gadget}; + use crate::hades::ScalarPermutation; use core::result::Result; use ff::Field; @@ -148,24 +154,24 @@ mod tests { 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 i_wit: [Witness; WIDTH] = [zero; WIDTH]; + self.i.iter().zip(i_wit.iter_mut()).for_each(|(i, w)| { + *w = 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); + let mut o_wit: [Witness; WIDTH] = [zero; WIDTH]; + self.o.iter().zip(o_wit.iter_mut()).for_each(|(o, w)| { + *w = composer.append_witness(*o); }); // Apply Hades gadget permutation. - permute_gadget(composer, &mut i_var); + GadgetPermutation::new(composer).permute(&mut i_wit); // Copy the result of the permutation into the perm. - perm.copy_from_slice(&i_var); + perm.copy_from_slice(&i_wit); // Check that the Gadget perm results = BlsScalar perm results - i_var.iter().zip(o_var.iter()).for_each(|(p, o)| { + i_wit.iter().zip(o_wit.iter()).for_each(|(p, o)| { composer.assert_equal(*p, *o); }); @@ -186,7 +192,7 @@ mod tests { let mut output = [BlsScalar::zero(); WIDTH]; output.copy_from_slice(&input); - permute(&mut output); + ScalarPermutation::new().permute(&mut output); (input, output) } @@ -228,7 +234,7 @@ mod tests { // Prepare input & output let i = [BlsScalar::from(5000u64); WIDTH]; let mut o = [BlsScalar::from(5000u64); WIDTH]; - permute(&mut o); + ScalarPermutation::new().permute(&mut o); let circuit = TestCircuit { i, o }; let mut rng = StdRng::seed_from_u64(0xbeef); @@ -255,7 +261,7 @@ mod tests { i[1] = x_scalar; let mut o = [BlsScalar::from(31u64); WIDTH]; - permute(&mut o); + ScalarPermutation::new().permute(&mut o); let circuit = TestCircuit { i, o }; let mut rng = StdRng::seed_from_u64(0xbeef); diff --git a/src/hades/permutation/scalar.rs b/src/hades/permutation/scalar.rs index 023cc54..806643a 100644 --- a/src/hades/permutation/scalar.rs +++ b/src/hades/permutation/scalar.rs @@ -5,44 +5,53 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use dusk_bls12_381::BlsScalar; +use dusk_safe::Safe; -use crate::hades::{ - Permutation as HadesPermutation, MDS_MATRIX, ROUND_CONSTANTS, WIDTH, -}; +use super::Hades; +use crate::hades::{MDS_MATRIX, ROUND_CONSTANTS, WIDTH}; -/// An implementation of the [`HadesPermutation`] for `BlsScalar` as input -/// values. +/// An implementation of the [`Permutation`] for `BlsScalar` as input values. #[derive(Default)] -pub(crate) struct ScalarPermutation { - round: usize, -} +pub(crate) struct ScalarPermutation(); impl ScalarPermutation { /// Constructs a new `ScalarPermutation`. pub fn new() -> Self { - Self { round: 0 } + Self() } } -impl HadesPermutation for ScalarPermutation { - fn increment_round(&mut self) { - self.round += 1; +impl Safe for ScalarPermutation { + fn permute(&mut self, state: &mut [BlsScalar; WIDTH]) { + self.perm(state); + } + + fn tag(&mut self, input: &[u8]) -> BlsScalar { + BlsScalar::hash_to_scalar(input.as_ref()) } - fn add_round_constants(&mut self, state: &mut [BlsScalar; WIDTH]) { + fn add(&mut self, right: &BlsScalar, left: &BlsScalar) -> BlsScalar { + right + left + } +} + +impl Hades for ScalarPermutation { + fn add_round_constants( + &mut self, + round: usize, + state: &mut [BlsScalar; WIDTH], + ) { state .iter_mut() .enumerate() - // the rounds start counting at 1, so the respective round constants - // are stored at index `round - 1` - .for_each(|(i, s)| *s += ROUND_CONSTANTS[self.round - 1][i]); + .for_each(|(i, s)| *s += ROUND_CONSTANTS[round][i]); } fn quintic_s_box(&mut self, value: &mut BlsScalar) { *value = value.square().square() * *value; } - fn mul_matrix(&mut self, state: &mut [BlsScalar; WIDTH]) { + fn mul_matrix(&mut self, _round: usize, state: &mut [BlsScalar; WIDTH]) { let mut result = [BlsScalar::zero(); WIDTH]; for (j, value) in state.iter().enumerate() { @@ -59,17 +68,15 @@ impl HadesPermutation for ScalarPermutation { mod tests { use super::*; - use crate::hades::permute; - #[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]; - permute(&mut x); - permute(&mut y); - permute(&mut z); + ScalarPermutation::new().permute(&mut x); + ScalarPermutation::new().permute(&mut y); + ScalarPermutation::new().permute(&mut z); assert_eq!(x, y); assert_ne!(x, z); diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..f1f9dcd --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,173 @@ +// 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 alloc::vec::Vec; + +use dusk_bls12_381::BlsScalar; +use dusk_jubjub::JubJubScalar; +use dusk_safe::{Call, Sponge}; + +use crate::hades::ScalarPermutation; +use crate::Error; + +#[cfg(feature = "zk")] +pub(crate) mod gadget; + +/// The Domain Separation for Poseidon +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Domain { + /// Domain to specify hashing of 4-arity merkle tree + Merkle4, + /// Domain to specify hashing of 2-arity merkle tree + Merkle2, + /// Domain to specify hash used for encryption + Encryption, + /// Domain to specify hash for any other input + Other, +} + +impl Domain { + /// Encryption for the domain-separator are taken from section 4.2 of the + /// paper adapted to u64. + /// When `Other` is selected we set the domain-separator to zero. We can do + /// this since the io-pattern will be encoded in the tag in any case, + /// ensuring safety from collision attacks. + pub const fn encoding(&self) -> u64 { + match self { + // 2^4 - 1 + Domain::Merkle4 => 0x0000_0000_0000_000f, + // 2^2 - 1 + Domain::Merkle2 => 0x0000_0000_0000_0003, + // 2^32 + Domain::Encryption => 0x0000_0001_0000_0000, + // 0 + Domain::Other => 0x0000_0000_0000_0000, + } + } +} + +fn io_pattern( + domain: Domain, + input: &[&[T]], + output_len: usize, +) -> Result, Error> { + let mut io_pattern = Vec::new(); + // check total input length against domain + let input_len = input.iter().fold(0, |acc, input| acc + input.len()); + match domain { + Domain::Merkle2 if input_len != 2 || output_len != 1 => { + return Err(Error::IOPatternViolation); + } + Domain::Merkle4 if input_len != 4 || output_len != 1 => { + return Err(Error::IOPatternViolation); + } + _ => {} + } + for input in input.iter() { + io_pattern.push(Call::Absorb(input.len())); + } + io_pattern.push(Call::Squeeze(output_len)); + + Ok(io_pattern) +} + +/// Hash any given input into one or several scalar using the Hades +/// permutation strategy. The Hash can absorb multiple chunks of input but will +/// only call `squeeze` once at the finalization of the hash. +/// The output length is set to 1 element per default, but this can be +/// overridden with [`Hash::output_len`]. +pub struct Hash<'a> { + domain: Domain, + input: Vec<&'a [BlsScalar]>, + output_len: usize, +} + +impl<'a> Hash<'a> { + /// Create a new hash. + pub fn new(domain: Domain) -> Self { + Self { + domain, + input: Vec::new(), + output_len: 1, + } + } + + /// Override the length of the hash output (default value is 1). + pub fn output_len(&mut self, output_len: usize) { + self.output_len = output_len; + } + + /// Update the hash input. + pub fn update(&mut self, input: &'a [BlsScalar]) { + self.input.push(input); + } + + /// Finalize the hash. + pub fn finalize(&self) -> Result, Error> { + // generate the io-pattern + let io_pattern = io_pattern(self.domain, &self.input, self.output_len)?; + + // set the domain-separator + let domain_sep = self.domain.encoding(); + + // Generate the hash using the sponge framework. + // initialize the sponge + let mut sponge = + Sponge::start(ScalarPermutation::new(), io_pattern, domain_sep)?; + // absorb the input + for input in self.input.iter() { + sponge.absorb(input.len(), input)?; + } + // squeeze the output + sponge.squeeze(self.output_len)?; + + // return the result + Ok(sponge.finish()?) + } + + /// Finalize the hash and output the result as a `JubJubScalar` by + /// truncating the `BlsScalar` output to 250 bits. + pub fn finalize_truncated(&self) -> Result, Error> { + // bit-mask to 'cast' a bls-scalar result to a jubjub-scalar by + // truncating the 6 highest bits + const TRUNCATION_MASK: BlsScalar = BlsScalar::from_raw([ + 0xffff_ffff_ffff_ffff, + 0xffff_ffff_ffff_ffff, + 0xffff_ffff_ffff_ffff, + 0x03ff_ffff_ffff_ffff, + ]); + + // finalize the hash as bls-scalar + let bls_output = self.finalize()?; + + Ok(bls_output + .iter() + .map(|bls| { + JubJubScalar::from_raw((bls & &TRUNCATION_MASK).reduce().0) + }) + .collect()) + } + + /// Digest an input and calculate the hash immediately + pub fn digest( + domain: Domain, + input: &'a [BlsScalar], + ) -> Result, Error> { + let mut hash = Self::new(domain); + hash.update(input); + hash.finalize() + } + + /// Digest an input and calculate the hash as jubjub-scalar immediately + pub fn digest_truncated( + domain: Domain, + input: &'a [BlsScalar], + ) -> Result, Error> { + let mut hash = Self::new(domain); + hash.update(input); + hash.finalize_truncated() + } +} diff --git a/src/hash/gadget.rs b/src/hash/gadget.rs new file mode 100644 index 0000000..f7867dd --- /dev/null +++ b/src/hash/gadget.rs @@ -0,0 +1,109 @@ +// 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 alloc::vec::Vec; + +use dusk_plonk::prelude::{Composer, Witness}; +use dusk_safe::Sponge; + +use crate::hades::GadgetPermutation; +use crate::{Domain, Error}; + +use super::io_pattern; + +/// Hash struct. +pub struct HashGadget<'a> { + domain: Domain, + input: Vec<&'a [Witness]>, + output_len: usize, +} + +impl<'a> HashGadget<'a> { + /// Create a new hash. + pub fn new(domain: Domain) -> Self { + Self { + domain, + input: Vec::new(), + output_len: 1, + } + } + + /// Override the length of the hash output (default value is 1). + pub fn output_len(&mut self, output_len: usize) { + self.output_len = output_len; + } + + /// Update the hash input. + pub fn update(&mut self, input: &'a [Witness]) { + self.input.push(input); + } + + /// Finalize the hash. + pub fn finalize( + &self, + composer: &mut Composer, + ) -> Result, Error> { + // generate the io-pattern + let io_pattern = io_pattern(self.domain, &self.input, self.output_len)?; + + // get the domain-separator + let domain_sep = self.domain.encoding(); + + // Generate the hash using the sponge framework. + // initialize the sponge + let mut sponge = Sponge::start( + GadgetPermutation::new(composer), + io_pattern, + domain_sep, + )?; + // absorb the input + for input in self.input.iter() { + sponge.absorb(input.len(), input)?; + } + // squeeze the output + sponge.squeeze(self.output_len as usize)?; + + // return the result + Ok(sponge.finish()?) + } + + /// Finalize the hash and output JubJubScalar. + pub fn finalize_truncated( + &self, + composer: &mut Composer, + ) -> Result, Error> { + // finalize the hash as bls-scalar witnesses + let bls_output = self.finalize(composer)?; + + // truncate the bls witnesses to 250 bits + Ok(bls_output + .iter() + .map(|bls| composer.append_logic_xor::<125>(*bls, Composer::ZERO)) + .collect()) + } + + /// Digest an input and calculate the hash immediately + pub fn digest( + domain: Domain, + composer: &mut Composer, + input: &'a [Witness], + ) -> Result, Error> { + let mut hash = Self::new(domain); + hash.update(input); + hash.finalize(composer) + } + + /// Digest an input and calculate the hash as jubjub-scalar immediately + pub fn digest_truncated( + domain: Domain, + composer: &mut Composer, + input: &'a [Witness], + ) -> Result, Error> { + let mut hash = Self::new(domain); + hash.update(input); + hash.finalize_truncated(composer) + } +} diff --git a/src/lib.rs b/src/lib.rs index 04de6b1..8139e53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,16 +8,25 @@ #![doc = include_str!("../README.md")] #![deny(missing_docs)] -/// Encryption and decryption implementation over a Poseidon cipher -#[cfg(feature = "cipher")] -pub mod cipher; +extern crate alloc; + +mod error; +pub use error::Error; -/// Module containing a fixed-length Poseidon hash implementation with one -/// input scalar and two output scalar -pub mod perm_uses; +mod hades; +pub use hades::WIDTH as HADES_WIDTH; -/// Implementation for the Poseidon Sponge hash function -pub mod sponge; +mod hash; +#[cfg(feature = "zk")] +pub use hash::gadget::HashGadget; +pub use hash::{Domain, Hash}; -/// Implementation of the Poseidon permutation based on the Hades strategy -pub mod hades; +#[cfg(feature = "cipher")] +mod cipher; +#[cfg(feature = "cipher")] +pub use cipher::PoseidonCipher; +#[cfg(feature = "cipher")] +#[cfg(feature = "zk")] +pub use cipher::{ + zk::decrypt as decrypt_gadget, zk::encrypt as encrypt_gadget, +}; diff --git a/src/perm_uses.rs b/src/perm_uses.rs deleted file mode 100644 index 47a4aa9..0000000 --- a/src/perm_uses.rs +++ /dev/null @@ -1,60 +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. - -//! The `pad` module implements the padding algorithm on the Poseidon hash. - -use dusk_bls12_381::BlsScalar; - -use crate::hades::{permute, 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(); 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. - permute(&mut words); - - [words[1], words[2]] -} - -#[cfg(test)] -mod tests { - use super::*; - use ff::Field; - use rand::rngs::OsRng; - - #[test] - fn hash_two_outputs() { - let m = BlsScalar::random(&mut OsRng); - - let h = two_outputs(m); - - assert_eq!(h.len(), 2); - assert_ne!(m, BlsScalar::zero()); - assert_ne!(h[0], BlsScalar::zero()); - assert_ne!(h[1], BlsScalar::zero()); - } - - #[test] - fn same_result() { - for _i in 0..100 { - let m = BlsScalar::random(&mut OsRng); - - let h = two_outputs(m); - let h_1 = two_outputs(m); - - assert_eq!(h, h_1); - } - } -} diff --git a/src/sponge.rs b/src/sponge.rs deleted file mode 100644 index b33a7fa..0000000 --- a/src/sponge.rs +++ /dev/null @@ -1,136 +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. - -#[cfg(feature = "merkle")] -pub mod merkle; - -pub mod truncated; - -use dusk_bls12_381::BlsScalar; - -use crate::hades::{permute, WIDTH}; - -#[cfg(feature = "zk")] -pub use zk::gadget; - -/// The `hash` function takes an arbitrary number of Scalars and returns the -/// hash in the form of one scalar by using the `Hades` permutation of a state -/// of `WIDTH` scalars. -/// -/// As the paper definition, the capacity `c` and the rate `r` equal `WIDTH` -/// and `c` is set to `1`. -/// -/// The first scalar of the state will be the capacity and will have no message -/// addition. The remainder scalars of the state will be filled by adding -/// `r`-sized chunks of the input. After each addition of `r`-sized chunks the -/// state does one permutation. -/// -/// The last permutation will append `1` to the message as a padding separator -/// 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 state = [BlsScalar::zero(); WIDTH]; - - // If exists an `m` such as `m · (WIDTH - 1) == l`, then the last iteration - // index should be `m - 1`. - // - // In other words, if `l` is a multiple of `WIDTH - 1`, then the last - // iteration of the chunk should have an extra appended padding `1`. - let l = messages.len(); - let m = l / (WIDTH - 1); - let n = m * (WIDTH - 1); - let last_iteration = if l == n { - m.saturating_sub(1) - } else { - l / (WIDTH - 1) - }; - - messages - .chunks(WIDTH - 1) - .enumerate() - .for_each(|(i, chunk)| { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s += c; - }); - - // Last chunk should have an added `1` followed by zeroes, if there - // is room for such - if i == last_iteration && chunk.len() < WIDTH - 1 { - state[chunk.len() + 1] += BlsScalar::one(); - - // If its the last iteration and there is no available room to - // append `1`, then there must be an extra permutation - // for the padding - } else if i == last_iteration { - permute(&mut state); - - state[1] += BlsScalar::one(); - } - - permute(&mut state); - }); - - state[1] -} - -#[cfg(feature = "zk")] -mod zk { - use crate::hades::{permute_gadget, WIDTH}; - use dusk_plonk::prelude::{Composer, Constraint, Witness}; - - /// Mirror the implementation of [`hash`] inside of a PLONK circuit. - /// - /// The circuit will be defined by the length of `messages`. This means that - /// the circuit description will be different for different messages sizes. - /// - /// The expected usage is the length of the message to be known publicly as - /// the circuit definition. Hence, the padding value `1` will be - /// appended as a constant in the circuit description. - /// - /// The returned value is the hashed witness data computed as a variable. - /// - /// [`hash`]: crate::sponge::hash - pub fn gadget(composer: &mut Composer, messages: &[Witness]) -> Witness { - let mut state = [Composer::ZERO; WIDTH]; - - let l = messages.len(); - let m = l / (WIDTH - 1); - let n = m * (WIDTH - 1); - let last_iteration = if l == n { m - 1 } else { l / (WIDTH - 1) }; - - messages - .chunks(WIDTH - 1) - .enumerate() - .for_each(|(i, chunk)| { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - let constraint = - Constraint::new().left(1).a(*s).right(1).b(*c); - - *s = composer.gate_add(constraint); - }); - - if i == last_iteration && chunk.len() < WIDTH - 1 { - let constraint = Constraint::new() - .left(1) - .a(state[chunk.len() + 1]) - .constant(1); - - state[chunk.len() + 1] = composer.gate_add(constraint); - } else if i == last_iteration { - permute_gadget(composer, &mut state); - - let constraint = - Constraint::new().left(1).a(state[1]).constant(1); - - state[1] = composer.gate_add(constraint); - } - - permute_gadget(composer, &mut state); - }); - - state[1] - } -} diff --git a/src/sponge/merkle.rs b/src/sponge/merkle.rs deleted file mode 100644 index 005c34b..0000000 --- a/src/sponge/merkle.rs +++ /dev/null @@ -1,100 +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. - -//! Implement the sponge framework specialized for merkle trees where the input -//! length is constant and the output is always exactly one scalar. - -use dusk_bls12_381::BlsScalar; - -use crate::hades::{permute, WIDTH}; - -#[cfg(feature = "zk")] -pub use zk::gadget; - -// Computes the tag from the domain-separator and arity. Output length is -// set to 1. -// Encoding: -// first 32 bits are set to arity with the MSB set to 1 -// last 32 bits is set to 1 (our output length) -fn tag() -> u64 { - let mut tag: u64 = 1 << 31; - tag |= A as u64; - tag <<= 32; - - tag |= 1; - tag -} - -/// This `hash` function is specialized for hashing the levels of a merkle tree -/// with arity `A` using the `Hades` ScalarStrategy. -/// NOTE: This hash should *only* be used for the hashing of the levels in a -/// merkle tree. When hashing variable length input (e.g. the data of the -/// leaves), use the generic `dusk_poseidon::sponge::hash` instead. -/// -/// As per the paper definition, the capacity `c` is 1, the rate `r` is `4`, -/// which makes the permutation container exactly `5` elements long -/// (= `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 -/// circuit as a constant. -/// -/// The other scalars of the state will have the scalars of the message added in -/// `r`-sized chunks, with one `Hades` permutation after each chunk addition. -/// -/// If `A` is not dividable by `r`, the padding values will be zeroes. -pub fn hash(messages: &[BlsScalar; A]) -> BlsScalar { - // initialize the state with zeros and set the first element to the tag - let mut state = [BlsScalar::zero(); WIDTH]; - state[0] = BlsScalar::from(tag::()); - - // add the message scalars in r-sized chunks and permute the state after - // each round - messages.chunks(WIDTH - 1).for_each(|chunk| { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s += c; - }); - permute(&mut state); - }); - - state[1] -} - -#[cfg(feature = "zk")] -mod zk { - use super::tag; - - use dusk_plonk::prelude::*; - - use crate::hades::{permute_gadget, 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 - /// appended to the circuit as a constant. This means that a pre-computed - /// circuit over one arity can never be verified with a circuit of another - /// arity. - /// - /// The returned value is the witness of the hash of the levels. - pub fn gadget( - composer: &mut Composer, - messages: &[Witness; A], - ) -> Witness { - // initialize the state with the capacity - let mut state = [Composer::ZERO; WIDTH]; - state[0] = composer.append_witness(BlsScalar::from(tag::())); - - messages.chunks(WIDTH - 1).for_each(|chunk| { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - let constraint = Constraint::new().left(1).a(*s).right(1).b(*c); - *s = composer.gate_add(constraint); - }); - permute_gadget(composer, &mut state); - }); - - state[1] - } -} diff --git a/src/sponge/truncated.rs b/src/sponge/truncated.rs deleted file mode 100644 index 9fc28cb..0000000 --- a/src/sponge/truncated.rs +++ /dev/null @@ -1,67 +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. - -//! Sponge hash and gadget definition - -use crate::sponge; -use dusk_bls12_381::BlsScalar; -use dusk_jubjub::JubJubScalar; - -#[cfg(feature = "zk")] -use dusk_plonk::prelude::*; - -/// The constant represents the bitmask used to truncate the hashing results of -/// a sponge application so that they fit inside of a -/// [`dusk_jubjub::JubJubScalar`] and it's equal to `2^250 - 1`. -/// -/// Let the bitmask size be `m` -/// Considering the field size of jubjub is 251 bits, `m < 251` -/// Plonk logical gates will accept only even `m + 1`, so `(m + 1) % 2 == 0` -/// -/// Plonk logical gates will perform the operation from the base bls `r` of -/// 255 bits + 1. `d = r + 1 - (m + 1) = 4`. But, `d = 4` don't respect the -/// previously set constraint, so it must be 6. -/// -/// This way, the scalar will be truncated to `m = r - d = 255 - 6 = 249 -/// bits` -const TRUNCATION_LIMIT: BlsScalar = BlsScalar([ - 0x432667a3f7cfca74, - 0x7905486e121a84be, - 0x19c02884cfe90d12, - 0xa62ffba6a1323be, -]); - -/// Applies [`hash`] to the `messages` received truncating the result to -/// make it fit inside a `JubJubScalar.` -/// -/// [`hash`]: crate::sponge::hash -pub fn hash(messages: &[BlsScalar]) -> JubJubScalar { - JubJubScalar::from_raw( - (sponge::hash(messages) & TRUNCATION_LIMIT).reduce().0, - ) -} - -/// Mirror the implementation of [`hash`] inside of a PLONK circuit. -/// -/// The circuit will be defined by the length of `messages`. This means that a -/// pre-computed circuit will not behave generically for different messages -/// sizes. -/// -/// The expected usage is the length of the message to be known publicly as the -/// circuit definition. Hence, the padding value `1` will be appended as a -/// circuit description. -/// -/// The returned value is the hashed witness data computed as a variable and -/// truncated to fit inside of a [`JubJubScalar`]. -/// -/// [`hash`]: crate::sponge::hash -#[cfg(feature = "zk")] -pub fn gadget(composer: &mut Composer, message: &[Witness]) -> Witness { - let h = sponge::gadget(composer, message); - - // Truncate to 250 bits - composer.append_logic_xor::<125>(h, Composer::ZERO) -} diff --git a/tests/cipher.rs b/tests/cipher.rs index 777de27..847a364 100644 --- a/tests/cipher.rs +++ b/tests/cipher.rs @@ -10,7 +10,7 @@ use core::ops::Mul; use dusk_bls12_381::BlsScalar; use dusk_bytes::Serializable; use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR}; -use dusk_poseidon::cipher::PoseidonCipher; +use dusk_poseidon::PoseidonCipher; use ff::Field; use rand::rngs::OsRng; use rand::RngCore; @@ -50,7 +50,7 @@ fn sanity() { // The hades permutation cannot be performed if the cipher is bigger than // hades width - assert!(dusk_poseidon::hades::WIDTH >= PoseidonCipher::cipher_size()); + assert!(dusk_poseidon::HADES_WIDTH >= PoseidonCipher::cipher_size()); } #[test] @@ -126,7 +126,7 @@ mod zk { use dusk_jubjub::{dhke, JubJubExtended, GENERATOR_EXTENDED}; use dusk_plonk::prelude::Error as PlonkError; use dusk_plonk::prelude::*; - use dusk_poseidon::cipher; + use dusk_poseidon::{decrypt_gadget, encrypt_gadget}; use rand::rngs::StdRng; use rand::SeedableRng; @@ -192,7 +192,7 @@ mod zk { }); let cipher_gadget = - cipher::encrypt(composer, &shared, nonce, &message_circuit); + encrypt_gadget(composer, &shared, nonce, &message_circuit); self.cipher .iter() @@ -203,7 +203,7 @@ mod zk { }); let message_gadget = - cipher::decrypt(composer, &shared, nonce, &cipher_gadget); + decrypt_gadget(composer, &shared, nonce, &cipher_gadget); self.message.iter().zip(message_gadget.iter()).for_each( |(m, g)| { diff --git a/tests/hash.rs b/tests/hash.rs new file mode 100644 index 0000000..aa0b5fd --- /dev/null +++ b/tests/hash.rs @@ -0,0 +1,300 @@ +// 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. + +#![cfg(feature = "zk")] + +use once_cell::sync::Lazy; +use rand::rngs::StdRng; +use rand::SeedableRng; + +use dusk_plonk::prelude::Error as PlonkError; +use dusk_plonk::prelude::*; +use dusk_poseidon::{Domain, Hash, HashGadget}; +use ff::Field; + +static PUB_PARAMS: Lazy = Lazy::new(|| { + let mut rng = StdRng::seed_from_u64(0xbeef); + + const CAPACITY: usize = 12; + PublicParameters::setup(1 << CAPACITY, &mut rng) + .expect("Cannot initialize Public Parameters") +}); + +fn compile_and_verify( + rng: &mut StdRng, + circuit: &C, + pi: &Vec, +) -> Result<(), PlonkError> +where + C: Circuit, +{ + let label = b"hash-gadget-tester"; + let (prover, verifier) = Compiler::compile::(&PUB_PARAMS, label)?; + + let (proof, _public_inputs) = prover.prove(rng, circuit)?; + + verifier.verify(&proof, pi) +} + +// ---------------- +// Test normal hash +// ---------------- + +#[derive(Debug)] +struct TestCircuit { + input: [BlsScalar; L], + output: BlsScalar, +} + +impl Default for TestCircuit { + fn default() -> Self { + Self { + input: [BlsScalar::zero(); L], + output: BlsScalar::zero(), + } + } +} + +impl TestCircuit { + pub fn random(rng: &mut StdRng) -> Self { + // create random input + let mut input = [BlsScalar::zero(); L]; + input + .iter_mut() + .for_each(|s| *s = BlsScalar::random(&mut *rng)); + + // calculate expected hash output + let output = Hash::digest(Domain::Other, &input) + .expect("hash creation should not fail"); + + Self { + input, + output: output[0], + } + } + + pub fn public_inputs(&self) -> Vec { + [self.output].to_vec() + } +} + +impl Circuit for TestCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { + // append input to the circuit + let mut input_witnesses = [Composer::ZERO; L]; + self.input + .iter() + .zip(input_witnesses.iter_mut()) + .for_each(|(i, w)| { + *w = composer.append_witness(*i); + }); + + // check that the gadget result is as expected + let gadget_output = + HashGadget::digest(Domain::Other, composer, &input_witnesses) + .expect("hash creation should not fail"); + composer.assert_equal_constant(gadget_output[0], 0, Some(self.output)); + + Ok(()) + } +} + +#[test] +fn test_gadget() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xbeef); + + // test for input of 3 scalar + let circuit = TestCircuit::<3>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 5 scalar + let circuit = TestCircuit::<5>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 15 scalar + let circuit = TestCircuit::<15>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs()) +} + +// ------------------- +// Test truncated hash +// ------------------- + +#[derive(Debug)] +struct TestTruncatedCircuit { + input: [BlsScalar; L], + output: JubJubScalar, +} + +impl Default for TestTruncatedCircuit { + fn default() -> Self { + Self { + input: [BlsScalar::zero(); L], + output: JubJubScalar::zero(), + } + } +} + +impl TestTruncatedCircuit { + pub fn random(rng: &mut StdRng) -> Self { + // create random input + let mut input = [BlsScalar::zero(); L]; + input + .iter_mut() + .for_each(|s| *s = BlsScalar::random(&mut *rng)); + + // calculate expected hash output + let output = Hash::digest_truncated(Domain::Other, &input) + .expect("hash creation should not fail"); + + Self { + input, + output: output[0], + } + } + + pub fn public_inputs(&self) -> Vec { + [self.output.into()].to_vec() + } +} + +impl Circuit for TestTruncatedCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { + // append input to the circuit + let mut input_witnesses = [Composer::ZERO; L]; + self.input + .iter() + .zip(input_witnesses.iter_mut()) + .for_each(|(i, w)| { + *w = composer.append_witness(*i); + }); + + // check that the gadget result is as expected + let mut hash = HashGadget::new(Domain::Other); + hash.update(&input_witnesses); + let gadget_output = HashGadget::digest_truncated( + Domain::Other, + composer, + &input_witnesses, + ) + .expect("hash creation should not fail"); + composer.assert_equal_constant( + gadget_output[0], + 0, + Some(self.output.into()), + ); + + Ok(()) + } +} + +#[test] +fn test_truncated_gadget() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xbeef); + + // test for input of 3 scalar + let circuit = TestTruncatedCircuit::<3>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 5 scalar + let circuit = TestTruncatedCircuit::<5>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 15 scalar + let circuit = TestTruncatedCircuit::<15>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs()) +} + +// -------------------- +// Test multiple output +// -------------------- + +#[derive(Debug)] +struct MultipleOutputCircuit { + input: [BlsScalar; I], + output: Vec, +} + +impl Default for MultipleOutputCircuit { + fn default() -> Self { + Self { + input: [BlsScalar::zero(); I], + output: [BlsScalar::zero(); O].to_vec(), + } + } +} + +impl MultipleOutputCircuit { + pub fn random(rng: &mut StdRng) -> Self { + // create random input + let mut input = [BlsScalar::zero(); I]; + input + .iter_mut() + .for_each(|s| *s = BlsScalar::random(&mut *rng)); + + // calculate expected hash output + let mut hash = Hash::new(Domain::Other); + hash.update(&input); + hash.output_len(O); + let output = hash.finalize().expect("Hash creation should pass"); + + assert_eq!(output.len(), O); + + Self { input, output } + } + + pub fn public_inputs(&self) -> Vec { + self.output.clone() + } +} + +impl Circuit for MultipleOutputCircuit { + fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { + // append input to the circuit + let mut input_witnesses = [Composer::ZERO; I]; + self.input + .iter() + .zip(input_witnesses.iter_mut()) + .for_each(|(i, w)| { + *w = composer.append_witness(*i); + }); + + // add hash-gadget to the circuit + let mut hash = HashGadget::new(Domain::Other); + hash.output_len(O); + hash.update(&input_witnesses); + let gadget_output = hash + .finalize(composer) + .expect("hash creation should not fail"); + + assert_eq!(gadget_output.len(), self.output.len()); + + // assert that gadget output is equal to expected output + self.output + .iter() + .zip(gadget_output) + .for_each(|(o, g)| composer.assert_equal_constant(g, 0, Some(*o))); + + Ok(()) + } +} + +#[test] +fn test_multiple_output() -> Result<(), Error> { + let mut rng = StdRng::seed_from_u64(0xbeef); + + // test for input of 3 scalar + let circuit = MultipleOutputCircuit::<3, 3>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 5 scalar + let circuit = MultipleOutputCircuit::<5, 2>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs())?; + + // test for input of 15 scalar + let circuit = MultipleOutputCircuit::<4, 7>::random(&mut rng); + compile_and_verify(&mut rng, &circuit, &circuit.public_inputs()) +} diff --git a/tests/merkle.rs b/tests/merkle.rs deleted file mode 100644 index c3be494..0000000 --- a/tests/merkle.rs +++ /dev/null @@ -1,72 +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. - -#![cfg(feature = "zk")] -#![cfg(feature = "merkle")] - -use dusk_plonk::prelude::Error as PlonkError; -use dusk_plonk::prelude::*; -use dusk_poseidon::sponge; -use ff::Field; -use rand::rngs::StdRng; -use rand::SeedableRng; - -const A: usize = 4; -const MERKLE_CAPACITY: usize = 12; - -#[derive(Debug, Default)] -pub struct MerkleCircuit { - input: [BlsScalar; A], - output: BlsScalar, -} - -impl MerkleCircuit { - pub fn new(input: [BlsScalar; A], output: BlsScalar) -> Self { - Self { input, output } - } -} - -impl Circuit for MerkleCircuit { - fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { - let mut input_witnesses = [Composer::ZERO; A]; - for (i, witness) in input_witnesses.iter_mut().enumerate() { - *witness = composer.append_witness(self.input[i]); - } - - let calculated = sponge::merkle::gadget(composer, &input_witnesses); - let constraint = Constraint::new() - .left(-BlsScalar::one()) - .a(calculated) - .public(self.output); - composer.append_gate(constraint); - - Ok(()) - } -} - -#[test] -fn merkle_sponge() -> Result<(), PlonkError> { - let mut rng = StdRng::seed_from_u64(0xbeef); - let label = b"merkle-sponge-tester"; - let pp = PublicParameters::setup(1 << MERKLE_CAPACITY, &mut rng)?; - let (prover, verifier) = Compiler::compile::(&pp, label) - .expect("Circuit should compile"); - - let mut input = [BlsScalar::zero(); A]; - for scalar in input.iter_mut() { - *scalar = BlsScalar::random(&mut rng); - } - let expected_output = sponge::merkle::hash(&input); - - let circuit = MerkleCircuit::new(input, expected_output); - - let (proof, _) = prover.prove(&mut rng, &circuit)?; - - let public_inputs = [expected_output]; - verifier.verify(&proof, &public_inputs)?; - - Ok(()) -} diff --git a/tests/sponge.rs b/tests/sponge.rs deleted file mode 100644 index 2e23b91..0000000 --- a/tests/sponge.rs +++ /dev/null @@ -1,228 +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. - -use dusk_bls12_381::BlsScalar; -use dusk_bytes::ParseHexStr; -use dusk_poseidon::sponge; - -const TEST_INPUTS: [&str; 32] = [ - "bb67ed265bf1db490ded2e1ede55c0d14c55521509dc73f9c354e98ab76c9625", - "7e74220084d75e10c89e9435d47bb5b8075991b2e29be3b84421dac3b1ee6007", - "5ce5481a4d78cca03498f72761da1b9f1d2aa8fb300be39f0e4fe2534f9d4308", - "b1e710e3c4a8c35154b0ce4e4f4af6f498ebd79f8e7cdf3150372c7501be250b", - "33c9e2025f86b5d82149f1ab8e20a168fc3d99d09b48cbce0286db8752cc3306", - "e98206bfdce791e4e5144079b997d4fc25006194b35655f0e48490b26e24ea35", - "86d2a95cc552de8d5bb20bd4a407fee5ffdc314e93dfe6b2dc792bc71fd8cc2d", - "4edd8307ce28a8c70963d20a7bc28df1e1720bbbc93878a18bd07fad7d51fa15", - "eabc7a296704a68aa01f95adc85f6dd758b175745336d8fc795a17984024b21e", - "cfc108673c93df305e31c283b9c767b7097ae4e174a223e0c24b15a67b701a3a", - "5e9073de60c35dccd19d52a5222616bc89ac677adf1fce33e20a3dcb63b61216", - "038591e101cb5d60d142574e3abb1a1d9bb8bbf1102bdaefe08cca549b988c1b", - "e44a54e74c8dd6d468c90dbd9555c8a2468d6161d794a55bd6ff8d7264d5c017", - "b74f0dac3af5ac492ea46d9087462e990f8ade709037c79b8c6a808f5a9a6c26", - "4f580037162bbac706d7228b6bd62f4e38032b06734530b818221e37bb1b972f", - "f5cfbc1185ccb3f0ecadb4ba5630f9260b881c83c924ca1332637df58be5170e", - "ed1b4cab775e86de9117b5dae0cee7ed75a6f0be8394dc42c3a7502bfb64942c", - "ce8bcf8952c3daf89ee9fe55ff3acf3bf83c17d28c50fb7fa0db3ce471cc1134", - "3ee00d2d773237f5f807715894f1a320019c34914b880d4c87299f83de7ece2e", - "3a1eef3d0a84798020b3016ae323f0c71916074b636c6ca55e53abd859dbd10e", - "6c4e854816920cc4b34820d6e5d5c4c210125a35289261c42c20beea88375439", - "8264f7a36717ab6149bd0c7b2a6496e9aa4952fa74f9e20075d712f61e6c3e12", - "0601f84b745cb0ee65ed275a3913566ca2948e8c7911c4c2f2e34ecaa446f23c", - "86126b269583662d1ea7c1a9045784dab704c8305218c621483a48aefbd1611c", - "56d655c6ae6136b9d7b22824999a182acdf68a8a5a5095e586a5c9038b635511", - "3ff4311953234ce812ef86ec4c0f3bf381a4a9d31a9025813ba69e7e3c19021b", - "8d9aec8c1b34e5f59ad4633a670e7bede86ef777395c7b14057f28c2c2ae4802", - "4f47cd90d7f732b7255dceb56084d0889824b66b929bf57255db3e95786f813a", - "535ac1999b63f38bf718ef12b98dd0f095975244aefc402ac6203878d8f6e93c", - "e1eb9d629f14b587e6c5eed82aefea704f2968edbb0bedbd906bfa31089f7412", - "958318907edb1b919a62fd62aeab05e2c6fea95fc731ba169ae8e406aec5361a", - "e111a0664ac113b960cd336643db4b34c5cd4f69de84d44be95cadaca4d19115", -]; - -#[test] -fn sponge_hash_test() { - let test_inputs: Vec = TEST_INPUTS - .iter() - .map(|input| BlsScalar::from_hex_str(input).unwrap()) - .collect(); - - assert_eq!( - "0x2885ca6d908b34ca83f2177d78283c25d8c5c7230877025bc8d558b8a94e6fe3", - format!("{:?}", sponge::hash(&test_inputs[..3])) - ); - - assert_eq!( - "0x55f7f755570a884cb1430bf5cba11fff94430bea63f3c108e6070dc86532ea75", - format!("{:?}", sponge::hash(&test_inputs[..4])) - ); - - assert_eq!( - "0x4288f8b92a9a8bc20f60aac68a318b3287ddb0a663cef5015bff0e98a0063153", - format!("{:?}", sponge::hash(&test_inputs[..5])) - ); - - assert_eq!( - "0x31673ed327fa548518084e7332c29d4c96ad314cee79d2c447ff4f686458811a", - format!("{:?}", sponge::hash(&test_inputs[..6])) - ); - - assert_eq!( - "0x5aa2df13cf6f910c19e5516c9222df7039d119472534e6488081df57d036b9a8", - format!("{:?}", sponge::hash(&test_inputs[..8])) - ); - - assert_eq!( - "0x0be3889073101db27cd27006256daedda56368a4dad19e6fc810041a23342998", - format!("{:?}", sponge::hash(&test_inputs[..10])) - ); -} - -#[cfg(feature = "zk")] -mod zk { - use super::*; - - use rand::rngs::{OsRng, StdRng}; - use rand::SeedableRng; - - use dusk_plonk::prelude::Error as PlonkError; - use dusk_plonk::prelude::*; - use ff::Field; - - const CAPACITY: usize = 12; - - fn poseidon_sponge_params(n: usize) -> (Vec, BlsScalar) { - let mut input = vec![BlsScalar::zero(); n]; - - input - .iter_mut() - .for_each(|s| *s = BlsScalar::random(&mut OsRng)); - - let output = sponge::hash(&input); - - (input, output) - } - - #[derive(Default, Debug)] - pub struct TestSpongeCircuit { - input: Vec, - output: BlsScalar, - } - - impl TestSpongeCircuit { - pub fn new(input: Vec, output: BlsScalar) -> Self { - Self { input, output } - } - } - - impl Circuit for TestSpongeCircuit { - fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { - let mut i_var = vec![Composer::ZERO; self.input.len()]; - self.input.iter().zip(i_var.iter_mut()).for_each(|(i, v)| { - *v = composer.append_witness(*i); - }); - - // Apply Poseidon Sponge hash to the inputs - let computed_o_var = sponge::gadget(composer, i_var.as_slice()); - - // Check that the Gadget sponge hash result = Scalar sponge hash - // result - let o_var = composer.append_witness(self.output); - composer.assert_equal(o_var, computed_o_var); - - Ok(()) - } - } - - #[test] - fn sponge_gadget() -> Result<(), Error> { - let label = b"sponge-tester"; - let pp = PublicParameters::setup(1 << CAPACITY, &mut OsRng)?; - - let mut rng = StdRng::seed_from_u64(0xbeef); - - for w in [3, 5, 15] { - let (i, o) = poseidon_sponge_params(w); - let circuit = TestSpongeCircuit::new(i, o); - - let (prover, verifier) = - Compiler::compile_with_circuit(&pp, label, &circuit)?; - - let (proof, public_inputs) = prover.prove(&mut rng, &circuit)?; - - verifier.verify(&proof, &public_inputs)?; - } - - Ok(()) - } - - #[derive(Debug, Default)] - pub struct TestTruncatedCircuit { - input: Vec, - output: JubJubScalar, - } - - const TRUNCATED_CAPACITY: usize = 17; - - impl TestTruncatedCircuit { - pub fn new(input: Vec, output: JubJubScalar) -> Self { - Self { input, output } - } - } - - impl Circuit for TestTruncatedCircuit { - fn circuit(&self, composer: &mut Composer) -> Result<(), PlonkError> { - let h = sponge::truncated::hash(self.input.as_slice()); - let p = JubJubAffine::from(dusk_jubjub::GENERATOR_EXTENDED * h); - let p = composer.append_point(p); - - let i: Vec = self - .input - .iter() - .map(|i| composer.append_witness(*i)) - .collect(); - - let o = composer.append_witness(self.output); - - let t = sponge::truncated::gadget(composer, i.as_slice()); - let p_p = composer - .component_mul_generator(t, dusk_jubjub::GENERATOR_EXTENDED) - .expect("Multiplying with the generator should succeed"); - - composer.assert_equal(t, o); - composer.assert_equal_point(p, p_p); - - Ok(()) - } - } - - #[test] - fn truncated_sponge() -> Result<(), PlonkError> { - let input: Vec = TEST_INPUTS - .iter() - .map(|input| BlsScalar::from_hex_str(input).unwrap()) - .collect(); - - let label = b"truncated-sponge-tester"; - let pp = PublicParameters::setup(1 << TRUNCATED_CAPACITY, &mut OsRng)?; - let mut rng = StdRng::seed_from_u64(0xbeef); - - for w in [3, 6, 9] { - let i = input[..w].to_vec(); - let o = sponge::truncated::hash(i.as_slice()); - - let circuit = TestTruncatedCircuit::new(i, o); - let (prover, verifier) = - Compiler::compile_with_circuit(&pp, label, &circuit)?; - - let (proof, public_inputs) = prover.prove(&mut rng, &circuit)?; - - verifier.verify(&proof, &public_inputs)?; - } - - Ok(()) - } -}