Skip to content

Commit 0b7805f

Browse files
committed
fix: expose MerkleTree as a real interface that is accumulator friendly
1 parent 0330c9d commit 0b7805f

File tree

1 file changed

+115
-105
lines changed
  • pythnet/pythnet_sdk/src/accumulators

1 file changed

+115
-105
lines changed

pythnet/pythnet_sdk/src/accumulators/merkle.rs

Lines changed: 115 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -35,120 +35,80 @@ const LEAF_PREFIX: &[u8] = &[0];
3535
const NODE_PREFIX: &[u8] = &[1];
3636
const NULL_PREFIX: &[u8] = &[2];
3737

38-
fn hash_leaf<H: Hasher>(leaf: &[u8]) -> H::Hash {
39-
H::hashv(&[LEAF_PREFIX, leaf])
40-
}
41-
42-
fn hash_node<H: Hasher>(l: &H::Hash, r: &H::Hash) -> H::Hash {
43-
H::hashv(&[
44-
NODE_PREFIX,
45-
(if l <= r { l } else { r }).as_ref(),
46-
(if l <= r { r } else { l }).as_ref(),
47-
])
48-
}
49-
50-
fn hash_null<H: Hasher>() -> H::Hash {
51-
H::hashv(&[NULL_PREFIX])
52-
}
53-
38+
/// A MerklePath contains a list of hashes that form a proof for membership in a tree.
5439
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
5540
pub struct MerklePath<H: Hasher>(Vec<H::Hash>);
5641

42+
/// A MerkleRoot contains the root hash of a MerkleTree.
5743
#[derive(Clone, Default, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
5844
pub struct MerkleRoot<H: Hasher>(H::Hash);
5945

46+
/// A MerkleTree is a binary tree where each node is the hash of its children.
47+
#[derive(
48+
Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, Default,
49+
)]
50+
pub struct MerkleTree<H: Hasher = Keccak256> {
51+
pub root: MerkleRoot<H>,
52+
#[serde(skip)]
53+
pub nodes: Vec<H::Hash>,
54+
}
55+
56+
/// Implements functionality for using standalone MerkleRoots.
6057
impl<H: Hasher> MerkleRoot<H> {
58+
/// Construct a MerkleRoot from an existing Hash.
6159
pub fn new(root: H::Hash) -> Self {
6260
Self(root)
6361
}
6462

63+
/// Given a item and corresponding MerklePath, check that it is a valid membership proof.
6564
pub fn check(&self, proof: MerklePath<H>, item: &[u8]) -> bool {
66-
let mut current: <H as Hasher>::Hash = hash_leaf::<H>(item);
65+
let mut current: <H as Hasher>::Hash = MerkleTree::<H>::hash_leaf(item);
6766
for hash in proof.0 {
68-
current = hash_node::<H>(&current, &hash);
67+
current = MerkleTree::<H>::hash_node(&current, &hash);
6968
}
7069
current == self.0
7170
}
7271
}
7372

73+
/// Implements functionality for working with MerklePath (proofs).
7474
impl<H: Hasher> MerklePath<H> {
75+
/// Given a Vector of hashes representing a merkle proof, construct a MerklePath.
7576
pub fn new(path: Vec<H::Hash>) -> Self {
7677
Self(path)
7778
}
7879
}
7980

80-
/// A MerkleAccumulator maintains a Merkle Tree.
81-
///
82-
/// The implementation is based on Solana's Merkle Tree implementation. This structure also stores
83-
/// the items that are in the tree due to the need to look-up the index of an item in the tree in
84-
/// order to create a proof.
85-
#[derive(
86-
Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize, Deserialize, Default,
87-
)]
88-
pub struct MerkleAccumulator<H: Hasher = Keccak256> {
89-
pub root: MerkleRoot<H>,
90-
#[serde(skip)]
91-
pub nodes: Vec<H::Hash>,
92-
}
93-
94-
// Layout:
95-
//
96-
// ```
97-
// 4 bytes: magic number
98-
// 1 byte: update type
99-
// 4 byte: storage id
100-
// 32 bytes: root hash
101-
// ```
102-
//
103-
// TODO: This code does not belong to MerkleAccumulator, we should be using the wire data types in
104-
// calling code to wrap this value.
105-
impl<'a, H: Hasher + 'a> MerkleAccumulator<H> {
106-
pub fn serialize(&self, slot: u64, ring_size: u32) -> Vec<u8> {
107-
let mut serialized = vec![];
108-
serialized.extend_from_slice(0x41555756u32.to_be_bytes().as_ref());
109-
serialized.extend_from_slice(0u8.to_be_bytes().as_ref());
110-
serialized.extend_from_slice(slot.to_be_bytes().as_ref());
111-
serialized.extend_from_slice(ring_size.to_be_bytes().as_ref());
112-
serialized.extend_from_slice(self.root.0.as_ref());
113-
serialized
114-
}
115-
}
116-
117-
impl<'a, H: Hasher + 'a> Accumulator<'a> for MerkleAccumulator<H> {
81+
/// Presents an Accumulator friendly interface for MerkleTree.
82+
impl<'a, H: Hasher + 'a> Accumulator<'a> for MerkleTree<H> {
11883
type Proof = MerklePath<H>;
11984

