2021-06-07 22:18:16 -07:00
|
|
|
use crate::{
|
2021-06-09 19:31:47 -07:00
|
|
|
constants::{
|
|
|
|
util::gen_const_array, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD,
|
|
|
|
},
|
2021-06-07 22:18:16 -07:00
|
|
|
note::commitment::ExtractedNoteCommitment,
|
2021-06-09 19:31:47 -07:00
|
|
|
primitives::sinsemilla::{i2lebsp_k, HashDomain},
|
2021-06-07 22:18:16 -07:00
|
|
|
};
|
|
|
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
|
|
|
|
2021-06-09 19:31:47 -07:00
|
|
|
use ff::{Field, PrimeFieldBits};
|
2021-04-14 21:14:34 -07:00
|
|
|
use rand::RngCore;
|
2021-06-09 09:44:30 -07:00
|
|
|
use std::iter;
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-01-20 12:30:35 -08:00
|
|
|
/// The root of an Orchard commitment tree.
|
2021-06-07 22:18:16 -07:00
|
|
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
2021-04-21 08:57:48 -07:00
|
|
|
pub struct Anchor(pub [u8; 32]);
|
2021-04-14 21:14:34 -07:00
|
|
|
|
2021-06-07 22:18:16 -07:00
|
|
|
impl From<pallas::Base> for Anchor {
|
|
|
|
fn from(anchor_field: pallas::Base) -> Anchor {
|
|
|
|
Anchor(anchor_field.to_bytes())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-14 21:14:34 -07:00
|
|
|
#[derive(Debug)]
|
2021-06-07 22:18:16 -07:00
|
|
|
pub struct MerklePath {
|
|
|
|
position: u32,
|
|
|
|
auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD],
|
|
|
|
}
|
2021-04-14 21:14:34 -07:00
|
|
|
|
|
|
|
impl MerklePath {
|
|
|
|
/// Generates a dummy Merkle path for use in dummy spent notes.
|
2021-06-07 22:18:16 -07:00
|
|
|
pub(crate) fn dummy(rng: &mut impl RngCore) -> Self {
|
2021-06-09 09:44:30 -07:00
|
|
|
fn dummy_inner(rng: &mut impl RngCore, _idx: usize) -> pallas::Base {
|
|
|
|
pallas::Base::random(rng)
|
|
|
|
}
|
|
|
|
|
2021-06-07 22:18:16 -07:00
|
|
|
MerklePath {
|
|
|
|
position: rng.next_u32(),
|
2021-06-09 09:44:30 -07:00
|
|
|
auth_path: gen_const_array(rng, dummy_inner),
|
2021-06-07 22:18:16 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-09 22:54:42 -07:00
|
|
|
/// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
|
|
|
|
/// 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.
|
2021-06-07 22:18:16 -07:00
|
|
|
pub fn root(&self, cmx: ExtractedNoteCommitment) -> Anchor {
|
2021-06-09 19:29:08 -07:00
|
|
|
let node = self
|
|
|
|
.auth_path
|
2021-06-07 22:18:16 -07:00
|
|
|
.iter()
|
|
|
|
.enumerate()
|
2021-06-09 19:29:08 -07:00
|
|
|
.fold(*cmx, |node, (l_star, sibling)| {
|
|
|
|
let swap = self.position & (1 << l_star) != 0;
|
2021-06-07 22:18:16 -07:00
|
|
|
hash_layer(l_star, cond_swap(swap, node, *sibling))
|
|
|
|
});
|
|
|
|
Anchor(node.to_bytes())
|
2021-04-14 21:14:34 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-07 22:18:16 -07:00
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
|
2021-06-09 22:54:42 -07:00
|
|
|
// 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.
|
2021-06-07 22:18:16 -07:00
|
|
|
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()
|
2021-06-09 19:31:47 -07:00
|
|
|
.chain(i2lebsp_k(l_star).iter().copied())
|
2021-06-07 22:18:16 -07:00
|
|
|
.chain(
|
|
|
|
pair.left
|
|
|
|
.to_le_bits()
|
|
|
|
.iter()
|
|
|
|
.by_val()
|
2021-06-09 19:31:47 -07:00
|
|
|
.take(L_ORCHARD_MERKLE),
|
2021-06-07 22:18:16 -07:00
|
|
|
)
|
|
|
|
.chain(
|
|
|
|
pair.right
|
|
|
|
.to_le_bits()
|
|
|
|
.iter()
|
|
|
|
.by_val()
|
2021-06-09 19:31:47 -07:00
|
|
|
.take(L_ORCHARD_MERKLE),
|
2021-06-07 22:18:16 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
.unwrap()
|
|
|
|
}
|
2021-06-08 00:27:08 -07:00
|
|
|
|
|
|
|
/// Generators for property testing.
|
|
|
|
#[cfg(any(test, feature = "test-dependencies"))]
|
|
|
|
pub mod testing {
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use std::convert::TryInto;
|
2021-06-09 22:54:42 -07:00
|
|
|
use std::iter;
|
2021-06-08 00:27:08 -07:00
|
|
|
|
|
|
|
use crate::{
|
2021-06-09 22:54:42 -07:00
|
|
|
constants::MERKLE_DEPTH_ORCHARD,
|
2021-06-08 00:27:08 -07:00
|
|
|
note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note},
|
|
|
|
value::{testing::arb_positive_note_value, MAX_NOTE_VALUE},
|
|
|
|
};
|
|
|
|
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<pallas::Base> = {
|
2021-06-09 22:54:42 -07:00
|
|
|
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)
|
|
|
|
}),
|
2021-06-08 00:27:08 -07:00
|
|
|
)
|
2021-06-09 22:54:42 -07:00
|
|
|
.collect()
|
2021-06-08 00:27:08 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
prop_compose! {
|
|
|
|
/// Generates an arbitrary Merkle tree of with `n_notes` nonempty leaves.
|
2021-06-09 22:54:42 -07:00
|
|
|
pub fn arb_tree(n_notes: usize)
|
2021-06-08 00:27:08 -07:00
|
|
|
(
|
|
|
|
// 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),
|
2021-06-09 22:54:42 -07:00
|
|
|
n_notes
|
2021-06-08 00:27:08 -07:00
|
|
|
),
|
|
|
|
)
|
|
|
|
-> (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;
|
2021-06-09 22:54:42 -07:00
|
|
|
let n_leaves = 1 << perfect_subtree_depth;
|
|
|
|
|
|
|
|
let commitments: Vec<Option<ExtractedNoteCommitment>> = notes.iter().map(|note| {
|
2021-06-08 00:27:08 -07:00
|
|
|
let cmx: ExtractedNoteCommitment = note.commitment().into();
|
2021-06-09 22:54:42 -07:00
|
|
|
Some(cmx)
|
2021-06-08 00:27:08 -07:00
|
|
|
}).collect();
|
|
|
|
|
|
|
|
let padded_leaves = {
|
|
|
|
let mut padded_leaves = commitments.clone();
|
|
|
|
|
2021-06-09 22:54:42 -07:00
|
|
|
let pad = (0..(n_leaves - n_notes)).map(
|
|
|
|
|_| None
|
2021-06-08 00:27:08 -07:00
|
|
|
).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
padded_leaves.extend_from_slice(&pad);
|
|
|
|
padded_leaves
|
|
|
|
};
|
|
|
|
|
2021-06-09 22:54:42 -07:00
|
|
|
let perfect_subtree = {
|
|
|
|
let mut perfect_subtree: Vec<Vec<Option<pallas::Base>>> = vec![
|
|
|
|
padded_leaves.iter().map(|cmx| cmx.map(|cmx| *cmx)).collect()
|
|
|
|
];
|
|
|
|
|
|
|
|
// <https://zips.z.cash/protocol/protocol.pdf#orchardmerklecrh>
|
|
|
|
// 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.
|
2021-06-08 00:27:08 -07:00
|
|
|
for height in 1..perfect_subtree_depth {
|
2021-06-09 22:54:42 -07:00
|
|
|
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.")
|
|
|
|
}
|
|
|
|
}
|
2021-06-08 00:27:08 -07:00
|
|
|
}).collect();
|
2021-06-09 22:54:42 -07:00
|
|
|
perfect_subtree.push(inner_nodes);
|
2021-06-08 00:27:08 -07:00
|
|
|
};
|
2021-06-09 22:54:42 -07:00
|
|
|
perfect_subtree
|
2021-06-08 00:27:08 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Get Merkle path for each note commitment
|
|
|
|
let auth_paths = {
|
|
|
|
let mut auth_paths: Vec<MerklePath> = Vec::new();
|
|
|
|
for (pos, _) in commitments.iter().enumerate() {
|
2021-06-09 22:54:42 -07:00
|
|
|
|
|
|
|
// Initialize the authentication path to the path for an empty tree.
|
2021-06-08 00:27:08 -07:00
|
|
|
let mut auth_path: [pallas::Base; MERKLE_DEPTH_ORCHARD] = (0..MERKLE_DEPTH_ORCHARD).map(|idx| EMPTY_ROOTS[idx]).collect::<Vec<_>>().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 {
|
2021-06-09 22:54:42 -07:00
|
|
|
// This node is the right sibling, so we need its left sibling at the current height.
|
|
|
|
perfect_subtree[height][layer_pos - 1]
|
2021-06-08 00:27:08 -07:00
|
|
|
} else {
|
2021-06-09 22:54:42 -07:00
|
|
|
// This node is the left sibling, so we need its right sibling at the current height.
|
|
|
|
perfect_subtree[height][layer_pos + 1]
|
2021-06-08 00:27:08 -07:00
|
|
|
};
|
2021-06-09 22:54:42 -07:00
|
|
|
if let Some(sibling) = sibling {
|
|
|
|
auth_path[height] = sibling;
|
|
|
|
}
|
2021-06-08 00:27:08 -07:00
|
|
|
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
|
2021-06-09 22:54:42 -07:00
|
|
|
let anchor = auth_paths[0].root(notes[0].commitment().into());
|
2021-06-08 00:27:08 -07:00
|
|
|
|
|
|
|
(
|
|
|
|
notes.into_iter().zip(auth_paths.into_iter()).map(|(note, auth_path)| (note, auth_path)).collect(),
|
|
|
|
anchor
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-06-09 22:54:42 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-06-08 00:27:08 -07:00
|
|
|
}
|