Skip to content

Commit b9c1abd

Browse files
authored
Merge pull request #11 from distributed-lab/proof_extension
Proof extension
2 parents e06f633 + a3a9f49 commit b9c1abd

33 files changed

+39249
-47
lines changed

README.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,61 @@ The list parameters to be passed:
3838
5. `minConfirmationsCount` - Number of required mainchain confirmation for the block to have.
3939

4040
> [!TIP]
41-
> Please check out [this test case](./test/SPVContract.test.ts#L223) for more integration information.
41+
> Please check out [this test case](./test/SPVGateway.test.ts#L223) for more integration information.
4242
4343
## Permissionlessness
4444

45-
In order for the gateway to be truly permissionless, the contract's bootstrapping needs to be permissionless as well. We are working on a "proof-of-bitcoin" ZK proof to initialize the gateway in a trustless manner.
45+
In order for the gateway to be truly permissionless, the contract's initialization needs to be permissionless as well. Alongside the regular `SPVGateway`, the repository hosts a `HistoricalSPVGateway` contract, that uses a "proof-of-bitcoin" ZK proof for its initialization. This enables verification of historical Bitcoin blocks and transactions otherwise too expensive to include. Since syncing up the gateway from Bitcoin's genesis would cost ~100 ETH on the mainnet.
4646

47-
This will enable verification of historical Bitcoin transactions otherwise too expensive to include. Syncing up the gateway from Bitcoin's genesis would cost ~100 ETH on the mainnet.
47+
# HistoricalSPVGateway
48+
49+
`HistoricalSPVGateway` is an extension of the basic `SPVGateway` contract. It uses "proof-of-bitcoin" ZK proof that compresses the entire Bitcoin block history into a single Merkle root to be used during the contract's initialization. This root can then used to verify the "historical" existence of some blocks and transactions.
50+
51+
> [!IMPORTANT]
52+
> Currently, the "proof-of-bitcoin" ZK proof is generated to the first *912384* Bitcoin blocks. The circuits source code can be found [here](https://github.com/distributed-lab/bitcoin-prover).
53+
54+
## Building the History Merkle Tree
55+
56+
In order to prove the historical block existence, you need to pass the corresponding Merkle path to a smart contract. For that, the entire historical Merkle tree needs to be built:
57+
58+
1. Fetch all block hashes from the genesis block up to the height of `provedBlocksCount - 1`.
59+
2. Split these blocks into *1024-block* chunks.
60+
3. Create Level1 Merkle trees for each chunk.
61+
4. Create an array containing all the Level1 tree roots.
62+
5. Pad the array from the previous step with zeros for its length to reach the next power of 2.
63+
6. Create a Level2 Merkle tree, using the array from the previous step as the tree's values.
64+
65+
> [!NOTE]
66+
> For the Level1 Merkle tree use `SHA256("leaf1" | blockHash)` and `SHA256("node1" | left | right)` for hashing leaves and nodes. And for the Level2 Merkle tree, `SHA256("leaf2" | level1MerkleRoot)` and `SHA256("node2" | left | right)` respectively.
67+
68+
## Verifying History Bitcoin Blocks Inclusion
69+
70+
To verify the existence of a historical Bitcoin block, call the `checkHistoryBlockInclusion` function.
71+
72+
This function requires a `HistoryBlockInclusionProofData` struct as a parameter, which contains the following fields:
73+
74+
1. `level1MerkleProof` - Level1 Merkle path for the block hash being checked.
75+
2. `level2MerkleProof` - Level2 Merkle path for the Level1 Merkle root (which is calculated from the `level1MerkleProof`)
76+
3. `blockHash` - Block hash to be checked.
77+
4. `blockHeight` - Block height of the passed block hash.
78+
79+
> [!TIP]
80+
> Please check out [this test cases](./test/HistoricalSPVGateway.test.ts#L181) for more integration information.
81+
82+
## Verifying History Bitcoin Tx Inclusion
83+
84+
In order to verify the tx existence in the proven Bitcoin history, the `checkHistoryTxInclusion` function needs to be called.
85+
86+
The list of parameters to be passed:
87+
88+
1. `merkleProof` - Merkle path for a given transaction to be checked. The Merkle path can either be built locally or by calling `gettxoutproof` on a Bitcoin node.
89+
2. `blockHeaderRaw` - Raw block header of the block to check the transaction's inclusion against.
90+
3. `txId` - Tx hash (Merkle leaf) to be checked.
91+
4. `txIndex` - The Merkle "direction bits" to decide on left or right hashing order.
92+
5. `blockInclusionProofData` - The proof data for the historical block hash inclusion.
93+
94+
> [!TIP]
95+
> Please check out [this test case](./test/HistoricalSPVGateway.test.ts#L309) for more integration information.
4896
4997
# Disclaimer
5098

contracts/HistoricalSPVGateway.sol

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {BlockHeader} from "@solarity/solidity-lib/libs/bitcoin/BlockHeader.sol";
5+
import {TxMerkleProof} from "@solarity/solidity-lib/libs/bitcoin/TxMerkleProof.sol";
6+
import {EndianConverter} from "@solarity/solidity-lib/libs/utils/EndianConverter.sol";
7+
8+
import {TargetsHelper} from "./libs/TargetsHelper.sol";
9+
import {BlockHistory} from "./libs/BlockHistory.sol";
10+
11+
import {IHistoricalSPVGateway} from "./interfaces/IHistoricalSPVGateway.sol";
12+
13+
import {SPVGateway} from "./SPVGateway.sol";
14+
15+
contract HistoricalSPVGateway is IHistoricalSPVGateway, SPVGateway {
16+
using BlockHeader for bytes;
17+
using TargetsHelper for bytes32;
18+
using EndianConverter for bytes32;
19+
using BlockHistory for bytes32[];
20+
21+
bytes32 public constant HISTORICAL_SPV_GATEWAY_STORAGE_SLOT =
22+
keccak256("spv.gateway.historical.spv.gateway.storage");
23+
24+
struct HistoricalSPVGatewayStorage {
25+
uint256 historyBlocksCount;
26+
bytes32 historyBlocksTreeRoot;
27+
}
28+
29+
function _getHistoricalSPVGatewayStorage()
30+
private
31+
pure
32+
returns (HistoricalSPVGatewayStorage storage _hspvs)
33+
{
34+
bytes32 slot_ = HISTORICAL_SPV_GATEWAY_STORAGE_SLOT;
35+
36+
assembly {
37+
_hspvs.slot := slot_
38+
}
39+
}
40+
41+
function __HistoricalSPVGateway_init(
42+
bytes calldata blockHeaderRaw_,
43+
uint64 blockHeight_,
44+
uint256 cumulativeWork_,
45+
bytes32 historyBlocksTreeRoot_,
46+
BlockHistory.HistoryProofData calldata proofData_
47+
) external initializer {
48+
(BlockHeader.HeaderData memory blockHeader_, bytes32 blockHash_) = _parseBlockHeaderRaw(
49+
blockHeaderRaw_
50+
);
51+
52+
BlockHistory.verifyHistoryProof(
53+
historyBlocksTreeRoot_,
54+
blockHash_,
55+
blockHeight_,
56+
cumulativeWork_,
57+
proofData_
58+
);
59+
60+
_initialize(blockHeader_, blockHash_, blockHeight_, cumulativeWork_);
61+
62+
_getHistoricalSPVGatewayStorage().historyBlocksCount = blockHeight_;
63+
_getHistoricalSPVGatewayStorage().historyBlocksTreeRoot = historyBlocksTreeRoot_;
64+
}
65+
66+
/// @inheritdoc IHistoricalSPVGateway
67+
function checkHistoryTxInclusion(
68+
bytes32[] calldata merkleProof_,
69+
bytes calldata blockHeaderRaw_,
70+
bytes32 txId_,
71+
uint256 txIndex_,
72+
HistoryBlockInclusionProofData calldata blockInclusionProofData_
73+
) external view returns (bool) {
74+
(BlockHeader.HeaderData memory blockHeader_, bytes32 blockHash_) = blockHeaderRaw_
75+
.parseBlockHeader(true);
76+
77+
require(
78+
blockHash_ == blockInclusionProofData_.blockHash,
79+
DifferentBlockHashes(blockHash_, blockInclusionProofData_.blockHash)
80+
);
81+
82+
require(
83+
checkHistoryBlockInclusion(blockInclusionProofData_),
84+
BlockHashNotInHistory(blockHash_)
85+
);
86+
87+
bytes32 leRoot_ = blockHeader_.merkleRoot.bytes32BEtoLE();
88+
89+
return TxMerkleProof.verify(merkleProof_, leRoot_, txId_, txIndex_);
90+
}
91+
92+
/// @inheritdoc IHistoricalSPVGateway
93+
function checkHistoryBlockInclusion(
94+
HistoryBlockInclusionProofData calldata inclusionProofData_
95+
) public view returns (bool) {
96+
bytes32 level1Root_ = inclusionProofData_.level1MerkleProof.processLevel1Proof(
97+
inclusionProofData_.blockHash,
98+
inclusionProofData_.blockHeight
99+
);
100+
101+
return
102+
inclusionProofData_.level2MerkleProof.verifyLevel2Proof(
103+
getHistoryBlocksTreeRoot(),
104+
level1Root_,
105+
BlockHistory.getChunkNumber(getHistoryBlocksCount()),
106+
BlockHistory.getChunkNumber(inclusionProofData_.blockHeight)
107+
);
108+
}
109+
110+
/// @inheritdoc IHistoricalSPVGateway
111+
function getHistoryBlocksCount() public view returns (uint256) {
112+
return _getHistoricalSPVGatewayStorage().historyBlocksCount;
113+
}
114+
115+
/// @inheritdoc IHistoricalSPVGateway
116+
function getHistoryBlocksTreeRoot() public view returns (bytes32) {
117+
return _getHistoricalSPVGatewayStorage().historyBlocksTreeRoot;
118+
}
119+
}

contracts/SPVGateway.sol

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ contract SPVGateway is ISPVGateway, Initializable {
5151
});
5252
bytes32 genesisBlockHash_ = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f;
5353

54-
_addBlock(genesisBlockHeader_, genesisBlockHash_, 0);
55-
56-
emit MainchainHeadUpdated(0, genesisBlockHash_);
54+
_initialize(genesisBlockHeader_, genesisBlockHash_, 0, 0);
5755
}
5856

5957
function __SPVGateway_init(
@@ -65,15 +63,7 @@ contract SPVGateway is ISPVGateway, Initializable {
6563
blockHeaderRaw_
6664
);
6765

68-
require(
69-
blockHeight_ == 0 || TargetsHelper.isTargetAdjustmentBlock(blockHeight_),
70-
InvalidInitialBlockHeight(blockHeight_)
71-
);
72-
73-
_addBlock(blockHeader_, blockHash_, blockHeight_);
74-
_getSPVGatewayStorage().lastEpochCumulativeWork = cumulativeWork_;
75-
76-
emit MainchainHeadUpdated(blockHeight_, blockHash_);
66+
_initialize(blockHeader_, blockHash_, blockHeight_, cumulativeWork_);
7767
}
7868

7969
function _getSPVGatewayStorage() private pure returns (SPVGatewayStorage storage _spvs) {
@@ -248,6 +238,31 @@ contract SPVGateway is ISPVGateway, Initializable {
248238
return getBlockHash(getBlockHeight(blockHash_)) == blockHash_;
249239
}
250240

241+
function _initialize(
242+
BlockHeader.HeaderData memory blockHeader_,
243+
bytes32 blockHash_,
244+
uint64 blockHeight_,
245+
uint256 cumulativeWork_
246+
) internal onlyInitializing {
247+
_addBlock(blockHeader_, blockHash_, blockHeight_);
248+
249+
if (blockHeight_ > 0) {
250+
uint256 lastEpochCumulativeWork_ = cumulativeWork_;
251+
252+
if (!TargetsHelper.isTargetAdjustmentBlock(blockHeight_)) {
253+
bytes32 target_ = TargetsHelper.bitsToTarget(blockHeader_.bits);
254+
255+
lastEpochCumulativeWork_ -= target_.countCumulativeWork(
256+
TargetsHelper.getEpochBlockNumber(blockHeight_) + 1
257+
);
258+
}
259+
260+
_getSPVGatewayStorage().lastEpochCumulativeWork = lastEpochCumulativeWork_;
261+
}
262+
263+
emit MainchainHeadUpdated(blockHeight_, blockHash_);
264+
}
265+
251266
function _addBlock(
252267
BlockHeader.HeaderData memory blockHeader_,
253268
bytes32 blockHash_,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import {ISPVGateway} from "./ISPVGateway.sol";
5+
6+
/**
7+
* @title IHistoricalSPVGateway
8+
* @notice An interface for the Historical SPV Gateway that extends ISPVGateway.
9+
*
10+
* This contract allows for the trustless initialization of the gateway using a "proof-of-bitcoin" ZK proof.
11+
* It also includes functions to verify the inclusion of historical blocks and transactions using Merkle proofs.
12+
*/
13+
interface IHistoricalSPVGateway is ISPVGateway {
14+
/**
15+
* @notice Data structure for block inclusion proof in history.
16+
* @param level1MerkleProof Merkle proof for the first level of the history tree.
17+
* @param level2MerkleProof Merkle proof for the second level of the history tree.
18+
* @param blockHash Hash of the block being proved.
19+
* @param blockHeight Height of the block in the Bitcoin blockchain.
20+
*/
21+
struct HistoryBlockInclusionProofData {
22+
bytes32[] level1MerkleProof;
23+
bytes32[] level2MerkleProof;
24+
bytes32 blockHash;
25+
uint256 blockHeight;
26+
}
27+
28+
/**
29+
* @notice Error thrown when block header hash and inclusion proof hash differ.
30+
* @param blockHeaderHash Hash of the block header.
31+
* @param inclusionProofHash Hash from the inclusion proof.
32+
*/
33+
error DifferentBlockHashes(bytes32 blockHeaderHash, bytes32 inclusionProofHash);
34+
35+
/**
36+
* @notice Error thrown when a block hash is not found in the history.
37+
* @param blockHash Hash of the block not found in history.
38+
*/
39+
error BlockHashNotInHistory(bytes32 blockHash);
40+
41+
/**
42+
* @notice Checks whether a transaction is included in a historical block.
43+
* @param merkleProof_ Merkle proof for transaction inclusion.
44+
* @param blockHeaderRaw_ Raw block header data.
45+
* @param txId_ Transaction ID to verify.
46+
* @param txIndex_ Index of the transaction in the block.
47+
* @param blockInclusionProofData_ Proof data for block inclusion in history.
48+
* @return Returns true if the transaction is included in the historical block, false - otherwise.
49+
*/
50+
function checkHistoryTxInclusion(
51+
bytes32[] calldata merkleProof_,
52+
bytes calldata blockHeaderRaw_,
53+
bytes32 txId_,
54+
uint256 txIndex_,
55+
HistoryBlockInclusionProofData calldata blockInclusionProofData_
56+
) external view returns (bool);
57+
58+
/**
59+
* @notice Checks whether a block is included in the historical blocks tree.
60+
* @param inclusionProofData_ Proof data for block inclusion in history.
61+
* @return Returns true if the block is included in the historical blocks tree, false - otherwise.
62+
*/
63+
function checkHistoryBlockInclusion(
64+
HistoryBlockInclusionProofData calldata inclusionProofData_
65+
) external view returns (bool);
66+
67+
/**
68+
* @notice Gets the total number of historical blocks stored.
69+
* @return Returns the number of historical blocks.
70+
*/
71+
function getHistoryBlocksCount() external view returns (uint256);
72+
73+
/**
74+
* @notice Gets the root of the historical blocks tree.
75+
* @return Returns the root hash of the historical blocks tree.
76+
*/
77+
function getHistoryBlocksTreeRoot() external view returns (bytes32);
78+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
/**
5+
* @title IHistoryProofVerifier
6+
* @notice An interface for verifying a historical proof using a ZK-SNARK.
7+
*/
8+
interface IHistoryProofVerifier {
9+
/**
10+
* @notice Verifies a ZK-SNARK proof against a set of public inputs.
11+
* @param proof_ The serialized ZK-SNARK proof.
12+
* @param publicInputs_ An array of public inputs required for proof verification.
13+
* @return A boolean value indicating whether the proof is valid (true) or not (false).
14+
*/
15+
function verify(
16+
bytes calldata proof_,
17+
bytes32[] calldata publicInputs_
18+
) external view returns (bool);
19+
}

0 commit comments

Comments
 (0)