New implementation of incremental merkle tree

This is a new implementation of the incremental merkle tree used by our
scheme to witness commitments to spendable value. It serves as a fixed-sized
accumulator.

This new construction has a much simpler API surface area, avoids memory
safety issues, remains pruned at all times, avoids serialization edge cases,
has more efficient insertion, and is abstract over the depth and hash
function used at the type level.

Further, it lays the groundwork for efficient "fast-forwarding" of witnesses
into the tree as the treestate is updated.
This commit is contained in:
Sean Bowe
2016-03-28 02:40:21 -06:00
parent 291b191bd7
commit e1ff849d8d
12 changed files with 889 additions and 5 deletions

View File

@@ -0,0 +1,287 @@
#include <stdexcept>
#include <boost/foreach.hpp>
#include "zcash/IncrementalMerkleTree.hpp"
#include "crypto/sha256.h"
#include "zerocash/utils/util.h" // TODO: remove these utilities
namespace libzcash {
SHA256Compress SHA256Compress::combine(const SHA256Compress& a, const SHA256Compress& b)
{
// This is a performance optimization.
if (a.IsNull() && b.IsNull()) {
return a;
}
SHA256Compress res = SHA256Compress();
CSHA256 hasher;
hasher.Write(a.begin(), 32);
hasher.Write(b.begin(), 32);
hasher.FinalizeNoPadding(res.begin());
return res;
}
template <typename Hash>
class PathFiller {
private:
std::deque<Hash> queue;
public:
PathFiller() : queue() { }
PathFiller(std::deque<Hash> queue) : queue(queue) { }
Hash next() {
if (queue.size() > 0) {
Hash h = queue.front();
queue.pop_front();
return h;
} else {
return Hash();
}
}
};
template<size_t Depth, typename Hash>
void IncrementalMerkleTree<Depth, Hash>::wfcheck() const {
if (parents.size() >= Depth) {
throw std::ios_base::failure("tree has too many parents");
}
}
template<size_t Depth, typename Hash>
void IncrementalMerkleTree<Depth, Hash>::append(Hash obj) {
if (is_complete(Depth)) {
throw std::runtime_error("tree is full");
}
if (!left) {
// Set the left leaf
left = obj;
} else if (!right) {
// Set the right leaf
right = obj;
} else {
// Combine the leaves and propagate it up the tree
boost::optional<Hash> combined = Hash::combine(*left, *right);
// Set the left leaf to the object and make the right object none
left = obj;
right = boost::none;
// Propagate up the tree as much as needed
BOOST_FOREACH(boost::optional<Hash>& parent, parents) {
if (parent) {
combined = Hash::combine(*parent, *combined);
parent = boost::none;
} else {
parent = *combined;
combined = boost::none;
break;
}
}
if (combined) {
// Create a new parent
parents.push_back(combined);
}
}
}
// This is for allowing the witness to determine if a subtree has filled
// to a particular depth, or for append() to ensure we're not appending
// to a full tree.
template<size_t Depth, typename Hash>
bool IncrementalMerkleTree<Depth, Hash>::is_complete(size_t depth) const {
if (!left || !right) {
return false;
}
if (parents.size() != (depth - 1)) {
return false;
}
BOOST_FOREACH(const boost::optional<Hash>& parent, parents) {
if (!parent) {
return false;
}
}
return true;
}
// This finds the next "depth" of an unfilled subtree, given that we've filled
// `skip` uncles/subtrees.
template<size_t Depth, typename Hash>
size_t IncrementalMerkleTree<Depth, Hash>::next_depth(size_t skip) const {
if (!left) {
if (skip) {
skip--;
} else {
return 0;
}
}
if (!right) {
if (skip) {
skip--;
} else {
return 0;
}
}
size_t d = 1;
BOOST_FOREACH(const boost::optional<Hash>& parent, parents) {
if (!parent) {
if (skip) {
skip--;
} else {
return d;
}
}
d++;
}
return d + skip;
}
// This calculates the root of the tree.
template<size_t Depth, typename Hash>
Hash IncrementalMerkleTree<Depth, Hash>::root(size_t depth,
std::deque<Hash> filler_hashes) const {
PathFiller<Hash> filler(filler_hashes);
Hash combine_left = left ? *left : filler.next();
Hash combine_right = right ? *right : filler.next();
Hash root = Hash::combine(combine_left, combine_right);
size_t d = 1;
BOOST_FOREACH(const boost::optional<Hash>& parent, parents) {
if (parent) {
root = Hash::combine(*parent, root);
} else {
root = Hash::combine(root, filler.next());
}
d++;
}
// We may not have parents for ancestor trees, so we fill
// the rest in here.
while (d < depth) {
root = Hash::combine(root, filler.next());
d++;
}
return root;
}
// This constructs an authentication path into the tree in the format that the circuit
// wants. The caller provides `filler_hashes` to fill in the uncle subtrees.
template<size_t Depth, typename Hash>
MerklePath IncrementalMerkleTree<Depth, Hash>::path(std::deque<Hash> filler_hashes) const {
if (!left) {
throw std::runtime_error("can't create an authentication path for the beginning of the tree");
}
PathFiller<Hash> filler(filler_hashes);
std::vector<Hash> path;
std::vector<bool> index;
if (right) {
index.push_back(true);
path.push_back(*left);
} else {
index.push_back(false);
path.push_back(filler.next());
}
size_t d = 1;
BOOST_FOREACH(const boost::optional<Hash>& parent, parents) {
if (parent) {
index.push_back(true);
path.push_back(*parent);
} else {
index.push_back(false);
path.push_back(filler.next());
}
d++;
}
while (d < Depth) {
index.push_back(false);
path.push_back(filler.next());
d++;
}
std::vector<std::vector<bool>> merkle_path;
BOOST_FOREACH(Hash b, path)
{
std::vector<unsigned char> hashv(b.begin(), b.end());
std::vector<bool> tmp_b;
libzerocash::convertBytesVectorToVector(hashv, tmp_b);
merkle_path.push_back(tmp_b);
}
std::reverse(merkle_path.begin(), merkle_path.end());
std::reverse(index.begin(), index.end());
return MerklePath(merkle_path, index);
}
template<size_t Depth, typename Hash>
std::deque<Hash> IncrementalWitness<Depth, Hash>::uncle_train() const {
std::deque<Hash> uncles(filled.begin(), filled.end());
if (cursor) {
uncles.push_back(cursor->root(cursor_depth));
}
return uncles;
}
template<size_t Depth, typename Hash>
void IncrementalWitness<Depth, Hash>::append(Hash obj) {
if (cursor) {
cursor->append(obj);
if (cursor->is_complete(cursor_depth)) {
filled.push_back(cursor->root(cursor_depth));
cursor = boost::none;
}
} else {
cursor_depth = tree.next_depth(filled.size());
if (cursor_depth >= Depth) {
throw std::runtime_error("tree is full");
}
if (cursor_depth == 0) {
filled.push_back(obj);
} else {
cursor = IncrementalMerkleTree<Depth, Hash>();
cursor->append(obj);
}
}
}
template class IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH, SHA256Compress>;
template class IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, SHA256Compress>;
template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH, SHA256Compress>;
template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, SHA256Compress>;
} // end namespace `libzcash`