Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit f9d1dcd

Browse files
authored
Optimize merkle proofs for efficient verification in Solidity (#12857)
* Sort hashes when generating & verifying MMR proofs * Remove unused variables * Double-hash leaves in beefy-merkle-tree * Revert "Double-hash leaves in beefy-merkle-tree" This reverts commit f788f5e. * Retry Polkadot companion CI jobs
1 parent 6d3596f commit f9d1dcd

File tree

3 files changed

+31
-28
lines changed

3 files changed

+31
-28
lines changed

frame/beefy-mmr/primitives/src/lib.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
//! Merkle Tree is constructed from arbitrary-length leaves, that are initially hashed using the
2828
//! same hasher as the inner nodes.
2929
//! Inner nodes are created by concatenating child hashes and hashing again. The implementation
30-
//! does not perform any sorting of the input data (leaves) nor when inner nodes are created.
30+
//! sorts each pair of hashes before every hash operation. This makes proof verification more
31+
//! efficient by removing the need to track which side each intermediate hash is concatenated on.
3132
//!
3233
//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer.
3334
@@ -45,7 +46,7 @@ use beefy_primitives::mmr::{BeefyAuthoritySet, BeefyNextAuthoritySet};
4546
pub fn merkle_root<H, I>(leaves: I) -> H::Output
4647
where
4748
H: HashT,
48-
H::Output: Default + AsRef<[u8]>,
49+
H::Output: Default + AsRef<[u8]> + PartialOrd,
4950
I: IntoIterator,
5051
I::Item: AsRef<[u8]>,
5152
{
@@ -56,7 +57,7 @@ where
5657
fn merkelize<H, V, I>(leaves: I, visitor: &mut V) -> H::Output
5758
where
5859
H: HashT,
59-
H::Output: Default + AsRef<[u8]>,
60+
H::Output: Default + AsRef<[u8]> + PartialOrd,
6061
V: Visitor<H::Output>,
6162
I: Iterator<Item = H::Output>,
6263
{
@@ -141,7 +142,7 @@ impl<T> Visitor<T> for () {
141142
pub fn merkle_proof<H, I, T>(leaves: I, leaf_index: usize) -> MerkleProof<H::Output, T>
142143
where
143144
H: HashT,
144-
H::Output: Default + Copy + AsRef<[u8]>,
145+
H::Output: Default + Copy + AsRef<[u8]> + PartialOrd,
145146
I: IntoIterator<Item = T>,
146147
I::IntoIter: ExactSizeIterator,
147148
T: AsRef<[u8]>,
@@ -241,7 +242,7 @@ pub fn verify_proof<'a, H, P, L>(
241242
) -> bool
242243
where
243244
H: HashT,
244-
H::Output: PartialEq + AsRef<[u8]>,
245+
H::Output: PartialEq + AsRef<[u8]> + PartialOrd,
245246
P: IntoIterator<Item = H::Output>,
246247
L: Into<Leaf<'a, H::Output>>,
247248
{
@@ -256,15 +257,13 @@ where
256257

257258
let hash_len = <H as sp_core::Hasher>::LENGTH;
258259
let mut combined = vec![0_u8; hash_len * 2];
259-
let mut position = leaf_index;
260-
let mut width = number_of_leaves;
261260
let computed = proof.into_iter().fold(leaf_hash, |a, b| {
262-
if position % 2 == 1 || position + 1 == width {
263-
combined[..hash_len].copy_from_slice(&b.as_ref());
264-
combined[hash_len..].copy_from_slice(&a.as_ref());
265-
} else {
261+
if a < b {
266262
combined[..hash_len].copy_from_slice(&a.as_ref());
267263
combined[hash_len..].copy_from_slice(&b.as_ref());
264+
} else {
265+
combined[..hash_len].copy_from_slice(&b.as_ref());
266+
combined[hash_len..].copy_from_slice(&a.as_ref());
268267
}
269268
let hash = <H as HashT>::hash(&combined);
270269
#[cfg(feature = "debug")]
@@ -275,8 +274,6 @@ where
275274
array_bytes::bytes2hex("", &hash.as_ref()),
276275
array_bytes::bytes2hex("", &combined.as_ref())
277276
);
278-
position /= 2;
279-
width = ((width - 1) / 2) + 1;
280277
hash
281278
});
282279

@@ -295,7 +292,7 @@ fn merkelize_row<H, V, I>(
295292
) -> Result<H::Output, Vec<H::Output>>
296293
where
297294
H: HashT,
298-
H::Output: AsRef<[u8]>,
295+
H::Output: AsRef<[u8]> + PartialOrd,
299296
V: Visitor<H::Output>,
300297
I: Iterator<Item = H::Output>,
301298
{
@@ -321,8 +318,13 @@ where
321318
index += 2;
322319
match (a, b) {
323320
(Some(a), Some(b)) => {
324-
combined[..hash_len].copy_from_slice(a.as_ref());
325-
combined[hash_len..].copy_from_slice(b.as_ref());
321+
if a < b {
322+
combined[..hash_len].copy_from_slice(a.as_ref());
323+
combined[hash_len..].copy_from_slice(b.as_ref());
324+
} else {
325+
combined[..hash_len].copy_from_slice(b.as_ref());
326+
combined[hash_len..].copy_from_slice(a.as_ref());
327+
}
326328

327329
next.push(<H as HashT>::hash(&combined));
328330
},
@@ -428,12 +430,12 @@ mod tests {
428430
};
429431

430432
test(
431-
"aff1208e69c9e8be9b584b07ebac4e48a1ee9d15ce3afe20b77a4d29e4175aa3",
433+
"5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7",
432434
vec!["a", "b", "c"],
433435
);
434436

435437
test(
436-
"b8912f7269068901f231a965adfefbc10f0eedcfa61852b103efd54dac7db3d7",
438+
"7b84bec68b13c39798c6c50e9e40a0b268e3c1634db8f4cb97314eb243d4c514",
437439
vec!["a", "b", "a"],
438440
);
439441

@@ -443,7 +445,7 @@ mod tests {
443445
);
444446

445447
test(
446-
"fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239",
448+
"cc50382cfd3c9a617741e9a85efee8752b8feb95a2cbecd6365fb21366ce0c8c",
447449
vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
448450
);
449451
}
@@ -761,7 +763,7 @@ mod tests {
761763
"0xc26B34D375533fFc4c5276282Fa5D660F3d8cbcB",
762764
];
763765
let root: H256 = array_bytes::hex2array_unchecked(
764-
"72b0acd7c302a84f1f6b6cefe0ba7194b7398afb440e1b44a9dbbe270394ca53",
766+
"7b2c6eebec6e85b2e272325a11c31af71df52bc0534d2d4f903e0ced191f022e",
765767
)
766768
.into();
767769

@@ -806,11 +808,11 @@ mod tests {
806808
)
807809
.into(),
808810
array_bytes::hex2array_unchecked(
809-
"d02609d2bbdb28aa25f58b85afec937d5a4c85d37925bce6d0cf802f9d76ba79"
811+
"1fad92ed8d0504ef6c0231bbbeeda960a40693f297c64e87b582beb92ecfb00f"
810812
)
811813
.into(),
812814
array_bytes::hex2array_unchecked(
813-
"ae3f8991955ed884613b0a5f40295902eea0e0abe5858fc520b72959bc016d4e"
815+
"0b84c852cbcf839d562d826fd935e1b37975ccaa419e1def8d219df4b83dcbf4"
814816
)
815817
.into(),
816818
],

frame/beefy-mmr/src/tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fn should_contain_mmr_digest() {
7070
ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap()
7171
)),
7272
beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked(
73-
"95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc"
73+
"200e73880940ac0b66735ffb560fa0a3989292463d262deac6ad61e78a3e46a4"
7474
)))
7575
]
7676
);
@@ -85,13 +85,13 @@ fn should_contain_mmr_digest() {
8585
ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap()
8686
)),
8787
beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked(
88-
"95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc"
88+
"200e73880940ac0b66735ffb560fa0a3989292463d262deac6ad61e78a3e46a4"
8989
))),
9090
beefy_log(ConsensusLog::AuthoritiesChange(
9191
ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap()
9292
)),
9393
beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked(
94-
"a73271a0974f1e67d6e9b8dd58e506177a2e556519a330796721e98279a753e2"
94+
"ba37d8d5d195ac8caec391da35472f9ecf1116ff1642409148b62e08896d3884"
9595
))),
9696
]
9797
);
@@ -124,7 +124,7 @@ fn should_contain_valid_leaf_data() {
124124
)
125125
},
126126
leaf_extra: array_bytes::hex2bytes_unchecked(
127-
"55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648"
127+
"5572d58c82bddf323f4fc7aecab8a8f0ad6ed2f06ab2bfb8ade36a77a45fcc68"
128128
)
129129
}
130130
);
@@ -149,7 +149,7 @@ fn should_contain_valid_leaf_data() {
149149
)
150150
},
151151
leaf_extra: array_bytes::hex2bytes_unchecked(
152-
"55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648"
152+
"5572d58c82bddf323f4fc7aecab8a8f0ad6ed2f06ab2bfb8ade36a77a45fcc68"
153153
)
154154
}
155155
);

frame/merkle-mountain-range/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ pub mod pallet {
164164
+ codec::Codec
165165
+ codec::EncodeLike
166166
+ scale_info::TypeInfo
167-
+ MaxEncodedLen;
167+
+ MaxEncodedLen
168+
+ PartialOrd;
168169

169170
/// Data stored in the leaf nodes.
170171
///

0 commit comments

Comments
 (0)