From 4bb252eb0c82ed38d6d850544691085e1d44d2ec Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 15 Jun 2021 22:09:25 -0600 Subject: [PATCH] Add Orchard incremental merkle tree digests. --- Cargo.toml | 1 + src/primitives/sinsemilla.rs | 2 +- src/tree.rs | 72 +++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7cba8670..2d7326e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ rand = "0.8" nonempty = "0.6" subtle = "2.3" zcash_note_encryption = "0.0" +incrementalmerkletree = { git = "https://github.com/nuttycom/incrementalmerkletree.git", branch = "merkle_bridge" } # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index 0922f17c..24ec76d6 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -90,7 +90,7 @@ impl> Iterator for Pad { /// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can /// be used. -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(non_snake_case)] pub struct HashDomain { Q: pallas::Point, diff --git a/src/tree.rs b/src/tree.rs index 724be800..f4a18ee0 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -5,11 +5,13 @@ use crate::{ note::commitment::ExtractedNoteCommitment, primitives::sinsemilla::{i2lebsp_k, HashDomain}, }; -use pasta_curves::pallas; +use incrementalmerkletree::{Hashable, Level}; +use pasta_curves::{arithmetic::FieldExt, pallas}; use ff::{Field, PrimeField, PrimeFieldBits}; use rand::RngCore; use std::iter; +use subtle::CtOption; /// The root of an Orchard commitment tree. #[derive(Eq, PartialEq, Clone, Copy, Debug)] @@ -63,7 +65,7 @@ impl MerklePath { .enumerate() .fold(*cmx, |node, (l_star, sibling)| { let swap = self.position & (1 << l_star) != 0; - hash_layer(l_star, cond_swap(swap, node, *sibling)) + hash_layer(l_star, cond_swap(swap, node, *sibling)).unwrap() }); Anchor(node) } @@ -106,30 +108,47 @@ fn cond_swap(swap: bool, node: pallas::Base, sibling: pallas::Base) -> Pair { /// - 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 { +fn hash_layer(l_star: usize, pair: Pair) -> CtOption { // MerkleCRH Sinsemilla hash domain. let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); - domain - .hash( - iter::empty() - .chain(i2lebsp_k(l_star).iter().copied()) - .chain( - pair.left - .to_le_bits() - .iter() - .by_val() - .take(L_ORCHARD_MERKLE), - ) - .chain( - pair.right - .to_le_bits() - .iter() - .by_val() - .take(L_ORCHARD_MERKLE), - ), - ) - .unwrap() + domain.hash( + iter::empty() + .chain(i2lebsp_k(l_star).iter().copied()) + .chain( + pair.left + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_MERKLE), + ) + .chain( + pair.right + .to_le_bits() + .iter() + .by_val() + .take(L_ORCHARD_MERKLE), + ), + ) +} + +#[derive(Clone)] +struct OrchardIncrementalTreeDigest(CtOption); + +impl Hashable for OrchardIncrementalTreeDigest { + fn empty_leaf() -> Self { + OrchardIncrementalTreeDigest(CtOption::new(pallas::Base::from_u64(2), 1.into())) + } + + fn combine(level: Level, left_opt: &Self, right_opt: &Self) -> Self { + let level: usize = level.into(); + let l_star: usize = MERKLE_DEPTH_ORCHARD - 1 - level; + OrchardIncrementalTreeDigest(left_opt.0.and_then(|left| { + right_opt + .0 + .and_then(|right| hash_layer(l_star, Pair { left, right })) + })) + } } /// Generators for property testing. @@ -166,7 +185,8 @@ pub mod testing { left: *state, right: *state, }, - ); + ) + .unwrap(); Some(*state) }, )) @@ -236,10 +256,10 @@ pub mod testing { (None, None) => None, (Some(left), None) => { let right = EMPTY_ROOTS[height - 1]; - Some(hash_layer(l_star, Pair {left, right})) + Some(hash_layer(l_star, Pair {left, right}).unwrap()) }, (Some(left), Some(right)) => { - Some(hash_layer(l_star, Pair {left, right})) + Some(hash_layer(l_star, Pair {left, right}).unwrap()) }, (None, Some(_)) => { unreachable!("The perfect subtree is left-packed.")