diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml index 096e3af..50bee7e 100644 --- a/.github/workflows/dusk_ci.yml +++ b/.github/workflows/dusk_ci.yml @@ -11,8 +11,6 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: install @@ -29,8 +27,6 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: check @@ -43,12 +39,11 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: test args: --release + test_nightly_no_std: name: Nightly tests no_std runs-on: ubuntu-latest @@ -57,12 +52,11 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: test args: --release --no-default-features + test_nightly_canon_std: name: Nightly tests canon std runs-on: ubuntu-latest @@ -71,8 +65,6 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: test @@ -86,50 +78,10 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly - override: true - uses: actions-rs/cargo@v1 with: command: test args: --release --no-default-features --features canon - - test_nightly_canon_host_std: - name: Nightly tests canon_host std - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --features canon_host - - uses: actions-rs/cargo@v1 - with: - comand: test - args: --release --features canon_host - - test_nightly_canon_host_no_std: - name: Nightly tests canon_host no_std - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --features canon_host - - uses: actions-rs/cargo@v1 - with: - comand: test - args: --release --no-default-features --features canon_host fmt: name: Rustfmt @@ -139,8 +91,6 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2020-10-25 - override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a96ce4..c5781e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.21.0] - 2021-07-05 +### Added + +- Add integration tests with examples of custom walker iterators [#134](https://github.com/dusk-network/poseidon252/issues/134) +- Add `persistance` feature to the crate [#151](https://github.com/dusk-network/poseidon252/issues/151) +- Add `truncated` module in sponge to deal with scalar conversions [#153](https://github.com/dusk-network/poseidon252/issues/153) + +### Changed + +- Change the tree logic to be compatible with `microkelvin v0.9` [#151](https://github.com/dusk-network/poseidon252/issues/151) +- Changed toolchain-file version to nightly-2021-06-06 [#149](https://github.com/dusk-network/poseidon252/issues/149) +- Change featureset config for the crate [#138](https://github.com/dusk-network/poseidon252/issues/138) +- Update `error` module to be no_std compatible [#132](https://github.com/dusk-network/poseidon252/issues/132) +- Update to latest `dusk-poseidon`, `dusk-bls12_381` and `dusk-jubjub` [#126](https://github.com/dusk-network/poseidon252/issues/126) +- Update to latest `microkelvin v0.9`, `nstack v0.9` and `canonical v0.6` [#125](https://github.com/dusk-network/poseidon252/issues/125) +- Update randomness provider to `rand_core` [#127](https://github.com/dusk-network/poseidon252/issues/127) +- Change trait bound system for `PoseidonTree` [#125](https://github.com/dusk-network/poseidon252/issues/125) +- Update `PoseidonTreeAnnotation` to be an autotrait [#125](https://github.com/dusk-network/poseidon252/issues/125) +- Update feature system for the crate [#138](https://github.com/dusk-network/poseidon252/issues/138) +- Change `PoseidonLeaf` getter methods to return refs [#143](https://github.com/dusk-network/poseidon252/issues/143) + +### Removed +- Remove `anyhow` and `thiserror` from deps [#132](https://github.com/dusk-network/poseidon252/issues/132) +- Remove `PoseidonWalkableIterator` and `PoseidonWalkableAnnotation` [#125](https://github.com/dusk-network/poseidon252/issues/125) +- Remove `canon_host` feature checks from CI [#136](https://github.com/dusk-network/poseidon252/issues/136) +- Remove `anyhow` and `thiserror` usage [#132](https://github.com/dusk-network/poseidon252/issues/132) +- Remove `microkelvin` requirements from Tree [#146](https://github.com/dusk-network/Poseidon252/issues/146) + +### Fixed + +- Fix Readme.md import from lib.rs [#148](https://github.com/dusk-network/poseidon252/issues/148) + ## [0.20.0] - 2021-04-06 ### Changed diff --git a/Cargo.toml b/Cargo.toml index c47d1de..0342b5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dusk-poseidon" -version = "0.20.0" +version = "0.21.0" authors = [ "zer0 ", "vlopes11 ", "CPerezz ", "Kristoffer Ström " ] @@ -12,34 +12,28 @@ license = "MPL-2.0" repository = "https://github.com/dusk-network/poseidon252" [dependencies] -dusk-bls12_381 = {version = "0.6", default-features = false} -dusk-jubjub = {version = "0.8", default-features = false} +dusk-bls12_381 = {version = "0.8", default-features = false} +dusk-jubjub = {version = "0.10", default-features = false} dusk-bytes = "0.1" -dusk-hades = { version = "0.15", default-features = false } -canonical = {version = "0.5", optional = true} -canonical_derive = {version = "0.5", optional = true} -microkelvin = {version = "0.6", optional = true} -nstack = {version = "0.7", optional = true} - -dusk-plonk = {version="0.7", default-features = false, optional = true} -anyhow = { version = "1.0", optional = true } -thiserror = { version = "1.0", optional = true } +dusk-hades = "0.16" +canonical = {version = "0.6", optional = true} +canonical_derive = {version = "0.6", optional = true} +microkelvin = {version = "0.9", optional = true} +nstack = {version = "0.9", optional = true} +dusk-plonk = {version="0.8", default-features = false, features = ["alloc"]} [dev-dependencies] -canonical_host = "0.5" -rand = "0.7" +rand_core = {version="0.6", default-features=false} criterion = "0.3" [features] -default = ["std"] +default = ["std", "canon"] alloc = [] std = [ - "dusk-hades/default", + "dusk-plonk/default", + "dusk-hades/plonk-std", "dusk-bls12_381/default", "dusk-jubjub/std", - "dusk-plonk", - "anyhow", - "thiserror" ] canon = [ "dusk-bls12_381/canon", @@ -50,10 +44,7 @@ canon = [ "nstack", "alloc" ] -canon_host = [ - "canon", - "canonical/host" -] +persistance = ["microkelvin/persistance"] [profile.dev] opt-level = 3 diff --git a/README.md b/README.md index deae154..7a655cc 100644 --- a/README.md +++ b/README.md @@ -68,16 +68,13 @@ majority of the configurations that the user may need: ### Zero Knowledge Merkle Opening Proof example: -```no_run -#[cfg(all(feature = "canon", feature = "std"))] +```rust +#[cfg(feature = "canon")] { - -use anyhow::Result; -use canonical::Canon; use canonical_derive::Canon; -use canonical_host::MemStore; use dusk_plonk::prelude::*; use dusk_poseidon::tree::{PoseidonAnnotation, PoseidonLeaf, PoseidonTree, merkle_opening}; +use rand_core::OsRng; // Constant depth of the merkle tree const DEPTH: usize = 17; @@ -100,15 +97,15 @@ impl From for DataLeaf { } // Any leaf of the poseidon tree must implement `PoseidonLeaf` -impl PoseidonLeaf for DataLeaf { +impl PoseidonLeaf for DataLeaf { // Cryptographic hash of the data leaf fn poseidon_hash(&self) -> BlsScalar { self.data } // Position on the tree - fn pos(&self) -> u64 { - self.pos + fn pos(&self) -> &u64 { + &self.pos } // Method used to set the position on the tree after the `PoseidonTree::push` call @@ -117,13 +114,13 @@ impl PoseidonLeaf for DataLeaf { } } -fn main() -> Result<()> { +fn main() -> Result<(), Error> { // Create the ZK keys - let pub_params = PublicParameters::setup(1 << 15, &mut rand::thread_rng())?; + let pub_params = PublicParameters::setup(1 << 15, &mut OsRng)?; let (ck, ok) = pub_params.trim(1 << 15)?; - // Instantiate a new tree with the MemStore implementation - let mut tree: PoseidonTree = + // Instantiate a new tree + let mut tree: PoseidonTree = PoseidonTree::new(); // Append 1024 elements to the tree @@ -135,9 +132,9 @@ fn main() -> Result<()> { // Create a merkle opening tester gadget let gadget_tester = |composer: &mut StandardComposer, - tree: &PoseidonTree, + tree: &PoseidonTree, n: usize| { - let branch = tree.branch(n).unwrap().unwrap(); + let branch = tree.branch(n as u64).unwrap().unwrap(); let root = tree.root().unwrap(); let root_p = merkle_opening::(composer, &branch); @@ -171,11 +168,10 @@ fn main() -> Result<()> { The canonical implementations aim to make available a single representation of the Merkle tree to constrained (referred to as "hosted") and unconstrained (referred to as "host") environments. -For that, we rely on the features `canon` and `canon_host`. +For that, we rely on the feature `canon`. `canon` feature will require all the crates needed for the Merkle tree to function. -`canon_host` feature will require `canon`, with the addition of the host environment implementations. ## Documentation diff --git a/rust-toolchain b/rust-toolchain index bf867e0..36bb077 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly +nightly-2021-06-06 diff --git a/src/cipher/cipher.rs b/src/cipher/cipher.rs index 6234fd3..354a521 100644 --- a/src/cipher/cipher.rs +++ b/src/cipher/cipher.rs @@ -6,8 +6,6 @@ use crate::Error; -#[cfg(feature = "canon")] -use canonical::Canon; #[cfg(feature = "canon")] use canonical_derive::Canon; @@ -140,7 +138,7 @@ impl PoseidonCipher { &self, secret: &JubJubAffine, nonce: &BlsScalar, - ) -> Result<[BlsScalar; MESSAGE_CAPACITY], Error<()>> { + ) -> Result<[BlsScalar; MESSAGE_CAPACITY], Error> { let zero = BlsScalar::zero(); let mut strategy = ScalarStrategy::new(); diff --git a/src/cipher/mod.rs b/src/cipher/mod.rs index 8478c17..1fbc3ac 100644 --- a/src/cipher/mod.rs +++ b/src/cipher/mod.rs @@ -25,6 +25,7 @@ //! use dusk_bls12_381::BlsScalar; //! use dusk_jubjub::{dhke, JubJubExtended, JubJubScalar, GENERATOR}; //! use dusk_poseidon::cipher::PoseidonCipher; +//! use rand_core::OsRng; //! //! fn sender( //! sender_secret: &JubJubScalar, @@ -35,7 +36,7 @@ //! let shared_secret = dhke(sender_secret, receiver_public); //! //! // Generate a random nonce that will be public -//! let nonce = BlsScalar::random(&mut rand::thread_rng()); +//! let nonce = BlsScalar::random(&mut OsRng); //! //! // Encrypt the message //! let cipher = PoseidonCipher::encrypt(&message, &shared_secret, &nonce); @@ -58,7 +59,7 @@ //! .expect("Failed to decrypt!") //! } //! -//! let mut rng = rand::thread_rng(); +//! let mut rng = OsRng; //! //! // Generate a secret and a public key for Bob //! let bob_secret = JubJubScalar::random(&mut rng); @@ -89,10 +90,6 @@ mod cipher; #[cfg(test)] mod tests; -#[cfg(feature = "std")] mod zk; - pub use cipher::PoseidonCipher; - -#[cfg(feature = "std")] pub use zk::{decrypt, encrypt}; diff --git a/src/cipher/tests.rs b/src/cipher/tests.rs index cd66509..15745d5 100644 --- a/src/cipher/tests.rs +++ b/src/cipher/tests.rs @@ -10,15 +10,14 @@ use core::ops::Mul; use dusk_bls12_381::BlsScalar; use dusk_bytes::Serializable; use dusk_jubjub::{JubJubAffine, JubJubScalar, GENERATOR}; -use rand::RngCore; +use rand_core::{OsRng, RngCore}; fn gen() -> ( [BlsScalar; PoseidonCipher::capacity()], JubJubAffine, BlsScalar, ) { - let mut rng = rand::thread_rng(); - + let mut rng = OsRng; let mut message = [BlsScalar::zero(); PoseidonCipher::capacity()]; message .iter_mut() @@ -29,7 +28,7 @@ fn gen() -> ( let secret = JubJubScalar::from_bytes_wide(&secret); let secret = GENERATOR.to_niels().mul(&secret).into(); - let nonce = BlsScalar::random(&mut rng); + let nonce = BlsScalar::random(&mut OsRng); (message, secret, nonce) } @@ -52,7 +51,7 @@ fn sanity() { } #[test] -fn encrypt() -> Result<(), Error<()>> { +fn encrypt() -> Result<(), Error> { let (message, secret, nonce) = gen(); let cipher = PoseidonCipher::encrypt(&message, &secret, &nonce); @@ -64,9 +63,9 @@ fn encrypt() -> Result<(), Error<()>> { } #[test] -fn single_bit() -> Result<(), Error<()>> { +fn single_bit() -> Result<(), Error> { let (_, secret, nonce) = gen(); - let message = BlsScalar::random(&mut rand::thread_rng()); + let message = BlsScalar::random(&mut OsRng); let cipher = PoseidonCipher::encrypt(&[message], &secret, &nonce); let decrypt = cipher.decrypt(&secret, &nonce)?; @@ -77,10 +76,10 @@ fn single_bit() -> Result<(), Error<()>> { } #[test] -fn overflow() -> Result<(), Error<()>> { +fn overflow() -> Result<(), Error> { let (_, secret, nonce) = gen(); - let message = [BlsScalar::random(&mut rand::thread_rng()); - PoseidonCipher::capacity() + 1]; + let message = + [BlsScalar::random(&mut OsRng); PoseidonCipher::capacity() + 1]; let cipher = PoseidonCipher::encrypt(&message, &secret, &nonce); let decrypt = cipher.decrypt(&secret, &nonce)?; @@ -100,7 +99,7 @@ fn wrong_key_fail() { } #[test] -fn bytes() -> Result<(), Error<()>> { +fn bytes() -> Result<(), Error> { let (message, secret, nonce) = gen(); let cipher = PoseidonCipher::encrypt(&message, &secret, &nonce); diff --git a/src/cipher/zk.rs b/src/cipher/zk.rs index 16fb525..44d2069 100644 --- a/src/cipher/zk.rs +++ b/src/cipher/zk.rs @@ -111,38 +111,34 @@ pub fn decrypt( #[cfg(test)] mod tests { use crate::cipher::{decrypt, encrypt, PoseidonCipher}; - use anyhow::Result; use dusk_bls12_381::BlsScalar; use dusk_jubjub::{dhke, JubJubExtended, GENERATOR_EXTENDED}; - use dusk_plonk::constraint_system::ecc::scalar_mul::variable_base::variable_base_scalar_mul; - use dusk_plonk::constraint_system::ecc::Point; use dusk_plonk::prelude::*; + use rand_core::OsRng; #[test] - fn gadget() -> Result<()> { - let mut rng = rand::thread_rng(); - + fn gadget() -> Result<(), Error> { // Generate a secret and a public key for Bob - let bob_secret = JubJubScalar::random(&mut rng); + let bob_secret = JubJubScalar::random(&mut OsRng); // Generate a secret and a public key for Alice - let alice_secret = JubJubScalar::random(&mut rng); - let alice_public = GENERATOR_EXTENDED * &alice_secret; + let alice_secret = JubJubScalar::random(&mut OsRng); + let alice_public = GENERATOR_EXTENDED * alice_secret; // Generate a shared secret let shared_secret = dhke(&bob_secret, &alice_public); // Generate a secret message - let a = BlsScalar::random(&mut rng); - let b = BlsScalar::random(&mut rng); + let a = BlsScalar::random(&mut OsRng); + let b = BlsScalar::random(&mut OsRng); let message = [a, b]; // Perform the encryption - let nonce = BlsScalar::random(&mut rng); + let nonce = BlsScalar::random(&mut OsRng); let cipher = PoseidonCipher::encrypt(&message, &shared_secret, &nonce); let size = 13; - let pp = PublicParameters::setup(1 << size, &mut rng)?; + let pp = PublicParameters::setup(1 << size, &mut OsRng)?; let (ck, vk) = pp.trim(1 << size)?; let label = b"poseidon-cipher"; @@ -158,9 +154,9 @@ mod tests { let nonce = composer.add_input(nonce); let secret = composer.add_input((secret).into()); - let public = Point::from_private_affine(composer, public.into()); + let public = composer.add_affine(public.into()); - let shared = variable_base_scalar_mul(composer, secret, public); + let shared = composer.variable_base_scalar_mul(secret, public); let mut message_circuit = [zero; PoseidonCipher::capacity()]; message.iter().zip(message_circuit.iter_mut()).for_each( @@ -170,7 +166,7 @@ mod tests { ); let cipher_gadget = - encrypt(composer, shared.point(), nonce, &message_circuit); + encrypt(composer, &shared, nonce, &message_circuit); cipher.iter().zip(cipher_gadget.iter()).for_each(|(c, g)| { let x = composer.add_input(*c); @@ -178,7 +174,7 @@ mod tests { }); let message_gadget = - decrypt(composer, shared.point(), nonce, &cipher_gadget); + decrypt(composer, &shared, nonce, &cipher_gadget); message .iter() @@ -213,7 +209,7 @@ mod tests { ); verifier.preprocess(&ck)?; - assert!(verifier.verify(&proof, &vk, &vec![]).is_ok()); + assert!(verifier.verify(&proof, &vk, &[BlsScalar::zero()]).is_ok()); Ok(()) } diff --git a/src/error.rs b/src/error.rs index aeb9e6c..71af525 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,39 +4,27 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use core::fmt; - -#[cfg(feature = "std")] -use std::{error as std_error, fmt as std_fmt}; +use core::fmt::{self, Display, Result}; /// Poseidon error variants -#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum Error { +#[derive(Clone, Debug)] +pub enum Error { /// Error pushing to the poseidon tree - TreePushFailed(E), + TreePushFailed, /// Error on pop of the tree - TreePopFailed(E), + TreePopFailed, /// Error fetching the Nth item from the tree - TreeGetFailed(E), + TreeGetFailed, + /// Failed to obtain a Branch from a tree. + TreeBranchFailed, + /// Failed to obtain an Iterator from a tree. + TreeIterFailed, /// Decryption failed for the provided secret+nonce CipherDecryptionFailed, } -#[cfg(feature = "std")] -impl std_fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std_fmt::Result { +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result { write!(f, "Dusk-Poseidon Error: {:?}", &self) } } - -#[cfg(feature = "std")] -impl std_error::Error for Error { - fn source(&self) -> Option<&(dyn std_error::Error + 'static)> { - match &self { - Self::TreePushFailed(e) => Some(e), - Self::TreePopFailed(e) => Some(e), - Self::TreeGetFailed(e) => Some(e), - _ => None, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 273834b..f6c0fcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,8 @@ // Copyright (c) DUSK NETWORK. All rights reserved. #![cfg_attr(not(feature = "std"), no_std)] -#![feature(external_doc)] -#![doc(include = "../README.md")] #![warn(missing_docs)] +#![doc = include_str!("../README.md")] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/src/perm_uses.rs b/src/perm_uses.rs index 51fa927..da07da0 100644 --- a/src/perm_uses.rs +++ b/src/perm_uses.rs @@ -30,10 +30,11 @@ pub fn two_outputs(message: BlsScalar) -> [BlsScalar; 2] { #[cfg(test)] mod tests { use super::*; + use rand_core::OsRng; #[test] fn hash_two_outputs() { - let m = BlsScalar::random(&mut rand::thread_rng()); + let m = BlsScalar::random(&mut OsRng); let h = two_outputs(m); @@ -46,7 +47,7 @@ mod tests { #[test] fn same_result() { for _i in 0..100 { - let m = BlsScalar::random(&mut rand::thread_rng()); + let m = BlsScalar::random(&mut OsRng); let h = two_outputs(m); let h_1 = two_outputs(m); diff --git a/src/sponge/mod.rs b/src/sponge/mod.rs index d3dce8a..fde4aea 100644 --- a/src/sponge/mod.rs +++ b/src/sponge/mod.rs @@ -4,8 +4,8 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -mod sponge; +pub mod sponge; +pub mod truncated; -#[cfg(feature = "std")] pub use sponge::sponge_gadget as gadget; pub use sponge::sponge_hash as hash; diff --git a/src/sponge/sponge.rs b/src/sponge/sponge.rs index 1797ef7..8063f43 100644 --- a/src/sponge/sponge.rs +++ b/src/sponge/sponge.rs @@ -7,14 +7,10 @@ //! Sponge hash and gadget definition use dusk_bls12_381::BlsScalar; +use dusk_hades::GadgetStrategy; use dusk_hades::{ScalarStrategy, Strategy, WIDTH}; - -#[cfg(feature = "std")] use dusk_plonk::prelude::*; -#[cfg(feature = "std")] -use dusk_hades::GadgetStrategy; - /// The `hash` function takes an arbitrary number of Scalars and returns the /// hash, using the `Hades` ScalarStragegy. /// @@ -50,44 +46,30 @@ pub fn sponge_hash(messages: &[BlsScalar]) -> BlsScalar { .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[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s += c; - }); - state[chunk.len() + 1] += BlsScalar::one(); - h.perm(&mut state); // 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 { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s += c; - }); - h.perm(&mut state); state[1] += BlsScalar::one(); - - h.perm(&mut state); - // If its not the last permutation, add the chunk of the message to - // the corresponding `r` elements - } else { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s += c; - }); - - h.perm(&mut state); } + + h.perm(&mut state); }); state[1] } -#[cfg(feature = "std")] /// Mirror the implementation of [`sponge_hash`] inside of a PLONK circuit. /// /// The circuit will be defined by the length of `messages`. This means that a @@ -117,34 +99,23 @@ pub fn sponge_gadget( .chunks(WIDTH - 1) .enumerate() .for_each(|(i, chunk)| { - if i == last_iteration && chunk.len() < WIDTH - 1 { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s = composer.add( - (BlsScalar::one(), *s), - (BlsScalar::one(), *c), - BlsScalar::zero(), - None, - ); - }); + state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { + *s = composer.add( + (BlsScalar::one(), *s), + (BlsScalar::one(), *c), + BlsScalar::zero(), + None, + ); + }); + if i == last_iteration && chunk.len() < WIDTH - 1 { state[chunk.len() + 1] = composer.add( (BlsScalar::one(), state[chunk.len() + 1]), (BlsScalar::zero(), zero), BlsScalar::one(), None, ); - - GadgetStrategy::new(composer).perm(&mut state); } else if i == last_iteration { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s = composer.add( - (BlsScalar::one(), *s), - (BlsScalar::one(), *c), - BlsScalar::zero(), - None, - ); - }); - GadgetStrategy::new(composer).perm(&mut state); state[1] = composer.add( @@ -153,223 +124,9 @@ pub fn sponge_gadget( BlsScalar::one(), None, ); - - GadgetStrategy::new(composer).perm(&mut state); - } else { - state[1..].iter_mut().zip(chunk.iter()).for_each(|(s, c)| { - *s = composer.add( - (BlsScalar::one(), *s), - (BlsScalar::one(), *c), - BlsScalar::zero(), - None, - ); - }); - - GadgetStrategy::new(composer).perm(&mut state); } + GadgetStrategy::new(composer).perm(&mut state); }); state[1] } - -#[cfg(test)] -#[cfg(feature = "std")] -mod tests { - use anyhow::Result; - use dusk_hades::WIDTH; - - use super::*; - - const CAPACITY: usize = 1 << 12; - - fn poseidon_sponge_params() -> ([BlsScalar; N], BlsScalar) { - let mut input = [BlsScalar::zero(); N]; - input - .iter_mut() - .for_each(|s| *s = BlsScalar::random(&mut rand::thread_rng())); - let output = sponge_hash(&input); - (input, output) - } - - // Checks that the result of the hades permutation is the same as the one - // obtained by the sponge gadget - fn sponge_gadget_tester( - i: &[BlsScalar], - out: BlsScalar, - composer: &mut StandardComposer, - ) { - let zero = composer.add_input(BlsScalar::zero()); - composer.constrain_to_constant(zero, BlsScalar::zero(), None); - - let mut i_var = vec![zero; N]; - i.iter().zip(i_var.iter_mut()).for_each(|(i, v)| { - *v = composer.add_input(*i); - }); - - let o_var = composer.add_input(out); - - // Apply Poseidon Sponge hash to the inputs - let computed_o_var = sponge_gadget(composer, &i_var); - - // Check that the Gadget sponge hash result = Scalar sponge hash result - composer.add_gate( - o_var, - computed_o_var, - zero, - BlsScalar::one(), - -BlsScalar::one(), - BlsScalar::zero(), - BlsScalar::zero(), - None, - ); - } - - #[test] - fn sponge_gadget_width_3() -> Result<()> { - // Setup OG params. - let public_parameters = - PublicParameters::setup(CAPACITY, &mut rand::thread_rng())?; - let (ck, vk) = public_parameters.trim(CAPACITY)?; - - // Test with width = 3 - - // Proving - let (i, o) = poseidon_sponge_params::<3>(); - let mut prover = Prover::new(b"sponge_tester"); - sponge_gadget_tester::<3>(&i, o, prover.mut_cs()); - prover.preprocess(&ck)?; - let proof = prover.prove(&ck)?; - - // Verify - let mut verifier = Verifier::new(b"sponge_tester"); - sponge_gadget_tester::<3>(&i, o, verifier.mut_cs()); - verifier.preprocess(&ck)?; - verifier.verify(&proof, &vk, &vec![BlsScalar::zero()])?; - - Ok(()) - } - - #[test] - fn sponge_gadget_hades_width() -> Result<()> { - // Setup OG params. - let public_parameters = - PublicParameters::setup(CAPACITY, &mut rand::thread_rng())?; - let (ck, vk) = public_parameters.trim(CAPACITY)?; - - // Test with width = 5 - - // Proving - let (i, o) = poseidon_sponge_params::(); - let mut prover = Prover::new(b"sponge_tester"); - sponge_gadget_tester::(&i, o, prover.mut_cs()); - prover.preprocess(&ck)?; - let proof = prover.prove(&ck)?; - - // Verify - let mut verifier = Verifier::new(b"sponge_tester"); - sponge_gadget_tester::(&i, o, verifier.mut_cs()); - verifier.preprocess(&ck)?; - verifier.verify(&proof, &vk, &vec![BlsScalar::zero()])?; - - Ok(()) - } - - #[test] - fn sponge_gadget_width_15() -> Result<()> { - // Setup OG params. - let public_parameters = - PublicParameters::setup(1 << 17, &mut rand::thread_rng())?; - let (ck, vk) = public_parameters.trim(1 << 17)?; - - // Test with width = 15 - - // Proving - let (i, o) = poseidon_sponge_params::<15>(); - let mut prover = Prover::new(b"sponge_tester"); - sponge_gadget_tester::<15>(&i, o, prover.mut_cs()); - prover.preprocess(&ck)?; - let proof = prover.prove(&ck)?; - - // Verify - let mut verifier = Verifier::new(b"sponge_tester"); - sponge_gadget_tester::<15>(&i, o, verifier.mut_cs()); - verifier.preprocess(&ck)?; - verifier.verify(&proof, &vk, &vec![BlsScalar::zero()])?; - - Ok(()) - } - - #[test] - fn sponge_hash_test() { - use dusk_bytes::ParseHexStr; - let test_inputs = [ - "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", - ]; - - let test_inputs: Vec = test_inputs - .iter() - .map(|input| BlsScalar::from_hex_str(input).unwrap()) - .collect(); - - assert_eq!( - "0xe36f4ea9b858d5c85b02770823c7c5d8253c28787d17f283ca348b906dca8528", - format!("{:#x}", sponge_hash(&test_inputs[0..3])) - ); - - assert_eq!( - "0x75ea3265c80d07e608c1f363ea0b4394ff1fa1cbf50b43b14c880a5755f7f755", - format!("{:#x}", sponge_hash(&test_inputs[0..4])) - ); - - assert_eq!( - "0x533106a0980eff5b01f5ce63a6b0dd87328b318ac6aa600fc28b9a2ab9f88842", - format!("{:#x}", sponge_hash(&test_inputs[0..5])) - ); - - assert_eq!( - "0x1a815864684fff47c4d279ee4c31ad964c9dc232734e08188554fa27d33e6731", - format!("{:#x}", sponge_hash(&test_inputs[0..6])) - ); - - assert_eq!( - "0xa8b936d057df818048e634254719d13970df22926c51e5190c916fcf13dfa25a", - format!("{:#x}", sponge_hash(&test_inputs[0..8])) - ); - - assert_eq!( - "0x982934231a0410c86f9ed1daa46863a5ddae6d250670d27cb21d10739088e30b", - format!("{:#x}", sponge_hash(&test_inputs[0..10])) - ); - } -} diff --git a/src/sponge/truncated.rs b/src/sponge/truncated.rs new file mode 100644 index 0000000..1299c3a --- /dev/null +++ b/src/sponge/truncated.rs @@ -0,0 +1,59 @@ +// 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 super::sponge::*; +use dusk_bls12_381::BlsScalar; +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 [`sponge_hash`] to the `messages` recieved truncating the result to make it fit +/// inside a `JubJubScalar.` +pub fn hash(messages: &[BlsScalar]) -> JubJubScalar { + JubJubScalar::from_raw( + (sponge_hash(messages) & TRUNCATION_LIMIT).reduce().0, + ) +} + +/// Mirror the implementation of [`truncate_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 publically 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`]. +pub fn gadget( + composer: &mut StandardComposer, + messages: &[Variable], +) -> Variable { + let hash_res = sponge_gadget(composer, messages); + composer.xor_gate(hash_res, composer.zero_var(), 250) +} diff --git a/src/tree/annotation/max.rs b/src/tree/annotation/max.rs index 18d055e..1d35864 100644 --- a/src/tree/annotation/max.rs +++ b/src/tree/annotation/max.rs @@ -4,111 +4,103 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use super::{ - PoseidonAnnotation, PoseidonTreeAnnotation, PoseidonWalkableAnnotation, -}; +use super::{PoseidonAnnotation, PoseidonTreeAnnotation}; use crate::tree::PoseidonLeaf; -use canonical::{Canon, Store}; +use canonical::Canon; use canonical_derive::Canon; use core::borrow::Borrow; use dusk_bls12_381::BlsScalar; -use microkelvin::{Annotation, Cardinality, Compound, Max, Step, Walk}; -use nstack::NStack; +use microkelvin::{ + AnnoIter, Annotation, Cardinality, Combine, Compound, Keyed, MaxKey, +}; /// Extends the standard [`PoseidonAnnotation`] with an annotation that holds an agnostic maximum /// value /// /// This maximum value is representes as `u64`, and the tree is iterable over it -#[derive(Debug, Clone, Canon)] -pub struct PoseidonMaxAnnotation { +#[derive(Debug, Clone, Canon, Default)] +pub struct PoseidonMaxAnnotation +where + K: Canon + Default + Clone + Ord, +{ poseidon: PoseidonAnnotation, - max: Max, + max: MaxKey, } -impl Borrow for PoseidonMaxAnnotation { +impl Borrow for PoseidonMaxAnnotation +where + K: Canon + Default + Clone + Ord, +{ fn borrow(&self) -> &Cardinality { self.poseidon.borrow() } } -impl Borrow for PoseidonMaxAnnotation { - fn borrow(&self) -> &BlsScalar { - self.poseidon.borrow() +impl Borrow for PoseidonMaxAnnotation +where + K: Canon + Default + Clone + Ord, +{ + fn borrow(&self) -> &PoseidonAnnotation { + &self.poseidon } } -impl Borrow> for PoseidonMaxAnnotation { - fn borrow(&self) -> &Max { - &self.max +impl Borrow for PoseidonMaxAnnotation +where + K: Canon + Default + Clone + Ord, +{ + fn borrow(&self) -> &BlsScalar { + self.poseidon.borrow() } } -impl PoseidonTreeAnnotation for PoseidonMaxAnnotation +impl Borrow> for PoseidonMaxAnnotation where - L: PoseidonLeaf, - L: Borrow, - S: Store, + K: Canon + Default + Clone + Ord, { + fn borrow(&self) -> &MaxKey { + &self.max + } } -impl Annotation, S> - for PoseidonMaxAnnotation +impl Annotation for PoseidonMaxAnnotation where - L: PoseidonLeaf, + L: PoseidonLeaf, L: Borrow, - S: Store, + L: Keyed, + K: Canon + Default + Clone + Ord, { - fn identity() -> Self { - let poseidon = , - S, - >>::identity(); - let max = as Annotation, S>>::identity(); - - Self { poseidon, max } - } - fn from_leaf(leaf: &L) -> Self { let poseidon = PoseidonAnnotation::from_leaf(leaf); - let max = - as Annotation, S>>::from_leaf(leaf); - - Self { poseidon, max } - } - - fn from_node(node: &NStack) -> Self { - let poseidon = PoseidonAnnotation::from_generic_node(node); - let max = - as Annotation, S>>::from_node(node); + let max = as Annotation>::from_leaf(leaf); Self { poseidon, max } } } -#[inline] -fn borrow_u64>>(ann: &A) -> u64 { - match ann.borrow() { - Max::NegativeInfinity => 0, - Max::Maximum(m) => *m, +impl Combine for PoseidonMaxAnnotation +where + A: Borrow + + Borrow> + + Borrow + + Borrow, + K: Canon + Default + Clone + Ord, +{ + fn combine(iter: AnnoIter) -> Self + where + C: Compound, + A: Annotation, + { + PoseidonMaxAnnotation { + poseidon: PoseidonAnnotation::combine(iter.clone()), + max: MaxKey::combine(iter), + } } } -impl PoseidonWalkableAnnotation for PoseidonMaxAnnotation +impl PoseidonTreeAnnotation for PoseidonMaxAnnotation where - L: PoseidonLeaf, - L: Borrow, - C: Clone, - C::Annotation: Annotation, - C: Compound, - S: Store, + L: PoseidonLeaf + Borrow + Keyed, + K: Canon + Default + Clone + Ord, { - fn poseidon_walk(walk: Walk<'_, C, S>, data: u64) -> Step<'_, C, S> { - match walk { - Walk::Leaf(l) if data <= *l.borrow() => Step::Found(l), - Walk::Node(n) if data <= borrow_u64(n.annotation()) => { - Step::Into(n) - } - _ => Step::Next, - } - } } diff --git a/src/tree/annotation/mod.rs b/src/tree/annotation/mod.rs index c908442..02f6767 100644 --- a/src/tree/annotation/mod.rs +++ b/src/tree/annotation/mod.rs @@ -5,11 +5,10 @@ // Copyright (c) DUSK NETWORK. All rights reserved. use crate::tree::PoseidonLeaf; -use canonical::{Canon, Store}; +use canonical::Canon; use core::borrow::Borrow; use dusk_bls12_381::BlsScalar; -use microkelvin::{Annotation, Cardinality, Compound, Step, Walk}; -use nstack::NStack; +use microkelvin::{Annotation, Cardinality}; mod max; mod poseidon; @@ -19,42 +18,9 @@ pub use poseidon::PoseidonAnnotation; /// Any structure that implements this trait is guaranteed to be compatible /// as a poseidon tree annotation -pub trait PoseidonTreeAnnotation: - Canon - + Annotation, S> - + Borrow - + Borrow +pub trait PoseidonTreeAnnotation: + Default + Canon + Annotation + Borrow + Borrow where - L: PoseidonLeaf, - S: Store, + L: PoseidonLeaf, { } - -/// This trait will grant the ability of tree traversal using the `Branch::walk` -/// for a provided annotation -pub trait PoseidonWalkableAnnotation: - PoseidonTreeAnnotation -where - C: Compound, - C: Clone, - D: Clone, - L: PoseidonLeaf, - S: Store, -{ - /// Traversal logic of the walkable annotation - /// - /// This will define the traversal path over the tree provided the generic data. - /// - /// The purpose of the data is to act as a filter over annotations, and this will be equally - /// passed to leaves and nodes. - fn poseidon_walk(walk: Walk<'_, C, S>, data: D) -> Step<'_, C, S>; - - /// Uses the internal implementation of `poseidon_walk` to check if a leaf is compatible with - /// a provided data. - fn poseidon_leaf_found(leaf: &L, data: D) -> bool { - match Self::poseidon_walk(Walk::Leaf(leaf), data) { - Step::Found(_) => true, - _ => false, - } - } -} diff --git a/src/tree/annotation/poseidon.rs b/src/tree/annotation/poseidon.rs index cdb8ba5..60c1b9f 100644 --- a/src/tree/annotation/poseidon.rs +++ b/src/tree/annotation/poseidon.rs @@ -6,74 +6,24 @@ use super::PoseidonTreeAnnotation; use crate::tree::PoseidonLeaf; -use canonical::{Canon, Store}; use canonical_derive::Canon; use core::borrow::Borrow; use dusk_bls12_381::BlsScalar; use dusk_hades::{ScalarStrategy, Strategy}; -use microkelvin::{Annotation, Cardinality}; -use nstack::NStack; +use microkelvin::{AnnoIter, Annotation, Cardinality, Combine, Compound}; /// A microkelvin annotation with the minimum data for a functional poseidon tree /// /// The recommended usage for extended annotations for poseidon trees is to have /// this structure as attribute of the concrete annotation, and reflect the borrows /// of the cardinality and scalar to the poseidon annotation implementation. -#[derive(Debug, Clone, Canon)] +#[derive(Debug, Clone, Canon, Default)] pub struct PoseidonAnnotation { cardinality: Cardinality, poseidon_root: BlsScalar, } impl PoseidonAnnotation { - /// Create a new poseidon annotation from a generic node implementation - pub fn from_generic_node(node: &NStack) -> Self - where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, - { - let cardinality = - , S>>::from_node(node); - - let mut perm = [BlsScalar::zero(); dusk_hades::WIDTH]; - let mut flag = 1; - let mut mask = 0; - - match node { - NStack::Leaf(l) => { - l.iter().zip(perm.iter_mut().skip(1)).for_each(|(l, p)| { - if let Some(l) = l { - mask |= flag; - *p = l.poseidon_hash(); - } - - flag <<= 1; - }); - } - - NStack::Node(n) => { - n.iter().zip(perm.iter_mut().skip(1)).for_each(|(n, p)| { - if let Some(n) = n { - mask |= flag; - *p = *n.annotation().borrow(); - } - - flag <<= 1; - }); - } - } - - perm[0] = BlsScalar::from(mask); - ScalarStrategy::new().perm(&mut perm); - let poseidon_root = perm[1]; - - Self { - cardinality, - poseidon_root, - } - } - /// Return the scalar representation of the root of the annotated subtree pub fn poseidon_root(&self) -> &BlsScalar { &self.poseidon_root @@ -92,46 +42,52 @@ impl Borrow for PoseidonAnnotation { } } -impl Annotation, S> - for PoseidonAnnotation +impl Annotation for PoseidonAnnotation where - L: PoseidonLeaf, - S: Store, + L: PoseidonLeaf, { - fn identity() -> Self { - let cardinality = , - S, - >>::identity(); - let poseidon_root = BlsScalar::zero(); + fn from_leaf(leaf: &L) -> Self { + let cardinality = Cardinality::from_leaf(leaf); + let poseidon_root = leaf.poseidon_hash(); Self { cardinality, poseidon_root, } } +} - fn from_leaf(leaf: &L) -> Self { - let cardinality = , - S, - >>::from_leaf(leaf); - let poseidon_root = leaf.poseidon_hash(); +impl Combine for PoseidonAnnotation +where + A: Borrow + Borrow + Borrow, +{ + fn combine(iter: AnnoIter) -> Self + where + C: Compound, + A: Annotation, + { + let cardinality = Cardinality::combine(iter.clone()); + + let mut perm = [BlsScalar::zero(); dusk_hades::WIDTH]; + let mut flag = 1; + let mut mask = 0; + + for (i, anno) in iter.enumerate() { + mask |= flag; + perm[i + 1] = *(*anno).borrow(); + + flag <<= 1; + } + + perm[0] = BlsScalar::from(mask); + ScalarStrategy::new().perm(&mut perm); + let poseidon_root = perm[1]; Self { cardinality, poseidon_root, } } - - fn from_node(node: &NStack) -> Self { - Self::from_generic_node(node) - } } -impl PoseidonTreeAnnotation for PoseidonAnnotation -where - L: PoseidonLeaf, - S: Store, -{ -} +impl PoseidonTreeAnnotation for PoseidonAnnotation where L: PoseidonLeaf {} diff --git a/src/tree/branch.rs b/src/tree/branch.rs index 7655178..0533421 100644 --- a/src/tree/branch.rs +++ b/src/tree/branch.rs @@ -7,7 +7,6 @@ use super::{PoseidonLeaf, PoseidonTreeAnnotation}; use alloc::vec::Vec; -use canonical::{Canon, Store}; use canonical_derive::Canon; use core::iter; use core::ops::Deref; @@ -99,14 +98,13 @@ impl AsRef<[PoseidonLevel]> for PoseidonBranch { } } -impl From<&Branch<'_, NStack, S>> +impl From<&Branch<'_, NStack, A>> for PoseidonBranch where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { - fn from(b: &Branch<'_, NStack, S>) -> Self { + fn from(b: &Branch<'_, NStack, A>) -> Self { let mut branch = PoseidonBranch::default(); let mut depth = 0; diff --git a/src/tree/leaf.rs b/src/tree/leaf.rs index ec6cd8a..8efe09e 100644 --- a/src/tree/leaf.rs +++ b/src/tree/leaf.rs @@ -4,24 +4,21 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use canonical::{Canon, Store}; +use canonical::Canon; use dusk_bls12_381::BlsScalar; -/// A struct that will be used as a poseidon tree leaf must implement this trait +/// A struct that will be used as a poseidon tree leaf must implement this trait. /// /// After `PoseidonTree::push`, `tree_pos_mut` will be called to set the /// index of the leaf on the tree -pub trait PoseidonLeaf: Canon + Clone -where - S: Store, -{ +pub trait PoseidonLeaf: Canon + Clone { /// Poseidon hash implementation of the leaf structure. /// /// The result of this function will be used as opening for the merkle tree. fn poseidon_hash(&self) -> BlsScalar; /// Index of the leaf structure on the merkle tree. - fn pos(&self) -> u64; + fn pos(&self) -> &u64; /// Index of the leaf structure on the merkle tree. /// diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 8f2b420..d840f29 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -11,14 +11,11 @@ //! ### Example //! //! ```rust -//! #[cfg(feature = "std")] //! { -//! use anyhow::Result; -//! use canonical::Canon; //! use canonical_derive::Canon; -//! use canonical_host::MemStore; //! use dusk_plonk::prelude::*; //! use dusk_poseidon::tree::{merkle_opening, PoseidonAnnotation, PoseidonLeaf, PoseidonTree}; +//! use rand_core::OsRng; //! //! // Constant depth of the merkle tree //! const DEPTH: usize = 17; @@ -41,15 +38,15 @@ //! } //! //! // Any leaf of the poseidon tree must implement `PoseidonLeaf` -//! impl PoseidonLeaf for DataLeaf { +//! impl PoseidonLeaf for DataLeaf { //! // Cryptographic hash of the data leaf //! fn poseidon_hash(&self) -> BlsScalar { //! self.data //! } //! //! // Position on the tree -//! fn pos(&self) -> u64 { -//! self.pos +//! fn pos(&self) -> &u64 { +//! &self.pos //! } //! //! // Method used to set the position on the tree after the `PoseidonTree::push` call @@ -58,13 +55,13 @@ //! } //! } //! -//! fn main() -> Result<()> { +//! fn main() -> Result<(), Error> { //! // Create the ZK keys -//! let pub_params = PublicParameters::setup(1 << 15, &mut rand::thread_rng())?; +//! let pub_params = PublicParameters::setup(1 << 15, &mut OsRng)?; //! let (ck, ok) = pub_params.trim(1 << 15)?; //! -//! // Instantiate a new tree with the MemStore implementation -//! let mut tree: PoseidonTree = PoseidonTree::new(); +//! // Instantiate a new tree. +//! let mut tree: PoseidonTree = PoseidonTree::new(); //! //! // Append 1024 elements to the tree //! for i in 0..1024 { @@ -74,9 +71,9 @@ //! //! // Create a merkle opening tester gadget //! let gadget_tester = |composer: &mut StandardComposer, -//! tree: &PoseidonTree, +//! tree: &PoseidonTree, //! n: usize| { -//! let branch = tree.branch(n).unwrap().unwrap(); +//! let branch = tree.branch(n as u64).unwrap().unwrap(); //! let root = tree.root().unwrap(); //! //! let root_p = merkle_opening::(composer, &branch); @@ -109,20 +106,12 @@ mod annotation; mod branch; mod leaf; mod tree; - -#[cfg(feature = "std")] mod zk; -#[cfg(test)] -mod tests; - pub use annotation::{ PoseidonAnnotation, PoseidonMaxAnnotation, PoseidonTreeAnnotation, - PoseidonWalkableAnnotation, }; pub use branch::{PoseidonBranch, PoseidonLevel}; pub use leaf::PoseidonLeaf; -pub use tree::{PoseidonTree, PoseidonTreeIterator}; - -#[cfg(feature = "std")] +pub use tree::PoseidonTree; pub use zk::merkle_opening; diff --git a/src/tree/tree.rs b/src/tree/tree.rs index 1792585..306c66f 100644 --- a/src/tree/tree.rs +++ b/src/tree/tree.rs @@ -4,71 +4,59 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use super::{ - PoseidonBranch, PoseidonLeaf, PoseidonTreeAnnotation, - PoseidonWalkableAnnotation, -}; +use super::{PoseidonBranch, PoseidonLeaf, PoseidonTreeAnnotation}; use crate::Error; -use canonical::{Canon, Store}; +use canonical::CanonError; use canonical_derive::Canon; use dusk_bls12_381::BlsScalar; -use microkelvin::{Branch, Cardinality, Nth}; +use microkelvin::{Branch, Cardinality, Combine, Compound, Nth, Walker}; use nstack::NStack; -/// Represents a Merkle Tree with a given depth that will be calculated using poseidon hash -/// -/// The `BlsScalar` borrow of the annotation must represent the root poseidon merkle opening -/// for the annotated subtree +/// Represents a Merkle Tree with a given depth that will be calculated using +/// the Poseidon Hash technique. #[derive(Debug, Clone, Canon)] -pub struct PoseidonTree +pub struct PoseidonTree where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { - inner: NStack, + inner: NStack, } -impl AsRef> - for PoseidonTree +impl AsRef> for PoseidonTree where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { - fn as_ref(&self) -> &NStack { + fn as_ref(&self) -> &NStack { &self.inner } } -impl AsMut> - for PoseidonTree +impl AsMut> for PoseidonTree where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { - fn as_mut(&mut self) -> &mut NStack { + fn as_mut(&mut self) -> &mut NStack { &mut self.inner } } -impl Default for PoseidonTree +impl Default for PoseidonTree where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { fn default() -> Self { PoseidonTree::new() } } -impl PoseidonTree +impl PoseidonTree where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, + L: PoseidonLeaf, + A: PoseidonTreeAnnotation, { /// Creates a new poseidon tree pub fn new() -> Self { @@ -78,51 +66,34 @@ where } /// Append a leaf to the tree. Return the index of the appended leaf. - /// - /// Will call the `tree_pos_mut` implementation of the leaf to - /// set its index - pub fn push(&mut self, mut leaf: L) -> Result> { - let size = match &self.inner { - NStack::Leaf(l) => l.iter().filter(|l| l.is_some()).count(), - NStack::Node(n) => n - .iter() - .filter_map(|n| n.as_ref()) - .map::<&Cardinality, _>(|n| n.annotation().borrow()) - .map::(|c| c.into()) - .map(|n| n as usize) - .sum(), - }; - - leaf.set_pos(size as u64); - self.inner - .push(leaf) - .map_err(|e| Error::TreePushFailed(e))?; + pub fn push(&mut self, mut leaf: L) -> Result { + let size = Cardinality::combine(self.inner.annotations()).into(); + + leaf.set_pos(size); + self.inner.push(leaf).map_err(|_| Error::TreePushFailed)?; Ok(size) } /// Fetch, remove and return the last inserted leaf, if present. - pub fn pop(&mut self) -> Result, Error> { - self.inner.pop().map_err(|e| Error::TreePopFailed(e)) + pub fn pop(&mut self) -> Result, Error> { + self.inner.pop().map_err(|_| Error::TreePopFailed) } /// Fetch a leaf on a provided index. - pub fn get(&self, n: usize) -> Result, Error> { + pub fn get(&self, n: u64) -> Result, Error> { self.inner - .nth(n as u64) + .nth(n) .map(|o| o.map(|l| l.clone())) - .map_err(|e| Error::TreeGetFailed(e)) + .map_err(|_| Error::TreePopFailed) } /// Return a full merkle opening for this poseidon tree for a given index. pub fn branch( &self, - n: usize, - ) -> Result>, Error> { - let branch = self - .inner - .nth(n as u64) - .map_err(|e| Error::TreeGetFailed(e))?; + n: u64, + ) -> Result>, Error> { + let branch = self.inner.nth(n).map_err(|_| Error::TreeBranchFailed)?; match branch { Some(b) => Ok(Some(PoseidonBranch::from(&b))), @@ -131,100 +102,40 @@ where } /// Return the current root/state of the tree. - pub fn root(&self) -> Result> { - self.branch(0).map(|b| b.unwrap_or_default().root().clone()) + pub fn root(&self) -> Result { + self.branch(0) + .map(|b| *b.unwrap_or_default().root()) + .or(Err(Error::TreeBranchFailed)) } - /// Iterates over the tree, provided its annotation implements [`PoseidonWalkableAnnotation`] - pub fn iter_walk( + /// Provides an iterator over the leaves of the tree from a provided starting point. + /// To iterate the entire tree, simply provide `0` as `start`. + pub fn iter_walk( &self, - data: D, - ) -> Result, &'static str> - where - A: PoseidonWalkableAnnotation, D, L, S>, - { - PoseidonTreeIterator::new(&self, data) - } -} - -/// Main iterator of the poseidon tree. -/// -/// Depends on an implementation of `PoseidonWalkableAnnotation` for the tree annotation -/// -/// Every iteration will check for a valid `PoseidonWalkableAnnotation::poseidon_walk` call and -/// return a next leaf if the provided data holds true for the implemented logic -/// -/// The data can be any struct that implements `Clone`, and will be used to define the traversal -/// path over the tree. -pub struct PoseidonTreeIterator -where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - S: Store, - D: Clone, -{ - tree: PoseidonTree, - pos: usize, - data: D, -} - -impl PoseidonTreeIterator -where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - A: PoseidonWalkableAnnotation, D, L, S>, - S: Store, - D: Clone, -{ - /// Iterator constructor - pub fn new( - tree: &PoseidonTree, - data: D, - ) -> Result { - let tree = tree.clone(); - - // TODO - Naive implementation until iterable branch is implemented - // https://github.com/dusk-network/microkelvin/issues/23 - let pos = , S>>::walk(&tree.inner, |w| { - A::poseidon_walk(w, data.clone()) - }) - .map_err(|_| "Error fetching the branch!")? - .map(|l| l.pos()) - .unwrap_or(u64::max_value()) as usize; - - Ok(Self { tree, pos, data }) + start: u64, + ) -> Result>, Error> { + let result = self.inner.nth(start); + match result { + Ok(Some(iter)) => Ok(iter), + _ => Err(Error::TreeIterFailed), + } } -} - -impl Iterator - for PoseidonTreeIterator -where - L: PoseidonLeaf, - A: PoseidonTreeAnnotation, - A: PoseidonWalkableAnnotation, D, L, S>, - S: Store, - D: Clone, -{ - type Item = Result>; - fn next(&mut self) -> Option { - let pos = self.pos; - let (pos_p, overflow) = self.pos.overflowing_add(1); - if overflow { - return None; - } - self.pos = pos_p; - - match self.tree.get(pos) { - // Hack until iterable branch is available - // This will prevent the iteration over non-filtered data - // https://github.com/dusk-network/microkelvin/issues/23 - Ok(Some(l)) if A::poseidon_leaf_found(&l, self.data.clone()) => { - Some(Ok(l)) - } - Ok(Some(_)) => self.next(), - Err(e) => Some(Err(e)), - _ => None, + /// Provides an iterator over the leaves of the tree which have been previously annotated via a custom `Walker` passed + /// as argument. + /// + /// # Note + /// This is only useful if annotate the tree is going to make the iteration perform sub-linearly. + pub fn annotated_iter_walk( + &self, + walker: W, + ) -> Result>, Error> + where + W: Walker, A>, + { + match Branch::walk(&self.inner, walker) { + Ok(Some(iter)) => Ok(iter), + _ => Err(Error::TreeIterFailed), } } } diff --git a/src/tree/zk.rs b/src/tree/zk.rs index 50aa164..8fd1675 100644 --- a/src/tree/zk.rs +++ b/src/tree/zk.rs @@ -82,102 +82,3 @@ pub fn merkle_opening( root } - -#[cfg(test)] -mod tests { - use crate::tree::tests::MockLeaf; - use crate::tree::{self, PoseidonAnnotation, PoseidonBranch, PoseidonTree}; - use canonical_host::MemStore; - use dusk_plonk::circuit; - use dusk_plonk::error::Error as PlonkError; - use dusk_plonk::prelude::*; - use rand::rngs::StdRng; - use rand::SeedableRng; - use rand::{CryptoRng, RngCore}; - - const DEPTH: usize = 17; - const CAPACITY: usize = 1 << 15; - type Tree = PoseidonTree; - - struct MerkleOpeningCircuit { - branch: PoseidonBranch, - } - - impl MerkleOpeningCircuit { - pub fn random( - rng: &mut R, - tree: &mut Tree, - ) -> Self { - let leaf = MockLeaf::random(rng); - let pos = tree - .push(leaf.clone()) - .expect("Failed to append to the tree"); - - let branch = tree - .branch(pos) - .expect("Failed to read the tree for the branch") - .expect("Failed to fetch the branch of the created leaf from the tree"); - - Self { branch } - } - - pub fn public_inputs(&self) -> Vec { - vec![(*self.branch.root()).into()] - } - } - - impl Circuit for MerkleOpeningCircuit { - const CIRCUIT_ID: [u8; 32] = [0xff; 32]; - - fn gadget( - &mut self, - composer: &mut StandardComposer, - ) -> Result<(), PlonkError> { - let root = self.branch.root(); - let root_p = tree::merkle_opening::(composer, &self.branch); - - composer.constrain_to_constant( - root_p, - BlsScalar::zero(), - Some(-root), - ); - - Ok(()) - } - - fn padded_circuit_size(&self) -> usize { - CAPACITY - } - } - - #[test] - fn tree_merkle_opening() { - let mut rng = StdRng::seed_from_u64(0xbeef); - let pp = PublicParameters::setup(CAPACITY, &mut rng).unwrap(); - let label = b"dusk-network"; - - let mut tree = Tree::default(); - let mut circuit = MerkleOpeningCircuit::random(&mut rng, &mut tree); - let (pk, vd) = circuit.compile(&pp).expect("Failed to compile circuit"); - - let mut tree = Tree::default(); - for _ in 0..13 { - let mut circuit = MerkleOpeningCircuit::random(&mut rng, &mut tree); - - let proof = circuit - .gen_proof(&pp, &pk, label) - .expect("Failed to generate proof"); - let pi = circuit.public_inputs(); - - circuit::verify_proof( - &pp, - vd.key(), - &proof, - pi.as_slice(), - vd.pi_pos(), - label, - ) - .expect("Proof verification failed"); - } - } -} diff --git a/tests/block_height.rs b/tests/block_height.rs new file mode 100644 index 0000000..23e93d3 --- /dev/null +++ b/tests/block_height.rs @@ -0,0 +1,155 @@ +// 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 = "canon")] +use canonical_derive::Canon; +use core::borrow::Borrow; +use dusk_bls12_381::BlsScalar; +use dusk_poseidon::tree::{PoseidonLeaf, PoseidonMaxAnnotation, PoseidonTree}; +use dusk_poseidon::Error; +use microkelvin::{ + Annotation, Child, Compound, Keyed, MaxKey, Step, Walk, Walker, +}; + +#[derive( + Debug, Default, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Canon, +)] +pub struct TestLeaf { + hash: BlsScalar, + pos: u64, + height: BlockHeight, +} + +impl TestLeaf { + fn new(height: u64) -> TestLeaf { + TestLeaf { + hash: BlsScalar::zero(), + pos: 0, + height: BlockHeight(height), + } + } +} + +impl Keyed for TestLeaf { + fn key(&self) -> &BlockHeight { + &self.height + } +} + +impl PoseidonLeaf for TestLeaf { + fn poseidon_hash(&self) -> BlsScalar { + self.hash + } + + fn pos(&self) -> &u64 { + &self.pos + } + + fn set_pos(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl Borrow for TestLeaf { + fn borrow(&self) -> &u64 { + &self.pos + } +} + +#[derive( + Copy, Clone, Default, Debug, Canon, Ord, PartialOrd, Eq, PartialEq, +)] +pub struct BlockHeight(pub(crate) u64); + +// Walker method to find the elements that are avobe a certain a block height. +pub struct BlockHeightFilter(u64); + +impl Walker for BlockHeightFilter +where + C: Compound, + C::Leaf: Keyed, + A: Annotation + Borrow>, +{ + fn walk(&mut self, walk: Walk) -> Step { + for i in 0.. { + match walk.child(i) { + Child::Leaf(l) => { + if l.key().0 >= self.0 { + return Step::Found(i); + } else { + self.0 -= 1 + } + } + Child::Node(n) => { + let max_node_block_height: BlockHeight = + match *(*n.annotation()).borrow() { + MaxKey::NegativeInfinity => return Step::Abort, + MaxKey::Maximum(value) => value, + }; + if max_node_block_height.0 >= self.0 { + return Step::Into(i); + } else { + self.0 -= 1 + } + } + Child::Empty => (), + Child::EndOfNode => return Step::Advance, + } + } + unreachable!() + } +} + +#[test] +fn custom_walker_iter() -> Result<(), Error> { + let mut tree = + PoseidonTree::, 17>::new(); + + // Fill the tree with different leafs with different block heights. + for i in 0..18 { + let leaf = TestLeaf::new(i); + let pos = tree.push(leaf)?; + assert_eq!(pos, i); + let key: BlockHeight = *leaf.key(); + assert_eq!(key, BlockHeight(i as u64)); + } + + let mut leaf_count = 0; + // For a block_height of 0, the custom walker should iterate over all the leaves. + tree.annotated_iter_walk(BlockHeightFilter(0))? + .into_iter() + .enumerate() + .for_each(|(idx, l)| { + if l.is_ok() { + leaf_count += 1 + } + // Check that the heights are the expected ones + let leaf_height: BlockHeight = *l.unwrap().key(); + assert_eq!(leaf_height, BlockHeight(idx as u64)); + }); + assert_eq!(leaf_count, 18); + + // For a block_height of 20, we should fail to get an iterator over the tree as no leaf + // satisfies the criteria. + assert!(tree.annotated_iter_walk(BlockHeightFilter(20)).is_err()); + + leaf_count = 0; + // For a block_height of 15, the custom walker should iterate over the last two subtrees which means from + // leaves [12, 13, 14, 15] & [16, 17, _, _]. + tree.annotated_iter_walk(BlockHeightFilter(15))? + .into_iter() + .enumerate() + .for_each(|(idx, l)| { + if let Ok(leaf) = l { + leaf_count += 1; + // Check that the heights are the expected ones + let leaf_height: BlockHeight = *leaf.key(); + assert_eq!(leaf_height, BlockHeight(idx as u64 + 12)); + } + }); + assert_eq!(leaf_count, 6); + Ok(()) +} diff --git a/src/tree/tests.rs b/tests/max_annotation.rs similarity index 72% rename from src/tree/tests.rs rename to tests/max_annotation.rs index 9dacef4..72c8861 100644 --- a/src/tree/tests.rs +++ b/tests/max_annotation.rs @@ -4,24 +4,32 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::tree::{ - PoseidonAnnotation, PoseidonLeaf, PoseidonMaxAnnotation, PoseidonTree, -}; -use canonical::Canon; +#![cfg(feature = "canon")] use canonical_derive::Canon; -use canonical_host::MemStore; use core::borrow::Borrow; use dusk_bls12_381::BlsScalar; use dusk_hades::{ScalarStrategy, Strategy}; -use rand::{CryptoRng, RngCore}; +use dusk_poseidon::tree::{ + PoseidonAnnotation, PoseidonLeaf, PoseidonMaxAnnotation, PoseidonTree, +}; +use microkelvin::Keyed; +use rand_core::{CryptoRng, RngCore}; -#[derive(Debug, Default, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Canon)] +#[derive( + Debug, Default, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Canon, +)] pub struct MockLeaf { s: BlsScalar, pub pos: u64, pub expiration: u64, } +impl Keyed for MockLeaf { + fn key(&self) -> &u64 { + &self.pos + } +} + impl MockLeaf { pub fn random(rng: &mut R) -> Self { let s = BlsScalar::random(rng); @@ -42,13 +50,13 @@ impl From for MockLeaf { } } -impl PoseidonLeaf for MockLeaf { +impl PoseidonLeaf for MockLeaf { fn poseidon_hash(&self) -> BlsScalar { self.s } - fn pos(&self) -> u64 { - self.pos + fn pos(&self) -> &u64 { + &self.pos } fn set_pos(&mut self, pos: u64) { @@ -64,21 +72,21 @@ impl Borrow for MockLeaf { #[test] fn tree_append_fetch() { - const MAX: usize = 4097; - let mut v = [MockLeaf::default(); MAX]; + const MAX: u64 = 4097; + let mut v = [MockLeaf::default(); MAX as usize]; - let mut tree: PoseidonTree = + let mut tree: PoseidonTree = PoseidonTree::new(); for i in 0..MAX { - let mut s = MockLeaf::from(i as u64); + let mut s = MockLeaf::from(i); let pos = tree.push(s).unwrap(); assert_eq!(i, pos); - s.pos = i as u64; - v[i] = s; + s.pos = i; + v[i as usize] = s; } v.iter().enumerate().for_each(|(i, s)| { - let l = tree.get(i).unwrap().unwrap(); + let l = tree.get(i as u64).unwrap().unwrap(); assert_eq!(s, &l); }); @@ -90,38 +98,35 @@ fn tree_append_fetch() { #[test] fn tree_max_walk() { - const MAX: usize = 1025; - let mut v = [MockLeaf::default(); MAX]; + const MAX: u64 = 1025; - let mut tree: PoseidonTree = + let mut tree: PoseidonTree, 17> = PoseidonTree::new(); for i in 0..MAX { - let mut s = MockLeaf::from(i as u64); + let s = MockLeaf::from(i as u64); let pos = tree.push(s).unwrap(); assert_eq!(i, pos); - s.pos = i as u64; - v[i] = s; } let w = 170; let pos = w * 3; - tree.iter_walk(w) + tree.iter_walk(pos) .unwrap() + .into_iter() .map(|l| l.unwrap()) .enumerate() .for_each(|(i, leaf)| { - assert_eq!(pos + i as u64, leaf.pos()); + assert_eq!(pos + i as u64, *leaf.pos()); }); - assert!(tree.iter_walk((MAX + 1) as u64).unwrap().next().is_none()); + assert!(tree.iter_walk((MAX + 1) as u64).is_err()); } #[test] fn tree_max_walk_non_continuous() { - const MAX: usize = 1025; - let mut v = [MockLeaf::default(); MAX]; + const MAX: u64 = 1025u64; - let mut tree: PoseidonTree = + let mut tree: PoseidonTree, 17> = PoseidonTree::new(); for i in 0..MAX { @@ -130,27 +135,25 @@ fn tree_max_walk_non_continuous() { if i % 4 == 0 { s.expiration = 0; } - let pos = tree.push(s).unwrap(); assert_eq!(i, pos); - s.pos = i as u64; - v[i] = s; } let w = 170; let mut pos = w * 3; - tree.iter_walk(w) + tree.iter_walk(pos) .unwrap() + .into_iter() .map(|l| l.unwrap()) .for_each(|leaf| { if pos % 4 == 0 { - pos += 1; + //pos += 1; } - assert_eq!(pos, leaf.pos()); + assert_eq!(pos, *leaf.pos()); pos += 1; }); - assert!(tree.iter_walk((MAX + 1) as u64).unwrap().next().is_none()); + assert!(tree.iter_walk((MAX + 1) as u64).is_err()); } #[test] @@ -168,12 +171,8 @@ fn tree_branch_leaf() { .for_each(|w| { let w = *w; - let mut tree: PoseidonTree< - MockLeaf, - PoseidonAnnotation, - MemStore, - DEPTH, - > = PoseidonTree::new(); + let mut tree: PoseidonTree = + PoseidonTree::new(); for i in 0..w { let l = MockLeaf::from(i as u64); @@ -207,7 +206,7 @@ fn tree_branch_leaf() { #[test] fn tree_branch_depth() { let mut h = ScalarStrategy::new(); - let mut tree: PoseidonTree = + let mut tree: PoseidonTree = PoseidonTree::new(); let leaf = MockLeaf::from(1); diff --git a/tests/sponge_test.rs b/tests/sponge_test.rs new file mode 100644 index 0000000..93fe729 --- /dev/null +++ b/tests/sponge_test.rs @@ -0,0 +1,261 @@ +// 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_hades::WIDTH; +use dusk_plonk::prelude::*; +use dusk_poseidon::sponge; +use rand_core::OsRng; + +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", +]; + +const CAPACITY: usize = 1 << 12; + +fn poseidon_sponge_params() -> ([BlsScalar; N], BlsScalar) { + let mut input = [BlsScalar::zero(); N]; + input + .iter_mut() + .for_each(|s| *s = BlsScalar::random(&mut OsRng)); + let output = sponge::hash(&input); + (input, output) +} + +// Checks that the result of the hades permutation is the same as the one +// obtained by the sponge gadget +fn sponge_gadget_tester( + i: &[BlsScalar], + out: BlsScalar, + composer: &mut StandardComposer, +) { + let zero = composer.add_input(BlsScalar::zero()); + composer.constrain_to_constant(zero, BlsScalar::zero(), None); + + let mut i_var = vec![zero; N]; + i.iter().zip(i_var.iter_mut()).for_each(|(i, v)| { + *v = composer.add_input(*i); + }); + + let o_var = composer.add_input(out); + + // Apply Poseidon Sponge hash to the inputs + let computed_o_var = sponge::gadget(composer, &i_var); + + // Check that the Gadget sponge hash result = Scalar sponge hash result + composer.add_gate( + o_var, + computed_o_var, + zero, + BlsScalar::one(), + -BlsScalar::one(), + BlsScalar::zero(), + BlsScalar::zero(), + None, + ); +} + +#[test] +fn sponge_gadget_width_3() -> Result<(), Error> { + // Setup OG params. + let public_parameters = PublicParameters::setup(CAPACITY, &mut OsRng)?; + let (ck, vk) = public_parameters.trim(CAPACITY)?; + + // Test with width = 3 + + // Proving + let (i, o) = poseidon_sponge_params::<3>(); + let mut prover = Prover::new(b"sponge_tester"); + sponge_gadget_tester::<3>(&i, o, prover.mut_cs()); + prover.preprocess(&ck)?; + let proof = prover.prove(&ck)?; + + // Verify + let mut verifier = Verifier::new(b"sponge_tester"); + sponge_gadget_tester::<3>(&i, o, verifier.mut_cs()); + verifier.preprocess(&ck)?; + verifier.verify(&proof, &vk, &[BlsScalar::zero()])?; + + Ok(()) +} + +#[test] +fn sponge_gadget_hades_width() -> Result<(), Error> { + // Setup OG params. + let public_parameters = PublicParameters::setup(CAPACITY, &mut OsRng)?; + let (ck, vk) = public_parameters.trim(CAPACITY)?; + + // Test with width = 5 + + // Proving + let (i, o) = poseidon_sponge_params::(); + let mut prover = Prover::new(b"sponge_tester"); + sponge_gadget_tester::(&i, o, prover.mut_cs()); + prover.preprocess(&ck)?; + let proof = prover.prove(&ck)?; + + // Verify + let mut verifier = Verifier::new(b"sponge_tester"); + sponge_gadget_tester::(&i, o, verifier.mut_cs()); + verifier.preprocess(&ck)?; + verifier.verify(&proof, &vk, &[BlsScalar::zero()])?; + + Ok(()) +} + +#[test] +fn sponge_gadget_width_15() -> Result<(), Error> { + // Setup OG params. + let public_parameters = PublicParameters::setup(1 << 17, &mut OsRng)?; + let (ck, vk) = public_parameters.trim(1 << 17)?; + + // Test with width = 15 + + // Proving + let (i, o) = poseidon_sponge_params::<15>(); + let mut prover = Prover::new(b"sponge_tester"); + sponge_gadget_tester::<15>(&i, o, prover.mut_cs()); + prover.preprocess(&ck)?; + let proof = prover.prove(&ck)?; + + // Verify + let mut verifier = Verifier::new(b"sponge_tester"); + sponge_gadget_tester::<15>(&i, o, verifier.mut_cs()); + verifier.preprocess(&ck)?; + verifier.verify(&proof, &vk, &[BlsScalar::zero()])?; + + Ok(()) +} + +#[test] +fn sponge_hash_test() { + let test_inputs: Vec = TEST_INPUTS + .iter() + .map(|input| BlsScalar::from_hex_str(input).unwrap()) + .collect(); + + assert_eq!( + "0xe36f4ea9b858d5c85b02770823c7c5d8253c28787d17f283ca348b906dca8528", + format!("{:#x}", sponge::hash(&test_inputs[..3])) + ); + + assert_eq!( + "0x75ea3265c80d07e608c1f363ea0b4394ff1fa1cbf50b43b14c880a5755f7f755", + format!("{:#x}", sponge::hash(&test_inputs[..4])) + ); + + assert_eq!( + "0x533106a0980eff5b01f5ce63a6b0dd87328b318ac6aa600fc28b9a2ab9f88842", + format!("{:#x}", sponge::hash(&test_inputs[..5])) + ); + + assert_eq!( + "0x1a815864684fff47c4d279ee4c31ad964c9dc232734e08188554fa27d33e6731", + format!("{:#x}", sponge::hash(&test_inputs[..6])) + ); + + assert_eq!( + "0xa8b936d057df818048e634254719d13970df22926c51e5190c916fcf13dfa25a", + format!("{:#x}", sponge::hash(&test_inputs[..8])) + ); + + assert_eq!( + "0x982934231a0410c86f9ed1daa46863a5ddae6d250670d27cb21d10739088e30b", + format!("{:#x}", sponge::hash(&test_inputs[..10])) + ); +} + +mod truncated { + use super::*; + use sponge::truncated; + + fn truncated_gadget( + composer: &mut StandardComposer, + messages: &[BlsScalar], + ) { + let truncated_res = truncated::hash(messages); + let final_point = + JubJubAffine::from(dusk_jubjub::GENERATOR_EXTENDED * truncated_res); + + // Circuit + let input_vars: Vec = messages + .iter() + .map(|message| composer.add_input(*message)) + .collect(); + let truncated_res_var = truncated::gadget(composer, &input_vars); + let point_result = composer.fixed_base_scalar_mul( + truncated_res_var, + dusk_jubjub::GENERATOR_EXTENDED, + ); + composer.assert_equal_public_point(point_result, final_point); + } + + #[test] + fn truncation_sponges_match() -> Result<(), Error> { + // Setup OG params. + let public_parameters = PublicParameters::setup(1 << 17, &mut OsRng)?; + let (ck, vk) = public_parameters.trim(1 << 17)?; + + let test_inputs: Vec = TEST_INPUTS + .iter() + .map(|input| BlsScalar::from_hex_str(input).unwrap()) + .collect(); + + let gadget_tester = |messages: &[BlsScalar]| -> Result<(), Error> { + let mut prover = Prover::new(b"sponge_tester"); + truncated_gadget(prover.mut_cs(), messages); + let pi = prover.mut_cs().construct_dense_pi_vec(); + prover.preprocess(&ck)?; + let proof = prover.prove(&ck)?; + + // Verify + let mut verifier = Verifier::new(b"sponge_tester"); + truncated_gadget(verifier.mut_cs(), messages); + verifier.preprocess(&ck)?; + verifier.verify(&proof, &vk, &pi) + }; + + assert!(gadget_tester(&test_inputs[..3]).is_ok()); + + assert!(gadget_tester(&test_inputs[..6]).is_ok()); + + assert!(gadget_tester(&test_inputs[..9]).is_ok()); + + Ok(()) + } +} diff --git a/tests/walkable_iter.rs b/tests/walkable_iter.rs new file mode 100644 index 0000000..23e93d3 --- /dev/null +++ b/tests/walkable_iter.rs @@ -0,0 +1,155 @@ +// 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 = "canon")] +use canonical_derive::Canon; +use core::borrow::Borrow; +use dusk_bls12_381::BlsScalar; +use dusk_poseidon::tree::{PoseidonLeaf, PoseidonMaxAnnotation, PoseidonTree}; +use dusk_poseidon::Error; +use microkelvin::{ + Annotation, Child, Compound, Keyed, MaxKey, Step, Walk, Walker, +}; + +#[derive( + Debug, Default, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Canon, +)] +pub struct TestLeaf { + hash: BlsScalar, + pos: u64, + height: BlockHeight, +} + +impl TestLeaf { + fn new(height: u64) -> TestLeaf { + TestLeaf { + hash: BlsScalar::zero(), + pos: 0, + height: BlockHeight(height), + } + } +} + +impl Keyed for TestLeaf { + fn key(&self) -> &BlockHeight { + &self.height + } +} + +impl PoseidonLeaf for TestLeaf { + fn poseidon_hash(&self) -> BlsScalar { + self.hash + } + + fn pos(&self) -> &u64 { + &self.pos + } + + fn set_pos(&mut self, pos: u64) { + self.pos = pos; + } +} + +impl Borrow for TestLeaf { + fn borrow(&self) -> &u64 { + &self.pos + } +} + +#[derive( + Copy, Clone, Default, Debug, Canon, Ord, PartialOrd, Eq, PartialEq, +)] +pub struct BlockHeight(pub(crate) u64); + +// Walker method to find the elements that are avobe a certain a block height. +pub struct BlockHeightFilter(u64); + +impl Walker for BlockHeightFilter +where + C: Compound, + C::Leaf: Keyed, + A: Annotation + Borrow>, +{ + fn walk(&mut self, walk: Walk) -> Step { + for i in 0.. { + match walk.child(i) { + Child::Leaf(l) => { + if l.key().0 >= self.0 { + return Step::Found(i); + } else { + self.0 -= 1 + } + } + Child::Node(n) => { + let max_node_block_height: BlockHeight = + match *(*n.annotation()).borrow() { + MaxKey::NegativeInfinity => return Step::Abort, + MaxKey::Maximum(value) => value, + }; + if max_node_block_height.0 >= self.0 { + return Step::Into(i); + } else { + self.0 -= 1 + } + } + Child::Empty => (), + Child::EndOfNode => return Step::Advance, + } + } + unreachable!() + } +} + +#[test] +fn custom_walker_iter() -> Result<(), Error> { + let mut tree = + PoseidonTree::, 17>::new(); + + // Fill the tree with different leafs with different block heights. + for i in 0..18 { + let leaf = TestLeaf::new(i); + let pos = tree.push(leaf)?; + assert_eq!(pos, i); + let key: BlockHeight = *leaf.key(); + assert_eq!(key, BlockHeight(i as u64)); + } + + let mut leaf_count = 0; + // For a block_height of 0, the custom walker should iterate over all the leaves. + tree.annotated_iter_walk(BlockHeightFilter(0))? + .into_iter() + .enumerate() + .for_each(|(idx, l)| { + if l.is_ok() { + leaf_count += 1 + } + // Check that the heights are the expected ones + let leaf_height: BlockHeight = *l.unwrap().key(); + assert_eq!(leaf_height, BlockHeight(idx as u64)); + }); + assert_eq!(leaf_count, 18); + + // For a block_height of 20, we should fail to get an iterator over the tree as no leaf + // satisfies the criteria. + assert!(tree.annotated_iter_walk(BlockHeightFilter(20)).is_err()); + + leaf_count = 0; + // For a block_height of 15, the custom walker should iterate over the last two subtrees which means from + // leaves [12, 13, 14, 15] & [16, 17, _, _]. + tree.annotated_iter_walk(BlockHeightFilter(15))? + .into_iter() + .enumerate() + .for_each(|(idx, l)| { + if let Ok(leaf) = l { + leaf_count += 1; + // Check that the heights are the expected ones + let leaf_height: BlockHeight = *leaf.key(); + assert_eq!(leaf_height, BlockHeight(idx as u64 + 12)); + } + }); + assert_eq!(leaf_count, 6); + Ok(()) +} diff --git a/tests/zk.rs b/tests/zk.rs new file mode 100644 index 0000000..e245071 --- /dev/null +++ b/tests/zk.rs @@ -0,0 +1,99 @@ +// 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 = "canon")] + +mod max_annotation; +use dusk_plonk::circuit; +use dusk_plonk::error::Error as PlonkError; +use dusk_plonk::prelude::*; +use dusk_poseidon::tree::{ + self, PoseidonAnnotation, PoseidonBranch, PoseidonTree, +}; +use max_annotation::MockLeaf; +use rand_core::{CryptoRng, OsRng, RngCore}; + +const DEPTH: usize = 17; +const CAPACITY: usize = 1 << 15; +type Tree = PoseidonTree; + +struct MerkleOpeningCircuit { + branch: PoseidonBranch, +} + +impl MerkleOpeningCircuit { + pub fn random( + rng: &mut R, + tree: &mut Tree, + ) -> Self { + let leaf = MockLeaf::random(rng); + let pos = tree.push(leaf).expect("Failed to append to the tree"); + + let branch = tree + .branch(pos) + .expect("Failed to read the tree for the branch") + .expect( + "Failed to fetch the branch of the created leaf from the tree", + ); + + Self { branch } + } + + pub fn public_inputs(&self) -> Vec { + vec![(*self.branch.root()).into()] + } +} + +impl Circuit for MerkleOpeningCircuit { + const CIRCUIT_ID: [u8; 32] = [0xff; 32]; + + fn gadget( + &mut self, + composer: &mut StandardComposer, + ) -> Result<(), PlonkError> { + let root = self.branch.root(); + let root_p = tree::merkle_opening::(composer, &self.branch); + + composer.constrain_to_constant(root_p, BlsScalar::zero(), Some(-root)); + + Ok(()) + } + + fn padded_circuit_size(&self) -> usize { + CAPACITY + } +} + +#[test] +fn tree_merkle_opening() { + let mut rng = OsRng; + let pp = PublicParameters::setup(CAPACITY, &mut rng).unwrap(); + let label = b"dusk-network"; + + let mut tree = Tree::default(); + let mut circuit = MerkleOpeningCircuit::random(&mut rng, &mut tree); + let (pk, vd) = circuit.compile(&pp).expect("Failed to compile circuit"); + + let mut tree = Tree::default(); + for _ in 0..13 { + let mut circuit = MerkleOpeningCircuit::random(&mut rng, &mut tree); + + let proof = circuit + .gen_proof(&pp, &pk, label) + .expect("Failed to generate proof"); + let pi = circuit.public_inputs(); + + circuit::verify_proof( + &pp, + vd.key(), + &proof, + pi.as_slice(), + vd.pi_pos(), + label, + ) + .expect("Proof verification failed"); + } +}