blobstream-contracts 0.0.1-security → 3.3.3

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.

Files changed (64) hide show
  1. package/.codecov.yml +51 -0
  2. package/.github/CODEOWNERS +7 -0
  3. package/.github/dependabot.yml +18 -0
  4. package/.github/workflows/code-analysis.yml +41 -0
  5. package/.github/workflows/contract-inheritance-check.yml +28 -0
  6. package/.github/workflows/go-check.yml +25 -0
  7. package/.github/workflows/labels.yml +19 -0
  8. package/.github/workflows/lint.yml +37 -0
  9. package/.github/workflows/tests.yml +72 -0
  10. package/.gitmodules +12 -0
  11. package/.golangci.yml +64 -0
  12. package/.markdownlint.yaml +5 -0
  13. package/.markdownlint.yml +4 -0
  14. package/.markdownlintignore +1 -0
  15. package/.prettierrc.json +11 -0
  16. package/LICENSE +201 -0
  17. package/Makefile +18 -0
  18. package/README.md +102 -5
  19. package/docs/inclusion-proofs.md +69 -0
  20. package/foundry.toml +4 -0
  21. package/go.mod +34 -0
  22. package/go.sum +212 -0
  23. package/hardhat.config.ts +46 -0
  24. package/index.js +94 -0
  25. package/package.json +31 -3
  26. package/remappings.txt +6 -0
  27. package/scripts/Dockerfile_Environment +39 -0
  28. package/scripts/deploy.ts +12 -0
  29. package/scripts/gen.sh +34 -0
  30. package/scripts/upgradability_check.sh +22 -0
  31. package/slither.config.json +3 -0
  32. package/src/Blobstream.sol +366 -0
  33. package/src/Constants.sol +10 -0
  34. package/src/DataRootTuple.sol +15 -0
  35. package/src/IDAOracle.sol +18 -0
  36. package/src/lib/tree/Constants.sol +23 -0
  37. package/src/lib/tree/Types.sol +37 -0
  38. package/src/lib/tree/Utils.sol +106 -0
  39. package/src/lib/tree/binary/BinaryMerkleMultiproof.sol +12 -0
  40. package/src/lib/tree/binary/BinaryMerkleProof.sol +12 -0
  41. package/src/lib/tree/binary/BinaryMerkleTree.sol +256 -0
  42. package/src/lib/tree/binary/TreeHasher.sol +23 -0
  43. package/src/lib/tree/binary/test/BinaryMerkleTree.t.sol +365 -0
  44. package/src/lib/tree/binary/test/TreeHasher.t.sol +40 -0
  45. package/src/lib/tree/namespace/NamespaceMerkleMultiproof.sol +14 -0
  46. package/src/lib/tree/namespace/NamespaceMerkleProof.sol +14 -0
  47. package/src/lib/tree/namespace/NamespaceMerkleTree.sol +306 -0
  48. package/src/lib/tree/namespace/NamespaceNode.sol +23 -0
  49. package/src/lib/tree/namespace/TreeHasher.sol +69 -0
  50. package/src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol +108 -0
  51. package/src/lib/tree/namespace/test/NamespaceMerkleTree.t.sol +644 -0
  52. package/src/lib/tree/namespace/test/TreeHasher.t.sol +66 -0
  53. package/src/lib/tree/test/Utils.t.sol +48 -0
  54. package/src/lib/tree/test/blob.dat +0 -0
  55. package/src/lib/tree/test/header.dat +0 -0
  56. package/src/lib/tree/test/proofs.json +1 -0
  57. package/src/lib/verifier/DAVerifier.sol +328 -0
  58. package/src/lib/verifier/test/DAVerifier.t.sol +396 -0
  59. package/src/lib/verifier/test/RollupInclusionProofs.t.sol +589 -0
  60. package/src/test/Blobstream.t.sol +200 -0
  61. package/src/test/BlobstreamBenchmark.t.sol +137 -0
  62. package/tsconfig.json +11 -0
  63. package/wrappers/Blobstream.sol/wrapper.go +1325 -0
  64. package/wrappers/ERC1967Proxy.sol/wrapper.go +668 -0
@@ -0,0 +1,39 @@
1
+ # This Dockerfile will contain the CI environment used to run the tests and generate the wrappers.
2
+ # It would help not having to worry about the versions difference between the local setup and that of the CI
3
+ # and would avoid the issues related to wrappers generation using different versions.
4
+ # Note: this image is not build to be distributed or pushed to remote registries. Thus, it does not optimise the build layers and build stages.
5
+ #
6
+ # How to use:
7
+ # First, build the docker image using:
8
+ # $ docker build -t blobstream-env -f Dockerfile_Environment .
9
+ # Then, run the docker image:
10
+ # $ docker run -it blobstream-env
11
+ # This should give you a shell inside the image where you have all the dependencies installed.
12
+ #
13
+ # For example, if you want to generate the wrappers for this repo, run the following inside the shell:
14
+ # $ git clone https://github.com/celestiaorg/blobstream-contracts
15
+ # $ cd blobstream-contracts
16
+ # $ make
17
+ # And you will see that the wrappers are being regenerated for this repo.
18
+ # Finally, you can push the changes to your branch using git add/commit/push.
19
+ FROM ubuntu:22.04
20
+
21
+ # install necessary dependencies
22
+ RUN apt update && apt install -y git build-essential software-properties-common curl protobuf-compiler wget jq
23
+
24
+ # install forge
25
+ RUN curl -L https://foundry.paradigm.xyz | bash && . /root/.bashrc && foundryup
26
+
27
+ # install solc
28
+ RUN wget https://github.com/ethereum/solidity/releases/download/v0.8.22/solc-static-linux -O /usr/bin/solc && chmod +x /usr/bin/solc
29
+
30
+ # install go
31
+ RUN wget https://go.dev/dl/go1.24.0.linux-arm64.tar.gz && rm -rf /usr/local/go && tar -C /usr/local -xzf go1.24.0.linux-arm64.tar.gz && echo 'PATH=$PATH:/usr/local/go/bin:/root/go/bin' >> ~/.bashrc
32
+
33
+ # install abigen
34
+ RUN git clone --depth 1 --branch v1.15.3 https://github.com/ethereum/go-ethereum.git && cd go-ethereum && PATH=$PATH:/usr/local/go/bin make devtools
35
+
36
+ WORKDIR /root
37
+ ENTRYPOINT bash
38
+
39
+ # at this level, you can clone the blobstream-contracts repo and build the wrappers, run the tests etc.
@@ -0,0 +1,12 @@
1
+ import { ethers } from "hardhat";
2
+
3
+ async function main() {
4
+ // TODO add deploy script
5
+ }
6
+
7
+ // We recommend this pattern to be able to use async/await everywhere
8
+ // and properly handle errors.
9
+ main().catch((error) => {
10
+ console.error(error);
11
+ process.exitCode = 1;
12
+ });
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,3 @@
1
+ {
2
+ "filter_paths": "(lib/)"
3
+ }
@@ -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
+ }