Merge pull request #110 from zcash/tree-logic

Implement tree logic in Builder
This commit is contained in:
str4d 2021-06-11 21:39:40 +01:00 committed by GitHub
commit 0ead91a88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 575 additions and 27 deletions

View File

@ -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"

View File

@ -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,
@ -485,8 +484,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,
};
@ -506,7 +504,7 @@ pub mod testing {
rng: R,
sk: SpendingKey,
anchor: Anchor,
notes: Vec<Note>,
notes: Vec<(Note, MerklePath)>,
recipient_amounts: Vec<(Address, NoteValue)>,
}
@ -518,8 +516,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() {
@ -543,16 +541,11 @@ 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,
)
(
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)
@ -566,7 +559,7 @@ pub mod testing {
rng: StdRng::from_seed(rng_seed),
sk: sk.clone(),
anchor,
notes,
notes: notes_and_auth_paths,
recipient_amounts
}
}

View File

@ -114,7 +114,7 @@ impl<T> Action<T> {
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,
}

View File

@ -19,6 +19,12 @@ pub mod util;
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;
@ -243,3 +249,27 @@ fn test_zs_and_us<C: CurveAffine>(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);
}
}

View File

@ -40,6 +40,18 @@ pub fn evaluate<C: CurveAffine>(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<Output: Copy + Default, const LEN: usize>(
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(idx))) {
*bit = val;
}
ret
}
#[cfg(test)]
mod tests {
use super::decompose_scalar_fixed;

View File

@ -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,

View File

@ -43,7 +43,7 @@ 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 {
@ -63,3 +63,11 @@ impl From<NoteCommitment> for ExtractedNoteCommitment {
ExtractedNoteCommitment(extract_p(&cm.0))
}
}
impl std::ops::Deref for ExtractedNoteCommitment {
type Target = pallas::Base;
fn deref(&self) -> &pallas::Base {
&self.0
}
}

View File

@ -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;
@ -20,6 +21,13 @@ 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));
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
/// multiple of $K$ bits.
struct Pad<I: Iterator<Item = bool>> {
@ -186,7 +194,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() {
@ -225,4 +234,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::<Vec<_>>();
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
);
}
}
}

View File

@ -1 +1,2 @@
pub(crate) mod commitment_tree;
pub(crate) mod keys;

View File

@ -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,
],
],
}
}

View File

@ -1,15 +1,294 @@
use crate::{
constants::{
util::gen_const_array, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION, MERKLE_DEPTH_ORCHARD,
},
note::commitment::ExtractedNoteCommitment,
primitives::sinsemilla::{i2lebsp_k, HashDomain},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
use ff::{Field, PrimeFieldBits};
use rand::RngCore;
use std::iter;
/// The root of an Orchard commitment tree.
#[derive(Clone, Debug)]
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Anchor(pub [u8; 32]);
impl From<pallas::Base> 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(mut rng: &mut impl RngCore) -> Self {
MerklePath {
position: rng.next_u32(),
auth_path: gen_const_array(|_| pallas::Base::random(&mut rng)),
}
}
/// <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.
pub fn root(&self, cmx: ExtractedNoteCommitment) -> Anchor {
let node = self
.auth_path
.iter()
.enumerate()
.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())
}
/// 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 {
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>
/// 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);
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()
}
/// Generators for property testing.
#[cfg(any(test, feature = "test-dependencies"))]
pub mod testing {
use lazy_static::lazy_static;
use std::convert::TryInto;
use std::iter;
use crate::{
constants::MERKLE_DEPTH_ORCHARD,
note::{commitment::ExtractedNoteCommitment, testing::arb_note, Note},
value::{testing::arb_positive_note_value, MAX_NOTE_VALUE},
};
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).
// <https://zips.z.cash/protocol/protocol.pdf#thmuncommittedorchard>
lazy_static! {
static ref EMPTY_ROOTS: Vec<pallas::Base> = {
iter::empty()
.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 {
left: *state,
right: *state,
},
);
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)
(
// 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<Option<ExtractedNoteCommitment>> = 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::<Vec<_>>();
padded_leaves.extend_from_slice(&pad);
padded_leaves
};
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.
for height in 1..perfect_subtree_depth {
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();
perfect_subtree.push(inner_nodes);
};
perfect_subtree
};
// Get Merkle path for each note commitment
let auth_paths = {
let mut auth_paths: Vec<MerklePath> = 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::<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 {
// 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! {
#[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);
}
}
}
}