Skip to content

Commit b354b53

Browse files
authored
Merge pull request #6 from distributed-lab/feat/add-tx-verify
Feat/add tx verify
2 parents 898765a + 7c98db2 commit b354b53

File tree

5 files changed

+748
-2
lines changed

5 files changed

+748
-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+
TxMerkleProof.HashDirection[] 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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.28;
33

44
import {BlockHeaderData} from "../libs/BlockHeader.sol";
5+
import {TxMerkleProof} from "../libs/TxMerkleProof.sol";
56

67
/**
78
* @notice Interface for an SPV (Simplified Payment Verification) contract.
@@ -120,6 +121,20 @@ interface ISPVContract {
120121
*/
121122
function validateBlockHash(bytes32 blockHash_) external view returns (bool, uint256);
122123

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

contracts/libs/TxMerkleProof.sol

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
/**
10+
* @notice Possible directions for hashing:
11+
* Left: computed hash is on the left, sibling hash is on the right.
12+
* Right: computed hash is on the right, sibling hash is on the left.
13+
* Self: node has no sibling and is hashed with itself
14+
* */
15+
enum HashDirection {
16+
Left,
17+
Right,
18+
Self
19+
}
20+
21+
/**
22+
* @notice Emitted when the proof and directions array are of different length.
23+
* This error ensures that only correctly sized proofs are processed
24+
*/
25+
error InvalidLengths();
26+
27+
/**
28+
* @notice Returns true if `leaf_` can be proven to be part of a Merkle tree
29+
* defined by `root_`. Requires a `proof_` containing the sibling hashes along
30+
* the path from the leaf to the root. Each element of `directions_` indicates
31+
* the hashing order for each pair. Uses double SHA-256 hashing
32+
*/
33+
function verify(
34+
bytes32[] calldata proof_,
35+
HashDirection[] calldata directions_,
36+
bytes32 root_,
37+
bytes32 leaf_
38+
) internal pure returns (bool) {
39+
require(directions_.length == proof_.length, InvalidLengths());
40+
41+
return processProof(proof_, directions_, leaf_) == root_;
42+
}
43+
44+
/**
45+
* @notice Returns the rebuilt hash obtained by traversing the Merkle tree
46+
* from `leaf_` using `proof_`. A `proof_` is valid if and only if the rebuilt
47+
* hash matches the given tree root. The pre-images are hashed in the order
48+
* specified by the `directions_` elements. Uses double SHA-256 hashing
49+
*/
50+
function processProof(
51+
bytes32[] calldata proof_,
52+
HashDirection[] calldata directions_,
53+
bytes32 leaf_
54+
) internal pure returns (bytes32) {
55+
bytes32 computedHash_ = leaf_;
56+
uint256 proofLength_ = proof_.length;
57+
58+
for (uint256 i = 0; i < proofLength_; ++i) {
59+
if (directions_[i] == HashDirection.Left) {
60+
computedHash_ = _doubleSHA256(computedHash_, proof_[i]);
61+
} else if (directions_[i] == HashDirection.Right) {
62+
computedHash_ = _doubleSHA256(proof_[i], computedHash_);
63+
} else {
64+
computedHash_ = _doubleSHA256(computedHash_, computedHash_);
65+
}
66+
}
67+
68+
return computedHash_;
69+
}
70+
71+
function _doubleSHA256(bytes32 left_, bytes32 right_) private pure returns (bytes32) {
72+
return sha256(abi.encodePacked(sha256(abi.encodePacked(left_, right_))));
73+
}
74+
}

0 commit comments

Comments
 (0)