Sapling commitment tree

This commit is contained in:
Jack Grigg 2018-10-08 14:52:27 +01:00
parent 8f3f95ee08
commit f4059a5faa
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
1 changed files with 262 additions and 1 deletions

View File

@ -1,6 +1,7 @@
use ff::PrimeField;
use pairing::bls12_381::{Bls12, Fr, FrRepr};
use sapling_crypto::primitives::Note;
use std::collections::VecDeque;
use sapling::merkle_hash;
@ -56,12 +57,147 @@ lazy_static! {
};
}
struct PathFiller {
queue: VecDeque<Node>,
}
impl PathFiller {
fn empty() -> Self {
PathFiller {
queue: VecDeque::new(),
}
}
fn next(&mut self, depth: usize) -> Node {
self.queue.pop_front().unwrap_or_else(|| EMPTY_ROOTS[depth])
}
}
/// A Merkle tree of Sapling note commitments.
pub struct CommitmentTree {
left: Option<Node>,
right: Option<Node>,
parents: Vec<Option<Node>>,
}
impl CommitmentTree {
/// Creates an empty tree.
pub fn new() -> Self {
CommitmentTree {
left: None,
right: None,
parents: vec![],
}
}
/// Returns the number of notes in the tree.
pub fn size(&self) -> usize {
self.parents.iter().enumerate().fold(
match (self.left.is_some(), self.right.is_some()) {
(false, false) => 0,
(true, false) | (false, true) => 1,
(true, true) => 2,
},
|acc, (i, p)| {
// Treat occupation of parents array as a binary number
// (right-shifted by 1)
acc + if p.is_some() { 1 << (i + 1) } else { 0 }
},
)
}
fn is_complete(&self, depth: usize) -> bool {
self.left.is_some()
&& self.right.is_some()
&& self.parents.len() == depth - 1
&& self.parents.iter().fold(true, |acc, p| acc && p.is_some())
}
/// Adds a note to the tree. Returns an error if the tree is full.
pub fn append(&mut self, node: Node) -> Result<(), ()> {
self.append_inner(node, SAPLING_COMMITMENT_TREE_DEPTH)
}
fn append_inner(&mut self, node: Node, depth: usize) -> Result<(), ()> {
if self.is_complete(depth) {
// Tree is full
return Err(());
}
match (self.left, self.right) {
(None, _) => self.left = Some(node),
(_, None) => self.right = Some(node),
(Some(l), Some(r)) => {
let mut combined = Node::combine(0, &l, &r);
self.left = Some(node);
self.right = None;
for i in 0..depth {
if i < self.parents.len() {
if let Some(p) = self.parents[i] {
combined = Node::combine(i + 1, &p, &combined);
self.parents[i] = None;
} else {
self.parents[i] = Some(combined);
break;
}
} else {
self.parents.push(Some(combined));
break;
}
}
}
}
Ok(())
}
/// Returns the current root of the tree.
pub fn root(&self) -> Node {
self.root_inner(SAPLING_COMMITMENT_TREE_DEPTH, PathFiller::empty())
}
fn root_inner(&self, depth: usize, mut filler: PathFiller) -> Node {
assert!(depth > 0);
// 1) Hash left and right leaves together.
// - Empty leaves are used as needed.
let leaf_root = Node::combine(
0,
&match self.left {
Some(node) => node,
None => filler.next(0),
},
&match self.right {
Some(node) => node,
None => filler.next(0),
},
);
// 2) Hash in parents up to the currently-filled depth.
// - Roots of the empty subtrees are used as needed.
let mid_root = self
.parents
.iter()
.enumerate()
.fold(leaf_root, |root, (i, p)| match p {
Some(node) => Node::combine(i + 1, node, &root),
None => Node::combine(i + 1, &root, &filler.next(i + 1)),
});
// 3) Hash in roots of the empty subtrees up to the final depth.
((self.parents.len() + 1)..depth)
.fold(mid_root, |root, d| Node::combine(d, &root, &filler.next(d)))
}
}
#[cfg(test)]
mod tests {
use super::EMPTY_ROOTS;
use super::{CommitmentTree, Hashable, Node, PathFiller, EMPTY_ROOTS};
use ff::PrimeFieldRepr;
use hex;
use pairing::bls12_381::FrRepr;
const HEX_EMPTY_ROOTS: [&str; 33] = [
"0100000000000000000000000000000000000000000000000000000000000000",
@ -99,6 +235,28 @@ mod tests {
"fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e",
];
const TESTING_DEPTH: usize = 4;
struct TestCommitmentTree(CommitmentTree);
impl TestCommitmentTree {
fn new() -> Self {
TestCommitmentTree(CommitmentTree::new())
}
fn size(&self) -> usize {
self.0.size()
}
fn append(&mut self, node: Node) -> Result<(), ()> {
self.0.append_inner(node, TESTING_DEPTH)
}
fn root(&self) -> Node {
self.0.root_inner(TESTING_DEPTH, PathFiller::empty())
}
}
#[test]
fn empty_root_test_vectors() {
let mut tmp = [0u8; 32];
@ -110,4 +268,107 @@ mod tests {
assert_eq!(hex::encode(tmp), HEX_EMPTY_ROOTS[i]);
}
}
#[test]
fn sapling_empty_root() {
let mut tmp = [0u8; 32];
CommitmentTree::new()
.root()
.repr
.write_le(&mut tmp[..])
.expect("length is 32 bytes");
assert_eq!(
hex::encode(tmp),
"fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e"
);
}
#[test]
fn empty_commitment_tree_roots() {
let tree = CommitmentTree::new();
let mut tmp = [0u8; 32];
for i in 1..HEX_EMPTY_ROOTS.len() {
tree.root_inner(i, PathFiller::empty())
.repr
.write_le(&mut tmp[..])
.expect("length is 32 bytes");
assert_eq!(hex::encode(tmp), HEX_EMPTY_ROOTS[i]);
}
}
#[test]
fn test_sapling_tree() {
// From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_commitments_sapling.json
// Byte-reversed because the original test vectors are loaded using uint256S()
let commitments = [
"b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55",
"225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b11458",
"7c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e036c",
"50421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a030",
"aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc12",
"f76748d40d5ee5f9a608512e7954dd515f86e8f6d009141c89163de1cf351a02",
"bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0b2e",
"da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a511",
"3a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f77446",
"c7ca8f7df8fd997931d33985d935ee2d696856cc09cc516d419ea6365f163008",
"f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c3702",
"e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c608",
"8cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e826",
"22fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c03",
"f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c",
"3a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac15",
];
// From https://github.com/zcash/zcash/blob/master/src/test/data/merkle_roots_sapling.json
let roots = [
"8c3daa300c9710bf24d2595536e7c80ff8d147faca726636d28e8683a0c27703",
"8611f17378eb55e8c3c3f0a5f002e2b0a7ca39442fc928322b8072d1079c213d",
"3db73b998d536be0e1c2ec124df8e0f383ae7b602968ff6a5276ca0695023c46",
"7ac2e6442fec5970e116dfa4f2ee606f395366cafb1fa7dfd6c3de3ce18c4363",
"6a8f11ab2a11c262e39ed4ea3825ae6c94739ccf94479cb69402c5722b034532",
"149595eed0b54a7e694cc8a68372525b9ae2c7b102514f527460db91eb690565",
"8c0432f1994a2381a7a4b5fda770336011f9e0b30784f9a5597901619c797045",
"e780c48d70420601f3313ff8488d7766b70c059c53aa3cda2ff1ef57ff62383c",
"f919f03caaed8a2c60f58c0d43838f83e670dc7e8ccd25daa04a13f3e8f45541",
"74f32b36629724038e71cbd6823b5a666440205a7d1a9242e95870b53d81f34a",
"a4af205a4e1ee02102866b23a68930ac33efda9235832f49b17fcc4939be4525",
"a946a42f1636045a16e65b2308e036d9da70089686c87c692e45912bd1cab772",
"a1db2dbac055364c1cb43cbeb49c7e2815bff855122602a2ad0fb981a91e0e39",
"16329b3ba4f0640f4d306532d9ea6ba0fbf0e70e44ed57d27b4277ed9cda6849",
"7b6523b2d9b23f72fec6234aa6a1f8fae3dba1c6a266023ea8b1826feba7a25c",
"5c0bea7e17bde5bee4eb795c2eec3d389a68da587b36dd687b134826ecc09308",
];
fn assert_root_eq(root: Node, expected: &str) {
let mut tmp = [0u8; 32];
root.repr
.write_le(&mut tmp[..])
.expect("length is 32 bytes");
assert_eq!(hex::encode(tmp), expected);
}
let mut tree = TestCommitmentTree::new();
assert_eq!(tree.size(), 0);
for i in 0..16 {
let mut cm = FrRepr::default();
cm.read_le(&hex::decode(commitments[i]).unwrap()[..])
.expect("length is 32 bytes");
let cm = Node::new(cm);
// Append a commitment to the tree
assert!(tree.append(cm).is_ok());
// Size incremented by one.
assert_eq!(tree.size(), i + 1);
// Check tree root consistency
assert_root_eq(tree.root(), roots[i]);
}
// Tree should be full now
let node = Node::blank();
assert!(tree.append(node).is_err());
}
}