fix: expose MerkleTree as a real interface that is accumulator friendly
This commit is contained in:
parent
0330c9d387
commit
0b7805f285
|
@ -35,120 +35,80 @@ const LEAF_PREFIX: &[u8] = &[0];
|
|||
const NODE_PREFIX: &[u8] = &[1];
|
||||
const NULL_PREFIX: &[u8] = &[2];
|
||||
|
||||
fn hash_leaf<H: Hasher>(leaf: &[u8]) -> H::Hash {
|
||||
H::hashv(&[LEAF_PREFIX, leaf])
|
||||
}
|
||||
|
||||
fn hash_node<H: Hasher>(l: &H::Hash, r: &H::Hash) -> H::Hash {
|
||||
H::hashv(&[
|
||||
NODE_PREFIX,
|
||||
(if l <= r { l } else { r }).as_ref(),
|
||||
(if l <= r { r } else { l }).as_ref(),
|
||||
])
|
||||
}
|
||||
|
||||
fn hash_null<H: Hasher>() -> H::Hash {
|
||||
H::hashv(&[NULL_PREFIX])
|
||||
}
|
||||
|
||||
/// A MerklePath contains a list of hashes that form a proof for membership in a tree.
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MerklePath<H: Hasher>(Vec<H::Hash>);
|
||||
|
||||
/// A MerkleRoot contains the root hash of a MerkleTree.
|
||||
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MerkleRoot<H: Hasher>(H::Hash);
|
||||
|
||||
impl<H: Hasher> MerkleRoot<H> {
|
||||
pub fn new(root: H::Hash) -> Self {
|
||||
Self(root)
|
||||
}
|
||||
|
||||
pub fn check(&self, proof: MerklePath<H>, item: &[u8]) -> bool {
|
||||
let mut current: <H as Hasher>::Hash = hash_leaf::<H>(item);
|
||||
for hash in proof.0 {
|
||||
current = hash_node::<H>(¤t, &hash);
|
||||
}
|
||||
current == self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> MerklePath<H> {
|
||||
pub fn new(path: Vec<H::Hash>) -> Self {
|
||||
Self(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// A MerkleAccumulator maintains a Merkle Tree.
|
||||
///
|
||||
/// The implementation is based on Solana's Merkle Tree implementation. This structure also stores
|
||||
/// the items that are in the tree due to the need to look-up the index of an item in the tree in
|
||||
/// order to create a proof.
|
||||
/// A MerkleTree is a binary tree where each node is the hash of its children.
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, Default,
|
||||
)]
|
||||
pub struct MerkleAccumulator<H: Hasher = Keccak256> {
|
||||
pub struct MerkleTree<H: Hasher = Keccak256> {
|
||||
pub root: MerkleRoot<H>,
|
||||
#[serde(skip)]
|
||||
pub nodes: Vec<H::Hash>,
|
||||
}
|
||||
|
||||
// Layout:
|
||||
//
|
||||
// ```
|
||||
// 4 bytes: magic number
|
||||
// 1 byte: update type
|
||||
// 4 byte: storage id
|
||||
// 32 bytes: root hash
|
||||
// ```
|
||||
//
|
||||
// TODO: This code does not belong to MerkleAccumulator, we should be using the wire data types in
|
||||
// calling code to wrap this value.
|
||||
impl<'a, H: Hasher + 'a> MerkleAccumulator<H> {
|
||||
pub fn serialize(&self, slot: u64, ring_size: u32) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
serialized.extend_from_slice(0x41555756u32.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(0u8.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(slot.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(ring_size.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(self.root.0.as_ref());
|
||||
serialized
|
||||
/// Implements functionality for using standalone MerkleRoots.
|
||||
impl<H: Hasher> MerkleRoot<H> {
|
||||
/// Construct a MerkleRoot from an existing Hash.
|
||||
pub fn new(root: H::Hash) -> Self {
|
||||
Self(root)
|
||||
}
|
||||
|
||||
/// Given a item and corresponding MerklePath, check that it is a valid membership proof.
|
||||
pub fn check(&self, proof: MerklePath<H>, item: &[u8]) -> bool {
|
||||
let mut current: <H as Hasher>::Hash = MerkleTree::<H>::hash_leaf(item);
|
||||
for hash in proof.0 {
|
||||
current = MerkleTree::<H>::hash_node(¤t, &hash);
|
||||
}
|
||||
current == self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, H: Hasher + 'a> Accumulator<'a> for MerkleAccumulator<H> {
|
||||
/// Implements functionality for working with MerklePath (proofs).
|
||||
impl<H: Hasher> MerklePath<H> {
|
||||
/// Given a Vector of hashes representing a merkle proof, construct a MerklePath.
|
||||
pub fn new(path: Vec<H::Hash>) -> Self {
|
||||
Self(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Presents an Accumulator friendly interface for MerkleTree.
|
||||
impl<'a, H: Hasher + 'a> Accumulator<'a> for MerkleTree<H> {
|
||||
type Proof = MerklePath<H>;
|
||||
|
||||
/// Construct a MerkleTree from an iterator of items.
|
||||
fn from_set(items: impl Iterator<Item = &'a [u8]>) -> Option<Self> {
|
||||
let items: Vec<&[u8]> = items.collect();
|
||||
Self::new(&items)
|
||||
}
|
||||
|
||||
/// Prove an item is in the tree by returning a MerklePath.
|
||||
fn prove(&'a self, item: &[u8]) -> Option<Self::Proof> {
|
||||
let item = hash_leaf::<H>(item);
|
||||
let item = MerkleTree::<H>::hash_leaf(item);
|
||||
let index = self.nodes.iter().position(|i| i == &item)?;
|
||||
Some(self.find_path(index))
|
||||
}
|
||||
|
||||
// NOTE: This `check` call is intended to be generic accross accumulator implementations, but
|
||||
// for a merkle tree the proof does not use the `self` parameter as the proof is standalone
|
||||
// and doesn't need the original nodes. Normally a merkle API would be something like:
|
||||
//
|
||||
// ```
|
||||
// MerkleTree::check(proof)
|
||||
// ```
|
||||
//
|
||||
// or even:
|
||||
//
|
||||
// ```
|
||||
// proof.verify()
|
||||
// ```
|
||||
//
|
||||
// But to stick to the Accumulator trait we do it via the trait method.
|
||||
// NOTE: This `check` call is intended to fit the generic accumulator implementation, but for a
|
||||
// merkle tree the proof does not usually need the `self` parameter as the proof is standalone
|
||||
// and doesn't need the original nodes.
|
||||
fn check(&'a self, proof: Self::Proof, item: &[u8]) -> bool {
|
||||
self.root.check(proof, item)
|
||||
self.verify_path(proof, item)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hasher> MerkleAccumulator<H> {
|
||||
/// Implement a MerkleTree-specific interface for interacting with trees.
|
||||
impl<H: Hasher> MerkleTree<H> {
|
||||
/// Construct a new MerkleTree from a list of byte slices.
|
||||
///
|
||||
/// This list does not have to be a set which means the tree may contain duplicate items. It is
|
||||
/// up to the caller to enforce a strict set-like object if that is desired.
|
||||
pub fn new(items: &[&[u8]]) -> Option<Self> {
|
||||
if items.is_empty() {
|
||||
return None;
|
||||
|
@ -160,9 +120,9 @@ impl<H: Hasher> MerkleAccumulator<H> {
|
|||
// Filling the leaf hashes
|
||||
for i in 0..(1 << depth) {
|
||||
if i < items.len() {
|
||||
tree[(1 << depth) + i] = hash_leaf::<H>(items[i]);
|
||||
tree[(1 << depth) + i] = MerkleTree::<H>::hash_leaf(items[i]);
|
||||
} else {
|
||||
tree[(1 << depth) + i] = hash_null::<H>();
|
||||
tree[(1 << depth) + i] = MerkleTree::<H>::hash_null();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +132,7 @@ impl<H: Hasher> MerkleAccumulator<H> {
|
|||
let level_num_nodes = 1 << level;
|
||||
for i in 0..level_num_nodes {
|
||||
let id = (1 << level) + i;
|
||||
tree[id] = hash_node::<H>(&tree[id * 2], &tree[id * 2 + 1]);
|
||||
tree[id] = MerkleTree::<H>::hash_node(&tree[id * 2], &tree[id * 2 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +142,8 @@ impl<H: Hasher> MerkleAccumulator<H> {
|
|||
})
|
||||
}
|
||||
|
||||
fn find_path(&self, mut index: usize) -> MerklePath<H> {
|
||||
/// Produces a Proof of membership for an index in the tree.
|
||||
pub fn find_path(&self, mut index: usize) -> MerklePath<H> {
|
||||
let mut path = Vec::new();
|
||||
while index > 1 {
|
||||
path.push(self.nodes[index ^ 1]);
|
||||
|
@ -190,6 +151,53 @@ impl<H: Hasher> MerkleAccumulator<H> {
|
|||
}
|
||||
MerklePath::new(path)
|
||||
}
|
||||
|
||||
/// Check if a given MerklePath is a valid proof for a corresponding item.
|
||||
pub fn verify_path(&self, proof: MerklePath<H>, item: &[u8]) -> bool {
|
||||
self.root.check(proof, item)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash_leaf(leaf: &[u8]) -> H::Hash {
|
||||
H::hashv(&[LEAF_PREFIX, leaf])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash_node(l: &H::Hash, r: &H::Hash) -> H::Hash {
|
||||
H::hashv(&[
|
||||
NODE_PREFIX,
|
||||
(if l <= r { l } else { r }).as_ref(),
|
||||
(if l <= r { r } else { l }).as_ref(),
|
||||
])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash_null() -> H::Hash {
|
||||
H::hashv(&[NULL_PREFIX])
|
||||
}
|
||||
|
||||
/// Serialize a MerkleTree into a Vec<u8>.
|
||||
///
|
||||
///Layout:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// 4 bytes: magic number
|
||||
/// 1 byte: update type
|
||||
/// 4 byte: storage id
|
||||
/// 32 bytes: root hash
|
||||
/// ```
|
||||
///
|
||||
/// TODO: This code does not belong to MerkleTree, we should be using the wire data types in
|
||||
/// calling code to wrap this value.
|
||||
pub fn serialize(&self, slot: u64, ring_size: u32) -> Vec<u8> {
|
||||
let mut serialized = vec![];
|
||||
serialized.extend_from_slice(0x41555756u32.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(0u8.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(slot.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(ring_size.to_be_bytes().as_ref());
|
||||
serialized.extend_from_slice(self.root.0.as_ref());
|
||||
serialized
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -231,12 +239,12 @@ mod test {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MerkleAccumulatorDataWrapper {
|
||||
pub accumulator: MerkleAccumulator,
|
||||
struct MerkleTreeDataWrapper {
|
||||
pub accumulator: MerkleTree,
|
||||
pub data: BTreeSet<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Arbitrary for MerkleAccumulatorDataWrapper {
|
||||
impl Arbitrary for MerkleTreeDataWrapper {
|
||||
type Parameters = usize;
|
||||
|
||||
fn arbitrary_with(size: Self::Parameters) -> Self::Strategy {
|
||||
|
@ -248,9 +256,8 @@ mod test {
|
|||
.prop_map(|v| {
|
||||
let data: BTreeSet<Vec<u8>> = v.into_iter().collect();
|
||||
let accumulator =
|
||||
MerkleAccumulator::<Keccak256>::from_set(data.iter().map(|i| i.as_ref()))
|
||||
.unwrap();
|
||||
MerkleAccumulatorDataWrapper { accumulator, data }
|
||||
MerkleTree::<Keccak256>::from_set(data.iter().map(|i| i.as_ref())).unwrap();
|
||||
MerkleTreeDataWrapper { accumulator, data }
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
@ -303,14 +310,14 @@ mod test {
|
|||
set.insert(&item_b);
|
||||
set.insert(&item_c);
|
||||
|
||||
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let proof = accumulator.prove(&item_a).unwrap();
|
||||
|
||||
assert!(accumulator.check(proof, &item_a));
|
||||
assert!(accumulator.verify_path(proof, &item_a));
|
||||
let proof = accumulator.prove(&item_a).unwrap();
|
||||
assert_eq!(size_of::<<Keccak256 as Hasher>::Hash>(), 32);
|
||||
|
||||
assert!(!accumulator.check(proof, &item_d));
|
||||
assert!(!accumulator.verify_path(proof, &item_d));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -327,11 +334,11 @@ mod test {
|
|||
set.insert(&item_b);
|
||||
|
||||
// Attempt to prove empty proofs that are not in the accumulator.
|
||||
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let proof = MerklePath::<Keccak256>::default();
|
||||
assert!(!accumulator.check(proof, &item_a));
|
||||
assert!(!accumulator.verify_path(proof, &item_a));
|
||||
let proof = MerklePath::<Keccak256>(vec![Default::default()]);
|
||||
assert!(!accumulator.check(proof, &item_a));
|
||||
assert!(!accumulator.verify_path(proof, &item_a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -349,7 +356,7 @@ mod test {
|
|||
set.insert(&item_d);
|
||||
|
||||
// Accumulate
|
||||
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
|
||||
// For each hash in the resulting proofs, corrupt one hash and confirm that the proof
|
||||
// cannot pass check.
|
||||
|
@ -358,7 +365,7 @@ mod test {
|
|||
for (i, _) in proof.0.iter().enumerate() {
|
||||
let mut corrupted_proof = proof.clone();
|
||||
corrupted_proof.0[i] = Default::default();
|
||||
assert!(!accumulator.check(corrupted_proof, item));
|
||||
assert!(!accumulator.verify_path(corrupted_proof, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,9 +388,9 @@ mod test {
|
|||
set.insert(&item_d);
|
||||
|
||||
// Accumulate into a 2 level tree.
|
||||
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
|
||||
let proof = accumulator.prove(&item_a).unwrap();
|
||||
assert!(accumulator.check(proof, &item_a));
|
||||
assert!(accumulator.verify_path(proof, &item_a));
|
||||
|
||||
// We now have a 2 level tree with 4 nodes:
|
||||
//
|
||||
|
@ -410,7 +417,7 @@ mod test {
|
|||
// implementation did not use a different hash for nodes and leaves then it is possible to
|
||||
// falsely prove `A` was in the original tree by tricking the implementation into performing
|
||||
// H(a || b) at the leaf.
|
||||
let faulty_accumulator = MerkleAccumulator::<Keccak256> {
|
||||
let faulty_accumulator = MerkleTree::<Keccak256> {
|
||||
root: accumulator.root,
|
||||
nodes: vec![
|
||||
accumulator.nodes[0],
|
||||
|
@ -422,30 +429,33 @@ mod test {
|
|||
|
||||
// `a || b` is the concatenation of a and b, which when hashed without pre-image fixes in
|
||||
// place generates A as a leaf rather than a pair node.
|
||||
let fake_leaf_A = &[
|
||||
hash_leaf::<Keccak256>(&item_b),
|
||||
hash_leaf::<Keccak256>(&item_a),
|
||||
let fake_leaf = &[
|
||||
MerkleTree::<Keccak256>::hash_leaf(&item_b),
|
||||
MerkleTree::<Keccak256>::hash_leaf(&item_a),
|
||||
]
|
||||
.concat();
|
||||
|
||||
// Confirm our combined hash existed as a node pair in the original tree.
|
||||
assert_eq!(hash_leaf::<Keccak256>(fake_leaf_A), accumulator.nodes[2]);
|
||||
assert_eq!(
|
||||
MerkleTree::<Keccak256>::hash_leaf(fake_leaf),
|
||||
accumulator.nodes[2]
|
||||
);
|
||||
|
||||
// Now we can try and prove leaf membership in the faulty accumulator. NOTE: this should
|
||||
// fail but to confirm that the test is actually correct you can remove the PREFIXES from
|
||||
// the hash functions and this test will erroneously pass.
|
||||
let proof = faulty_accumulator.prove(fake_leaf_A).unwrap();
|
||||
assert!(faulty_accumulator.check(proof, fake_leaf_A));
|
||||
let proof = faulty_accumulator.prove(fake_leaf).unwrap();
|
||||
assert!(faulty_accumulator.verify_path(proof, fake_leaf));
|
||||
}
|
||||
|
||||
proptest! {
|
||||
// Use proptest to generate arbitrary Merkle trees as part of our fuzzing strategy. This
|
||||
// will help us identify any edge cases or unexpected behavior in the implementation.
|
||||
#[test]
|
||||
fn test_merkle_tree(v in any::<MerkleAccumulatorDataWrapper>()) {
|
||||
fn test_merkle_tree(v in any::<MerkleTreeDataWrapper>()) {
|
||||
for d in v.data {
|
||||
let proof = v.accumulator.prove(&d).unwrap();
|
||||
assert!(v.accumulator.check(proof, &d));
|
||||
assert!(v.accumulator.verify_path(proof, &d));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,7 +463,7 @@ mod test {
|
|||
// passes which should not.
|
||||
#[test]
|
||||
fn test_fake_merkle_proofs(
|
||||
v in any::<MerkleAccumulatorDataWrapper>(),
|
||||
v in any::<MerkleTreeDataWrapper>(),
|
||||
p in any::<MerklePath<Keccak256>>(),
|
||||
) {
|
||||
// Reject 1-sized trees as they will always pass due to root being the only elements
|
||||
|
@ -463,7 +473,7 @@ mod test {
|
|||
}
|
||||
|
||||
for d in v.data {
|
||||
assert!(!v.accumulator.check(p.clone(), &d));
|
||||
assert!(!v.accumulator.verify_path(p.clone(), &d));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue