Skip to content

Commit

Permalink
wip: merkle tree tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mattrltrent committed Dec 17, 2024
1 parent 451a04f commit a5884e7
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 11 deletions.
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 2 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
[workspace]
members = [
"lib",
"lib2",
"program",
"script",
]
members = ["lib", "lib2", "lib3", "program", "script"]
resolver = "2"

[workspace.dependencies]
alloy-sol-types = "0.7.7"
alloy-sol-types = "0.7.7"
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ edition = "2021"

[dependencies]
alloy-sol-types = { workspace = true }
sha2 = "0.10"
3 changes: 2 additions & 1 deletion lib2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[package]
name = "merkle-tree-lib"
name = "merkletree-lib"
version = "0.1.0"
edition = "2021"

[dependencies]
alloy-sol-types = { workspace = true }
sha2 = "0.10"
hex = "0.4"
rs_merkle = "1.4.2"
6 changes: 3 additions & 3 deletions lib2/src/merkle_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ impl MerkleProof {
MerkleProof { proof }
}

pub fn verify(&self, root: Node<T>) -> Result<bool, Box<dyn std::error::Error>> {
Ok(true) // todo: implement
}
// pub fn verify(&self, root: Node<T>) -> Result<bool, Box<dyn std::error::Error>> {
// Ok(true) // todo: implement
// }
}
10 changes: 10 additions & 0 deletions lib3/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "merkle-tree-lib"
version = "0.1.0"
edition = "2021"

[dependencies]
alloy-sol-types = "0.7"
rs_merkle = "1.4.2"
sha2 = "0.10"
hex = "0.4"
141 changes: 141 additions & 0 deletions lib3/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use alloy_sol_types::sol;
use hex;
use rs_merkle::{algorithms::Sha256 as RsSha256, MerkleProof, MerkleTree};
use sha2::{Digest, Sha256}; // For hashing inputs

sol! {
struct MerkleProofStruct {
bytes32[] proof; // Array of sibling hashes
bytes32 leaf; // Hash of the leaf node
bytes32 root; // Merkle root
}
}

/// Hashes input data using the `Sha256` hashing algorithm.
fn hash_input(data: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().into()
}

/// Compute the Merkle root for a list of data.
///
/// # Arguments
/// * `data` - A vector of string slices.
///
/// # Returns
/// Merkle root as a hex string.
pub fn compute_merkle_root(data: &[&str]) -> String {
let leaves: Vec<[u8; 32]> = data.iter().map(|d| hash_input(d.as_bytes())).collect();
let tree = MerkleTree::<RsSha256>::from_leaves(&leaves);
hex::encode(tree.root().expect("Root computation failed"))
}

/// Generate a Merkle proof for a specific index in a list of data.
///
/// # Arguments
/// * `data` - A vector of string slices.
/// * `index` - The index of the leaf.
///
/// # Returns
/// A Solidity-compatible struct with proof, leaf, and root.
pub fn generate_proof_for_solidity(data: &[&str], index: usize) -> MerkleProofStruct {
let leaves: Vec<[u8; 32]> = data.iter().map(|d| hash_input(d.as_bytes())).collect();

let tree = MerkleTree::<RsSha256>::from_leaves(&leaves);
let proof = tree.proof(&[index]);
let root = tree.root().expect("Failed to compute Merkle root");
let leaf = leaves[index];

MerkleProofStruct {
proof: proof
.proof_hashes()
.iter()
.map(|hash| (*hash).into()) // Convert the proof hash into the correct format
.collect(),
leaf: leaf.into(), // Convert leaf into the required format
root: root.into(), // Convert root into the required format
}
}

/// Verify a Merkle proof.
///
/// # Arguments
/// * `proof_struct` - A Solidity-compatible proof structure.
/// * `data` - The leaf data as a string.
/// * `index` - The index of the leaf.
/// * `total_leaves` - Total number of leaves in the tree.
///
/// # Returns
/// `true` if the proof is valid, `false` otherwise.
pub fn verify_proof(
proof_struct: &MerkleProofStruct,
_data: &str, // We will no longer use `data` for recomputing the leaf
index: usize,
total_leaves: usize,
) -> bool {
let proof_hashes: Vec<[u8; 32]> = proof_struct.proof.iter().map(|hash| hash.0).collect();
let proof = MerkleProof::<RsSha256>::new(proof_hashes);

// Use the `proof_struct.leaf` directly instead of recomputing
proof.verify(
proof_struct.root.0,
&[index],
&[proof_struct.leaf.0],
total_leaves,
)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_merkle_root_computation() {
let data = vec!["a", "b", "c", "d"];
let root = compute_merkle_root(&data);
println!("Computed Merkle Root: {}", root);

assert_ne!(root, hex::encode([0u8; 32]), "Root should not be zero");
}

#[test]
fn test_merkle_proof_generation_and_verification() {
let data = vec!["a", "b", "c", "d"];
let index = 2;

// Generate Merkle proof
let proof_struct = generate_proof_for_solidity(&data, index);

println!("Merkle Root: {:?}", hex::encode(proof_struct.root));
println!("Leaf: {:?}", hex::encode(proof_struct.leaf));
println!(
"Proof Hashes: {:?}",
proof_struct
.proof
.iter()
.map(hex::encode)
.collect::<Vec<_>>()
);

// Verify proof
let is_valid = verify_proof(&proof_struct, data[index], index, data.len());
assert!(is_valid, "Proof should be valid");
}

#[test]
fn test_invalid_proof_verification() {
let data = vec!["a", "b", "c", "d"];
let index = 2;

// Generate valid proof
let mut proof_struct = generate_proof_for_solidity(&data, index);

// Tamper with the proof
proof_struct.leaf = hash_input(b"fake data 123 123").into();

// Verify tampered proof
let is_valid = verify_proof(&proof_struct, data[index], index, data.len());
assert!(!is_valid, "Tampered proof should be invalid");
}
}

0 comments on commit a5884e7

Please sign in to comment.