@@ -19,16 +19,107 @@ pragma solidity ^0.8.9;
19
19
/// @title Steel Library
20
20
/// @notice This library provides a collection of utilities to work with Steel commitments in Solidity.
21
21
library Steel {
22
- /// @notice A Commitment struct representing a block number and its block hash.
22
+ /// @notice Represents a commitment to a specific block in the blockchain.
23
+ /// @dev The `blockID` encodes both the block identifier (block number or timestamp) and the version.
24
+ /// @dev The `blockDigest` is the block hash or beacon block root, used for validation.
23
25
struct Commitment {
24
- uint256 blockNumber; // Block number at which the commitment was made.
25
- bytes32 blockHash; // Hash of the block at the specified block number.
26
+ uint256 blockID;
27
+ bytes32 blockDigest;
26
28
}
27
29
30
+ /// @notice The version of the Commitment is incorrect.
31
+ error InvalidCommitmentVersion ();
32
+
33
+ /// @notice The Commitment is too old and can no longer be validated.
34
+ error CommitmentTooOld ();
35
+
28
36
/// @notice Validates if the provided Commitment matches the block hash of the given block number.
29
37
/// @param commitment The Commitment struct to validate.
30
- /// @return isValid True if the commitment's block hash matches the block hash of the block number, false otherwise.
31
- function validateCommitment (Commitment memory commitment ) internal view returns (bool isValid ) {
32
- return commitment.blockHash == blockhash (commitment.blockNumber);
38
+ /// @return True if the commitment's block hash matches the block hash of the block number, false otherwise.
39
+ function validateCommitment (Commitment memory commitment ) internal view returns (bool ) {
40
+ (uint240 blockID , uint16 version ) = Encoding.decodeVersionedID (commitment.blockID);
41
+ if (version == 0 ) {
42
+ return validateBlockCommitment (blockID, commitment.blockDigest);
43
+ } else if (version == 1 ) {
44
+ return validateBeaconCommitment (blockID, commitment.blockDigest);
45
+ } else {
46
+ revert InvalidCommitmentVersion ();
47
+ }
48
+ }
49
+
50
+ /// @notice Validates if the provided block commitment matches the block hash of the given block number.
51
+ /// @param blockNumber The block number to compare against.
52
+ /// @param blockHash The block hash to validate.
53
+ /// @return True if the block's block hash matches the block hash, false otherwise.
54
+ function validateBlockCommitment (uint256 blockNumber , bytes32 blockHash ) internal view returns (bool ) {
55
+ if (block .number - blockNumber > 256 ) {
56
+ revert CommitmentTooOld ();
57
+ }
58
+ return blockHash == blockhash (blockNumber);
59
+ }
60
+
61
+ /// @notice Validates if the provided beacon commitment matches the block root of the given timestamp.
62
+ /// @param blockTimestamp The timestamp to compare against.
63
+ /// @param blockRoot The block root to validate.
64
+ /// @return True if the block's block root matches the block root, false otherwise.
65
+ function validateBeaconCommitment (uint256 blockTimestamp , bytes32 blockRoot ) internal view returns (bool ) {
66
+ if (block .timestamp - blockTimestamp > 12 * 8191 ) {
67
+ revert CommitmentTooOld ();
68
+ }
69
+ return blockRoot == Beacon.blockRoot (blockTimestamp);
70
+ }
71
+ }
72
+
73
+ /// @title Beacon Library
74
+ library Beacon {
75
+ /// @notice The address of the Beacon roots contract.
76
+ /// @dev https://eips.ethereum.org/EIPS/eip-4788
77
+ address internal constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02 ;
78
+
79
+ /// @notice The Beacon block root could not be found as the next block has not been issued yet.
80
+ error NoParentBeaconBlock ();
81
+
82
+ /// @notice Attempts to find the root of the Beacon block with the given timestamp.
83
+ /// @dev Since the Beacon roots contract only returns the parent Beacon block’s root, we need to find the next
84
+ /// Beacon block instead. This is done by adding the block time of 12s until a value is returned.
85
+ function blockRoot (uint256 timestamp ) internal view returns (bytes32 root ) {
86
+ uint256 blockTimestamp = block .timestamp ;
87
+ while (true ) {
88
+ timestamp += 12 ; // Beacon block time is 12 seconds
89
+ if (timestamp > blockTimestamp) revert NoParentBeaconBlock ();
90
+
91
+ (bool success , bytes memory result ) = BEACON_ROOTS_ADDRESS.staticcall (abi.encode (timestamp));
92
+ if (success) {
93
+ return abi.decode (result, (bytes32 ));
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ /// @title Encoding Library
100
+ library Encoding {
101
+ /// @notice Encodes a version and ID into a single uint256 value.
102
+ /// @param id The base ID to be encoded, limited by 240 bits (or the maximum value of a uint240).
103
+ /// @param version The version number to be encoded, limited by 16 bits (or the maximum value of a uint16).
104
+ /// @return Returns a single uint256 value that contains both the `id` and the `version` encoded into it.
105
+ function encodeVersionedID (uint240 id , uint16 version ) internal pure returns (uint256 ) {
106
+ uint256 encoded;
107
+ assembly {
108
+ encoded := or (shl (240 , version), id)
109
+ }
110
+ return encoded;
111
+ }
112
+
113
+ /// @notice Decodes a version and ID from a single uint256 value.
114
+ /// @param id The single uint256 value to be decoded.
115
+ /// @return Returns two values: a uint240 for the original base ID and a uint16 for the version number encoded into it.
116
+ function decodeVersionedID (uint256 id ) internal pure returns (uint240 , uint16 ) {
117
+ uint240 decoded;
118
+ uint16 version;
119
+ assembly {
120
+ decoded := and (id, 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff )
121
+ version := shr (240 , id)
122
+ }
123
+ return (decoded, version);
33
124
}
34
125
}
0 commit comments