From d47c157ae0c1cd5d0552a1eef1446dd9bd7a7480 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 16 Sep 2021 20:44:42 +0200 Subject: [PATCH] Replace arb_tree proptest with incrementalmerkletree impl. --- src/builder.rs | 25 +++++++- src/tree.rs | 151 ++++++------------------------------------------- 2 files changed, 40 insertions(+), 136 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index edc14fa8..89d18cf9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -569,6 +569,8 @@ impl Bundle, V> { /// Generators for property testing. #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { + use incrementalmerkletree::{bridgetree::BridgeTree, Frontier, Tree}; + use rand::{rngs::StdRng, CryptoRng, SeedableRng}; use std::convert::TryFrom; use std::fmt::Debug; @@ -584,7 +586,8 @@ pub mod testing { testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendingKey, }, - tree::{testing::arb_tree, Anchor, MerklePath}, + note::testing::arb_note, + tree::{Anchor, MerkleHashOrchard, MerklePath}, value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE}, Address, Note, }; @@ -647,7 +650,11 @@ pub mod testing { n_recipients in 1..30, ) ( - (notes_and_auth_paths, anchor) in arb_tree(n_notes), + // generate note values that we're certain won't exceed MAX_NOTE_VALUE in total + notes in vec( + arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note), + n_notes + ), recipient_amounts in vec( arb_address().prop_flat_map(move |a| { arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64) @@ -657,10 +664,22 @@ pub mod testing { ), rng_seed in prop::array::uniform32(prop::num::u8::ANY) ) -> ArbitraryBundleInputs { + let mut tree = BridgeTree::::new(100); + let mut notes_and_auth_paths: Vec<(Note, MerklePath)> = Vec::new(); + + for note in notes.iter() { + let leaf = MerkleHashOrchard::from_cmx(¬e.commitment().into()); + tree.append(&leaf); + tree.witness(); + + let path = tree.authentication_path(&leaf).unwrap().into(); + notes_and_auth_paths.push((*note, path)); + } + ArbitraryBundleInputs { rng: StdRng::from_seed(rng_seed), sk, - anchor, + anchor: tree.root().into(), notes: notes_and_auth_paths, recipient_amounts } diff --git a/src/tree.rs b/src/tree.rs index 6d2b5123..d3101e73 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -80,6 +80,19 @@ pub struct MerklePath { auth_path: [MerkleHashOrchard; MERKLE_DEPTH_ORCHARD], } +#[cfg(any(test, feature = "test-dependencies"))] +impl From<(incrementalmerkletree::Position, Vec)> for MerklePath { + fn from(path: (incrementalmerkletree::Position, Vec)) -> Self { + use std::convert::TryInto; + + let position: u64 = path.0.into(); + Self { + position: position as u32, + auth_path: path.1.try_into().unwrap(), + } + } +} + impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. pub(crate) fn dummy(mut rng: &mut impl RngCore) -> Self { @@ -253,7 +266,6 @@ impl<'de> Deserialize<'de> for MerkleHashOrchard { /// Generators for property testing. #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { - use incrementalmerkletree::Hashable; #[cfg(test)] use incrementalmerkletree::{ bridgetree::{BridgeTree, Frontier as BridgeFrontier}, @@ -261,19 +273,11 @@ pub mod testing { }; #[cfg(test)] - use std::convert::TryInto; - - use crate::{ - constants::MERKLE_DEPTH_ORCHARD, - note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note}, - tree::{Anchor, MerkleHashOrchard, MerklePath, EMPTY_ROOTS}, - value::{testing::arb_positive_note_value, MAX_NOTE_VALUE}, - }; + use crate::tree::{MerkleHashOrchard, EMPTY_ROOTS}; #[cfg(test)] use pasta_curves::{arithmetic::FieldExt, pallas}; - - use proptest::collection::vec; - use proptest::prelude::*; + #[cfg(test)] + use std::convert::TryInto; #[test] fn test_vectors() { @@ -313,129 +317,10 @@ pub mod testing { } } - prop_compose! { - /// Generates an arbitrary Merkle tree of with `n_notes` nonempty leaves. - pub fn arb_tree(n_notes: usize) - ( - // generate note values that we're certain won't exceed MAX_NOTE_VALUE in total - notes in vec( - arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note), - n_notes - ), - ) - -> (Vec<(Note, MerklePath)>, Anchor) { - // Inefficient algorithm to build a perfect subtree containing all notes. - let perfect_subtree_depth = (n_notes as f64).log2().ceil() as usize; - let n_leaves = 1 << perfect_subtree_depth; - - let commitments: Vec> = notes.iter().map(|note| { - let cmx: ExtractedNoteCommitment = note.commitment().into(); - Some(cmx) - }).collect(); - - let padded_leaves = { - let mut padded_leaves = commitments.clone(); - - let pad = (0..(n_leaves - n_notes)).map( - |_| None - ).collect::>(); - - padded_leaves.extend_from_slice(&pad); - padded_leaves - }; - - let perfect_subtree = { - let mut perfect_subtree: Vec>> = vec![ - padded_leaves.iter().map(|cmx| cmx.map(|cmx| MerkleHashOrchard::from_cmx(&cmx))).collect() - ]; - - // - // The layer with 2^n nodes is called "layer n": - // - leaves are at layer MERKLE_DEPTH_ORCHARD = 32; - // - the root is at layer 0. - // `l` is MERKLE_DEPTH_ORCHARD - layer - 1. - // - when hashing two leaves, we produce a node on the layer above the leaves, i.e. - // layer = 31, l = 0 - // - when hashing to the final root, we produce the anchor with layer = 0, l = 31. - for l in 0..perfect_subtree_depth { - let inner_nodes = (0..(n_leaves >> (l + 1))).map(|pos| { - let left = perfect_subtree[l][pos * 2]; - let right = perfect_subtree[l][pos * 2 + 1]; - match (left, right) { - (None, None) => None, - (Some(left), None) => { - let right = EMPTY_ROOTS[l]; - Some(MerkleHashOrchard::combine((l as u8).into(), &left, &right)) - }, - (Some(left), Some(right)) => { - Some(MerkleHashOrchard::combine((l as u8).into(), &left, &right)) - }, - (None, Some(_)) => { - unreachable!("The perfect subtree is left-packed.") - } - } - }).collect(); - perfect_subtree.push(inner_nodes); - }; - perfect_subtree - }; - - // Get Merkle path for each note commitment - let auth_paths = { - let mut auth_paths: Vec = Vec::new(); - for (pos, _) in commitments.iter().enumerate() { - - // Initialize the authentication path to the path for an empty tree. - let mut auth_path = [MerkleHashOrchard::empty_leaf(); MERKLE_DEPTH_ORCHARD]; - - let mut layer_pos = pos; - for height in 0..perfect_subtree_depth { - let is_right_sibling = layer_pos & 1 == 1; - let sibling = if is_right_sibling { - // This node is the right sibling, so we need its left sibling at the current height. - perfect_subtree[height][layer_pos - 1] - } else { - // This node is the left sibling, so we need its right sibling at the current height. - perfect_subtree[height][layer_pos + 1] - }; - if let Some(sibling) = sibling { - auth_path[height] = sibling; - } - layer_pos = (layer_pos - is_right_sibling as usize) / 2; - }; - - let path = MerklePath {position: pos as u32, auth_path}; - auth_paths.push(path); - } - auth_paths - }; - - // Compute anchor for this tree - let anchor = auth_paths[0].root(notes[0].commitment().into()); - - ( - notes.into_iter().zip(auth_paths.into_iter()).map(|(note, auth_path)| (note, auth_path)).collect(), - anchor - ) - } - } - - proptest! { - #![proptest_config(ProptestConfig::with_cases(10))] - #[allow(clippy::redundant_closure)] - #[test] - fn tree( - (notes_and_auth_paths, anchor) in (1usize..4).prop_flat_map(|n_notes| arb_tree(n_notes)) - ) { - for (note, auth_path) in notes_and_auth_paths.iter() { - let computed_anchor = auth_path.root(note.commitment().into()); - assert_eq!(anchor, computed_anchor); - } - } - } - #[test] fn empty_roots_incremental() { + use incrementalmerkletree::Hashable; + let tv_empty_roots = crate::test_vectors::commitment_tree::test_vectors().empty_roots; for (altitude, tv_root) in tv_empty_roots.iter().enumerate() {