This project demonstrates a Merkle Proof verification system. It includes:
- A Solidity smart contract to verify whether a transaction (leaf) belongs to a Merkle tree given its proof.
- A Rust program using the
ethers
library to interact with the smart contract, construct proofs, and verify their validity.
The MerkleProofVerifier
contract provides a method to verify the inclusion of a leaf in a Merkle tree based on its proof.
- File:
MerkleProofVerifier.sol
- Language: Solidity 0.8+
- Functionality:
verify
:- Inputs:
bytes32[] proof
: The Merkle proof containing sibling hashes.bytes32 root
: The root hash of the Merkle tree.bytes32 leaf
: The hash of the leaf node (transaction hash).uint256 index
: The index of the leaf node in the Merkle tree.
- Returns:
bool
:true
if the leaf is part of the Merkle tree,false
otherwise.
- Inputs:
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf,
uint256 index
) public pure returns (bool) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (index % 2 == 0) {
computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
} else {
computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
}
index = index / 2;
}
return computedHash == root;
}
The Rust program interacts with the deployed smart contract to verify Merkle proofs on-chain. It uses the ethers
crate for Ethereum interaction.
-
Connect to Ethereum Node:
- Uses an HTTP provider (
http://localhost:8545
) to communicate with an Ethereum node.
- Uses an HTTP provider (
-
Define Smart Contract:
- The contract is defined using
ethers::abigen!
to generate type-safe bindings.
- The contract is defined using
-
Convert Data to Bytes:
- Transaction hashes, root, and proof elements are converted to the
bytes32
format.
- Transaction hashes, root, and proof elements are converted to the
-
Merkle Proof Construction:
- Example transactions and proof elements are hardcoded for testing.
-
Verify Proof:
- Calls the
verify
method of theMerkleProofVerifier
contract to validate the proof.
- Calls the
let transactions = vec![
"0x2ebbeb5ba2fb0742366d00121750a978d3b72fbec340750fee872a5763ff46f7",
"0x5194ead3df889a15f3d33e47bcc128114dbb9dcd1147f2de8a8ffba6a815f248",
// Additional transactions...
];
let merkle_root = "0x5d68e1af5c97e158bf9eb63489d05ae7da229e264607323c5ec51a927fb90fe1";
let index = 50; // Example index
let is_valid = contract
.verify(proof_elements, root, leaf, index.into())
.call()
.await?;
println!("Is the transaction order valid? {}", is_valid);
-
Deploy the Contract:
- Compile and deploy
MerkleProofVerifier.sol
using your preferred Ethereum deployment method. - Note the deployed contract address.
- Compile and deploy
-
Run the Rust Program:
- Update the Rust code with the deployed contract address.
- Ensure the Ethereum node URL is correct.
- Build and run the Rust program:
cargo run
-
Check Output:
- The program will print whether the Merkle proof is valid for the given transaction.
Proof for transaction 0x66106f1d7f95c702b9f8e2dc0d6be112857ed9a107993f2df9dab1ea26fb1580 at index 50: ["0x6b90f6d59fb5cc6950407d5343d8dcbfe80c684450a47cf9526aeb0b33f8f0ce", ...]
Is the transaction order valid? true