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

Optimize merkle proofs for efficient verification in Solidity #12857

Merged
merged 7 commits into from
Jan 12, 2023

Conversation

doubledup
Copy link
Contributor

@doubledup doubledup commented Dec 7, 2022

This is an optimisation to proof verification in the beefy-mmr pallet. It's a breaking change for proof verification as it modifies the order in which leaves are hashed. For more context, see #12820.

Sorting each pair of hashes is more efficient because it removes the need to determine the order in which hashes should be combined. The order in which hashes should be combined can be stored in a proof by:

  • storing a boolean for each hash in the proof representing the side on which it should be hashed.
  • storing the number of leaves and leaf index and walking from the leaf to the root of the complete binary tree with the given number of leaves.
  • sorting the hashes when creating & verifying proofs.

This sorting is more efficient than the alternatives because it is a single comparison between hashes and requires no extra space in the proof structure, unlike the other options.

There is no Polkadot companion PR, as there are no changes to the APIs used by Polkadot.

Gas estimates

I've put up a separate repo to show the different proof structures working with verification from Snowbridge (Unoptimised) & OpenZeppelin (Optimised). The gas estimates I get are:

MerkleProofTest:testOptimisedProof() (gas: 11179)
MerkleProofTest:testUnoptimisedProofLengthIndex() (gas: 18899)
MerkleProofTest:testUnoptimisedProofSidesArray() (gas: 18079)

This looks like a 38% reduction in gas costs (100 - 11179/18079*100), but it is an estimate and please call out any issues you see with the testing process.

Tests

OpenZeppelin MerkleProof verification

The test case testOptimisedProofBeefyMerkleTree uses the OpenZeppelin MerkleProof library to verify a proof generated after this change. This MerkleProof library uses the optimization in this PR, so this test shows that this optimization is compatible with an existing proof library. See the test case for the code used to generate the proof.

Substrate

Running cargo test --features=runtime-benchmarks only produces failures for some benchmarks, but the same errors occur on master, at least as of 13664c3.

Polkadot

Set up a Polkadot repo that uses this branch for its Substrate dependencies per the CONTRIBUTING doc. Running cargo test --features=runtime-benchmarks exits successfully. As far as I can tell, this should be a breaking change, but it's possible that there are no test cases in Polkadot for the proofs generated by beefy-mmr.

@cla-bot-2021
Copy link

cla-bot-2021 bot commented Dec 7, 2022

User @doubledup, please sign the CLA here.

@doubledup doubledup changed the title Sort hashes when generating & verifying MMR proofs Optimize merkle proofs for efficient verification in Solidity Dec 13, 2022
Copy link
Contributor

@acatangiu acatangiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To add some context: this PR does not target MMR code or logic, but only BEEFY authority set (validator set) Merkle proof.

BEEFY justifications also known as SignedCommitment identify the authority set only by the ValidatorSetId.

pallet-beefy-mmr can then be used to get the BeefyAuthoritySet which ties a ValidatorSetId to a MerkleRoot over all validator keys in that set.

Changing how the above MerkleRoot is built (its internal hashing mechanism) should have no effect on BEEFY protocol or on pallet-mmr. It only affect Light Clients that need this root to confirm that the commitments are signed by the correct validator set.

As such, afaict PR looks good.

@vgeddes
Copy link
Contributor

vgeddes commented Dec 23, 2022

@acatangiu @doubledup beefy-merkle-tree is also used to generate the extra data stored in MMR leafs. Concretely, this is the root for the latest parachain heads in the current relay chain block:

https://github.com/paritytech/polkadot/blob/8ac29d3846075ec98fbb386d4aa829687df97efe/runtime/rococo/src/lib.rs#L1285

So our BEEFY light client has to verify proofs both for validators and parachain headers.

Copy link
Contributor

@Lederstrumpf Lederstrumpf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm 👍. I've looked into it and this trick remains compatible with batch proofs & prefix/ancestry proofs (say, prove that mmr root r descends from an older root r').

Prefix proofs won't matter for these authority set membership checks, but batch proofs I reckon you'll eventually wanna use for gas savings in verifyCommitment, so that membership of the entire subset of validators being sampled can be checked within one proof verification (back of the envelope saving third of proof items in worst case for validator set of 300 with subset sample size 10).

@doubledup
Copy link
Contributor Author

@acatangiu Removed double-hashing as it's not strictly necessary for this change, though it is useful for preventing second-preimage attacks. I can't find an existing issue or PR discussing "double hash" or "second preimage" - is it worth opening an issue to discuss it?

@doubledup doubledup marked this pull request as ready for review January 10, 2023 08:32
@acatangiu acatangiu added A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D2-notlive 💤 PR contains changes in a runtime directory that is not deployed to a chain that requires an audit. labels Jan 10, 2023
@Lederstrumpf
Copy link
Contributor

bot merge

@paritytech-processbot paritytech-processbot bot merged commit f9d1dcd into paritytech:master Jan 12, 2023
@yrong yrong mentioned this pull request Feb 16, 2023
2 tasks
ltfschoen pushed a commit to ltfschoen/substrate that referenced this pull request Feb 22, 2023
…tech#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
ark0f pushed a commit to gear-tech/substrate that referenced this pull request Feb 27, 2023
…tech#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
Lederstrumpf added a commit to Lederstrumpf/substrate that referenced this pull request May 18, 2023
Lederstrumpf added a commit to Lederstrumpf/substrate that referenced this pull request May 18, 2023
…paritytech#12857)"

This reverts commit f9d1dcd since we
still require commitment to the leaves - see paritytech#12820.
Lederstrumpf added a commit to Lederstrumpf/substrate that referenced this pull request May 18, 2023
…paritytech#12857)"

This reverts commit f9d1dcd since we
still require commitment to the leaves - see paritytech#12820.
paritytech-processbot bot pushed a commit that referenced this pull request May 22, 2023
…#12857)" (#14176)

* Revert "Optimize merkle proofs for efficient verification in Solidity (#12857)"

This reverts commit f9d1dcd since we
still require commitment to the leaves - see #12820.

* remove PartialOrd trait from mmr hash type
gpestana pushed a commit that referenced this pull request May 27, 2023
…#12857)" (#14176)

* Revert "Optimize merkle proofs for efficient verification in Solidity (#12857)"

This reverts commit f9d1dcd since we
still require commitment to the leaves - see #12820.

* remove PartialOrd trait from mmr hash type
@Polkadot-Forum
Copy link

This pull request has been mentioned on Polkadot Forum. There might be relevant details there:

https://forum.polkadot.network/t/exploring-beefy-authority-set-subsampling-optimisations/3106/1

nathanwhit pushed a commit to nathanwhit/substrate that referenced this pull request Jul 19, 2023
…paritytech#12857)" (paritytech#14176)

* Revert "Optimize merkle proofs for efficient verification in Solidity (paritytech#12857)"

This reverts commit f9d1dcd since we
still require commitment to the leaves - see paritytech#12820.

* remove PartialOrd trait from mmr hash type
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D2-notlive 💤 PR contains changes in a runtime directory that is not deployed to a chain that requires an audit.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants