From b33248bdb0b21e67fd7bc45beaaea2a3277aa590 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 8 Jun 2021 13:18:16 +0800 Subject: [PATCH] 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() +}