diff --git a/Cargo.toml b/Cargo.toml index 42d69489b..c5aae7f43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "eth2/utils/boolean-bitfield", "eth2/utils/hashing", "eth2/utils/honey-badger-split", + "eth2/utils/merkle_proof", "eth2/utils/int_to_bytes", "eth2/utils/slot_clock", "eth2/utils/ssz", diff --git a/eth2/utils/merkle_proof/Cargo.toml b/eth2/utils/merkle_proof/Cargo.toml new file mode 100644 index 000000000..b7cd81216 --- /dev/null +++ b/eth2/utils/merkle_proof/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "merkle_proof" +version = "0.1.0" +authors = ["Michael Sproul "] +edition = "2018" + +[dependencies] +ethereum-types = "0.5" +hashing = { path = "../hashing" } diff --git a/eth2/utils/merkle_proof/src/lib.rs b/eth2/utils/merkle_proof/src/lib.rs new file mode 100644 index 000000000..4d0abcea3 --- /dev/null +++ b/eth2/utils/merkle_proof/src/lib.rs @@ -0,0 +1,148 @@ +use ethereum_types::H256; +use hashing::hash; + +/// 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 +/// node hashes such that the root can be reconstructed (in bottom-up order). +pub fn verify_merkle_proof( + leaf: H256, + branch: &[H256], + depth: usize, + index: usize, + root: H256, +) -> bool { + if branch.len() == depth { + merkle_root_from_branch(leaf, branch, depth, index) == root + } else { + false + } +} + +/// Compute a root hash from a leaf and a Merkle proof. +fn merkle_root_from_branch(leaf: H256, branch: &[H256], depth: usize, index: usize) -> H256 { + assert_eq!(branch.len(), depth, "proof length should equal depth"); + + let mut merkle_root = leaf.as_bytes().to_vec(); + + for i in 0..depth { + let ith_bit = (index >> i) & 0x01; + if ith_bit == 1 { + let input = concat(branch[i].as_bytes().to_vec(), merkle_root); + merkle_root = hash(&input); + } else { + let mut input = merkle_root; + input.extend_from_slice(branch[i].as_bytes()); + merkle_root = hash(&input); + } + } + + H256::from_slice(&merkle_root) +} + +/// Concatenate two vectors. +fn concat(mut vec1: Vec, mut vec2: Vec) -> Vec { + vec1.append(&mut vec2); + vec1 +} + +#[cfg(test)] +mod tests { + use super::*; + + fn hash_concat(h1: H256, h2: H256) -> H256 { + H256::from_slice(&hash(&concat( + h1.as_bytes().to_vec(), + h2.as_bytes().to_vec(), + ))) + } + + #[test] + fn verify_small_example() { + // Construct a small merkle tree manually + 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); + + // Run some proofs + assert!(verify_merkle_proof( + leaf_b00, + &[leaf_b01, node_b1x], + 2, + 0b00, + root + )); + assert!(verify_merkle_proof( + leaf_b01, + &[leaf_b00, node_b1x], + 2, + 0b01, + root + )); + assert!(verify_merkle_proof( + leaf_b10, + &[leaf_b11, node_b0x], + 2, + 0b10, + root + )); + assert!(verify_merkle_proof( + leaf_b11, + &[leaf_b10, node_b0x], + 2, + 0b11, + root + )); + assert!(verify_merkle_proof( + leaf_b11, + &[leaf_b10], + 1, + 0b11, + node_b1x + )); + + // Ensure that incorrect proofs fail + // Zero-length proof + assert!(!verify_merkle_proof(leaf_b01, &[], 2, 0b01, root)); + // Proof in reverse order + assert!(!verify_merkle_proof( + leaf_b01, + &[node_b1x, leaf_b00], + 2, + 0b01, + root + )); + // Proof too short + assert!(!verify_merkle_proof(leaf_b01, &[leaf_b00], 2, 0b01, root)); + // Wrong index + assert!(!verify_merkle_proof( + leaf_b01, + &[leaf_b00, node_b1x], + 2, + 0b10, + root + )); + // Wrong root + assert!(!verify_merkle_proof( + leaf_b01, + &[leaf_b00, node_b1x], + 2, + 0b01, + node_b1x + )); + } + + #[test] + fn verify_zero_depth() { + let leaf = H256::from([0xD6; 32]); + let junk = H256::from([0xD7; 32]); + assert!(verify_merkle_proof(leaf, &[], 0, 0, leaf)); + assert!(!verify_merkle_proof(leaf, &[], 0, 7, junk)); + } +}