85+
/// Construct a MerkleTree from an iterator of items.
12086
fn from_set(items: impl Iterator<Item = &'a [u8]>) -> Option<Self> {
12187
let items: Vec<&[u8]> = items.collect();
12288
Self::new(&items)
12389
}
12490

91+
/// Prove an item is in the tree by returning a MerklePath.
12592
fn prove(&'a self, item: &[u8]) -> Option<Self::Proof> {
126-
let item = hash_leaf::<H>(item);
93+
let item = MerkleTree::<H>::hash_leaf(item);
12794
let index = self.nodes.iter().position(|i| i == &item)?;
12895
Some(self.find_path(index))
12996
}
13097

131-
// NOTE: This `check` call is intended to be generic accross accumulator implementations, but
132-
// for a merkle tree the proof does not use the `self` parameter as the proof is standalone
133-
// and doesn't need the original nodes. Normally a merkle API would be something like:
134-
//
135-
// ```
136-
// MerkleTree::check(proof)
137-
// ```
138-
//
139-
// or even:
140-
//
141-
// ```
142-
// proof.verify()
143-
// ```
144-
//
145-
// But to stick to the Accumulator trait we do it via the trait method.
98+
// NOTE: This `check` call is intended to fit the generic accumulator implementation, but for a
99+
// merkle tree the proof does not usually need the `self` parameter as the proof is standalone
100+
// and doesn't need the original nodes.
146101
fn check(&'a self, proof: Self::Proof, item: &[u8]) -> bool {
147-
self.root.check(proof, item)
102+
self.verify_path(proof, item)
148103
}
149104
}
150105

