From b33248bdb0b21e67fd7bc45beaaea2a3277aa590 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 8 Jun 2021 13:18:16 +0800 Subject: [PATCH 01/11] src::tree.rs: Implement MerklePath.root() method. Co-authored-by: Kris Nuttycombe --- src/builder.rs | 17 +++---- src/constants.rs | 3 ++ src/note/commitment.rs | 8 +++ src/primitives/sinsemilla.rs | 13 +++++ src/tree.rs | 96 ++++++++++++++++++++++++++++++++++-- 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 6ed10616..11e7272c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -198,12 +198,11 @@ impl Builder { } // Consistency check: all anchors must be equal. - let _cm = note.commitment(); - // TODO: Once we have tree logic. - // let path_root: bls12_381::Scalar = merkle_path.root(cmu).into(); - // if path_root != anchor { - // return Err(Error::AnchorMismatch); - // } + let cm = note.commitment(); + let path_root: Anchor = merkle_path.root(cm.into()); + if path_root != self.anchor { + return Err("All anchors must be equal."); + } self.spends.push(SpendInfo { dummy_sk: None, @@ -487,7 +486,7 @@ pub mod testing { rng: R, sk: SpendingKey, anchor: Anchor, - notes: Vec, + notes: Vec<(Note, MerklePath)>, recipient_amounts: Vec<(Address, NoteValue)>, } @@ -499,8 +498,8 @@ pub mod testing { let flags = Flags::from_parts(true, true); let mut builder = Builder::new(flags, self.anchor); - for note in self.notes.into_iter() { - builder.add_spend(fvk.clone(), note, MerklePath).unwrap(); + for (note, path) in self.notes.into_iter() { + builder.add_spend(fvk.clone(), note, path).unwrap(); } for (addr, value) in self.recipient_amounts.into_iter() { diff --git a/src/constants.rs b/src/constants.rs index 72daeaa0..37f9817f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -19,6 +19,9 @@ pub mod util; pub use load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV}; +/// $\mathsf{MerkleDepth^{Orchard}}$ +pub(crate) const MERKLE_DEPTH_ORCHARD: usize = 32; + /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; diff --git a/src/note/commitment.rs b/src/note/commitment.rs index f7de3ef2..d543c0f3 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -63,3 +63,11 @@ impl From for ExtractedNoteCommitment { ExtractedNoteCommitment(extract_p(&cm.0)) } } + +impl std::ops::Deref for ExtractedNoteCommitment { + type Target = pallas::Base; + + fn deref(&self) -> &pallas::Base { + &self.0 + } +} diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 685b7699..090e0b40 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -5,6 +5,7 @@ use pasta_curves::pallas; use subtle::CtOption; use crate::spec::extract_p_bottom; +use std::convert::TryInto; mod addition; use self::addition::IncompletePoint; @@ -20,6 +21,18 @@ fn lebs2ip_k(bits: &[bool]) -> u32 { .fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) } +/// The sequence of K bits in little-endian order representing an integer +/// up to `2^K` - 1. +pub fn i2lebsp_k(int: usize) -> [bool; K] { + assert!(int < (1 << K)); + + (0..K) + .map(|mask| ((int & (1 << mask)) >> mask) == 1) + .collect::>() + .try_into() + .unwrap() +} + /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a /// multiple of $K$ bits. struct Pad> { diff --git a/src/tree.rs b/src/tree.rs index e0d14582..a7f94799 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,15 +1,103 @@ +use crate::{ + constants::{MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + note::commitment::ExtractedNoteCommitment, + primitives::sinsemilla::{i2lebsp_k, HashDomain, K}, +}; +use pasta_curves::{arithmetic::FieldExt, pallas}; + +use ff::{PrimeField, PrimeFieldBits}; use rand::RngCore; +use std::{convert::TryInto, iter}; /// The root of an Orchard commitment tree. -#[derive(Clone, Debug)] +#[derive(Eq, PartialEq, Clone, Debug)] pub struct Anchor(pub [u8; 32]); +impl From for Anchor { + fn from(anchor_field: pallas::Base) -> Anchor { + Anchor(anchor_field.to_bytes()) + } +} + #[derive(Debug)] -pub struct MerklePath; +pub struct MerklePath { + position: u32, + auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD], +} impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. - pub(crate) fn dummy(_rng: &mut impl RngCore) -> Self { - MerklePath + pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { + MerklePath { + position: rng.next_u32(), + auth_path: (0..MERKLE_DEPTH_ORCHARD) + .map(|_| pallas::Base::rand()) + .collect::>() + .try_into() + .unwrap(), + } + } + + pub fn root(&self, cmx: ExtractedNoteCommitment) -> Anchor { + // Initialize `node` to the first hash. + let init_node = { + let pos = self.position % 2 == 1; + hash_layer(0, cond_swap(pos, *cmx, self.auth_path[0])) + }; + let node = self.auth_path[1..] + .iter() + .enumerate() + .fold(init_node, |node, (i, sibling)| { + let l_star = i + 1; + let swap = (self.position >> l_star) == 1; + hash_layer(l_star, cond_swap(swap, node, *sibling)) + }); + Anchor(node.to_bytes()) } } + +struct Pair { + left: pallas::Base, + right: pallas::Base, +} + +fn cond_swap(swap: bool, node: pallas::Base, sibling: pallas::Base) -> Pair { + if swap { + Pair { + left: sibling, + right: node, + } + } else { + Pair { + left: node, + right: sibling, + } + } +} + +// +fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { + // MerkleCRH Sinsemilla hash domain. + let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); + + domain + .hash( + iter::empty() + .chain(i2lebsp_k(l_star).iter().copied().take(K)) + .chain( + pair.left + .to_le_bits() + .iter() + .by_val() + .take(pallas::Base::NUM_BITS as usize), + ) + .chain( + pair.right + .to_le_bits() + .iter() + .by_val() + .take(pallas::Base::NUM_BITS as usize), + ), + ) + .unwrap() +} From 8f8eff23d81076090f52b54df8cdad5d3d4b8d59 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 8 Jun 2021 15:27:08 +0800 Subject: [PATCH 02/11] Update proptests to generate Merkle paths --- Cargo.toml | 1 + src/builder.rs | 12 +--- src/bundle.rs | 2 +- src/note.rs | 2 +- src/note/commitment.rs | 7 ++- src/tree.rs | 135 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 147 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5690a069..4842be2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ blake2b_simd = "0.5" ff = "0.10" fpe = "0.4" group = "0.10" +lazy_static = "1" pasta_curves = "0.1" proptest = { version = "1.0.0", optional = true } rand = "0.8" diff --git a/src/builder.rs b/src/builder.rs index 11e7272c..22aaea8e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -465,8 +465,7 @@ pub mod testing { testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey, SpendingKey, }, - note::testing::arb_note, - tree::{Anchor, MerklePath}, + tree::{testing::arb_tree, Anchor, MerklePath}, value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE}, Address, Note, }; @@ -527,12 +526,7 @@ pub mod testing { n_recipients in 1..30, ) ( - anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor), - // 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 as usize - ), + (notes_and_auth_paths, anchor) in arb_tree(n_notes), recipient_amounts in vec( arb_address().prop_flat_map(move |a| { arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64) @@ -546,7 +540,7 @@ pub mod testing { rng: StdRng::from_seed(rng_seed), sk: sk.clone(), anchor, - notes, + notes: notes_and_auth_paths, recipient_amounts } } diff --git a/src/bundle.rs b/src/bundle.rs index ac41e2fb..e5c758c7 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -114,7 +114,7 @@ impl Action { cv_net: self.cv_net.clone(), nf_old: self.nf, rk: self.rk.clone(), - cmx: self.cmx.clone(), + cmx: self.cmx, enable_spend: flags.spends_enabled, enable_output: flags.outputs_enabled, } diff --git a/src/note.rs b/src/note.rs index 860b0021..18153f61 100644 --- a/src/note.rs +++ b/src/note.rs @@ -59,7 +59,7 @@ impl RandomSeed { } /// A discrete amount of funds received by an address. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Note { /// The recipient of the funds. recipient: Address, diff --git a/src/note/commitment.rs b/src/note/commitment.rs index d543c0f3..efdd1037 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -43,10 +43,15 @@ impl NoteCommitment { } /// The x-coordinate of the commitment to a note. -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub struct ExtractedNoteCommitment(pub(super) pallas::Base); impl ExtractedNoteCommitment { + /// ExtractedNoteCommitment for an uncommitted note. + pub fn uncommitted() -> Self { + Self(pallas::Base::zero()) + } + /// Deserialize the extracted note commitment from a byte array. pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment) diff --git a/src/tree.rs b/src/tree.rs index a7f94799..b9267595 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -101,3 +101,138 @@ fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { ) .unwrap() } + +/// Generators for property testing. +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use lazy_static::lazy_static; + use std::convert::TryInto; + + use crate::{ + constants::{MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note}, + primitives::sinsemilla::{i2lebsp_k, HashDomain, K}, + value::{testing::arb_positive_note_value, MAX_NOTE_VALUE}, + }; + use ff::{PrimeField, PrimeFieldBits}; + use pasta_curves::pallas; + + use proptest::collection::vec; + use proptest::prelude::*; + + use super::{hash_layer, Anchor, MerklePath, Pair}; + + lazy_static! { + static ref EMPTY_ROOTS: Vec = { + // MerkleCRH Sinsemilla hash domain. + let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); + + let mut v = vec![pallas::Base::zero()]; + for l_star in 0..MERKLE_DEPTH_ORCHARD { + let next = domain + .hash( + std::iter::empty() + .chain(i2lebsp_k(l_star).iter().copied().take(K)) + .chain( + v[l_star] + .to_le_bits() + .iter() + .by_val() + .take(pallas::Base::NUM_BITS as usize) + ) + .chain( + v[l_star] + .to_le_bits() + .iter() + .by_val() + .take(pallas::Base::NUM_BITS as usize) + ), + ) + .unwrap(); + v.push(next); + } + v + }; + } + + prop_compose! { + /// Generates an arbitrary Merkle tree of with `n_notes` nonempty leaves. + pub fn arb_tree(n_notes: i32) + ( + // 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 as usize + ), + ) + -> (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 commitments: Vec = notes.iter().map(|note| { + let cmx: ExtractedNoteCommitment = note.commitment().into(); + cmx + }).collect(); + + let padded_leaves = { + let mut padded_leaves = commitments.clone(); + + let pad = (0..((1 << perfect_subtree_depth) - n_notes as usize)).map( + |_| ExtractedNoteCommitment::uncommitted() + ).collect::>(); + + padded_leaves.extend_from_slice(&pad); + padded_leaves + }; + + let tree = { + let mut tree: Vec> = vec![padded_leaves.into_iter().map(|leaf| *leaf).collect()]; + for height in 1..perfect_subtree_depth { + let inner_nodes = (0..(perfect_subtree_depth >> height)).map(|pos| { + hash_layer(height, Pair { + left: tree[height - 1][pos * 2], + right: tree[height - 1][pos * 2 + 1], + }) + }).collect(); + tree.push(inner_nodes); + }; + tree + }; + + // Get Merkle path for each note commitment + let auth_paths = { + let mut auth_paths: Vec = Vec::new(); + for (pos, _) in commitments.iter().enumerate() { + let mut auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD] = (0..MERKLE_DEPTH_ORCHARD).map(|idx| EMPTY_ROOTS[idx]).collect::>().try_into().unwrap(); + + 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 { + tree[height][layer_pos - 1] + } else { + tree[height][layer_pos + 1] + }; + 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(commitments[0]); + for (cmx, auth_path) in commitments.iter().zip(auth_paths.iter()) { + let computed_anchor = auth_path.root(*cmx); + assert_eq!(anchor, computed_anchor); + } + + ( + notes.into_iter().zip(auth_paths.into_iter()).map(|(note, auth_path)| (note, auth_path)).collect(), + anchor + ) + } + } +} From 78182911183bb35448065e9e9ecdd7f0ee3a367a Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 9 Jun 2021 23:50:59 +0800 Subject: [PATCH 03/11] primitives::sinsemilla.rs: Optimize and test i2lebsp_k Co-authored-by: Jack Grigg --- src/primitives/sinsemilla.rs | 56 +++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 090e0b40..eb93ed79 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -5,7 +5,6 @@ use pasta_curves::pallas; use subtle::CtOption; use crate::spec::extract_p_bottom; -use std::convert::TryInto; mod addition; use self::addition::IncompletePoint; @@ -26,11 +25,14 @@ fn lebs2ip_k(bits: &[bool]) -> u32 { pub fn i2lebsp_k(int: usize) -> [bool; K] { assert!(int < (1 << K)); - (0..K) - .map(|mask| ((int & (1 << mask)) >> mask) == 1) - .collect::>() - .try_into() - .unwrap() + let mut ret = [false; K]; + for (bit, val) in ret + .iter_mut() + .zip((0..K).map(|mask| ((int & (1 << mask)) >> mask) == 1)) + { + *bit = val; + } + ret } /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a @@ -199,7 +201,8 @@ impl CommitDomain { #[cfg(test)] mod tests { - use super::Pad; + use super::{i2lebsp_k, lebs2ip_k, Pad, K}; + use rand::{self, rngs::OsRng, Rng}; #[test] fn pad() { @@ -238,4 +241,43 @@ mod tests { ] ); } + + #[test] + fn lebs2ip_k_round_trip() { + let mut rng = OsRng; + { + let int = rng.gen_range(0..(1 << K)); + assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int); + } + + assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0); + assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1); + } + + #[test] + fn i2lebsp_k_round_trip() { + { + let bitstring = (0..K).map(|_| rand::random()).collect::>(); + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + + { + let bitstring = [false; K]; + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + + { + let bitstring = [true; K]; + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + } } From 18535894d65853043571c7b80f19aa2e32e2faf6 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 10 Jun 2021 00:44:30 +0800 Subject: [PATCH 04/11] constants::util.rs: Factor out gen_fixed_array() method. --- src/constants/util.rs | 16 ++++++++++++++++ src/primitives/sinsemilla.rs | 13 ++++--------- src/tree.rs | 16 ++++++++-------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/constants/util.rs b/src/constants/util.rs index cee84c3e..3dc0f346 100644 --- a/src/constants/util.rs +++ b/src/constants/util.rs @@ -40,6 +40,22 @@ pub fn evaluate(x: u8, coeffs: &[C::Base]) -> C::Base { .fold(C::Base::default(), |acc, coeff| acc * x + coeff) } +/// Takes in an FnMut closure and returns a constant-length array with elements of +/// type `Output`. +pub fn gen_const_array( + mut input: Input, + mut closure: impl FnMut(&mut Input, usize) -> Output, +) -> [Output; LEN] { + let mut ret: [Output; LEN] = [Default::default(); LEN]; + for (bit, val) in ret + .iter_mut() + .zip((0..LEN).map(|idx| closure(&mut input, idx))) + { + *bit = val; + } + ret +} + #[cfg(test)] mod tests { use super::decompose_scalar_fixed; diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index eb93ed79..8e5426b8 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -4,6 +4,7 @@ use halo2::arithmetic::CurveExt; use pasta_curves::pallas; use subtle::CtOption; +use crate::constants::util::gen_const_array; use crate::spec::extract_p_bottom; mod addition; @@ -24,15 +25,9 @@ fn lebs2ip_k(bits: &[bool]) -> u32 { /// up to `2^K` - 1. pub fn i2lebsp_k(int: usize) -> [bool; K] { assert!(int < (1 << K)); - - let mut ret = [false; K]; - for (bit, val) in ret - .iter_mut() - .zip((0..K).map(|mask| ((int & (1 << mask)) >> mask) == 1)) - { - *bit = val; - } - ret + gen_const_array(int, |int: &mut usize, mask: usize| { + ((*int & (1 << mask)) >> mask) == 1 + }) } /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a diff --git a/src/tree.rs b/src/tree.rs index b9267595..ebc76a6a 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,13 +1,13 @@ use crate::{ - constants::{MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + constants::{util::gen_const_array, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, note::commitment::ExtractedNoteCommitment, primitives::sinsemilla::{i2lebsp_k, HashDomain, K}, }; use pasta_curves::{arithmetic::FieldExt, pallas}; -use ff::{PrimeField, PrimeFieldBits}; +use ff::{Field, PrimeField, PrimeFieldBits}; use rand::RngCore; -use std::{convert::TryInto, iter}; +use std::iter; /// The root of an Orchard commitment tree. #[derive(Eq, PartialEq, Clone, Debug)] @@ -28,13 +28,13 @@ pub struct MerklePath { impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { + fn dummy_inner(rng: &mut impl RngCore, _idx: usize) -> pallas::Base { + pallas::Base::random(rng) + } + MerklePath { position: rng.next_u32(), - auth_path: (0..MERKLE_DEPTH_ORCHARD) - .map(|_| pallas::Base::rand()) - .collect::>() - .try_into() - .unwrap(), + auth_path: gen_const_array(rng, dummy_inner), } } From e8e22886f4ca9f217404a6d72fcfd582e280357e Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 10 Jun 2021 10:29:08 +0800 Subject: [PATCH 05/11] tree.rs: MerklePath.root(): Fix missing bitmask in swap calculation Co-authored-by: Jack Grigg --- src/tree.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index ebc76a6a..ce6bfa10 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -39,17 +39,12 @@ impl MerklePath { } pub fn root(&self, cmx: ExtractedNoteCommitment) -> Anchor { - // Initialize `node` to the first hash. - let init_node = { - let pos = self.position % 2 == 1; - hash_layer(0, cond_swap(pos, *cmx, self.auth_path[0])) - }; - let node = self.auth_path[1..] + let node = self + .auth_path .iter() .enumerate() - .fold(init_node, |node, (i, sibling)| { - let l_star = i + 1; - let swap = (self.position >> l_star) == 1; + .fold(*cmx, |node, (l_star, sibling)| { + let swap = self.position & (1 << l_star) != 0; hash_layer(l_star, cond_swap(swap, node, *sibling)) }); Anchor(node.to_bytes()) From 2d0afe9357a3c5ce82129e4c4b656c3d834d4ae8 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 10 Jun 2021 10:31:47 +0800 Subject: [PATCH 06/11] constants.rs: Introduce L_ORCHARD_MERKLE constant Also test that L_ORCHARD_BASE, L_ORCHARD_SCALAR, L_ORCHARD_MERKLE are consistent with the Pallas curve. Co-authored-by: Jack Grigg --- src/constants.rs | 27 +++++++++++++++++++++++++++ src/tree.rs | 14 ++++++++------ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 37f9817f..4df36f86 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -22,6 +22,9 @@ pub use load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV}; /// $\mathsf{MerkleDepth^{Orchard}}$ pub(crate) const MERKLE_DEPTH_ORCHARD: usize = 32; +/// $\ell^\mathsf{Orchard}_\mathsf{Merkle}$ +pub(crate) const L_ORCHARD_MERKLE: usize = 255; + /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; @@ -246,3 +249,27 @@ fn test_zs_and_us(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_w } } } + +#[cfg(test)] +mod tests { + use ff::PrimeField; + use pasta_curves::pallas; + + #[test] + // Nodes in the Merkle tree are Pallas base field elements. + fn l_orchard_merkle() { + assert_eq!(super::L_ORCHARD_MERKLE, pallas::Base::NUM_BITS as usize); + } + + #[test] + // Orchard uses the Pallas base field as its base field. + fn l_orchard_base() { + assert_eq!(super::L_ORCHARD_BASE, pallas::Base::NUM_BITS as usize); + } + + #[test] + // Orchard uses the Pallas base field as its base field. + fn l_orchard_scalar() { + assert_eq!(super::L_ORCHARD_SCALAR, pallas::Scalar::NUM_BITS as usize); + } +} diff --git a/src/tree.rs b/src/tree.rs index ce6bfa10..8ffef8c7 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,11 +1,13 @@ use crate::{ - constants::{util::gen_const_array, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + constants::{ + util::gen_const_array, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD, + }, note::commitment::ExtractedNoteCommitment, - primitives::sinsemilla::{i2lebsp_k, HashDomain, K}, + primitives::sinsemilla::{i2lebsp_k, HashDomain}, }; use pasta_curves::{arithmetic::FieldExt, pallas}; -use ff::{Field, PrimeField, PrimeFieldBits}; +use ff::{Field, PrimeFieldBits}; use rand::RngCore; use std::iter; @@ -78,20 +80,20 @@ fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { domain .hash( iter::empty() - .chain(i2lebsp_k(l_star).iter().copied().take(K)) + .chain(i2lebsp_k(l_star).iter().copied()) .chain( pair.left .to_le_bits() .iter() .by_val() - .take(pallas::Base::NUM_BITS as usize), + .take(L_ORCHARD_MERKLE), ) .chain( pair.right .to_le_bits() .iter() .by_val() - .take(pallas::Base::NUM_BITS as usize), + .take(L_ORCHARD_MERKLE), ), ) .unwrap() From b3daeb0861a2f44865125f399dbf06d664032690 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 10 Jun 2021 13:54:42 +0800 Subject: [PATCH 07/11] tree::testing: Fix and test arb_tree(). --- src/builder.rs | 2 +- src/note/commitment.rs | 5 -- src/tree.rs | 147 ++++++++++++++++++++++++++--------------- 3 files changed, 95 insertions(+), 59 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 22aaea8e..6bc34f43 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -522,7 +522,7 @@ pub mod testing { /// Produce a random valid Orchard bundle. fn arb_bundle_inputs(sk: SpendingKey) ( - n_notes in 1..30, + n_notes in 1usize..30, n_recipients in 1..30, ) ( diff --git a/src/note/commitment.rs b/src/note/commitment.rs index efdd1037..b0775655 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -47,11 +47,6 @@ impl NoteCommitment { pub struct ExtractedNoteCommitment(pub(super) pallas::Base); impl ExtractedNoteCommitment { - /// ExtractedNoteCommitment for an uncommitted note. - pub fn uncommitted() -> Self { - Self(pallas::Base::zero()) - } - /// Deserialize the extracted note commitment from a byte array. pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment) diff --git a/src/tree.rs b/src/tree.rs index 8ffef8c7..c59f2e7f 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -40,6 +40,14 @@ impl MerklePath { } } + /// + /// 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_star` 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_star = 0 + /// - when hashing to the final root, we produce the anchor with layer = 0, l_star = 31. pub fn root(&self, cmx: ExtractedNoteCommitment) -> Anchor { let node = self .auth_path @@ -73,6 +81,13 @@ fn cond_swap(swap: bool, node: pallas::Base, sibling: pallas::Base) -> Pair { } // +// 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_star` 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_star = 0 +// - when hashing to the final root, we produce the anchor with layer = 0, l_star = 31. fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { // MerkleCRH Sinsemilla hash domain. let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); @@ -104,14 +119,13 @@ fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { pub mod testing { use lazy_static::lazy_static; use std::convert::TryInto; + use std::iter; use crate::{ - constants::{MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD}, + constants::MERKLE_DEPTH_ORCHARD, note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note}, - primitives::sinsemilla::{i2lebsp_k, HashDomain, K}, value::{testing::arb_positive_note_value, MAX_NOTE_VALUE}, }; - use ff::{PrimeField, PrimeFieldBits}; use pasta_curves::pallas; use proptest::collection::vec; @@ -121,95 +135,113 @@ pub mod testing { lazy_static! { static ref EMPTY_ROOTS: Vec = { - // MerkleCRH Sinsemilla hash domain. - let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); - - let mut v = vec![pallas::Base::zero()]; - for l_star in 0..MERKLE_DEPTH_ORCHARD { - let next = domain - .hash( - std::iter::empty() - .chain(i2lebsp_k(l_star).iter().copied().take(K)) - .chain( - v[l_star] - .to_le_bits() - .iter() - .by_val() - .take(pallas::Base::NUM_BITS as usize) - ) - .chain( - v[l_star] - .to_le_bits() - .iter() - .by_val() - .take(pallas::Base::NUM_BITS as usize) - ), + iter::empty() + .chain(Some(pallas::Base::zero())) + .chain( + (0..MERKLE_DEPTH_ORCHARD).scan(pallas::Base::zero(), |state, l_star| { + *state = hash_layer( + l_star, + Pair { + left: *state, + right: *state, + }, + ); + Some(*state) + }), ) - .unwrap(); - v.push(next); - } - v + .collect() }; } prop_compose! { /// Generates an arbitrary Merkle tree of with `n_notes` nonempty leaves. - pub fn arb_tree(n_notes: i32) + 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 as usize + 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 commitments: Vec = notes.iter().map(|note| { + let n_leaves = 1 << perfect_subtree_depth; + + let commitments: Vec> = notes.iter().map(|note| { let cmx: ExtractedNoteCommitment = note.commitment().into(); - cmx + Some(cmx) }).collect(); let padded_leaves = { let mut padded_leaves = commitments.clone(); - let pad = (0..((1 << perfect_subtree_depth) - n_notes as usize)).map( - |_| ExtractedNoteCommitment::uncommitted() + let pad = (0..(n_leaves - n_notes)).map( + |_| None ).collect::>(); padded_leaves.extend_from_slice(&pad); padded_leaves }; - let tree = { - let mut tree: Vec> = vec![padded_leaves.into_iter().map(|leaf| *leaf).collect()]; + let perfect_subtree = { + let mut perfect_subtree: Vec>> = vec![ + padded_leaves.iter().map(|cmx| cmx.map(|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_star` 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_star = 0 + // - when hashing to the final root, we produce the anchor with layer = 0, l_star = 31. for height in 1..perfect_subtree_depth { - let inner_nodes = (0..(perfect_subtree_depth >> height)).map(|pos| { - hash_layer(height, Pair { - left: tree[height - 1][pos * 2], - right: tree[height - 1][pos * 2 + 1], - }) + let l_star = height - 1; + let inner_nodes = (0..(n_leaves >> height)).map(|pos| { + let left = perfect_subtree[height - 1][pos * 2]; + let right = perfect_subtree[height - 1][pos * 2 + 1]; + match (left, right) { + (None, None) => None, + (Some(left), None) => { + let right = EMPTY_ROOTS[height - 1]; + Some(hash_layer(l_star, Pair {left, right})) + }, + (Some(left), Some(right)) => { + Some(hash_layer(l_star, Pair {left, right})) + }, + (None, Some(_)) => { + unreachable!("The perfect subtree is left-packed.") + } + } }).collect(); - tree.push(inner_nodes); + perfect_subtree.push(inner_nodes); }; - tree + 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: [pallas::Base; MERKLE_DEPTH_ORCHARD] = (0..MERKLE_DEPTH_ORCHARD).map(|idx| EMPTY_ROOTS[idx]).collect::>().try_into().unwrap(); 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 { - tree[height][layer_pos - 1] + // This node is the right sibling, so we need its left sibling at the current height. + perfect_subtree[height][layer_pos - 1] } else { - tree[height][layer_pos + 1] + // This node is the left sibling, so we need its right sibling at the current height. + perfect_subtree[height][layer_pos + 1] }; - auth_path[height] = sibling; + if let Some(sibling) = sibling { + auth_path[height] = sibling; + } layer_pos = (layer_pos - is_right_sibling as usize) / 2; }; @@ -220,11 +252,7 @@ pub mod testing { }; // Compute anchor for this tree - let anchor = auth_paths[0].root(commitments[0]); - for (cmx, auth_path) in commitments.iter().zip(auth_paths.iter()) { - let computed_anchor = auth_path.root(*cmx); - assert_eq!(anchor, computed_anchor); - } + 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(), @@ -232,4 +260,17 @@ pub mod testing { ) } } + + proptest! { + #[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); + } + } + } } From 380128ed496417ca4a280f0842e4543959350867 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Thu, 10 Jun 2021 16:55:49 +0800 Subject: [PATCH 08/11] tree::MerklePath: Add postion() and auth_path() getters. --- src/tree.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tree.rs b/src/tree.rs index c59f2e7f..9777c18a 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -59,6 +59,16 @@ impl MerklePath { }); Anchor(node.to_bytes()) } + + /// Returns the position of the leaf using this Merkle path. + pub fn position(&self) -> u32 { + self.position + } + + /// Returns the authentication path. + pub fn auth_path(&self) -> [pallas::Base; MERKLE_DEPTH_ORCHARD] { + self.auth_path + } } struct Pair { From 0e9726ae6977d0facf2d2b9794631c24b298bd74 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 11 Jun 2021 19:03:14 +0800 Subject: [PATCH 09/11] tree.rs: Use 2 as uncommitted leaf and check against test vectors. --- src/test_vectors.rs | 1 + src/test_vectors/commitment_tree.rs | 176 ++++++++++++++++++++++++++++ src/tree.rs | 24 +++- 3 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 src/test_vectors/commitment_tree.rs diff --git a/src/test_vectors.rs b/src/test_vectors.rs index 88243a9d..c2991f4c 100644 --- a/src/test_vectors.rs +++ b/src/test_vectors.rs @@ -1 +1,2 @@ +pub(crate) mod commitment_tree; pub(crate) mod keys; diff --git a/src/test_vectors/commitment_tree.rs b/src/test_vectors/commitment_tree.rs new file mode 100644 index 00000000..d9532c58 --- /dev/null +++ b/src/test_vectors/commitment_tree.rs @@ -0,0 +1,176 @@ +pub(crate) struct TestVector { + pub empty_roots: [[u8; 32]; 33], +} + +pub(crate) fn test_vectors() -> TestVector { + // From https://github.com/zcash-hackworks/zcash-test-vectors/blob/master/orchard_empty_roots.py + TestVector { + empty_roots: [ + [ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ], + [ + 0xd1, 0xab, 0x25, 0x07, 0xc8, 0x09, 0xc2, 0x71, 0x3c, 0x00, 0x0f, 0x52, 0x5e, 0x9f, + 0xbd, 0xcb, 0x06, 0xc9, 0x58, 0x38, 0x4e, 0x51, 0xb9, 0xcc, 0x7f, 0x79, 0x2d, 0xde, + 0x6c, 0x97, 0xf4, 0x11, + ], + [ + 0xc7, 0x41, 0x3f, 0x46, 0x14, 0xcd, 0x64, 0x04, 0x3a, 0xbb, 0xab, 0x7c, 0xc1, 0x09, + 0x5c, 0x9b, 0xb1, 0x04, 0x23, 0x1c, 0xea, 0x89, 0xe2, 0xc3, 0xe0, 0xdf, 0x83, 0x76, + 0x95, 0x56, 0xd0, 0x30, + ], + [ + 0x21, 0x11, 0xfc, 0x39, 0x77, 0x53, 0xe5, 0xfd, 0x50, 0xec, 0x74, 0x81, 0x6d, 0xf2, + 0x7d, 0x6a, 0xda, 0x7e, 0xd2, 0xa9, 0xac, 0x38, 0x16, 0xaa, 0xb2, 0x57, 0x3c, 0x8f, + 0xac, 0x79, 0x42, 0x04, + ], + [ + 0x80, 0x6a, 0xfb, 0xfe, 0xb4, 0x5c, 0x64, 0xd4, 0xf2, 0x38, 0x4c, 0x51, 0xef, 0xf3, + 0x07, 0x64, 0xb8, 0x45, 0x99, 0xae, 0x56, 0xa7, 0xab, 0x3d, 0x4a, 0x46, 0xd9, 0xce, + 0x3a, 0xea, 0xb4, 0x31, + ], + [ + 0x87, 0x3e, 0x41, 0x57, 0xf2, 0xc0, 0xf0, 0xc6, 0x45, 0xe8, 0x99, 0x36, 0x00, 0x69, + 0xfc, 0xc9, 0xd2, 0xed, 0x9b, 0xc1, 0x1b, 0xf5, 0x98, 0x27, 0xaf, 0x02, 0x30, 0xed, + 0x52, 0xed, 0xab, 0x18, + ], + [ + 0x27, 0xab, 0x13, 0x20, 0x95, 0x3a, 0xe1, 0xad, 0x70, 0xc8, 0xc1, 0x5a, 0x12, 0x53, + 0xa0, 0xa8, 0x6f, 0xbc, 0x8a, 0x0a, 0xa3, 0x6a, 0x84, 0x20, 0x72, 0x93, 0xf8, 0xa4, + 0x95, 0xff, 0xc4, 0x02, + ], + [ + 0x4e, 0x14, 0x56, 0x3d, 0xf1, 0x91, 0xa2, 0xa6, 0x5b, 0x4b, 0x37, 0x11, 0x3b, 0x52, + 0x30, 0x68, 0x05, 0x55, 0x05, 0x1b, 0x22, 0xd7, 0x4a, 0x8e, 0x1f, 0x1d, 0x70, 0x6f, + 0x90, 0xf3, 0x13, 0x3b, + ], + [ + 0xb3, 0xbb, 0xe4, 0xf9, 0x93, 0xd1, 0x8a, 0x0f, 0x4e, 0xb7, 0xf4, 0x17, 0x4b, 0x1d, + 0x85, 0x55, 0xce, 0x33, 0x96, 0x85, 0x5d, 0x04, 0x67, 0x6f, 0x1c, 0xe4, 0xf0, 0x6d, + 0xda, 0x07, 0x37, 0x1f, + ], + [ + 0x4e, 0xf5, 0xbd, 0xe9, 0xc6, 0xf0, 0xd7, 0x6a, 0xeb, 0x9e, 0x27, 0xe9, 0x3f, 0xba, + 0x28, 0xc6, 0x79, 0xdf, 0xcb, 0x99, 0x1c, 0xbc, 0xb8, 0x39, 0x5a, 0x2b, 0x57, 0x92, + 0x4c, 0xbd, 0x17, 0x0e, + ], + [ + 0xa3, 0xc0, 0x25, 0x68, 0xac, 0xeb, 0xf5, 0xca, 0x1e, 0xc3, 0x0d, 0x6a, 0x7d, 0x7c, + 0xd2, 0x17, 0xa4, 0x7d, 0x6a, 0x1b, 0x83, 0x11, 0xbf, 0x94, 0x62, 0xa5, 0xf9, 0x39, + 0xc6, 0xb7, 0x43, 0x07, + ], + [ + 0x3e, 0xf9, 0xb3, 0x0b, 0xae, 0x61, 0x22, 0xda, 0x16, 0x05, 0xba, 0xd6, 0xec, 0x5d, + 0x49, 0xb4, 0x1d, 0x4d, 0x40, 0xca, 0xa9, 0x6c, 0x1c, 0xf6, 0x30, 0x2b, 0x66, 0xc5, + 0xd2, 0xd1, 0x0d, 0x39, + ], + [ + 0x22, 0xae, 0x28, 0x00, 0xcb, 0x93, 0xab, 0xe6, 0x3b, 0x70, 0xc1, 0x72, 0xde, 0x70, + 0x36, 0x2d, 0x98, 0x30, 0xe5, 0x38, 0x00, 0x39, 0x88, 0x84, 0xa7, 0xa6, 0x4f, 0xf6, + 0x8e, 0xd9, 0x9e, 0x0b, + ], + [ + 0x18, 0x71, 0x10, 0xd9, 0x26, 0x72, 0xc2, 0x4c, 0xed, 0xb0, 0x97, 0x9c, 0xdf, 0xc9, + 0x17, 0xa6, 0x05, 0x3b, 0x31, 0x0d, 0x14, 0x5c, 0x03, 0x1c, 0x72, 0x92, 0xbb, 0x1d, + 0x65, 0xb7, 0x66, 0x1b, + ], + [ + 0x3f, 0x98, 0xad, 0xbe, 0x36, 0x4f, 0x14, 0x8b, 0x0c, 0xc2, 0x04, 0x2c, 0xaf, 0xc6, + 0xbe, 0x11, 0x66, 0xfa, 0xe3, 0x90, 0x90, 0xab, 0x4b, 0x35, 0x4b, 0xfb, 0x62, 0x17, + 0xb9, 0x64, 0x45, 0x3b, + ], + [ + 0x63, 0xf8, 0xdb, 0xd1, 0x0d, 0xf9, 0x36, 0xf1, 0x73, 0x49, 0x73, 0xe0, 0xb3, 0xbd, + 0x25, 0xf4, 0xed, 0x44, 0x05, 0x66, 0xc9, 0x23, 0x08, 0x59, 0x03, 0xf6, 0x96, 0xbc, + 0x63, 0x47, 0xec, 0x0f, + ], + [ + 0x21, 0x82, 0x16, 0x3e, 0xac, 0x40, 0x61, 0x88, 0x5a, 0x31, 0x35, 0x68, 0x14, 0x8d, + 0xfa, 0xe5, 0x64, 0xe4, 0x78, 0x06, 0x6d, 0xcb, 0xe3, 0x89, 0xa0, 0xdd, 0xb1, 0xec, + 0xb7, 0xf5, 0xdc, 0x34, + ], + [ + 0xbd, 0x9d, 0xc0, 0x68, 0x19, 0x18, 0xa3, 0xf3, 0xf9, 0xcd, 0x1f, 0x9e, 0x06, 0xaa, + 0x1a, 0xd6, 0x89, 0x27, 0xda, 0x63, 0xac, 0xc1, 0x3b, 0x92, 0xa2, 0x57, 0x8b, 0x27, + 0x38, 0xa6, 0xd3, 0x31, + ], + [ + 0xca, 0x2c, 0xed, 0x95, 0x3b, 0x7f, 0xb9, 0x5e, 0x3b, 0xa9, 0x86, 0x33, 0x3d, 0xa9, + 0xe6, 0x9c, 0xd3, 0x55, 0x22, 0x3c, 0x92, 0x97, 0x31, 0x09, 0x4b, 0x6c, 0x21, 0x74, + 0xc7, 0x63, 0x8d, 0x2e, + ], + [ + 0x55, 0x35, 0x4b, 0x96, 0xb5, 0x6f, 0x9e, 0x45, 0xaa, 0xe1, 0xe0, 0x09, 0x4d, 0x71, + 0xee, 0x24, 0x8d, 0xab, 0xf6, 0x68, 0x11, 0x77, 0x78, 0xbd, 0xc3, 0xc1, 0x9c, 0xa5, + 0x33, 0x1a, 0x4e, 0x1a, + ], + [ + 0x70, 0x97, 0xb0, 0x4c, 0x2a, 0xa0, 0x45, 0xa0, 0xde, 0xff, 0xca, 0xca, 0x41, 0xc5, + 0xac, 0x92, 0xe6, 0x94, 0x46, 0x65, 0x78, 0xf5, 0x90, 0x9e, 0x72, 0xbb, 0x78, 0xd3, + 0x33, 0x10, 0xf7, 0x05, + ], + [ + 0xe8, 0x1d, 0x68, 0x21, 0xff, 0x81, 0x3b, 0xd4, 0x10, 0x86, 0x7a, 0x3f, 0x22, 0xe8, + 0xe5, 0xcb, 0x7a, 0xc5, 0x59, 0x9a, 0x61, 0x0a, 0xf5, 0xc3, 0x54, 0xeb, 0x39, 0x28, + 0x77, 0x36, 0x2e, 0x01, + ], + [ + 0x15, 0x7d, 0xe8, 0x56, 0x7f, 0x7c, 0x49, 0x96, 0xb8, 0xc4, 0xfd, 0xc9, 0x49, 0x38, + 0xfd, 0x80, 0x8c, 0x3b, 0x2a, 0x5c, 0xcb, 0x79, 0xd1, 0xa6, 0x38, 0x58, 0xad, 0xaa, + 0x9a, 0x6d, 0xd8, 0x24, + ], + [ + 0xfe, 0x1f, 0xce, 0x51, 0xcd, 0x61, 0x20, 0xc1, 0x2c, 0x12, 0x46, 0x95, 0xc4, 0xf9, + 0x8b, 0x27, 0x59, 0x18, 0xfc, 0xea, 0xe6, 0xeb, 0x20, 0x98, 0x73, 0xed, 0x73, 0xfe, + 0x73, 0x77, 0x5d, 0x0b, + ], + [ + 0x1f, 0x91, 0x98, 0x29, 0x12, 0x01, 0x26, 0x69, 0xf7, 0x4d, 0x0c, 0xfa, 0x10, 0x30, + 0xff, 0x37, 0xb1, 0x52, 0x32, 0x4e, 0x5b, 0x83, 0x46, 0xb3, 0x33, 0x5a, 0x0a, 0xae, + 0xb6, 0x3a, 0x0a, 0x2d, + ], + [ + 0x5d, 0xec, 0x15, 0xf5, 0x2a, 0xf1, 0x7d, 0xa3, 0x93, 0x13, 0x96, 0x18, 0x3c, 0xbb, + 0xbf, 0xbe, 0xa7, 0xed, 0x95, 0x07, 0x14, 0x54, 0x0a, 0xec, 0x06, 0xc6, 0x45, 0xc7, + 0x54, 0x97, 0x55, 0x22, + ], + [ + 0xe8, 0xae, 0x2a, 0xd9, 0x1d, 0x46, 0x3b, 0xab, 0x75, 0xee, 0x94, 0x1d, 0x33, 0xcc, + 0x58, 0x17, 0xb6, 0x13, 0xc6, 0x3c, 0xda, 0x94, 0x3a, 0x4c, 0x07, 0xf6, 0x00, 0x59, + 0x1b, 0x08, 0x8a, 0x25, + ], + [ + 0xd5, 0x3f, 0xde, 0xe3, 0x71, 0xce, 0xf5, 0x96, 0x76, 0x68, 0x23, 0xf4, 0xa5, 0x18, + 0xa5, 0x83, 0xb1, 0x15, 0x82, 0x43, 0xaf, 0xe8, 0x97, 0x00, 0xf0, 0xda, 0x76, 0xda, + 0x46, 0xd0, 0x06, 0x0f, + ], + [ + 0x15, 0xd2, 0x44, 0x4c, 0xef, 0xe7, 0x91, 0x4c, 0x9a, 0x61, 0xe8, 0x29, 0xc7, 0x30, + 0xec, 0xeb, 0x21, 0x62, 0x88, 0xfe, 0xe8, 0x25, 0xf6, 0xb3, 0xb6, 0x29, 0x8f, 0x6f, + 0x6b, 0x6b, 0xd6, 0x2e, + ], + [ + 0x4c, 0x57, 0xa6, 0x17, 0xa0, 0xaa, 0x10, 0xea, 0x7a, 0x83, 0xaa, 0x6b, 0x6b, 0x0e, + 0xd6, 0x85, 0xb6, 0xa3, 0xd9, 0xe5, 0xb8, 0xfd, 0x14, 0xf5, 0x6c, 0xdc, 0x18, 0x02, + 0x1b, 0x12, 0x25, 0x3f, + ], + [ + 0x3f, 0xd4, 0x91, 0x5c, 0x19, 0xbd, 0x83, 0x1a, 0x79, 0x20, 0xbe, 0x55, 0xd9, 0x69, + 0xb2, 0xac, 0x23, 0x35, 0x9e, 0x25, 0x59, 0xda, 0x77, 0xde, 0x23, 0x73, 0xf0, 0x6c, + 0xa0, 0x14, 0xba, 0x27, + ], + [ + 0x87, 0xd0, 0x63, 0xcd, 0x07, 0xee, 0x49, 0x44, 0x22, 0x2b, 0x77, 0x62, 0x84, 0x0e, + 0xb9, 0x4c, 0x68, 0x8b, 0xec, 0x74, 0x3f, 0xa8, 0xbd, 0xf7, 0x71, 0x5c, 0x8f, 0xe2, + 0x9f, 0x10, 0x4c, 0x2a, + ], + [ + 0xae, 0x29, 0x35, 0xf1, 0xdf, 0xd8, 0xa2, 0x4a, 0xed, 0x7c, 0x70, 0xdf, 0x7d, 0xe3, + 0xa6, 0x68, 0xeb, 0x7a, 0x49, 0xb1, 0x31, 0x98, 0x80, 0xdd, 0xe2, 0xbb, 0xd9, 0x03, + 0x1a, 0xe5, 0xd8, 0x2f, + ], + ], + } +} diff --git a/src/tree.rs b/src/tree.rs index 9777c18a..a8dc08aa 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -136,19 +136,22 @@ pub mod testing { note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note}, value::{testing::arb_positive_note_value, MAX_NOTE_VALUE}, }; - use pasta_curves::pallas; + use pasta_curves::{arithmetic::FieldExt, pallas}; use proptest::collection::vec; use proptest::prelude::*; use super::{hash_layer, Anchor, MerklePath, Pair}; + // The uncommitted leaf is defined as pallas::Base(2). + // lazy_static! { static ref EMPTY_ROOTS: Vec = { iter::empty() - .chain(Some(pallas::Base::zero())) - .chain( - (0..MERKLE_DEPTH_ORCHARD).scan(pallas::Base::zero(), |state, l_star| { + .chain(Some(pallas::Base::from_u64(2))) + .chain((0..MERKLE_DEPTH_ORCHARD).scan( + pallas::Base::from_u64(2), + |state, l_star| { *state = hash_layer( l_star, Pair { @@ -157,12 +160,21 @@ pub mod testing { }, ); Some(*state) - }), - ) + }, + )) .collect() }; } + #[test] + fn test_vectors() { + let tv_empty_roots = crate::test_vectors::commitment_tree::test_vectors().empty_roots; + + for (height, root) in EMPTY_ROOTS.iter().enumerate() { + assert_eq!(tv_empty_roots[height], root.to_bytes()); + } + } + prop_compose! { /// Generates an arbitrary Merkle tree of with `n_notes` nonempty leaves. pub fn arb_tree(n_notes: usize) From d8f2af898398c5abfe84350c5e2b3fc6f67393da Mon Sep 17 00:00:00 2001 From: str4d Date: Fri, 11 Jun 2021 20:20:41 +0100 Subject: [PATCH 10/11] Simplify `gen_const_array` implementation Also includes a performance improvement to `i2lebsp_k`. --- src/constants/util.rs | 10 +++------- src/primitives/sinsemilla.rs | 4 +--- src/tree.rs | 8 ++------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/constants/util.rs b/src/constants/util.rs index 3dc0f346..ef57cc63 100644 --- a/src/constants/util.rs +++ b/src/constants/util.rs @@ -42,15 +42,11 @@ pub fn evaluate(x: u8, coeffs: &[C::Base]) -> C::Base { /// Takes in an FnMut closure and returns a constant-length array with elements of /// type `Output`. -pub fn gen_const_array( - mut input: Input, - mut closure: impl FnMut(&mut Input, usize) -> Output, +pub fn gen_const_array( + mut closure: impl FnMut(usize) -> Output, ) -> [Output; LEN] { let mut ret: [Output; LEN] = [Default::default(); LEN]; - for (bit, val) in ret - .iter_mut() - .zip((0..LEN).map(|idx| closure(&mut input, idx))) - { + for (bit, val) in ret.iter_mut().zip((0..LEN).map(|idx| closure(idx))) { *bit = val; } ret diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 8e5426b8..a95679cf 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -25,9 +25,7 @@ fn lebs2ip_k(bits: &[bool]) -> u32 { /// up to `2^K` - 1. pub fn i2lebsp_k(int: usize) -> [bool; K] { assert!(int < (1 << K)); - gen_const_array(int, |int: &mut usize, mask: usize| { - ((*int & (1 << mask)) >> mask) == 1 - }) + gen_const_array(|mask: usize| (int & (1 << mask)) != 0) } /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a diff --git a/src/tree.rs b/src/tree.rs index a8dc08aa..935c71ab 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -29,14 +29,10 @@ pub struct MerklePath { impl MerklePath { /// Generates a dummy Merkle path for use in dummy spent notes. - pub(crate) fn dummy(rng: &mut impl RngCore) -> Self { - fn dummy_inner(rng: &mut impl RngCore, _idx: usize) -> pallas::Base { - pallas::Base::random(rng) - } - + pub(crate) fn dummy(mut rng: &mut impl RngCore) -> Self { MerklePath { position: rng.next_u32(), - auth_path: gen_const_array(rng, dummy_inner), + auth_path: gen_const_array(|_| pallas::Base::random(&mut rng)), } } From bd30783a528dc38760b1646298a469a519812cac Mon Sep 17 00:00:00 2001 From: str4d Date: Fri, 11 Jun 2021 20:23:40 +0100 Subject: [PATCH 11/11] Make `hash_layer` comment a doc comment This way, it renders with `cargo doc --document-private-items` --- src/tree.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tree.rs b/src/tree.rs index 935c71ab..713a2c6e 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -86,14 +86,14 @@ fn cond_swap(swap: bool, node: pallas::Base, sibling: pallas::Base) -> Pair { } } -// -// 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_star` 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_star = 0 -// - when hashing to the final root, we produce the anchor with layer = 0, l_star = 31. +/// +/// 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_star` 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_star = 0 +/// - when hashing to the final root, we produce the anchor with layer = 0, l_star = 31. fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base { // MerkleCRH Sinsemilla hash domain. let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION);