Skip to content

Commit 006533f

Browse files
committed
added function to verify that tx is included in the block;
added lib for merkle proof verification
1 parent 898765a commit 006533f

File tree

5 files changed

+523
-2
lines changed

5 files changed

+523
-2
lines changed

contracts/SPVContract.sol

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ pragma solidity ^0.8.28;
44
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
55

66
import {LibSort} from "solady/src/utils/LibSort.sol";
7+
import {LibBit} from "solady/src/utils/LibBit.sol";
78

89
import {BlockHeader, BlockHeaderData} from "./libs/BlockHeader.sol";
910
import {TargetsHelper} from "./libs/TargetsHelper.sol";
11+
import {TxMerkleProof} from "./libs/TxMerkleProof.sol";
1012

1113
import {ISPVContract} from "./interfaces/ISPVContract.sol";
1214

@@ -149,8 +151,17 @@ contract SPVContract is ISPVContract, Initializable {
149151
}
150152

151153
/// @inheritdoc ISPVContract
152-
function getBlockMerkleRoot(bytes32 blockHash_) external view returns (bytes32) {
153-
return _getBlockHeader(blockHash_).merkleRoot;
154+
function verifyTx(
155+
bytes32 blockHash_,
156+
bytes32 txid_,
157+
bytes32[] calldata merkleProof_,
158+
bytes calldata directions_
159+
) external view returns (bool) {
160+
bytes32 blockMerkleRoot_ = getBlockMerkleRoot(blockHash_);
161+
162+
bytes32 reversedRoot_ = bytes32(LibBit.reverseBytes(uint256(blockMerkleRoot_)));
163+
164+
return TxMerkleProof.verify(merkleProof_, directions_, reversedRoot_, txid_);
154165
}
155166

156167
/// @inheritdoc ISPVContract
@@ -173,6 +184,11 @@ contract SPVContract is ISPVContract, Initializable {
173184
return _getSPVContractStorage().lastEpochCumulativeWork;
174185
}
175186

187+
/// @inheritdoc ISPVContract
188+
function getBlockMerkleRoot(bytes32 blockHash_) public view returns (bytes32) {
189+
return _getBlockHeader(blockHash_).merkleRoot;
190+
}
191+
176192
/// @inheritdoc ISPVContract
177193
function getMainchainHead() public view returns (bytes32) {
178194
return _getSPVContractStorage().mainchainHead;

contracts/interfaces/ISPVContract.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,20 @@ interface ISPVContract {
120120
*/
121121
function validateBlockHash(bytes32 blockHash_) external view returns (bool, uint256);
122122

123+
/**
124+
* @notice Verifies that given txid is included in the specified block
125+
* @param blockHash_ The hash of the block in which to verify the transaction
126+
* @param txid_ The transaction id to verify
127+
* @param merkleProof_ The array of hashes used to build the Merkle root
128+
* @param directions_ The string indicating the hashing directions for the Merkle proof
129+
*/
130+
function verifyTx(
131+
bytes32 blockHash_,
132+
bytes32 txid_,
133+
bytes32[] memory merkleProof_,
134+
bytes calldata directions_
135+
) external view returns (bool);
136+
123137
/**
124138
* @notice Returns the cumulative work of the last epoch.
125139
* This represents the total difficulty accumulated up to the last epoch boundary

contracts/libs/TxMerkleProof.sol

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
/**
5+
* @notice A library for verifying transaction inclusion in Bitcoin block.
6+
* Provides functions for processing and verifying Merkle tree proofs
7+
*/
8+
library TxMerkleProof {
9+
error InvalidLengths();
10+
11+
function verify(
12+
bytes32[] memory proof_,
13+
bytes calldata directions_,
14+
bytes32 root_,
15+
bytes32 leaf_
16+
) internal pure returns (bool) {
17+
require(directions_.length == proof_.length, InvalidLengths());
18+
19+
return processProof(proof_, directions_, leaf_) == root_;
20+
}
21+
22+
function processProof(
23+
bytes32[] memory proof_,
24+
bytes calldata directions_,
25+
bytes32 leaf_
26+
) internal pure returns (bytes32) {
27+
bytes32 computedHash_ = leaf_;
28+
uint256 proofLength_ = proof_.length;
29+
30+
for (uint256 i = 0; i < proofLength_; ++i) {
31+
if (directions_[i] == hex"00") {
32+
computedHash_ = _sha256(computedHash_, proof_[i]);
33+
} else if (directions_[i] == hex"01") {
34+
computedHash_ = _sha256(proof_[i], computedHash_);
35+
} else {
36+
computedHash_ = _sha256(computedHash_, computedHash_);
37+
}
38+
}
39+
40+
return computedHash_;
41+
}
42+
43+
function _sha256(bytes32 left_, bytes32 right_) private pure returns (bytes32) {
44+
return sha256(abi.encodePacked(sha256(abi.encodePacked(left_, right_))));
45+
}
46+
}

0 commit comments

Comments
 (0)