151-
impl<H: Hasher> MerkleAccumulator<H> {
106+
/// Implement a MerkleTree-specific interface for interacting with trees.
107+
impl<H: Hasher> MerkleTree<H> {
108+
/// Construct a new MerkleTree from a list of byte slices.
109+
///
110+
/// This list does not have to be a set which means the tree may contain duplicate items. It is
111+
/// up to the caller to enforce a strict set-like object if that is desired.
152112
pub fn new(items: &[&[u8]]) -> Option<Self> {
153113
if items.is_empty() {
154114
return None;
@@ -160,9 +120,9 @@ impl<H: Hasher> MerkleAccumulator<H> {
160120
// Filling the leaf hashes
161121
for i in 0..(1 << depth) {
162122
if i < items.len() {
163-
tree[(1 << depth) + i] = hash_leaf::<H>(items[i]);
123+
tree[(1 << depth) + i] = MerkleTree::<H>::hash_leaf(items[i]);
164124
} else {
165-
tree[(1 << depth) + i] = hash_null::<H>();
125+
tree[(1 << depth) + i] = MerkleTree::<H>::hash_null();
166126
}
167127
}
168128

@@ -172,7 +132,7 @@ impl<H: Hasher> MerkleAccumulator<H> {
172132
let level_num_nodes = 1 << level;
173133
for i in 0..level_num_nodes {
174134
let id = (1 << level) + i;
175-
tree[id] = hash_node::<H>(&tree[id * 2], &tree[id * 2 + 1]);
135+
tree[id] = MerkleTree::<H>::hash_node(&tree[id * 2], &tree[id * 2 + 1]);
176136
}
177137
}
178138

@@ -182,14 +142,62 @@ impl<H: Hasher> MerkleAccumulator<H> {
182142
})
183143
}
184144

185-
fn find_path(&self, mut index: usize) -> MerklePath<H> {
145+
/// Produces a Proof of membership for an index in the tree.
146+
pub fn find_path(&self, mut index: usize) -> MerklePath<H> {
186147
let mut path = Vec::new();
187148
while index > 1 {
188149
path.push(self.nodes[index ^ 1]);
189150
index /= 2;
190151
}
191152
MerklePath::new(path)
192153
}
154+
155+
/// Check if a given MerklePath is a valid proof for a corresponding item.
156+
pub fn verify_path(&self, proof: MerklePath<H>, item: &[u8]) -> bool {
157+
self.root.check(proof, item)
158+
}
159+
160+
#[inline]
161+
pub fn hash_leaf(leaf: &[u8]) -> H::Hash {
162+
H::hashv(&[LEAF_PREFIX, leaf])
163+
}
164+
165+
#[inline]
166+
pub fn hash_node(l: &H::Hash, r: &H::Hash) -> H::Hash {
167+
H::hashv(&[
168+
NODE_PREFIX,
169+
(if l <= r { l } else { r }).as_ref(),
170+
(if l <= r { r } else { l }).as_ref(),
171+
])
172+
}
173+
174+
#[inline]
175+
pub fn hash_null() -> H::Hash {
176+
H::hashv(&[NULL_PREFIX])
177+
}
178+
179+
/// Serialize a MerkleTree into a Vec<u8>.
180+
///
181+
///Layout:
182+
///
183+
/// ```rust,ignore
184+
/// 4 bytes: magic number
185+
/// 1 byte: update type
186+
/// 4 byte: storage id
187+
/// 32 bytes: root hash
188+
/// ```
189+
///
190+
/// TODO: This code does not belong to MerkleTree, we should be using the wire data types in
191+
/// calling code to wrap this value.
192+
pub fn serialize(&self, slot: u64, ring_size: u32) -> Vec<u8> {
193+
let mut serialized = vec![];
194+
serialized.extend_from_slice(0x41555756u32.to_be_bytes().as_ref());
195+
serialized.extend_from_slice(0u8.to_be_bytes().as_ref());
196+
serialized.extend_from_slice(slot.to_be_bytes().as_ref());
197+
serialized.extend_from_slice(ring_size.to_be_bytes().as_ref());
198+
serialized.extend_from_slice(self.root.0.as_ref());
199+
serialized
200+
}
193201
}
194202

195203
#[cfg(test)]
@@ -231,12 +239,12 @@ mod test {
231239
}
232240

233241
#[derive(Debug)]
234-
struct MerkleAccumulatorDataWrapper {
235-
pub accumulator: MerkleAccumulator,
242+
struct MerkleTreeDataWrapper {
243+
pub accumulator: MerkleTree,
236244
pub data: BTreeSet<Vec<u8>>,
237245
}
238246

239-
impl Arbitrary for MerkleAccumulatorDataWrapper {
247+
impl Arbitrary for MerkleTreeDataWrapper {
240248
type Parameters = usize;
241249

242250
fn arbitrary_with(size: Self::Parameters) -> Self::Strategy {
@@ -248,9 +256,8 @@ mod test {
248256
.prop_map(|v| {
249257
let data: BTreeSet<Vec<u8>> = v.into_iter().collect();
250258
let accumulator =
251-
MerkleAccumulator::<Keccak256>::from_set(data.iter().map(|i| i.as_ref()))
252-
.unwrap();
253-
MerkleAccumulatorDataWrapper { accumulator, data }
259+
MerkleTree::<Keccak256>::from_set(data.iter().map(|i| i.as_ref())).unwrap();
260+
MerkleTreeDataWrapper { accumulator, data }
254261
})
255262
.boxed()
256263
}
@@ -303,14 +310,14 @@ mod test {
303310
set.insert(&item_b);
304311
set.insert(&item_c);
305312

306-
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
313+
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
307314
let proof = accumulator.prove(&item_a).unwrap();
308315

309-
assert!(accumulator.check(proof, &item_a));
316+
assert!(accumulator.verify_path(proof, &item_a));
310317
let proof = accumulator.prove(&item_a).unwrap();
311318
assert_eq!(size_of::<<Keccak256 as Hasher>::Hash>(), 32);
312319

313-
assert!(!accumulator.check(proof, &item_d));
320+
assert!(!accumulator.verify_path(proof, &item_d));
314321
}
315322

316323
#[test]
@@ -327,11 +334,11 @@ mod test {
327334
set.insert(&item_b);
328335

329336
// Attempt to prove empty proofs that are not in the accumulator.
330-
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
337+
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
331338
let proof = MerklePath::<Keccak256>::default();
332-
assert!(!accumulator.check(proof, &item_a));
339+
assert!(!accumulator.verify_path(proof, &item_a));
333340
let proof = MerklePath::<Keccak256>(vec![Default::default()]);
334-
assert!(!accumulator.check(proof, &item_a));
341+
assert!(!accumulator.verify_path(proof, &item_a));
335342
}
336343

337344
#[test]
@@ -349,7 +356,7 @@ mod test {
349356
set.insert(&item_d);
350357

351358
// Accumulate
352-
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
359+
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
353360

354361
// For each hash in the resulting proofs, corrupt one hash and confirm that the proof
355362
// cannot pass check.
@@ -358,7 +365,7 @@ mod test {
358365
for (i, _) in proof.0.iter().enumerate() {
359366
let mut corrupted_proof = proof.clone();
360367
corrupted_proof.0[i] = Default::default();
361-
assert!(!accumulator.check(corrupted_proof, item));
368+
assert!(!accumulator.verify_path(corrupted_proof, item));
362369
}
363370
}
364371
}
@@ -381,9 +388,9 @@ mod test {
381388
set.insert(&item_d);
382389

383390
// Accumulate into a 2 level tree.
384-
let accumulator = MerkleAccumulator::<Keccak256>::from_set(set.into_iter()).unwrap();
391+
let accumulator = MerkleTree::<Keccak256>::from_set(set.into_iter()).unwrap();
385392
let proof = accumulator.prove(&item_a).unwrap();
386-
assert!(accumulator.check(proof, &item_a));
393+
assert!(accumulator.verify_path(proof, &item_a));
387394

388395
// We now have a 2 level tree with 4 nodes:
389396
//
@@ -410,7 +417,7 @@ mod test {
410417
// implementation did not use a different hash for nodes and leaves then it is possible to
411418
// falsely prove `A` was in the original tree by tricking the implementation into performing
412419
// H(a || b) at the leaf.
413-
let faulty_accumulator = MerkleAccumulator::<Keccak256> {
420+
let faulty_accumulator = MerkleTree::<Keccak256> {
414421
root: accumulator.root,
415422
nodes: vec![
416423
accumulator.nodes[0],
@@ -422,38 +429,41 @@ mod test {
422429

423430
// `a || b` is the concatenation of a and b, which when hashed without pre-image fixes in
424431
// place generates A as a leaf rather than a pair node.
425-
let fake_leaf_A = &[
426-
hash_leaf::<Keccak256>(&item_b),
427-
hash_leaf::<Keccak256>(&item_a),
432+
let fake_leaf = &[
433+
MerkleTree::<Keccak256>::hash_leaf(&item_b),
434+
MerkleTree::<Keccak256>::hash_leaf(&item_a),
428435
]
429436
.concat();
430437

431438
// Confirm our combined hash existed as a node pair in the original tree.
432-
assert_eq!(hash_leaf::<Keccak256>(fake_leaf_A), accumulator.nodes[2]);
439+
assert_eq!(
440+
MerkleTree::<Keccak256>::hash_leaf(fake_leaf),
441+
accumulator.nodes[2]
442+
);
433443

434444
// Now we can try and prove leaf membership in the faulty accumulator. NOTE: this should
435445
// fail but to confirm that the test is actually correct you can remove the PREFIXES from
436446
// the hash functions and this test will erroneously pass.
437-
let proof = faulty_accumulator.prove(fake_leaf_A).unwrap();
438-
assert!(faulty_accumulator.check(proof, fake_leaf_A));
447+
let proof = faulty_accumulator.prove(fake_leaf).unwrap();
448+
assert!(faulty_accumulator.verify_path(proof, fake_leaf));
439449
}
440450

441451
proptest! {
442452
// Use proptest to generate arbitrary Merkle trees as part of our fuzzing strategy. This
443453
// will help us identify any edge cases or unexpected behavior in the implementation.
444454
#[test]
445-
fn test_merkle_tree(v in any::<MerkleAccumulatorDataWrapper>()) {
455+
fn test_merkle_tree(v in any::<MerkleTreeDataWrapper>()) {
446456
for d in v.data {
447457
let proof = v.accumulator.prove(&d).unwrap();
448-
assert!(v.accumulator.check(proof, &d));
458+
assert!(v.accumulator.verify_path(proof, &d));
449459
}
450460
}
451461

452462
// Use proptest to generate arbitrary proofs for Merkle Trees trying to find a proof that
453463
// passes which should not.
454464
#[test]
455465
fn test_fake_merkle_proofs(
456-
v in any::<MerkleAccumulatorDataWrapper>(),
466+
v in any::<MerkleTreeDataWrapper>(),
457467
p in any::<MerklePath<Keccak256>>(),
458468
) {
459469
// Reject 1-sized trees as they will always pass due to root being the only elements
@@ -463,7 +473,7 @@ mod test {
463473
}
464474

465475
for d in v.data {
466-
assert!(!v.accumulator.check(p.clone(), &d));
476+
assert!(!v.accumulator.verify_path(p.clone(), &d));
467477
}
468478
}
469479
}

0 commit comments

Comments
 (0)