mirror of https://github.com/zcash/orchard.git
Update proptests to generate Merkle paths
This commit is contained in:
parent
b33248bdb0
commit
8f8eff23d8
|
@ -26,6 +26,7 @@ blake2b_simd = "0.5"
|
||||||
ff = "0.10"
|
ff = "0.10"
|
||||||
fpe = "0.4"
|
fpe = "0.4"
|
||||||
group = "0.10"
|
group = "0.10"
|
||||||
|
lazy_static = "1"
|
||||||
pasta_curves = "0.1"
|
pasta_curves = "0.1"
|
||||||
proptest = { version = "1.0.0", optional = true }
|
proptest = { version = "1.0.0", optional = true }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
|
@ -465,8 +465,7 @@ pub mod testing {
|
||||||
testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey,
|
testing::arb_spending_key, FullViewingKey, OutgoingViewingKey, SpendAuthorizingKey,
|
||||||
SpendingKey,
|
SpendingKey,
|
||||||
},
|
},
|
||||||
note::testing::arb_note,
|
tree::{testing::arb_tree, Anchor, MerklePath},
|
||||||
tree::{Anchor, MerklePath},
|
|
||||||
value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
|
value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
|
||||||
Address, Note,
|
Address, Note,
|
||||||
};
|
};
|
||||||
|
@ -527,12 +526,7 @@ pub mod testing {
|
||||||
n_recipients in 1..30,
|
n_recipients in 1..30,
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
anchor in prop::array::uniform32(prop::num::u8::ANY).prop_map(Anchor),
|
(notes_and_auth_paths, anchor) in arb_tree(n_notes),
|
||||||
// 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
|
|
||||||
),
|
|
||||||
recipient_amounts in vec(
|
recipient_amounts in vec(
|
||||||
arb_address().prop_flat_map(move |a| {
|
arb_address().prop_flat_map(move |a| {
|
||||||
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64)
|
arb_positive_note_value(MAX_NOTE_VALUE / n_recipients as u64)
|
||||||
|
@ -546,7 +540,7 @@ pub mod testing {
|
||||||
rng: StdRng::from_seed(rng_seed),
|
rng: StdRng::from_seed(rng_seed),
|
||||||
sk: sk.clone(),
|
sk: sk.clone(),
|
||||||
anchor,
|
anchor,
|
||||||
notes,
|
notes: notes_and_auth_paths,
|
||||||
recipient_amounts
|
recipient_amounts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ impl<T> Action<T> {
|
||||||
cv_net: self.cv_net.clone(),
|
cv_net: self.cv_net.clone(),
|
||||||
nf_old: self.nf,
|
nf_old: self.nf,
|
||||||
rk: self.rk.clone(),
|
rk: self.rk.clone(),
|
||||||
cmx: self.cmx.clone(),
|
cmx: self.cmx,
|
||||||
enable_spend: flags.spends_enabled,
|
enable_spend: flags.spends_enabled,
|
||||||
enable_output: flags.outputs_enabled,
|
enable_output: flags.outputs_enabled,
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl RandomSeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A discrete amount of funds received by an address.
|
/// A discrete amount of funds received by an address.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Note {
|
pub struct Note {
|
||||||
/// The recipient of the funds.
|
/// The recipient of the funds.
|
||||||
recipient: Address,
|
recipient: Address,
|
||||||
|
|
|
@ -43,10 +43,15 @@ impl NoteCommitment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The x-coordinate of the commitment to a note.
|
/// The x-coordinate of the commitment to a note.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct ExtractedNoteCommitment(pub(super) pallas::Base);
|
pub struct ExtractedNoteCommitment(pub(super) pallas::Base);
|
||||||
|
|
||||||
impl ExtractedNoteCommitment {
|
impl ExtractedNoteCommitment {
|
||||||
|
/// ExtractedNoteCommitment for an uncommitted note.
|
||||||
|
pub fn uncommitted() -> Self {
|
||||||
|
Self(pallas::Base::zero())
|
||||||
|
}
|
||||||
|
|
||||||
/// Deserialize the extracted note commitment from a byte array.
|
/// Deserialize the extracted note commitment from a byte array.
|
||||||
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
|
||||||
pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment)
|
pallas::Base::from_bytes(bytes).map(ExtractedNoteCommitment)
|
||||||
|
|
135
src/tree.rs
135
src/tree.rs
|
@ -101,3 +101,138 @@ fn hash_layer(l_star: usize, pair: Pair) -> pallas::Base {
|
||||||
)
|
)
|
||||||
.unwrap()
|
.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<pallas::Base> = {
|
||||||
|
// 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<ExtractedNoteCommitment> = 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::<Vec<_>>();
|
||||||
|
|
||||||
|
padded_leaves.extend_from_slice(&pad);
|
||||||
|
padded_leaves
|
||||||
|
};
|
||||||
|
|
||||||
|
let tree = {
|
||||||
|
let mut tree: Vec<Vec<pallas::Base>> = 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<MerklePath> = 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::<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 {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue