blobstream-contracts 0.0.1-security → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of blobstream-contracts might be problematic. Click here for more details.
- package/.codecov.yml +51 -0
- package/.github/CODEOWNERS +7 -0
- package/.github/dependabot.yml +18 -0
- package/.github/workflows/code-analysis.yml +41 -0
- package/.github/workflows/contract-inheritance-check.yml +28 -0
- package/.github/workflows/go-check.yml +25 -0
- package/.github/workflows/labels.yml +19 -0
- package/.github/workflows/lint.yml +37 -0
- package/.github/workflows/tests.yml +72 -0
- package/.gitmodules +12 -0
- package/.golangci.yml +64 -0
- package/.markdownlint.yaml +5 -0
- package/.markdownlint.yml +4 -0
- package/.markdownlintignore +1 -0
- package/.prettierrc.json +11 -0
- package/LICENSE +201 -0
- package/Makefile +18 -0
- package/README.md +102 -5
- package/docs/inclusion-proofs.md +69 -0
- package/foundry.toml +4 -0
- package/go.mod +34 -0
- package/go.sum +212 -0
- package/hardhat.config.ts +46 -0
- package/index.js +40 -0
- package/package.json +29 -3
- package/remappings.txt +6 -0
- package/scripts/Dockerfile_Environment +39 -0
- package/scripts/deploy.ts +12 -0
- package/scripts/gen.sh +34 -0
- package/scripts/upgradability_check.sh +22 -0
- package/slither.config.json +3 -0
- package/src/Blobstream.sol +366 -0
- package/src/Constants.sol +10 -0
- package/src/DataRootTuple.sol +15 -0
- package/src/IDAOracle.sol +18 -0
- package/src/lib/tree/Constants.sol +23 -0
- package/src/lib/tree/Types.sol +37 -0
- package/src/lib/tree/Utils.sol +106 -0
- package/src/lib/tree/binary/BinaryMerkleMultiproof.sol +12 -0
- package/src/lib/tree/binary/BinaryMerkleProof.sol +12 -0
- package/src/lib/tree/binary/BinaryMerkleTree.sol +256 -0
- package/src/lib/tree/binary/TreeHasher.sol +23 -0
- package/src/lib/tree/binary/test/BinaryMerkleTree.t.sol +365 -0
- package/src/lib/tree/binary/test/TreeHasher.t.sol +40 -0
- package/src/lib/tree/namespace/NamespaceMerkleMultiproof.sol +14 -0
- package/src/lib/tree/namespace/NamespaceMerkleProof.sol +14 -0
- package/src/lib/tree/namespace/NamespaceMerkleTree.sol +306 -0
- package/src/lib/tree/namespace/NamespaceNode.sol +23 -0
- package/src/lib/tree/namespace/TreeHasher.sol +69 -0
- package/src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol +108 -0
- package/src/lib/tree/namespace/test/NamespaceMerkleTree.t.sol +644 -0
- package/src/lib/tree/namespace/test/TreeHasher.t.sol +66 -0
- package/src/lib/tree/test/Utils.t.sol +48 -0
- package/src/lib/tree/test/blob.dat +0 -0
- package/src/lib/tree/test/header.dat +0 -0
- package/src/lib/tree/test/proofs.json +1 -0
- package/src/lib/verifier/DAVerifier.sol +328 -0
- package/src/lib/verifier/test/DAVerifier.t.sol +396 -0
- package/src/lib/verifier/test/RollupInclusionProofs.t.sol +589 -0
- package/src/test/Blobstream.t.sol +200 -0
- package/src/test/BlobstreamBenchmark.t.sol +137 -0
- package/tsconfig.json +11 -0
- package/wrappers/Blobstream.sol/wrapper.go +1325 -0
- package/wrappers/ERC1967Proxy.sol/wrapper.go +668 -0
package/scripts/gen.sh
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
if (( $# < 2 )); then
|
6
|
+
echo "Go wrappers generator script. Make sure to specify the following params:"
|
7
|
+
echo " - first parameter: the contracts source directory"
|
8
|
+
echo " - second parameter: the contracts names (including the .sol extension) separated by a space"
|
9
|
+
echo "the output files will be in the ./wrappers directory."
|
10
|
+
exit 1
|
11
|
+
fi
|
12
|
+
|
13
|
+
# compile the Blobstream contracts
|
14
|
+
forge build > /dev/null
|
15
|
+
|
16
|
+
# compile the proxy contracts
|
17
|
+
forge build -C lib/openzeppelin-contracts/contracts/proxy > /dev/null
|
18
|
+
|
19
|
+
cd "$1"
|
20
|
+
|
21
|
+
for file in "${@:2}"; do
|
22
|
+
mkdir -p ../wrappers/"${file}"
|
23
|
+
contractName=$(basename "${file}" .sol)
|
24
|
+
|
25
|
+
jq .abi < ../out/"${file}"/"${contractName}".json > ../out/"${file}"/"${contractName}".abi
|
26
|
+
jq -r .bytecode.object < ../out/"${file}"/"${contractName}".json > ../out/"${file}"/"${contractName}".bin
|
27
|
+
|
28
|
+
abigen --pkg wrappers \
|
29
|
+
--out=../wrappers/"${file}"/wrapper.go \
|
30
|
+
--abi ../out/"${file}"/"${contractName}".abi \
|
31
|
+
--bin ../out/"${file}"/"${contractName}".bin
|
32
|
+
done
|
33
|
+
|
34
|
+
echo "done."
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# this script will check if the Blobstream contract is inheriting the correct upgradability contracts.
|
4
|
+
|
5
|
+
out=$(surya inheritance src/Blobstream.sol | grep -i "\"Blobstream\" ->" | cut -d ">" -f 2 | sed 's/[";]//g')
|
6
|
+
|
7
|
+
required_contracts=("Initializable" "UUPSUpgradeable" "OwnableUpgradeable")
|
8
|
+
missing_contracts=()
|
9
|
+
|
10
|
+
for field in "${required_contracts[@]}"; do
|
11
|
+
if ! grep -q "\<$field\>" <<< "$out"; then
|
12
|
+
missing_contracts+=("$field")
|
13
|
+
fi
|
14
|
+
done
|
15
|
+
|
16
|
+
if [ ${#missing_contracts[@]} -eq 0 ]; then
|
17
|
+
echo "The Blobstream contract is inheriting the right contracts. Exiting."
|
18
|
+
exit 0
|
19
|
+
else
|
20
|
+
echo "The Blobstream contract is missing the necessary inherited contracts: ${missing_contracts[*]}"
|
21
|
+
exit 1
|
22
|
+
fi
|
@@ -0,0 +1,366 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
|
5
|
+
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
|
6
|
+
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
|
7
|
+
import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
|
8
|
+
|
9
|
+
import "./Constants.sol";
|
10
|
+
import "./DataRootTuple.sol";
|
11
|
+
import "./IDAOracle.sol";
|
12
|
+
import "./lib/tree/binary/BinaryMerkleProof.sol";
|
13
|
+
import "./lib/tree/binary/BinaryMerkleTree.sol";
|
14
|
+
|
15
|
+
struct Validator {
|
16
|
+
address addr;
|
17
|
+
uint256 power;
|
18
|
+
}
|
19
|
+
|
20
|
+
struct Signature {
|
21
|
+
uint8 v;
|
22
|
+
bytes32 r;
|
23
|
+
bytes32 s;
|
24
|
+
}
|
25
|
+
|
26
|
+
/// @title Blobstream: Celestia -> EVM, Data Availability relay.
|
27
|
+
/// @dev The relay relies on a set of signers to attest to some event on
|
28
|
+
/// Celestia. These signers are the Celestia validator set, who sign over every
|
29
|
+
/// Celestia block. Keeping track of the Celestia validator set is accomplished
|
30
|
+
/// by updating this contract's view of the validator set with
|
31
|
+
/// `updateValidatorSet()`. At least 2/3 of the voting power of the current
|
32
|
+
/// view of the validator set must sign off on new relayed events, submitted
|
33
|
+
/// with `submitDataRootTupleRoot()`. Each event is a batch of `DataRootTuple`s
|
34
|
+
/// (see ./DataRootTuple.sol), with each tuple representing a single data root
|
35
|
+
/// in a Celestia block header. Relayed tuples are in the same order as the
|
36
|
+
/// block headers.
|
37
|
+
/// @dev DO NOT REMOVE INHERITANCE OF THE FOLLOWING CONTRACTS: Initializable, UUPSUpgradeable and
|
38
|
+
/// OwnableUpgradeable! They're essential for upgradability.
|
39
|
+
contract Blobstream is IDAOracle, Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
40
|
+
// Don't change the order of state for working upgrades AND BE AWARE OF
|
41
|
+
// INHERITANCE VARIABLES! Inherited contracts contain storage slots and must
|
42
|
+
// be accounted for in any upgrades. Always test an exact upgrade on testnet
|
43
|
+
// and localhost before mainnet upgrades.
|
44
|
+
|
45
|
+
/////////////
|
46
|
+
// Storage //
|
47
|
+
/////////////
|
48
|
+
|
49
|
+
/// @notice Domain-separated commitment to the latest validator set.
|
50
|
+
bytes32 public state_lastValidatorSetCheckpoint;
|
51
|
+
/// @notice Voting power required to submit a new update.
|
52
|
+
uint256 public state_powerThreshold;
|
53
|
+
/// @notice Nonce for bridge events. Must be incremented sequentially.
|
54
|
+
uint256 public state_eventNonce;
|
55
|
+
/// @notice Mapping of data root tuple root nonces to data root tuple roots.
|
56
|
+
mapping(uint256 => bytes32) public state_dataRootTupleRoots;
|
57
|
+
|
58
|
+
////////////
|
59
|
+
// Events //
|
60
|
+
////////////
|
61
|
+
|
62
|
+
/// @notice Emitted when a new root of data root tuples is relayed.
|
63
|
+
/// @param nonce Event nonce.
|
64
|
+
/// @param dataRootTupleRoot Merkle root of relayed data root tuples.
|
65
|
+
/// See `submitDataRootTupleRoot`.
|
66
|
+
event DataRootTupleRootEvent(uint256 indexed nonce, bytes32 dataRootTupleRoot);
|
67
|
+
|
68
|
+
/// @notice Emitted when the validator set is updated.
|
69
|
+
/// @param nonce Event nonce.
|
70
|
+
/// @param powerThreshold New voting power threshold.
|
71
|
+
/// @param validatorSetHash Hash of new validator set.
|
72
|
+
/// See `updateValidatorSet`.
|
73
|
+
event ValidatorSetUpdatedEvent(uint256 indexed nonce, uint256 powerThreshold, bytes32 validatorSetHash);
|
74
|
+
|
75
|
+
////////////
|
76
|
+
// Errors //
|
77
|
+
////////////
|
78
|
+
|
79
|
+
/// @notice Malformed current validator set.
|
80
|
+
error MalformedCurrentValidatorSet();
|
81
|
+
|
82
|
+
/// @notice Validator signature does not match.
|
83
|
+
error InvalidSignature();
|
84
|
+
|
85
|
+
/// @notice Submitted validator set signatures do not have enough power.
|
86
|
+
error InsufficientVotingPower();
|
87
|
+
|
88
|
+
/// @notice New validator set nonce must be greater than the current nonce.
|
89
|
+
error InvalidValidatorSetNonce();
|
90
|
+
|
91
|
+
/// @notice Supplied current validators and powers do not match checkpoint.
|
92
|
+
error SuppliedValidatorSetInvalid();
|
93
|
+
|
94
|
+
/// @notice Data root tuple root nonce must be greater than the current nonce.
|
95
|
+
error InvalidDataRootTupleRootNonce();
|
96
|
+
|
97
|
+
///////////////
|
98
|
+
// Functions //
|
99
|
+
///////////////
|
100
|
+
|
101
|
+
/// @param _nonce Initial event nonce.
|
102
|
+
/// @param _powerThreshold Initial voting power that is needed to approve
|
103
|
+
/// operations.
|
104
|
+
/// @param _validatorSetCheckpoint Initial checkpoint of the validator set. This does not need
|
105
|
+
/// to be the genesis validator set of the bridged chain, only the initial
|
106
|
+
/// validator set of the bridge.
|
107
|
+
/// @dev DO NOT REMOVE THE INITIALIZER! It is mandatory for upgradability.
|
108
|
+
function initialize(uint256 _nonce, uint256 _powerThreshold, bytes32 _validatorSetCheckpoint) public initializer {
|
109
|
+
// EFFECTS
|
110
|
+
|
111
|
+
state_eventNonce = _nonce;
|
112
|
+
state_lastValidatorSetCheckpoint = _validatorSetCheckpoint;
|
113
|
+
state_powerThreshold = _powerThreshold;
|
114
|
+
|
115
|
+
/// @dev Initialize the OwnableUpgradeable explicitly.
|
116
|
+
/// DO NOT REMOVE! It is mandatory for allowing the owner to upgrade.
|
117
|
+
__Ownable_init(_msgSender());
|
118
|
+
}
|
119
|
+
|
120
|
+
/// @dev only authorize the upgrade for the owner of the contract.
|
121
|
+
/// Additional access control logic can be added to allow multiple actors to upgrade.
|
122
|
+
/// @dev DO NOT REMOVE! It is mandatory for upgradability.
|
123
|
+
function _authorizeUpgrade(address) internal override onlyOwner {}
|
124
|
+
|
125
|
+
/// @notice Utility function to check if a signature is nil.
|
126
|
+
/// If all bytes of the 65-byte signature are zero, then it's a nil signature.
|
127
|
+
function isSigNil(Signature calldata _sig) private pure returns (bool) {
|
128
|
+
return (_sig.r == 0 && _sig.s == 0 && _sig.v == 0);
|
129
|
+
}
|
130
|
+
|
131
|
+
/// @notice Utility function to verify EIP-191 signatures.
|
132
|
+
function verifySig(address _signer, bytes32 _digest, Signature calldata _sig) private pure returns (bool) {
|
133
|
+
bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(_digest);
|
134
|
+
|
135
|
+
return _signer == ECDSA.recover(digest_eip191, _sig.v, _sig.r, _sig.s);
|
136
|
+
}
|
137
|
+
|
138
|
+
/// @dev Computes the hash of a validator set.
|
139
|
+
/// @param _validators The validator set to hash.
|
140
|
+
function computeValidatorSetHash(Validator[] calldata _validators) private pure returns (bytes32) {
|
141
|
+
return keccak256(abi.encode(_validators));
|
142
|
+
}
|
143
|
+
|
144
|
+
/// @dev Make a domain-separated commitment to the validator set.
|
145
|
+
/// A hash of all relevant information about the validator set.
|
146
|
+
/// The format of the hash is:
|
147
|
+
/// keccak256(VALIDATOR_SET_HASH_DOMAIN_SEPARATOR, nonce, power_threshold, validator_set_hash)
|
148
|
+
/// The elements in the validator set should be monotonically decreasing by power.
|
149
|
+
/// @param _nonce Nonce.
|
150
|
+
/// @param _powerThreshold The voting power threshold.
|
151
|
+
/// @param _validatorSetHash Validator set hash.
|
152
|
+
function domainSeparateValidatorSetHash(uint256 _nonce, uint256 _powerThreshold, bytes32 _validatorSetHash)
|
153
|
+
private
|
154
|
+
pure
|
155
|
+
returns (bytes32)
|
156
|
+
{
|
157
|
+
bytes32 c =
|
158
|
+
keccak256(abi.encode(VALIDATOR_SET_HASH_DOMAIN_SEPARATOR, _nonce, _powerThreshold, _validatorSetHash));
|
159
|
+
|
160
|
+
return c;
|
161
|
+
}
|
162
|
+
|
163
|
+
/// @dev Make a domain-separated commitment to a data root tuple root.
|
164
|
+
/// A hash of all relevant information about a data root tuple root.
|
165
|
+
/// The format of the hash is:
|
166
|
+
/// keccak256(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, nonce, dataRootTupleRoot)
|
167
|
+
/// @param _nonce Event nonce.
|
168
|
+
/// @param _dataRootTupleRoot Data root tuple root.
|
169
|
+
function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot)
|
170
|
+
private
|
171
|
+
pure
|
172
|
+
returns (bytes32)
|
173
|
+
{
|
174
|
+
bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot));
|
175
|
+
|
176
|
+
return c;
|
177
|
+
}
|
178
|
+
|
179
|
+
/// @dev Checks that enough voting power signed over a digest.
|
180
|
+
/// It expects the signatures to be in the same order as the _currentValidators.
|
181
|
+
/// @param _currentValidators The current validators.
|
182
|
+
/// @param _sigs The current validators' signatures.
|
183
|
+
/// @param _digest This is what we are checking they have signed.
|
184
|
+
/// @param _powerThreshold At least this much power must have signed.
|
185
|
+
function checkValidatorSignatures(
|
186
|
+
// The current validator set and their powers
|
187
|
+
Validator[] calldata _currentValidators,
|
188
|
+
Signature[] calldata _sigs,
|
189
|
+
bytes32 _digest,
|
190
|
+
uint256 _powerThreshold
|
191
|
+
) private pure {
|
192
|
+
uint256 cumulativePower = 0;
|
193
|
+
for (uint256 i = 0; i < _currentValidators.length; i++) {
|
194
|
+
// If the signature is nil, then it's not present so continue.
|
195
|
+
if (isSigNil(_sigs[i])) {
|
196
|
+
continue;
|
197
|
+
}
|
198
|
+
|
199
|
+
// Check that the current validator has signed off on the hash.
|
200
|
+
if (!verifySig(_currentValidators[i].addr, _digest, _sigs[i])) {
|
201
|
+
revert InvalidSignature();
|
202
|
+
}
|
203
|
+
|
204
|
+
// Sum up cumulative power.
|
205
|
+
cumulativePower += _currentValidators[i].power;
|
206
|
+
|
207
|
+
// Break early to avoid wasting gas.
|
208
|
+
if (cumulativePower >= _powerThreshold) {
|
209
|
+
break;
|
210
|
+
}
|
211
|
+
}
|
212
|
+
// Check that there was enough power.
|
213
|
+
if (cumulativePower < _powerThreshold) {
|
214
|
+
revert InsufficientVotingPower();
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
/// @notice This updates the validator set by checking that the validators
|
219
|
+
/// in the current validator set have signed off on the new validator set.
|
220
|
+
/// The signatures supplied are the signatures of the current validator set
|
221
|
+
/// over the checkpoint hash generated from the new validator set. Anyone
|
222
|
+
/// can call this function, but they must supply valid signatures of the
|
223
|
+
/// current validator set over the new validator set.
|
224
|
+
///
|
225
|
+
/// The validator set hash that is signed over is domain separated as per
|
226
|
+
/// `domainSeparateValidatorSetHash`.
|
227
|
+
/// @param _newNonce The new event nonce.
|
228
|
+
/// @param _oldNonce The nonce of the latest update to the validator set.
|
229
|
+
/// @param _newPowerThreshold At least this much power must have signed.
|
230
|
+
/// @param _newValidatorSetHash The hash of the new validator set.
|
231
|
+
/// @param _currentValidatorSet The current validator set.
|
232
|
+
/// @param _sigs Signatures.
|
233
|
+
function updateValidatorSet(
|
234
|
+
uint256 _newNonce,
|
235
|
+
uint256 _oldNonce,
|
236
|
+
uint256 _newPowerThreshold,
|
237
|
+
bytes32 _newValidatorSetHash,
|
238
|
+
Validator[] calldata _currentValidatorSet,
|
239
|
+
Signature[] calldata _sigs
|
240
|
+
) external {
|
241
|
+
// CHECKS
|
242
|
+
|
243
|
+
uint256 currentNonce = state_eventNonce;
|
244
|
+
uint256 currentPowerThreshold = state_powerThreshold;
|
245
|
+
bytes32 lastValidatorSetCheckpoint = state_lastValidatorSetCheckpoint;
|
246
|
+
|
247
|
+
// Check that the new nonce is one more than the current one.
|
248
|
+
if (_newNonce != currentNonce + 1) {
|
249
|
+
revert InvalidValidatorSetNonce();
|
250
|
+
}
|
251
|
+
|
252
|
+
// Check that current validators and signatures are well-formed.
|
253
|
+
if (_currentValidatorSet.length != _sigs.length) {
|
254
|
+
revert MalformedCurrentValidatorSet();
|
255
|
+
}
|
256
|
+
|
257
|
+
// Check that the supplied current validator set matches the saved checkpoint.
|
258
|
+
bytes32 currentValidatorSetHash = computeValidatorSetHash(_currentValidatorSet);
|
259
|
+
if (
|
260
|
+
domainSeparateValidatorSetHash(_oldNonce, currentPowerThreshold, currentValidatorSetHash)
|
261
|
+
!= lastValidatorSetCheckpoint
|
262
|
+
) {
|
263
|
+
revert SuppliedValidatorSetInvalid();
|
264
|
+
}
|
265
|
+
|
266
|
+
// Check that enough current validators have signed off on the new validator set.
|
267
|
+
bytes32 newCheckpoint = domainSeparateValidatorSetHash(_newNonce, _newPowerThreshold, _newValidatorSetHash);
|
268
|
+
checkValidatorSignatures(_currentValidatorSet, _sigs, newCheckpoint, currentPowerThreshold);
|
269
|
+
|
270
|
+
// EFFECTS
|
271
|
+
|
272
|
+
state_lastValidatorSetCheckpoint = newCheckpoint;
|
273
|
+
state_powerThreshold = _newPowerThreshold;
|
274
|
+
state_eventNonce = _newNonce;
|
275
|
+
|
276
|
+
// LOGS
|
277
|
+
|
278
|
+
emit ValidatorSetUpdatedEvent(_newNonce, _newPowerThreshold, _newValidatorSetHash);
|
279
|
+
}
|
280
|
+
|
281
|
+
/// @notice Relays a root of Celestia data root tuples to an EVM chain. Anyone
|
282
|
+
/// can call this function, but they must supply valid signatures of the
|
283
|
+
/// current validator set over the data root tuple root.
|
284
|
+
///
|
285
|
+
/// The data root root is the Merkle root of the binary Merkle tree
|
286
|
+
/// (https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#binary-merkle-tree)
|
287
|
+
/// where each leaf is in an ABI-encoded `DataRootTuple`. Each relayed data
|
288
|
+
/// root tuple will 1:1 mirror data roots as they are included in headers
|
289
|
+
/// on Celestia, _in order of inclusion_.
|
290
|
+
///
|
291
|
+
/// The data tuple root that is signed over is domain separated as per
|
292
|
+
/// `domainSeparateDataRootTupleRoot`.
|
293
|
+
/// @param _newNonce The new event nonce.
|
294
|
+
/// @param _validatorSetNonce The nonce of the latest update to the
|
295
|
+
/// validator set.
|
296
|
+
/// @param _dataRootTupleRoot The Merkle root of data root tuples.
|
297
|
+
/// @param _currentValidatorSet The current validator set.
|
298
|
+
/// @param _sigs Signatures.
|
299
|
+
function submitDataRootTupleRoot(
|
300
|
+
uint256 _newNonce,
|
301
|
+
uint256 _validatorSetNonce,
|
302
|
+
bytes32 _dataRootTupleRoot,
|
303
|
+
Validator[] calldata _currentValidatorSet,
|
304
|
+
Signature[] calldata _sigs
|
305
|
+
) external {
|
306
|
+
// CHECKS
|
307
|
+
|
308
|
+
uint256 currentNonce = state_eventNonce;
|
309
|
+
uint256 currentPowerThreshold = state_powerThreshold;
|
310
|
+
bytes32 lastValidatorSetCheckpoint = state_lastValidatorSetCheckpoint;
|
311
|
+
|
312
|
+
// Check that the new nonce is one more than the current one.
|
313
|
+
if (_newNonce != currentNonce + 1) {
|
314
|
+
revert InvalidDataRootTupleRootNonce();
|
315
|
+
}
|
316
|
+
|
317
|
+
// Check that current validators and signatures are well-formed.
|
318
|
+
if (_currentValidatorSet.length != _sigs.length) {
|
319
|
+
revert MalformedCurrentValidatorSet();
|
320
|
+
}
|
321
|
+
|
322
|
+
// Check that the supplied current validator set matches the saved checkpoint.
|
323
|
+
bytes32 currentValidatorSetHash = computeValidatorSetHash(_currentValidatorSet);
|
324
|
+
if (
|
325
|
+
domainSeparateValidatorSetHash(_validatorSetNonce, currentPowerThreshold, currentValidatorSetHash)
|
326
|
+
!= lastValidatorSetCheckpoint
|
327
|
+
) {
|
328
|
+
revert SuppliedValidatorSetInvalid();
|
329
|
+
}
|
330
|
+
|
331
|
+
// Check that enough current validators have signed off on the data
|
332
|
+
// root tuple root and nonce.
|
333
|
+
bytes32 c = domainSeparateDataRootTupleRoot(_newNonce, _dataRootTupleRoot);
|
334
|
+
checkValidatorSignatures(_currentValidatorSet, _sigs, c, currentPowerThreshold);
|
335
|
+
|
336
|
+
// EFFECTS
|
337
|
+
|
338
|
+
state_eventNonce = _newNonce;
|
339
|
+
state_dataRootTupleRoots[_newNonce] = _dataRootTupleRoot;
|
340
|
+
|
341
|
+
// LOGS
|
342
|
+
|
343
|
+
emit DataRootTupleRootEvent(_newNonce, _dataRootTupleRoot);
|
344
|
+
}
|
345
|
+
|
346
|
+
/// @dev see "./IDAOracle.sol"
|
347
|
+
function verifyAttestation(uint256 _tupleRootNonce, DataRootTuple memory _tuple, BinaryMerkleProof memory _proof)
|
348
|
+
external
|
349
|
+
view
|
350
|
+
override
|
351
|
+
returns (bool)
|
352
|
+
{
|
353
|
+
// Tuple must have been committed before.
|
354
|
+
if (_tupleRootNonce > state_eventNonce) {
|
355
|
+
return false;
|
356
|
+
}
|
357
|
+
|
358
|
+
// Load the tuple root at the given index from storage.
|
359
|
+
bytes32 root = state_dataRootTupleRoots[_tupleRootNonce];
|
360
|
+
|
361
|
+
// Verify the proof.
|
362
|
+
(bool isProofValid,) = BinaryMerkleTree.verify(root, _proof, abi.encode(_tuple));
|
363
|
+
|
364
|
+
return isProofValid;
|
365
|
+
}
|
366
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
/// @dev bytes32 encoding of the string "checkpoint"
|
5
|
+
bytes32 constant VALIDATOR_SET_HASH_DOMAIN_SEPARATOR =
|
6
|
+
0x636865636b706f696e7400000000000000000000000000000000000000000000;
|
7
|
+
|
8
|
+
/// @dev bytes32 encoding of the string "transactionBatch"
|
9
|
+
bytes32 constant DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR =
|
10
|
+
0x7472616e73616374696f6e426174636800000000000000000000000000000000;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
/// @notice A tuple of data root with metadata. Each data root is associated
|
5
|
+
/// with a Celestia block height.
|
6
|
+
/// @dev `availableDataRoot` in
|
7
|
+
/// https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#header
|
8
|
+
struct DataRootTuple {
|
9
|
+
// Celestia block height the data root was included in.
|
10
|
+
// Genesis block is height = 0.
|
11
|
+
// First queryable block is height = 1.
|
12
|
+
uint256 height;
|
13
|
+
// Data root.
|
14
|
+
bytes32 dataRoot;
|
15
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.19;
|
3
|
+
|
4
|
+
import "./DataRootTuple.sol";
|
5
|
+
import "./lib/tree/binary/BinaryMerkleProof.sol";
|
6
|
+
|
7
|
+
/// @notice Data Availability Oracle interface.
|
8
|
+
interface IDAOracle {
|
9
|
+
/// @notice Verify a Data Availability attestation.
|
10
|
+
/// @param _tupleRootNonce Nonce of the tuple root to prove against.
|
11
|
+
/// @param _tuple Data root tuple to prove inclusion of.
|
12
|
+
/// @param _proof Binary Merkle tree proof that `tuple` is in the root at `_tupleRootNonce`.
|
13
|
+
/// @return `true` is proof is valid, `false` otherwise.
|
14
|
+
function verifyAttestation(uint256 _tupleRootNonce, DataRootTuple memory _tuple, BinaryMerkleProof memory _proof)
|
15
|
+
external
|
16
|
+
view
|
17
|
+
returns (bool);
|
18
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "./Types.sol";
|
5
|
+
|
6
|
+
library Constants {
|
7
|
+
///////////////
|
8
|
+
// Constants //
|
9
|
+
///////////////
|
10
|
+
|
11
|
+
/// @dev Maximum tree height
|
12
|
+
uint256 internal constant MAX_HEIGHT = 256;
|
13
|
+
|
14
|
+
/// @dev The prefixes of leaves and nodes
|
15
|
+
bytes1 internal constant LEAF_PREFIX = 0x00;
|
16
|
+
bytes1 internal constant NODE_PREFIX = 0x01;
|
17
|
+
}
|
18
|
+
|
19
|
+
/// @dev Parity share namespace.
|
20
|
+
/// utility function to provide the parity share namespace as a Namespace struct.
|
21
|
+
function PARITY_SHARE_NAMESPACE() pure returns (Namespace memory) {
|
22
|
+
return Namespace(0xFF, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
23
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
/// @notice A representation of the Celestia-app namespace ID and its version.
|
5
|
+
/// See: https://celestiaorg.github.io/celestia-app/specs/namespace.html
|
6
|
+
struct Namespace {
|
7
|
+
// The namespace version.
|
8
|
+
bytes1 version;
|
9
|
+
// The namespace ID.
|
10
|
+
bytes28 id;
|
11
|
+
}
|
12
|
+
|
13
|
+
using {equalTo, lessThan, greaterThan, toBytes} for Namespace global;
|
14
|
+
|
15
|
+
function equalTo(Namespace memory l, Namespace memory r) pure returns (bool) {
|
16
|
+
return l.toBytes() == r.toBytes();
|
17
|
+
}
|
18
|
+
|
19
|
+
function lessThan(Namespace memory l, Namespace memory r) pure returns (bool) {
|
20
|
+
return l.toBytes() < r.toBytes();
|
21
|
+
}
|
22
|
+
|
23
|
+
function greaterThan(Namespace memory l, Namespace memory r) pure returns (bool) {
|
24
|
+
return l.toBytes() > r.toBytes();
|
25
|
+
}
|
26
|
+
|
27
|
+
function toBytes(Namespace memory n) pure returns (bytes29) {
|
28
|
+
return bytes29(abi.encodePacked(n.version, n.id));
|
29
|
+
}
|
30
|
+
|
31
|
+
function toNamespace(bytes29 n) pure returns (Namespace memory) {
|
32
|
+
bytes memory id = new bytes(28);
|
33
|
+
for (uint256 i = 1; i < 29; i++) {
|
34
|
+
id[i - 1] = n[i];
|
35
|
+
}
|
36
|
+
return Namespace(n[0], bytes28(id));
|
37
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "./Constants.sol";
|
5
|
+
|
6
|
+
/// @notice Calculate the starting bit of the path to a leaf
|
7
|
+
/// @param numLeaves : The total number of leaves in the tree
|
8
|
+
/// @return startingBit : The starting bit of the path
|
9
|
+
// solhint-disable-next-line func-visibility
|
10
|
+
function getStartingBit(uint256 numLeaves) pure returns (uint256 startingBit) {
|
11
|
+
// Determine height of the left subtree. This is the maximum path length, so all paths start at this offset from the right-most bit
|
12
|
+
startingBit = 0;
|
13
|
+
while ((1 << startingBit) < numLeaves) {
|
14
|
+
startingBit += 1;
|
15
|
+
}
|
16
|
+
return Constants.MAX_HEIGHT - startingBit;
|
17
|
+
}
|
18
|
+
|
19
|
+
/// @notice Calculate the length of the path to a leaf
|
20
|
+
/// @param key: The key of the leaf
|
21
|
+
/// @param numLeaves: The total number of leaves in the tree
|
22
|
+
/// @return pathLength : The length of the path to the leaf
|
23
|
+
// solhint-disable-next-line func-visibility
|
24
|
+
function pathLengthFromKey(uint256 key, uint256 numLeaves) pure returns (uint256 pathLength) {
|
25
|
+
if (numLeaves <= 1) {
|
26
|
+
// if the number of leaves of the tree is 1 or 0, the path always is 0.
|
27
|
+
return 0;
|
28
|
+
}
|
29
|
+
// Get the height of the left subtree. This is equal to the offset of the starting bit of the path
|
30
|
+
pathLength = Constants.MAX_HEIGHT - getStartingBit(numLeaves);
|
31
|
+
|
32
|
+
// Determine the number of leaves in the left subtree
|
33
|
+
uint256 numLeavesLeftSubTree = (1 << (pathLength - 1));
|
34
|
+
|
35
|
+
// If leaf is in left subtree, path length is full height of left subtree
|
36
|
+
if (key <= numLeavesLeftSubTree - 1) {
|
37
|
+
return pathLength;
|
38
|
+
}
|
39
|
+
// If left sub tree has only one leaf but key is not there, path has one additional step
|
40
|
+
else if (numLeavesLeftSubTree == 1) {
|
41
|
+
return 1;
|
42
|
+
}
|
43
|
+
// Otherwise, add 1 to height and recurse into right subtree
|
44
|
+
else {
|
45
|
+
return 1 + pathLengthFromKey(key - numLeavesLeftSubTree, numLeaves - numLeavesLeftSubTree);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
/// @notice Returns the minimum number of bits required to represent `x`; the
|
50
|
+
/// result is 0 for `x` == 0.
|
51
|
+
/// @param x Number.
|
52
|
+
function _bitsLen(uint256 x) pure returns (uint256) {
|
53
|
+
uint256 count = 0;
|
54
|
+
|
55
|
+
while (x != 0) {
|
56
|
+
count++;
|
57
|
+
x >>= 1;
|
58
|
+
}
|
59
|
+
|
60
|
+
return count;
|
61
|
+
}
|
62
|
+
|
63
|
+
/// @notice Returns the largest power of 2 less than `x`.
|
64
|
+
/// @param x Number.
|
65
|
+
function _getSplitPoint(uint256 x) pure returns (uint256) {
|
66
|
+
// Note: since `x` is always an unsigned int * 2, the only way for this
|
67
|
+
// to be violated is if the input == 0. Since the input is the end
|
68
|
+
// index exclusive, an input of 0 is guaranteed to be invalid (it would
|
69
|
+
// be a proof of inclusion of nothing, which is vacuous).
|
70
|
+
require(x >= 1);
|
71
|
+
|
72
|
+
uint256 bitLen = _bitsLen(x);
|
73
|
+
uint256 k = 1 << (bitLen - 1);
|
74
|
+
if (k == x) {
|
75
|
+
k >>= 1;
|
76
|
+
}
|
77
|
+
return k;
|
78
|
+
}
|
79
|
+
|
80
|
+
/// @notice Returns the size of the subtree adjacent to `begin` that does
|
81
|
+
/// not overlap `end`.
|
82
|
+
/// @param begin Begin index, inclusive.
|
83
|
+
/// @param end End index, exclusive.
|
84
|
+
function _nextSubtreeSize(uint256 begin, uint256 end) pure returns (uint256) {
|
85
|
+
uint256 ideal = _bitsTrailingZeroes(begin);
|
86
|
+
uint256 max = _bitsLen(end - begin) - 1;
|
87
|
+
if (ideal > max) {
|
88
|
+
return 1 << max;
|
89
|
+
}
|
90
|
+
return 1 << ideal;
|
91
|
+
}
|
92
|
+
|
93
|
+
/// @notice Returns the number of trailing zero bits in `x`; the result is
|
94
|
+
/// 256 for `x` == 0.
|
95
|
+
/// @param x Number.
|
96
|
+
function _bitsTrailingZeroes(uint256 x) pure returns (uint256) {
|
97
|
+
uint256 mask = 1;
|
98
|
+
uint256 count = 0;
|
99
|
+
|
100
|
+
while (x != 0 && mask & x == 0) {
|
101
|
+
count++;
|
102
|
+
x >>= 1;
|
103
|
+
}
|
104
|
+
|
105
|
+
return count;
|
106
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
/// @notice Merkle Tree Proof structure.
|
5
|
+
struct BinaryMerkleMultiproof {
|
6
|
+
// List of side nodes to verify and calculate tree.
|
7
|
+
bytes32[] sideNodes;
|
8
|
+
// The (included) beginning key of the leaves to verify.
|
9
|
+
uint256 beginKey;
|
10
|
+
// The (excluded) ending key of the leaves to verify.
|
11
|
+
uint256 endKey;
|
12
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
/// @notice Merkle Tree Proof structure.
|
5
|
+
struct BinaryMerkleProof {
|
6
|
+
// List of side nodes to verify and calculate tree.
|
7
|
+
bytes32[] sideNodes;
|
8
|
+
// The key of the leaf to verify.
|
9
|
+
uint256 key;
|
10
|
+
// The number of leaves in the tree
|
11
|
+
uint256 numLeaves;
|
12
|
+
}
|