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,200 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.22;
3
+
4
+ import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
5
+
6
+ import "../Constants.sol";
7
+ import "../DataRootTuple.sol";
8
+ import "../Blobstream.sol";
9
+ import "../lib/tree/binary/BinaryMerkleProof.sol";
10
+
11
+ import "ds-test/test.sol";
12
+
13
+ interface CheatCodes {
14
+ function addr(uint256 privateKey) external returns (address);
15
+
16
+ function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
17
+ }
18
+
19
+ contract RelayerTest is DSTest {
20
+ // Private keys used for test signatures.
21
+ uint256 constant testPriv1 = 0x64a1d6f0e760a8d62b4afdde4096f16f51b401eaaecc915740f71770ea76a8ad;
22
+ uint256 constant testPriv2 = 0x6e8bdfa979ab645b41c4d17cb1329b2a44684c82b61b1b060ea9b6e1c927a4f4;
23
+
24
+ Blobstream bridge;
25
+
26
+ Validator[] private validators;
27
+ uint256 private votingPower = 5000;
28
+
29
+ // Set up Foundry cheatcodes.
30
+ CheatCodes cheats = CheatCodes(HEVM_ADDRESS);
31
+
32
+ function setUp() public {
33
+ uint256 initialValsetNonce = 1;
34
+
35
+ validators.push(Validator(cheats.addr(testPriv1), votingPower));
36
+ bytes32 hash = computeValidatorSetHash(validators);
37
+ bytes32 validatorSetCheckpoint = domainSeparateValidatorSetHash(initialValsetNonce, (2 * votingPower) / 3, hash);
38
+ bridge = new Blobstream();
39
+ bridge.initialize(initialValsetNonce, (2 * votingPower) / 3, validatorSetCheckpoint);
40
+ }
41
+
42
+ function testUpdateValidatorSet() public {
43
+ uint256 initialValsetNonce = 1;
44
+
45
+ // Save the old test validator set before we add to it.
46
+ Validator[] memory oldVS = new Validator[](1);
47
+ oldVS[0] = Validator(cheats.addr(testPriv1), votingPower);
48
+
49
+ uint256 newNonce = 2;
50
+ validators.push(Validator(cheats.addr(testPriv2), votingPower));
51
+ votingPower += votingPower;
52
+ uint256 newPowerThreshold = (2 * votingPower) / 3;
53
+ bytes32 newVSHash = keccak256(abi.encode(validators));
54
+ bytes32 newCheckpoint = domainSeparateValidatorSetHash(newNonce, newPowerThreshold, newVSHash);
55
+
56
+ // Signature for the first validator set update.
57
+ Signature[] memory sigs = new Signature[](1);
58
+ bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newCheckpoint);
59
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(testPriv1, digest_eip191);
60
+ sigs[0] = Signature(v, r, s);
61
+
62
+ bridge.updateValidatorSet(newNonce, initialValsetNonce, newPowerThreshold, newVSHash, oldVS, sigs);
63
+
64
+ assertEq(bridge.state_eventNonce(), newNonce);
65
+ assertEq(bridge.state_powerThreshold(), newPowerThreshold);
66
+ assertEq(bridge.state_lastValidatorSetCheckpoint(), newCheckpoint);
67
+ }
68
+
69
+ function testSubmitDataRootTupleRoot() public {
70
+ uint256 initialValsetNonce = 1;
71
+ uint256 nonce = 2;
72
+ // 32 bytes, chosen at random.
73
+ bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e;
74
+
75
+ bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot);
76
+
77
+ // Signature for the update.
78
+ Signature[] memory sigs = new Signature[](1);
79
+ bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot);
80
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(testPriv1, digest_eip191);
81
+ sigs[0] = Signature(v, r, s);
82
+
83
+ Validator[] memory valSet = new Validator[](1);
84
+ valSet[0] = Validator(cheats.addr(testPriv1), votingPower);
85
+
86
+ bridge.submitDataRootTupleRoot(nonce, initialValsetNonce, newTupleRoot, valSet, sigs);
87
+
88
+ assertEq(bridge.state_eventNonce(), nonce);
89
+ assertEq(bridge.state_dataRootTupleRoots(nonce), newTupleRoot);
90
+ }
91
+
92
+ function testDeployContractAtCustomNonce() public {
93
+ uint256 initialValsetNonce = 1;
94
+ uint256 targetNonce = 200;
95
+
96
+ Validator[] memory valSet = new Validator[](1);
97
+ valSet[0] = Validator(cheats.addr(testPriv1), votingPower);
98
+
99
+ bytes32 hash = computeValidatorSetHash(valSet);
100
+ bytes32 validatorSetCheckpoint = domainSeparateValidatorSetHash(initialValsetNonce, (2 * votingPower) / 3, hash);
101
+ Blobstream newBridge = new Blobstream();
102
+ newBridge.initialize(targetNonce, (2 * votingPower) / 3, validatorSetCheckpoint);
103
+
104
+ // 32 bytes, chosen at random.
105
+ bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e;
106
+
107
+ bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(targetNonce + 1, newTupleRoot);
108
+
109
+ // Signature for the update.
110
+ Signature[] memory sigs = new Signature[](1);
111
+ bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot);
112
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(testPriv1, digest_eip191);
113
+ sigs[0] = Signature(v, r, s);
114
+
115
+ newBridge.submitDataRootTupleRoot(targetNonce + 1, initialValsetNonce, newTupleRoot, valSet, sigs);
116
+
117
+ assertEq(newBridge.state_eventNonce(), targetNonce + 1);
118
+ assertEq(newBridge.state_dataRootTupleRoots(targetNonce + 1), newTupleRoot);
119
+ }
120
+
121
+ /*
122
+ the values used in the verify attestation test are in the format `<height padded to 32 bytes || data root>`, which
123
+ represent an encoded `abi.encode(DataRootTuple)`:
124
+
125
+ 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
126
+ 0x00000000000000000000000000000000000000000000000000000000000000010101010101010101010101010101010101010101010101010101010101010101
127
+ 0x00000000000000000000000000000000000000000000000000000000000000020202020202020202020202020202020202020202020202020202020202020202
128
+ 0x00000000000000000000000000000000000000000000000000000000000000030303030303030303030303030303030303030303030303030303030303030303
129
+ */
130
+ function testVerifyAttestation() public {
131
+ uint256 initialValsetNonce = 1;
132
+ // data root tuple root nonce.
133
+ uint256 nonce = 2;
134
+ // commitment to a set of roots.
135
+ // these values were generated using the tendermint implementation of binary merkle trees:
136
+ // https://github.com/celestiaorg/celestia-core/blob/60310e7aa554bb76b735a010847a6613bcfe06e8/crypto/merkle/proof.go#L33-L48
137
+ bytes32 newTupleRoot = 0x82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013;
138
+ // a data root committed to by the above tuple root.
139
+ bytes32 newTuple = 0x0101010101010101010101010101010101010101010101010101010101010101;
140
+ // the height of the data root.
141
+ uint256 height = 1;
142
+ // the binary merkle proof of the data root to the data root tuple root.
143
+ bytes32[] memory sideNodes = new bytes32[](2);
144
+ sideNodes[0] = 0x98ce42deef51d40269d542f5314bef2c7468d401ad5d85168bfab4c0108f75f7;
145
+ sideNodes[1] = 0x575664048c9e64260eca2304d177b11d1566d0c954f1417fc76a4f9f27350063;
146
+ BinaryMerkleProof memory newTupleProof;
147
+ newTupleProof.sideNodes = sideNodes;
148
+ newTupleProof.key = 1;
149
+ newTupleProof.numLeaves = 4;
150
+
151
+ bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot);
152
+
153
+ // Signature for the update.
154
+ Signature[] memory sigs = new Signature[](1);
155
+ bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot);
156
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(testPriv1, digest_eip191);
157
+ sigs[0] = Signature(v, r, s);
158
+
159
+ Validator[] memory valSet = new Validator[](1);
160
+ valSet[0] = Validator(cheats.addr(testPriv1), votingPower);
161
+
162
+ bridge.submitDataRootTupleRoot(nonce, initialValsetNonce, newTupleRoot, valSet, sigs);
163
+
164
+ assertEq(bridge.state_eventNonce(), nonce);
165
+ assertEq(bridge.state_dataRootTupleRoots(nonce), newTupleRoot);
166
+
167
+ DataRootTuple memory t;
168
+ t.height = height;
169
+ t.dataRoot = newTuple;
170
+
171
+ // verify that the tuple was committed to
172
+ bool committedTo = bridge.verifyAttestation(nonce, t, newTupleProof);
173
+ assertTrue(committedTo);
174
+ }
175
+
176
+ function computeValidatorSetHash(Validator[] memory _validators) private pure returns (bytes32) {
177
+ return keccak256(abi.encode(_validators));
178
+ }
179
+
180
+ function domainSeparateValidatorSetHash(uint256 _nonce, uint256 _powerThreshold, bytes32 _validatorSetHash)
181
+ private
182
+ pure
183
+ returns (bytes32)
184
+ {
185
+ bytes32 c =
186
+ keccak256(abi.encode(VALIDATOR_SET_HASH_DOMAIN_SEPARATOR, _nonce, _powerThreshold, _validatorSetHash));
187
+
188
+ return c;
189
+ }
190
+
191
+ function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot)
192
+ private
193
+ pure
194
+ returns (bytes32)
195
+ {
196
+ bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot));
197
+
198
+ return c;
199
+ }
200
+ }
@@ -0,0 +1,137 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ pragma solidity ^0.8.19;
3
+
4
+ import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
5
+
6
+ import "../Constants.sol";
7
+ import "../DataRootTuple.sol";
8
+ import "../Blobstream.sol";
9
+ import "../lib/tree/binary/BinaryMerkleProof.sol";
10
+
11
+ import "ds-test/test.sol";
12
+
13
+ interface CheatCodes {
14
+ function addr(uint256 privateKey) external returns (address);
15
+ function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
16
+ function deriveKey(string calldata, string calldata, uint32) external returns (uint256);
17
+ }
18
+
19
+ /// @notice Example command to run the benchmark:
20
+ /// `forge test --match-test testBenchmarkSubmitDataRootTupleRoot -vvvvvv --gas-report`.
21
+ /// To change the validator set size, change the `numberOfValidators` constant.
22
+ /// To make custom calculations of the gas, you can use the `gasleft()` solidity
23
+ /// built-in function.
24
+ /// The following answer has some insights on using that:
25
+ /// https://ethereum.stackexchange.com/a/132325/65649
26
+ /// The gas estimations might not be accurate to the real cost in a real network,
27
+ /// and that's because foundry doesn't track calldata cost. source:
28
+ /// https://github.com/foundry-rs/foundry/issues/3475#issuecomment-1469940917
29
+ /// To have accurate results, make sure to add the following costs:
30
+ /// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value).
31
+ contract Benchmark is DSTest {
32
+ uint256 private constant numberOfValidators = 100;
33
+ uint256 private constant numberOfSigners = 30;
34
+
35
+ // Private keys used for test signatures.
36
+ uint256[] private privateKeys;
37
+
38
+ Blobstream private bridge;
39
+
40
+ Validator[] private validators;
41
+ uint256 private totalValidatorPower = 1000000;
42
+ uint256 private dataTupleRootNonce = 0;
43
+
44
+ // Set up Foundry cheatcodes.
45
+ CheatCodes cheats = CheatCodes(HEVM_ADDRESS);
46
+
47
+ function setUp() public {
48
+ uint256 initialValsetNonce = 0;
49
+ privateKeys = derivePrivateKeys(numberOfValidators);
50
+ validators = initializeValidators(privateKeys);
51
+
52
+ bytes32 hash = computeValidatorSetHash(validators);
53
+ bytes32 checkpoint = domainSeparateValidatorSetHash(initialValsetNonce, (2 * totalValidatorPower) / 3, hash);
54
+ bridge = new Blobstream();
55
+ bridge.initialize(initialValsetNonce, (2 * totalValidatorPower) / 3, checkpoint);
56
+ }
57
+
58
+ function testBenchmarkSubmitDataRootTupleRoot() public {
59
+ uint256 initialValsetNonce = 0;
60
+ uint256 nonce = 1;
61
+
62
+ // 32 bytes, chosen at random.
63
+ bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e;
64
+ bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot);
65
+
66
+ // Signature for the update.
67
+ Signature[] memory sigs = new Signature[](numberOfValidators);
68
+ bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot);
69
+ uint256 threshold = 2 * totalValidatorPower / 3;
70
+ uint256 cumulatedPower = 0;
71
+ for (uint256 i = 0; i < numberOfValidators; i++) {
72
+ if (cumulatedPower > threshold) {
73
+ break;
74
+ }
75
+ (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKeys[i], digest_eip191);
76
+ sigs[i] = Signature(v, r, s);
77
+ cumulatedPower += validators[i].power;
78
+ }
79
+
80
+ // these are called here so that they're part of the gas report.
81
+ // uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3;
82
+ // bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators);
83
+ // bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash);
84
+ // bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold);
85
+
86
+ bridge.submitDataRootTupleRoot(nonce, initialValsetNonce, newTupleRoot, validators, sigs);
87
+ }
88
+
89
+ function computeValidatorSetHash(Validator[] memory _validators) private pure returns (bytes32) {
90
+ return keccak256(abi.encode(_validators));
91
+ }
92
+
93
+ function domainSeparateValidatorSetHash(uint256 _nonce, uint256 _powerThreshold, bytes32 _validatorSetHash)
94
+ private
95
+ pure
96
+ returns (bytes32)
97
+ {
98
+ bytes32 c =
99
+ keccak256(abi.encode(VALIDATOR_SET_HASH_DOMAIN_SEPARATOR, _nonce, _powerThreshold, _validatorSetHash));
100
+
101
+ return c;
102
+ }
103
+
104
+ function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot)
105
+ private
106
+ pure
107
+ returns (bytes32)
108
+ {
109
+ bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot));
110
+
111
+ return c;
112
+ }
113
+
114
+ function derivePrivateKeys(uint256 count) private returns (uint256[] memory) {
115
+ string memory mnemonic = "test test test test test test test test test test test junk";
116
+ uint256[] memory keys = new uint256[](count);
117
+ for (uint32 i = 0; i < count; i++) {
118
+ keys[i] = cheats.deriveKey(mnemonic, "m/44'/60'/0'/0", i);
119
+ }
120
+ return keys;
121
+ }
122
+
123
+ function initializeValidators(uint256[] memory keys) private returns (Validator[] memory) {
124
+ Validator[] memory vs = new Validator[](keys.length);
125
+ uint256 threshold = 2 * totalValidatorPower / 3;
126
+ uint256 primaryPower = threshold / (numberOfSigners - 1);
127
+ uint256 secondaryPower = (totalValidatorPower - threshold) / (numberOfValidators - numberOfSigners + 1);
128
+ for (uint256 i = 0; i < keys.length; i++) {
129
+ if (i < numberOfSigners) {
130
+ vs[i] = Validator(cheats.addr(keys[i]), primaryPower);
131
+ } else {
132
+ vs[i] = Validator(cheats.addr(keys[i]), secondaryPower);
133
+ }
134
+ }
135
+ return vs;
136
+ }
137
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "commonjs",
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true
10
+ }
11
+ }