zcash-sync/src/commitment.rs

356 lines
11 KiB
Rust
Raw Normal View History

2021-06-18 01:17:41 -07:00
use zcash_primitives::merkle_tree::Hashable;
use zcash_primitives::sapling::Node;
use std::io::Write;
use zcash_primitives::serialize::{Optional, Vector};
use byteorder::WriteBytesExt;
use rayon::prelude::*;
2021-06-19 06:41:26 -07:00
/*
Same behavior and structure as CommitmentTree<Node> from librustzcash
It represents the data required to build a merkle path from a note commitment (leaf)
to the root.
The Merkle Path is the minimal set of nodes needed to recalculate the Merkle root
that includes our note.
It starts with our note commitment (because it is already a hash, it doesn't need
to be hashed). The value is stored in either `left` or `right` slot depending on the parity
of the note index. If there is a sibling, its value is in the other slot.
`parents` is the list of hashes that are siblings to the nodes along the path to the root.
If a hash has no sibling yet, then the parent is None. It means that the placeholder hash
value should be used there.
Remark: It's possible to have a grand parent but no parent.
*/
2021-06-18 01:17:41 -07:00
#[derive(Clone)]
pub struct CTree {
left: Option<Node>,
right: Option<Node>,
parents: Vec<Option<Node>>,
}
2021-06-19 06:41:26 -07:00
/*
Witness is the data required to maintain the Merkle Path of a given note after more
notes are added.
Once a node has two actual children values (i.e. not a placeholder), its value
is constant because leaves can't change.
However, it doesn't mean that our Merkle Path is immutable. As the tree fills up,
previous entries that were None could end up getting a value.
- `tree` is the Merkle Path at the time the note is inserted. It does not change
- `filled` are the hash values that replace the "None" values in `tree`. It gets populated as
more notes are added and the sibling sub trees fill up
- `cursor` is a sibling subtree that is not yet full. It is tracked as a sub Merkle Tree
Example:
Let's say the `tree` has parents [ hash, None, hash ] and left = hash, right = None.
Based on this information, we know the position is 1010b = 10 (11th leaf)
o
/ \
hash o
/ \ / \
* * o .
/ \ / \ / \
* * * * hash o
/\ /\ /\ /\ /\ /\
0 1 2 3 4 5 6 7 8 9 10 .
legend:
o is a hash value that we calculate as part of the merkle path verification
. is a placeholder hash and denotes a non existent node
We have two missing nodes (None):
- the `right` node,
- the 2nd parent
When node 11 comes, `filled` gets the value since it is filling the first None.
Then when node 12 comes, we are starting to fill a new sub tree in `cursor`
cursor -> left = 12, right = None, parents = []
After node 13, cursor continues to fill up:
cursor -> left = 12, right = 13, parents = []
With node 14, the cursor tree gains one level
cursor -> left = 14, right = None, parents = [hash(12,13)]
With node 15, the subtree is full, `filled` gets the value of the 2nd parent
and the cursor is empty
With node 16, the tree gains a level but `tree` remains the same (it is immutable).
Instead, a new cursor starts. Eventually, it fills up and a new value
gets pushed into `filled`.
*/
2021-06-18 01:17:41 -07:00
#[derive(Clone)]
pub struct Witness {
tree: CTree, // commitment tree at the moment the witness is created: immutable
filled: Vec<Node>, // as more nodes are added, levels get filled up: won't change anymore
cursor: CTree, // partial tree which still updates when nodes are added
}
impl Witness {
pub fn new() -> Witness {
Witness {
tree: CTree::new(),
filled: vec![],
cursor: CTree::new(),
}
}
pub fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
self.tree.write(&mut writer)?;
Vector::write(&mut writer, &self.filled, |w, n| n.write(w))?;
if self.cursor.left == None && self.cursor.right == None {
writer.write_u8(0)?;
}
else {
writer.write_u8(1)?;
self.cursor.write(writer)?;
};
Ok(())
}
}
pub struct NotePosition {
p: usize,
p2: Option<usize>,
c: usize,
pub witness: Witness,
is_last: bool,
}
fn collect(tree: &mut CTree, mut p: usize, depth: usize, commitments: &[Node]) -> usize {
if depth == 0 {
if p % 2 == 0 {
tree.left = Some(commitments[p]);
} else {
tree.left = Some(commitments[p - 1]);
tree.right = Some(commitments[p]);
p -= 1;
}
} else {
// the rest gets combined as a binary tree
if p % 2 != 0 {
tree.parents.push(Some(commitments[p - 1]));
} else if p != 0 {
tree.parents.push(None);
}
}
p
}
impl NotePosition {
fn new(position: usize, count: usize) -> NotePosition {
let is_last = position == count - 1;
let c = if !is_last {
cursor_start_position(position, count)
} else {
0
};
let cursor_length = count - c;
NotePosition {
p: position,
p2: if cursor_length > 0 {
Some(cursor_length - 1)
} else {
None
},
c,
witness: Witness::new(),
is_last,
}
}
fn collect(&mut self, depth: usize, commitments: &[Node]) {
let count = commitments.len();
let p = self.p;
self.p = collect(&mut self.witness.tree, p, depth, commitments);
if !self.is_last {
if p % 2 == 0 && p + 1 < commitments.len() {
let filler = commitments[p + 1];
self.witness.filled.push(filler);
}
}
if let Some(ref mut p2) = self.p2 {
if !self.is_last {
let cursor_commitments = &commitments[self.c..count];
*p2 = collect(&mut self.witness.cursor, *p2, depth, cursor_commitments);
}
*p2 /= 2;
}
self.p /= 2;
self.c /= 2;
}
}
fn cursor_start_position(mut position: usize, mut count: usize) -> usize {
assert!(position < count);
// same logic as filler
let mut depth = 0;
loop {
if position % 2 == 0 {
if position + 1 < count {
position += 1;
} else {
break;
}
}
position /= 2;
count /= 2;
depth += 1;
}
(position + 1) << depth
}
impl CTree {
pub fn calc_state(commitments: &mut [Node], positions: &[usize]) -> (CTree, Vec<NotePosition>) {
let mut n = commitments.len();
let mut positions: Vec<_> = positions.iter().map(|&p| NotePosition::new(p, n)).collect();
assert_ne!(n, 0);
let mut depth = 0usize;
let mut frontier = NotePosition::new(n - 1, n);
while n > 0 {
let commitment_slice = &commitments[0..n];
frontier.collect(depth, commitment_slice);
for p in positions.iter_mut() {
p.collect(depth, commitment_slice);
}
let nn = n / 2;
let next_level: Vec<_> = (0..nn).into_par_iter().map(|i| {
Node::combine(depth, &commitments[2 * i], &commitments[2 * i + 1])
}).collect();
commitments[0..nn].copy_from_slice(&next_level);
depth += 1;
n = nn;
}
(frontier.witness.tree, positions)
}
fn new() -> CTree {
CTree {
left: None,
right: None,
parents: vec![],
}
}
fn write<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
Optional::write(&mut writer, &self.left, |w, n| n.write(w))?;
Optional::write(&mut writer, &self.right, |w, n| n.write(w))?;
Vector::write(&mut writer, &self.parents, |w, e| {
Optional::write(w, e, |w, n| n.write(w))
})
}
}
#[cfg(test)]
mod tests {
use crate::commitment::{cursor_start_position, CTree};
#[allow(unused_imports)]
use crate::print::{print_tree, print_witness};
use std::time::Instant;
use zcash_primitives::merkle_tree::{CommitmentTree, IncrementalWitness};
use zcash_primitives::sapling::Node;
/*
Build incremental witnesses with both methods and compare their binary serialization
*/
#[test]
fn test_calc_witnesses() {
const NUM_NODES: u32 = 100000; // number of notes
const WITNESS_PERCENT: u32 = 1; // percentage of notes that are ours
const DEBUG_PRINT: bool = false;
let witness_freq = 100 / WITNESS_PERCENT;
let mut tree1: CommitmentTree<Node> = CommitmentTree::empty();
let mut nodes: Vec<Node> = vec![];
let mut witnesses: Vec<IncrementalWitness<Node>> = vec![];
let mut positions: Vec<usize> = vec![];
for i in 1..=NUM_NODES {
let mut bb = [0u8; 32];
bb[0..4].copy_from_slice(&i.to_be_bytes());
let node = Node::new(bb);
tree1.append(node).unwrap();
for w in witnesses.iter_mut() {
w.append(node).unwrap();
}
if i % witness_freq == 0 {
let w = IncrementalWitness::<Node>::from_tree(&tree1);
witnesses.push(w);
positions.push((i - 1) as usize);
}
nodes.push(node);
}
let start = Instant::now();
let (tree2, positions) = CTree::calc_state(&mut nodes, &positions);
eprintln!(
"Update State & Witnesses: {} ms",
start.elapsed().as_millis()
);
println!("# witnesses = {}", positions.len());
for (w, p) in witnesses.iter().zip(&positions) {
let mut bb1: Vec<u8> = vec![];
w.write(&mut bb1).unwrap();
let mut bb2: Vec<u8> = vec![];
p.witness.write(&mut bb2).unwrap();
assert_eq!(bb1.as_slice(), bb2.as_slice());
}
if DEBUG_PRINT {
print_witness(&witnesses[0]);
println!("Tree");
let t = &positions[0].witness.tree;
println!("{:?}", t.left.map(|n| hex::encode(n.repr)));
println!("{:?}", t.right.map(|n| hex::encode(n.repr)));
for p in t.parents.iter() {
println!("{:?}", p.map(|n| hex::encode(n.repr)));
}
println!("Filled");
for f in positions[0].witness.filled.iter() {
println!("{:?}", hex::encode(f.repr));
}
println!("Cursor");
let t = &positions[0].witness.cursor;
println!("{:?}", t.left.map(|n| hex::encode(n.repr)));
println!("{:?}", t.right.map(|n| hex::encode(n.repr)));
for p in t.parents.iter() {
println!("{:?}", p.map(|n| hex::encode(n.repr)));
}
2021-06-19 06:41:26 -07:00
println!("====");
2021-06-18 01:17:41 -07:00
println!("{:?}", tree1.left.map(|n| hex::encode(n.repr)));
println!("{:?}", tree1.right.map(|n| hex::encode(n.repr)));
for p in tree1.parents.iter() {
println!("{:?}", p.map(|n| hex::encode(n.repr)));
}
println!("-----");
println!("{:?}", tree2.left.map(|n| hex::encode(n.repr)));
println!("{:?}", tree2.right.map(|n| hex::encode(n.repr)));
for p in tree2.parents.iter() {
println!("{:?}", p.map(|n| hex::encode(n.repr)));
}
}
}
#[test]
fn test_cursor() {
// println!("{}", cursor_start_position(8, 14));
println!("{}", cursor_start_position(9, 14));
// println!("{}", cursor_start_position(10, 14));
}
}