Skip to content

Commit 0a2fa7a

Browse files
authored
Add ERC: Simplified Payment Verification Gateway
Merged by EIP-Bot.
1 parent 37426cb commit 0a2fa7a

File tree

4 files changed

+1231
-0
lines changed

4 files changed

+1231
-0
lines changed

ERCS/erc-8002.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
eip: 8002
3+
title: Simplified Payment Verification Gateway
4+
description: Trustless singleton contract for on-chain verification of Bitcoin transactions through block headers
5+
author: Artem Chystiakov (@arvolear), Oleh Komendant (@Hrom131)
6+
discussions-to: https://ethereum-magicians.org/t/eip-8002-simplified-payment-verification-gateway/25038
7+
status: Draft
8+
type: Standards Track
9+
category: ERC
10+
created: 2025-08-07
11+
---
12+
13+
## Abstract
14+
15+
Introduce a singleton contract for on-chain verification of transactions that happened on Bitcoin. The contract is available at "0xTODO" <!-- TODO -->, acting as a trustless Simplified Payment Verification (SPV) gateway where anyone can submit Bitcoin block headers. The gateway maintains the mainchain of blocks and allows the existence of Bitcoin transactions to be verified via Merkle proofs.
16+
17+
## Motivation
18+
19+
Ethereum's long-term mission has always been to revolutionize the financial world through decentralization, trustlessness, and programmable value enabled by smart contracts. Many great use cases have been discovered so far, including the renaissance of Decentralized Finance (DeFi), emergence of Real-World Assets (RWA), and rise of privacy-preserving protocols.
20+
21+
However, one gem has been unreachable to date -- Bitcoin. Due to its extremely constrained programmability, one can only hold and transfer bitcoins in a trustless manner. This EIP tries to expand its capabilities by laying a solid foundation for bitcoins to be also used in various EVM-based DeFi protocols, unlocking a whole new trillion-dollar market.
22+
23+
The singleton SPV gateway contract defined in this proposal acts as a trustless one-way bridge between Bitcoin and Ethereum, already enabling use cases such as using _native_ BTC as a lending collateral for stablecoin loans. Moreover, with the recent breakthroughs in the BitVM technology, the full-fledged, ownerless two-way bridge may soon become a reality, powering the permissionless and wrapless issuance of BTC on Ethereum.
24+
25+
## Specification
26+
27+
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
28+
29+
### General
30+
31+
#### Bitcoin Block Header Structure
32+
33+
In Bitcoin, each block contains a header, which has a fixed size of 80 bytes and follows the following structure:
34+
35+
| Field | Size | Format | Description |
36+
| :------------- | :------- | :----------------- | :------------------------------------------------------------------- |
37+
| Version | 4 bytes | little-endian | The version number of the block. |
38+
| Previous Block | 32 bytes | natural byte order | The block hash of a previous block this block is building on top of. |
39+
| Merkle Root | 32 bytes | natural byte order | A fingerprint for all of the transactions included in the block. |
40+
| Time | 4 bytes | little-endian | The current time as a Unix timestamp. |
41+
| Bits | 4 bytes | little-endian | A compact representation of the current target (difficulty). |
42+
| Nonce | 4 bytes | little-endian | A 32-bit number which miners compute to find a valid block hash. |
43+
44+
The fields within the block header are sequentially ordered as presented in the table above.
45+
46+
#### Difficulty Adjustment Mechanism
47+
48+
Bitcoin's Proof-of-Work (PoW) consensus mechanism has a probabilistic finality, thus relying on a dynamic **difficulty target** adjustments. The target's initial value is set to `0x00000000ffff0000000000000000000000000000000000000000000000000000`, which also serves as the minimum difficulty threshold.
49+
50+
The `target` is recalculated every 2016 blocks (approximately every two weeks), a period commonly referred to as a **difficulty adjustment period**.
51+
52+
The expected duration for each adjustment period is 1,209,600 seconds (2016 blocks * 10 minutes/block). The new `target` value is derived by multiplying the current `target` by the ratio of the actual time taken to mine the preceding 2016 blocks to this expected duration.
53+
54+
To prevent drastic difficulty fluctuations, the adjustment multiplier is capped at `4x` and `1/4x` respectively.
55+
56+
#### Block Header Validation Rules
57+
58+
For a Bitcoin block header to be considered valid and accepted into the chain, it MUST adhere to the following consensus rules:
59+
60+
1. **Chain Cohesion**: The `Previous Block` hash field MUST reference the hash of a valid block that is present in the set of existing block headers.
61+
62+
2. **Timestamp Rules**:
63+
* The `Time` field MUST be strictly greater than the **Median Time Past (MTP)** of the previous 11 blocks.
64+
* The `Time` field MUST NOT be more than 2 hours in the future relative to the validating node's network-adjusted time.
65+
66+
3. **PoW Constraint**: When the block header is hashed twice using `SHA256`, the resulting hash MUST be less than or equal to the current `target` value, as derived from the difficulty adjustment mechanism.
67+
68+
#### Transaction Inclusion Structure
69+
70+
Every Bitcoin block header has a field `Merkle Root` that corresponds to a Merkle root of the transactions tree included in this block.
71+
72+
The transactions Merkle tree is built recursively performing double `SHA256` hash on each pair of sibling nodes, where the tree leaves are the double `SHA256` hash of the raw transaction bytes. In case the node has no siblings, the hashing is done over the node with itself.
73+
74+
To verify the transaction inclusion into the block, one SHOULD build the Merkle root from the ground up and compare it with the `Merkle Root` stored in the selected block header. The corresponding Merkle path and hashing direction bits can be obtained and processed by querying a Bitcoin full node.
75+
76+
#### Mainchain Definition
77+
78+
Bitcoin's **mainchain** is determined not just by its length, but by the greatest **cumulative PoW** among all valid competing chains. This cumulative work represents the total computational effort expended to mine all blocks within a specific chain.
79+
80+
The work contributed by a single block is inversely proportional to its `target` value. Specifically, the work of a block can be calculated as `(2**256 - 1) / (target + 1)`. The `target` value for a block is derived from its `Bits` field, where the first byte encodes the required left hand bit shift, and the other three bytes the actual target value.
81+
82+
The total cumulative work of a chain is the sum of the work values of all blocks within that chain. A block is considered part of the mainchain if it extends the chain with the greatest cumulative PoW.
83+
84+
### SPV Gateway
85+
86+
The `SPVGateway` contract MUST provide a permissionless mechanism for its initialization. This mechanism MUST allow for the submission of a valid Bitcoin block header, its corresponding block height, and the cumulative PoW up to that block, without requiring special permissions.
87+
88+
The `SPVGateway` MUST implement the following interface:
89+
90+
```solidity
91+
pragma solidity ^0.8.0;
92+
93+
/**
94+
* @notice Interface for a Simplified Payment Verification Gateway contract.
95+
*/
96+
interface ISPVGateway {
97+
/**
98+
* @notice Represents the essential data contained within a Bitcoin block header
99+
* @param prevBlockHash The hash of the previous block
100+
* @param merkleRoot The Merkle root of the transactions in the block
101+
* @param version The block version number
102+
* @param time The block's timestamp
103+
* @param nonce The nonce used for mining
104+
* @param bits The encoded difficulty target for the block
105+
*/
106+
struct BlockHeaderData {
107+
bytes32 prevBlockHash;
108+
bytes32 merkleRoot;
109+
uint32 version;
110+
uint32 time;
111+
uint32 nonce;
112+
bytes4 bits;
113+
}
114+
115+
/**
116+
* MUST be emitted whenever the mainchain head changed (e.g. in the `addBlockHeader`, `addBlockHeaderBatch` functions)
117+
*/
118+
event MainchainHeadUpdated(
119+
uint64 indexed newMainchainHeight,
120+
bytes32 indexed newMainchainHead
121+
);
122+
123+
/**
124+
* MUST be emitted whenever the new block header added to the SPV contract state
125+
* (e.g. in the `addBlockHeader`, `addBlockHeaderBatch` functions)
126+
*/
127+
event BlockHeaderAdded(uint64 indexed blockHeight, bytes32 indexed blockHash);
128+
129+
/**
130+
* @notice Adds a single raw block header to the contract.
131+
* The block header is validated before being added
132+
* @param blockHeaderRaw The raw block header bytes
133+
*/
134+
function addBlockHeader(bytes calldata blockHeaderRaw) external;
135+
136+
/**
137+
* @notice OPTIONAL Function that adds a batch of the block headers to the contract.
138+
* Each block header is validated and added sequentially
139+
* @param blockHeaderRawArray An array of raw block header bytes
140+
*/
141+
function addBlockHeaderBatch(bytes[] calldata blockHeaderRawArray) external;
142+
143+
/**
144+
* @notice Checks that given txId is included in the specified block with a minimum number of confirmations.
145+
* @param merkleProof The array of hashes used to build the Merkle root
146+
* @param blockHash The hash of the block in which to verify the transaction
147+
* @param txId The transaction id to verify
148+
* @param txIndex The index of the transaction in the block's Merkle tree
149+
* @param minConfirmationsCount The minimum number of confirmations required for the block
150+
* @return True if the txId is present in the block's Merkle tree and the block has at least minConfirmationsCount confirmations, false otherwise
151+
*/
152+
function checkTxInclusion(
153+
bytes32[] memory merkleProof,
154+
bytes32 blockHash,
155+
bytes32 txId,
156+
uint256 txIndex,
157+
uint256 minConfirmationsCount
158+
) external view returns (bool);
159+
160+
/**
161+
* @notice Returns the hash of the current mainchain head.
162+
* This represents the highest block on the most accumulated work chain
163+
* @return The hash of the mainchain head
164+
*/
165+
function getMainchainHead() external view returns (bytes32);
166+
167+
/**
168+
* @notice Returns the height of the current mainchain head.
169+
* This represents the highest block number on the most accumulated work chain
170+
* @return The height of the mainchain head
171+
*/
172+
function getMainchainHeight() external view returns (uint64);
173+
174+
/**
175+
* @notice Returns the block header data for a given block hash.
176+
* @param blockHash The hash of the block
177+
* @return The block header data
178+
*/
179+
function getBlockHeader(bytes32 blockHash) external view returns (BlockHeaderData memory);
180+
181+
/**
182+
* @notice Returns the current status of a given block
183+
* @param blockHash The hash of the block to check
184+
* @return isInMainchain True if the block is in the mainchain, false otherwise
185+
* @return confirmationsCount The number of blocks that have been mined on top of
186+
* the given block if the block is in the mainchain
187+
*/
188+
function getBlockStatus(bytes32 blockHash) external view returns (bool, uint64);
189+
190+
/**
191+
* @notice Returns the Merkle root of a given block hash.
192+
* This function retrieves the Merkle root from the stored block header data
193+
* @param blockHash The hash of the block
194+
* @return The Merkle root of the block
195+
*/
196+
function getBlockMerkleRoot(bytes32 blockHash) external view returns (bytes32);
197+
198+
/**
199+
* @notice Returns the block height for a given block hash
200+
* This function retrieves the height at which the block exists in the chain
201+
* @param blockHash The hash of the block
202+
* @return The height of the block
203+
*/
204+
function getBlockHeight(bytes32 blockHash) external view returns (uint64);
205+
206+
/**
207+
* @notice Returns the block hash for a given block height.
208+
* This function retrieves the hash of the block from the mainchain at the specified height
209+
* @param blockHeight The height of the block
210+
* @return The hash of the block
211+
*/
212+
function getBlockHash(uint64 blockHeight) external view returns (bytes32);
213+
214+
/**
215+
* @notice Checks if a block exists in the contract's storage.
216+
* This function verifies the presence of a block by its hash
217+
* @param blockHash The hash of the block to check
218+
* @return True if the block exists, false otherwise
219+
*/
220+
function blockExists(bytes32 blockHash) external view returns (bool);
221+
}
222+
```
223+
224+
All fields within the `BlockHeaderData` struct MUST be converted to big-endian byte order for internal representation and processing within the smart contract.
225+
226+
The `addBlockHeader` function MUST perform the following checks:
227+
- Validate that the submitted raw block header has a fixed size of 80 bytes.
228+
- Enforce all block header validation rules as specified in the "Block Header Validation Rules" section.
229+
- Integrate the new block header into the known chain by calculating its cumulative PoW and managing potential chain reorganizations as defined in the "Mainchain Definition" section.
230+
- Emit a `BlockHeaderAdded` event upon successful addition of the block header.
231+
- Emit a `MainchainHeadUpdated` event if the mainchain was updated.
232+
233+
The `checkTxInclusion` function MUST perform the following steps:
234+
- Check whether the provided `blockHash` is part of the mainchain and ensure its number of confirmations is at least equal to the `minConfirmationsCount` parameter. If any of these checks fail, the function MUST return `false`.
235+
- Using the provided `merkleProof`, `txId`, and `txIndex`, the function MUST compute the Merkle root.
236+
- The computed Merkle root MUST be compared against the `Merkle Root` field stored within the block header identified by `blockHash`.
237+
- If the computed Merkle root matches the stored `Merkle Root`, the function MUST return `true`. Otherwise, it MUST return `false`.
238+
239+
## Rationale
240+
241+
During the design process of the `SPVGateway` contract, several decisions have been made that require clarification. The following initialization options of the smart contract were considered:
242+
243+
1. **Hardcoding the Bitcoin genesis block:** This approach is the simplest for contract deployment as it embeds the initial state directly in the code. While offering absolute trustlessness of the starting point, it limits availability, as the full sync of the gateway would cost around ~100 ETH at the gas price of 1 gwei.
244+
2. **Initialization from an arbitrary block height by trusting provided cumulative work and height:** Currently, the gateway adopts this method as its initialization mechanism. While implying trust in the initial submitted values, it's a common practice for bootstrapping light clients and can be secured via off-chain mechanisms for initial validation (e.g., community-verified checkpoints).
245+
3. **Initialization with Zero-Knowledge Proof (ZKP) for historical correctness:** This advanced method involves proving the entire history of Bitcoin up to a specific block using ZKP.
246+
247+
Upon submitting the raw block header, the gateway expects the `BlockHeaderData` fields to be converted to big-endian byte order. This is required to maintain EVM's efficiency, which is contrary to Bitcoin's native little-endian integer serialization.
248+
249+
There are no "finality" rules in the `SPVGateway` contract. The determination of such is left to consuming protocols, allowing individual definition to meet required security thresholds.
250+
251+
The inclusion of an OPTIONAL `addBlockHeaderBatch` function offers significant gas optimizations. For batches exceeding 11 blocks, MTP can be calculated using timestamps from `calldata`, substantially reducing storage reads and transaction costs.
252+
253+
## Backwards Compatibility
254+
255+
This EIP is fully backwards compatible.
256+
257+
### Deployment Method
258+
259+
<!-- TODO -->
260+
261+
TBD
262+
263+
## Test Cases
264+
265+
<!-- TODO -->
266+
267+
TBD
268+
269+
## Reference Implementation
270+
271+
A reference implementation of the `SPVGateway` contract can be found [here](../assets/eip-8002/contracts/SPVGateway.sol).
272+
273+
[`TargetsHelper`](../assets/eip-8002/contracts/libs/TargetsHelper.sol) is a supporting library that provides utility functions for working with Bitcoin's difficulty targets. It includes methods to convert the `Bits` field from a block header to the corresponding `target` value and vice versa, as well as functions to calculate the new difficulty target during adjustment periods.
274+
275+
> Please note that the reference implementation depends on the `@openzeppelin/contracts v5.2.0`, `@solarity/solidity-lib v3.2.0` and `solady v0.1.23`.
276+
277+
## Security Considerations
278+
279+
Among potential security issues, the following can be noted:
280+
281+
The security of the `SPVGateway` is directly dependent on the security of Bitcoin's underlying PoW consensus. A successful 51% attack on the Bitcoin network would allow an attacker to submit fraudulent block headers that would be accepted by the contract, compromising its state.
282+
283+
The block header validation rules require a Bitcoin node to check that the newly created block is not more than 2 hours ahead of the node's network-adjusted time. This check is impossible to implement on the `SPVGateway` smart contract, hence it is omitted.
284+
285+
Unlike other blockchain systems with deterministic finality, Bitcoin's consensus is probabilistic. The `SPVGateway` contract SHOULD be designed to handle chain reorganizations of arbitrary depth, but it cannot prevent them. As a result, transactions included in a block may not be permanently final. All dApps and protocols relying on this contract MUST implement their own security policies to determine a sufficient number of block confirmations before a transaction is considered "final" for their specific use case.
286+
287+
While the `addBlockHeader` function is permissionless and validates each new header cryptographically, the contract's initial state (its starting block header, height, and cumulative PoW) is a point of trust. The integrity of the entire chain history within the contract is built upon the correctness of this initial data. Although the EIP's design allows for flexible bootstrapping, the responsibility for verifying the initial state falls on the community and the dApps that choose to use a specific deployment of the `SPVGateway`.
288+
289+
## Copyright
290+
291+
Copyright and related rights waived via [CC0](../LICENSE.md).

0 commit comments

Comments
 (0)