merkle_proof: implement tree construction
Plus QuickCheck tests!
This commit is contained in:
parent
25f2e212c3
commit
e154b30232
@ -7,3 +7,8 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
ethereum-types = "0.6"
|
ethereum-types = "0.6"
|
||||||
eth2_hashing = { path = "../eth2_hashing" }
|
eth2_hashing = { path = "../eth2_hashing" }
|
||||||
|
lazy_static = "1.3.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck = "0.8"
|
||||||
|
quickcheck_macros = "0.8"
|
||||||
|
@ -1,6 +1,138 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
use eth2_hashing::hash;
|
use eth2_hashing::hash;
|
||||||
use ethereum_types::H256;
|
use ethereum_types::H256;
|
||||||
|
|
||||||
|
const MAX_TREE_DEPTH: usize = 32;
|
||||||
|
const EMPTY_SLICE: &[H256] = &[];
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Cached zero hashes where `ZERO_HASHES[i]` is the hash of a Merkle tree with 2^i zero leaves.
|
||||||
|
static ref ZERO_HASHES: Vec<H256> = {
|
||||||
|
let mut hashes = vec![H256::from([0; 32]); MAX_TREE_DEPTH + 1];
|
||||||
|
|
||||||
|
for i in 0..MAX_TREE_DEPTH {
|
||||||
|
hashes[i + 1] = hash_concat(hashes[i], hashes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Zero nodes to act as "synthetic" left and right subtrees of other zero nodes.
|
||||||
|
static ref ZERO_NODES: Vec<MerkleTree> = {
|
||||||
|
(0..MAX_TREE_DEPTH + 1).map(MerkleTree::Zero).collect()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Right-sparse Merkle tree.
|
||||||
|
///
|
||||||
|
/// Efficiently represents a Merkle tree of fixed depth where only the first N
|
||||||
|
/// indices are populated by non-zero leaves (perfect for the deposit contract tree).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MerkleTree {
|
||||||
|
/// Leaf node with the hash of its content.
|
||||||
|
Leaf(H256),
|
||||||
|
/// Internal node with hash, left subtree and right subtree.
|
||||||
|
Node(H256, Box<Self>, Box<Self>),
|
||||||
|
/// Zero subtree of a given depth.
|
||||||
|
///
|
||||||
|
/// It represents a Merkle tree of 2^depth zero leaves.
|
||||||
|
Zero(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MerkleTree {
|
||||||
|
/// Create a new Merkle tree from a list of leaves and a fixed depth.
|
||||||
|
pub fn create(leaves: &[H256], depth: usize) -> Self {
|
||||||
|
use MerkleTree::*;
|
||||||
|
|
||||||
|
if leaves.is_empty() {
|
||||||
|
return Zero(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
match depth {
|
||||||
|
0 => {
|
||||||
|
debug_assert_eq!(leaves.len(), 1);
|
||||||
|
Leaf(leaves[0])
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Split leaves into left and right subtrees
|
||||||
|
let subtree_capacity = 2usize.pow(depth as u32 - 1);
|
||||||
|
let (left_leaves, right_leaves) = if leaves.len() <= subtree_capacity {
|
||||||
|
(leaves, EMPTY_SLICE)
|
||||||
|
} else {
|
||||||
|
leaves.split_at(subtree_capacity)
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_subtree = MerkleTree::create(left_leaves, depth - 1);
|
||||||
|
let right_subtree = MerkleTree::create(right_leaves, depth - 1);
|
||||||
|
let hash = hash_concat(left_subtree.hash(), right_subtree.hash());
|
||||||
|
|
||||||
|
Node(hash, Box::new(left_subtree), Box::new(right_subtree))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the root hash of this Merkle tree.
|
||||||
|
pub fn hash(&self) -> H256 {
|
||||||
|
match *self {
|
||||||
|
MerkleTree::Leaf(h) => h,
|
||||||
|
MerkleTree::Node(h, _, _) => h,
|
||||||
|
MerkleTree::Zero(depth) => ZERO_HASHES[depth],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the left and right subtrees if they exist.
|
||||||
|
pub fn left_and_right_branches(&self) -> Option<(&Self, &Self)> {
|
||||||
|
match *self {
|
||||||
|
MerkleTree::Leaf(_) | MerkleTree::Zero(0) => None,
|
||||||
|
MerkleTree::Node(_, ref l, ref r) => Some((l, r)),
|
||||||
|
MerkleTree::Zero(depth) => Some((&ZERO_NODES[depth - 1], &ZERO_NODES[depth - 1])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this Merkle tree a leaf?
|
||||||
|
pub fn is_leaf(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
MerkleTree::Leaf(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the leaf at `index` and a Merkle proof of its inclusion.
|
||||||
|
///
|
||||||
|
/// The Merkle proof is in "bottom-up" order, starting with a leaf node
|
||||||
|
/// and moving up the tree. Its length will be exactly equal to `depth`.
|
||||||
|
pub fn generate_proof(&self, index: usize, depth: usize) -> (H256, Vec<H256>) {
|
||||||
|
let mut proof = vec![];
|
||||||
|
let mut current_node = self;
|
||||||
|
let mut current_depth = depth;
|
||||||
|
while current_depth > 0 {
|
||||||
|
let ith_bit = (index >> (current_depth - 1)) & 0x01;
|
||||||
|
// Note: unwrap is safe because leaves are only ever constructed at depth == 0.
|
||||||
|
let (left, right) = current_node.left_and_right_branches().unwrap();
|
||||||
|
|
||||||
|
// Go right, include the left branch in the proof.
|
||||||
|
if ith_bit == 1 {
|
||||||
|
proof.push(left.hash());
|
||||||
|
current_node = right;
|
||||||
|
} else {
|
||||||
|
proof.push(right.hash());
|
||||||
|
current_node = left;
|
||||||
|
}
|
||||||
|
current_depth -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert_eq!(proof.len(), depth);
|
||||||
|
debug_assert!(current_node.is_leaf());
|
||||||
|
|
||||||
|
// Put proof in bottom-up order.
|
||||||
|
proof.reverse();
|
||||||
|
|
||||||
|
(current_node.hash(), proof)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify a proof that `leaf` exists at `index` in a Merkle tree rooted at `root`.
|
/// Verify a proof that `leaf` exists at `index` in a Merkle tree rooted at `root`.
|
||||||
///
|
///
|
||||||
/// The `branch` argument is the main component of the proof: it should be a list of internal
|
/// The `branch` argument is the main component of the proof: it should be a list of internal
|
||||||
@ -46,15 +178,66 @@ fn concat(mut vec1: Vec<u8>, mut vec2: Vec<u8>) -> Vec<u8> {
|
|||||||
vec1
|
vec1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
/// Compute the hash of two other hashes concatenated.
|
||||||
mod tests {
|
fn hash_concat(h1: H256, h2: H256) -> H256 {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn hash_concat(h1: H256, h2: H256) -> H256 {
|
|
||||||
H256::from_slice(&hash(&concat(
|
H256::from_slice(&hash(&concat(
|
||||||
h1.as_bytes().to_vec(),
|
h1.as_bytes().to_vec(),
|
||||||
h2.as_bytes().to_vec(),
|
h2.as_bytes().to_vec(),
|
||||||
)))
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use quickcheck::TestResult;
|
||||||
|
use quickcheck_macros::quickcheck;
|
||||||
|
|
||||||
|
/// Check that we can:
|
||||||
|
/// 1. Build a MerkleTree from arbitrary leaves and an arbitrary depth.
|
||||||
|
/// 2. Generate valid proofs for all of the leaves of this MerkleTree.
|
||||||
|
#[quickcheck]
|
||||||
|
fn quickcheck_create_and_verify(int_leaves: Vec<u64>, depth: usize) -> TestResult {
|
||||||
|
if depth > MAX_TREE_DEPTH || int_leaves.len() > 2usize.pow(depth as u32) {
|
||||||
|
return TestResult::discard();
|
||||||
|
}
|
||||||
|
|
||||||
|
let leaves: Vec<_> = int_leaves.into_iter().map(H256::from_low_u64_be).collect();
|
||||||
|
let merkle_tree = MerkleTree::create(&leaves, depth);
|
||||||
|
let merkle_root = merkle_tree.hash();
|
||||||
|
|
||||||
|
let proofs_ok = (0..leaves.len()).into_iter().all(|i| {
|
||||||
|
let (leaf, branch) = merkle_tree.generate_proof(i, depth);
|
||||||
|
leaf == leaves[i] && verify_merkle_proof(leaf, &branch, depth, i, merkle_root)
|
||||||
|
});
|
||||||
|
|
||||||
|
TestResult::from_bool(proofs_ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sparse_zero_correct() {
|
||||||
|
let depth = 2;
|
||||||
|
let zero = H256::from([0x00; 32]);
|
||||||
|
let dense_tree = MerkleTree::create(&[zero, zero, zero, zero], depth);
|
||||||
|
let sparse_tree = MerkleTree::create(&[], depth);
|
||||||
|
assert_eq!(dense_tree.hash(), sparse_tree.hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_small_example() {
|
||||||
|
// Construct a small merkle tree manually and check that it's consistent with
|
||||||
|
// the MerkleTree type.
|
||||||
|
let leaf_b00 = H256::from([0xAA; 32]);
|
||||||
|
let leaf_b01 = H256::from([0xBB; 32]);
|
||||||
|
let leaf_b10 = H256::from([0xCC; 32]);
|
||||||
|
let leaf_b11 = H256::from([0xDD; 32]);
|
||||||
|
|
||||||
|
let node_b0x = hash_concat(leaf_b00, leaf_b01);
|
||||||
|
let node_b1x = hash_concat(leaf_b10, leaf_b11);
|
||||||
|
|
||||||
|
let root = hash_concat(node_b0x, node_b1x);
|
||||||
|
|
||||||
|
let tree = MerkleTree::create(&[leaf_b00, leaf_b01, leaf_b10, leaf_b11], 2);
|
||||||
|
assert_eq!(tree.hash(), root);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user