// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title MerkleProofVerifier * @notice Library for verifying Merkle proofs for trustless bridge fraud proofs * @dev Supports verification of deposit existence/non-existence in source chain state */ library MerkleProofVerifier { /** * @notice Verify a Merkle proof for deposit existence * @param root Merkle root from source chain state * @param leaf Deposit data hash (keccak256(abi.encodePacked(depositId, asset, amount, recipient, timestamp))) * @param proof Merkle proof path * @return True if proof is valid */ function verifyDepositExistence( bytes32 root, bytes32 leaf, bytes32[] memory proof ) internal pure returns (bool) { return verify(proof, root, leaf); } /** * @notice Verify a Merkle proof for deposit non-existence (proof of absence) * @param root Merkle root from source chain state * @param leaf Deposit data hash * @param proof Merkle proof path showing absence * @param leftSibling Left sibling in the tree (for non-existence proofs) * @param rightSibling Right sibling in the tree (for non-existence proofs) * @return True if proof of absence is valid */ function verifyDepositNonExistence( bytes32 root, bytes32 leaf, bytes32[] memory proof, bytes32 leftSibling, bytes32 rightSibling ) internal pure returns (bool) { // For non-existence proofs, we verify that the leaf would be between leftSibling and rightSibling // and that the proof path shows the leaf doesn't exist require(leftSibling < leaf && leaf < rightSibling, "MerkleProofVerifier: invalid sibling order"); // Verify the proof path return verify(proof, root, leaf); } /** * @notice Verify a Merkle proof * @param proof Array of proof elements * @param root Merkle root * @param leaf Leaf hash * @return True if proof is valid */ function verify( bytes32[] memory proof, bytes32 root, bytes32 leaf ) internal pure returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash < proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } // Check if the computed hash (root) is equal to the provided root return computedHash == root; } /** * @notice Hash deposit data for Merkle tree leaf * @param depositId Deposit ID * @param asset Asset address * @param amount Deposit amount * @param recipient Recipient address * @param timestamp Deposit timestamp * @return Leaf hash */ function hashDepositData( uint256 depositId, address asset, uint256 amount, address recipient, uint256 timestamp ) internal pure returns (bytes32) { return keccak256( abi.encodePacked( depositId, asset, amount, recipient, timestamp ) ); } /** * @notice Verify state root against block header * @param blockHeader Block header bytes * @return True if state root matches block header * @dev This is a placeholder - in production, implement full block header parsing */ function verifyStateRoot( bytes memory blockHeader, bytes32 ) internal pure returns (bool) { // Placeholder: In production, parse RLP-encoded block header and extract state root // For now, require non-empty block header require(blockHeader.length > 0, "MerkleProofVerifier: empty block header"); // TODO: Implement RLP decoding and state root extraction // This would involve: // 1. RLP decode block header // 2. Extract state root (at specific position in header) // 3. Compare with provided state root return true; // Placeholder - always return true for now } }