diff --git a/.env.example b/.env.example index ab17c1c..1fdfe84 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,7 @@ export ECDSA_VS="[]" export ECDSA_RS="[]" export ECDSA_SS="[]" ## IScribe::drop -export FEED_INDEX= +export FEED_ID= ## IScribeOptimistic::setOpChallengePeriod export OP_CHALLENGE_PERIOD= ## IScribeOptimistic::setMaxChallengeReward @@ -51,4 +51,4 @@ export TEST_POKE_VAL= export TEST_POKE_AGE= export TEST_SCHNORR_SIGNATURE= export TEST_SCHNORR_COMMITMENT= -export TEST_SCHNORR_SIGNERS_BLOB= +export TEST_SCHNORR_FEED_IDS= diff --git a/.gas-snapshot b/.gas-snapshot index 9fd277d..0adcb5a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,77 +1,69 @@ -LibBytesTest:test_getByteAtIndex() (gas: 378) LibSecp256k1Test:testVectors_addAffinePoint() (gas: 2502307) LibSecp256k1Test:test_isZeroPoint() (gas: 465) LibSecp256k1Test:test_yParity() (gas: 530) ScribeInvariantTest:invariant_bar_IsNeverZero() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_feeds_ImageIsZeroToLengthOfPubKeys() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_feeds_LinkToTheirPublicKeys() (runs: 256, calls: 3840, reverts: 0) ScribeInvariantTest:invariant_poke_PokeTimestampsAreStrictlyMonotonicallyIncreasing() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_pubKeys_AtIndexZeroIsZeroPoint() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_pubKeys_LengthIsStrictlyMonotonicallyIncreasing() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_pubKeys_NonZeroPubKeyExistsAtMostOnce() (runs: 256, calls: 3840, reverts: 0) -ScribeInvariantTest:invariant_pubKeys_ZeroPointIsNeverAddedAsPubKey() (runs: 256, calls: 3840, reverts: 0) -ScribeOptimisticTest:test_Deployment() (gas: 59359) -ScribeOptimisticTest:test_afterAuthedAction_1_drop() (gas: 248804) -ScribeOptimisticTest:test_afterAuthedAction_1_setBar() (gas: 295659) -ScribeOptimisticTest:test_afterAuthedAction_1_setChallengePeriod() (gas: 294180) -ScribeOptimisticTest:test_afterAuthedAction_2_drop() (gas: 325741) -ScribeOptimisticTest:test_afterAuthedAction_2_setBar() (gas: 384823) -ScribeOptimisticTest:test_afterAuthedAction_2_setChallengePeriod() (gas: 384423) -ScribeOptimisticTest:test_afterAuthedAction_3_drop() (gas: 251454) -ScribeOptimisticTest:test_afterAuthedAction_3_setBar() (gas: 298843) -ScribeOptimisticTest:test_afterAuthedAction_3_setChallengePeriod() (gas: 298316) -ScribeOptimisticTest:test_afterAuthedAction_4_drop() (gas: 327250) -ScribeOptimisticTest:test_afterAuthedAction_4_setBar() (gas: 386044) -ScribeOptimisticTest:test_afterAuthedAction_4_setChallengePeriod() (gas: 384786) -ScribeOptimisticTest:test_drop_IndexZero() (gas: 44243) -ScribeOptimisticTest:test_drop_Multiple_IsAuthProtected() (gas: 14770) -ScribeOptimisticTest:test_drop_Single_IsAuthProtected() (gas: 12782) -ScribeOptimisticTest:test_latestAnswer_isTollProtected() (gas: 12505) +ScribeInvariantTest:invariant_pubKeys_CannotIndexOutOfBoundsViaUint8Index() (runs: 256, calls: 3840, reverts: 0) +ScribeInvariantTest:invariant_pubKeys_IndexedViaFeedId() (runs: 256, calls: 3840, reverts: 0) +ScribeOptimisticTest:test_Deployment() (gas: 1204337) +ScribeOptimisticTest:test_afterAuthedAction_1_drop() (gas: 202463) +ScribeOptimisticTest:test_afterAuthedAction_1_setBar() (gas: 238545) +ScribeOptimisticTest:test_afterAuthedAction_1_setChallengePeriod() (gas: 237041) +ScribeOptimisticTest:test_afterAuthedAction_2_drop() (gas: 285432) +ScribeOptimisticTest:test_afterAuthedAction_2_setBar() (gas: 325402) +ScribeOptimisticTest:test_afterAuthedAction_2_setChallengePeriod() (gas: 324955) +ScribeOptimisticTest:test_afterAuthedAction_3_drop() (gas: 205138) +ScribeOptimisticTest:test_afterAuthedAction_3_setBar() (gas: 241782) +ScribeOptimisticTest:test_afterAuthedAction_3_setChallengePeriod() (gas: 241184) +ScribeOptimisticTest:test_afterAuthedAction_4_drop() (gas: 286761) +ScribeOptimisticTest:test_afterAuthedAction_4_setBar() (gas: 326467) +ScribeOptimisticTest:test_afterAuthedAction_4_setChallengePeriod() (gas: 325228) +ScribeOptimisticTest:test_drop_Multiple_IsAuthProtected() (gas: 14650) +ScribeOptimisticTest:test_drop_Single_IsAuthProtected() (gas: 13533) +ScribeOptimisticTest:test_latestAnswer_isTollProtected() (gas: 12518) ScribeOptimisticTest:test_latestRoundData_isTollProtected() (gas: 14030) -ScribeOptimisticTest:test_lift_Multiple_FailsIf_ECDSADataInvalid() (gas: 106973) -ScribeOptimisticTest:test_lift_Multiple_FailsIf_MaxFeedsReached() (gas: 20286949) -ScribeOptimisticTest:test_lift_Multiple_IsAuthProtected() (gas: 16490) -ScribeOptimisticTest:test_lift_Single_FailsIf_ECDSADataInvalid() (gas: 22135) -ScribeOptimisticTest:test_lift_Single_FailsIf_MaxFeedsReached() (gas: 19731489) -ScribeOptimisticTest:test_lift_Single_IsAuthProtected() (gas: 12905) -ScribeOptimisticTest:test_opChallenge_FailsIf_CalledSubsequently() (gas: 311729) -ScribeOptimisticTest:test_opChallenge_FailsIf_InvalidSchnorrDataGiven() (gas: 283298) -ScribeOptimisticTest:test_opChallenge_FailsIf_NoOpPokeToChallenge() (gas: 14897) -ScribeOptimisticTest:test_peek_isTollProtected() (gas: 12870) -ScribeOptimisticTest:test_peep_isTollProtected() (gas: 13200) -ScribeOptimisticTest:test_poke_Initial_FailsIf_AgeIsZero() (gas: 242775) -ScribeOptimisticTest:test_readWithAge_isTollProtected() (gas: 12357) -ScribeOptimisticTest:test_read_isTollProtected() (gas: 11823) -ScribeOptimisticTest:test_setBar_FailsIf_BarIsZero() (gas: 11793) -ScribeOptimisticTest:test_setBar_IsAuthProtected() (gas: 13730) -ScribeOptimisticTest:test_setMaxChallengeReward_IsAuthProtected() (gas: 13730) -ScribeOptimisticTest:test_setOpChallengePeriod_DropsFinalizedOpPoke_If_NonFinalizedAfterUpdate() (gas: 299764) -ScribeOptimisticTest:test_setOpChallengePeriod_FailsIf_OpChallengePeriodIsZero() (gas: 11567) -ScribeOptimisticTest:test_setOpChallengePeriod_IsAuthProtected() (gas: 12332) -ScribeOptimisticTest:test_toll_diss_IsAuthProtected() (gas: 13260) -ScribeOptimisticTest:test_toll_kiss_IsAuthProtected() (gas: 12419) -ScribeOptimisticTest:test_tryReadWithAge_isTollProtected() (gas: 13746) -ScribeOptimisticTest:test_tryRead_isTollProtected() (gas: 12986) -ScribeTest:test_Deployment() (gas: 42326) -ScribeTest:test_drop_IndexZero() (gas: 16904) -ScribeTest:test_drop_Multiple_IsAuthProtected() (gas: 13794) -ScribeTest:test_drop_Single_IsAuthProtected() (gas: 12148) -ScribeTest:test_latestAnswer_isTollProtected() (gas: 12092) -ScribeTest:test_latestRoundData_isTollProtected() (gas: 13132) -ScribeTest:test_lift_Multiple_FailsIf_ECDSADataInvalid() (gas: 106626) -ScribeTest:test_lift_Multiple_FailsIf_MaxFeedsReached() (gas: 20286275) -ScribeTest:test_lift_Multiple_IsAuthProtected() (gas: 15605) -ScribeTest:test_lift_Single_FailsIf_ECDSADataInvalid() (gas: 21437) -ScribeTest:test_lift_Single_FailsIf_MaxFeedsReached() (gas: 19732318) -ScribeTest:test_lift_Single_IsAuthProtected() (gas: 12535) -ScribeTest:test_peek_isTollProtected() (gas: 12373) -ScribeTest:test_peep_isTollProtected() (gas: 12461) -ScribeTest:test_poke_Initial_FailsIf_AgeIsZero() (gas: 239335) -ScribeTest:test_readWithAge_isTollProtected() (gas: 11969) -ScribeTest:test_read_isTollProtected() (gas: 11695) -ScribeTest:test_setBar_FailsIf_BarIsZero() (gas: 11392) -ScribeTest:test_setBar_IsAuthProtected() (gas: 12823) -ScribeTest:test_toll_diss_IsAuthProtected() (gas: 12533) -ScribeTest:test_toll_kiss_IsAuthProtected() (gas: 12088) -ScribeTest:test_tryReadWithAge_isTollProtected() (gas: 12871) -ScribeTest:test_tryRead_isTollProtected() (gas: 12287) \ No newline at end of file +ScribeOptimisticTest:test_lift_Multiple_FailsIf_ECDSADataInvalid() (gas: 78843) +ScribeOptimisticTest:test_lift_Multiple_IsAuthProtected() (gas: 16509) +ScribeOptimisticTest:test_lift_Single_FailsIf_ECDSADataInvalid() (gas: 21864) +ScribeOptimisticTest:test_lift_Single_FailsIf_FeedIdAlreadyLifted() (gas: 73859) +ScribeOptimisticTest:test_lift_Single_IsAuthProtected() (gas: 13053) +ScribeOptimisticTest:test_opChallenge_CalldataEncodingAttack() (gas: 1528876) +ScribeOptimisticTest:test_opChallenge_FailsIf_CalledSubsequently() (gas: 256890) +ScribeOptimisticTest:test_opChallenge_FailsIf_InvalidSchnorrDataGiven() (gas: 226291) +ScribeOptimisticTest:test_opChallenge_FailsIf_NoOpPokeToChallenge() (gas: 14879) +ScribeOptimisticTest:test_peek_isTollProtected() (gas: 12886) +ScribeOptimisticTest:test_peep_isTollProtected() (gas: 13216) +ScribeOptimisticTest:test_poke_Initial_FailsIf_AgeIsZero() (gas: 185331) +ScribeOptimisticTest:test_readWithAge_isTollProtected() (gas: 12397) +ScribeOptimisticTest:test_read_isTollProtected() (gas: 11836) +ScribeOptimisticTest:test_setBar_FailsIf_BarIsZero() (gas: 11908) +ScribeOptimisticTest:test_setBar_IsAuthProtected() (gas: 13803) +ScribeOptimisticTest:test_setMaxChallengeReward_IsAuthProtected() (gas: 13698) +ScribeOptimisticTest:test_setOpChallengePeriod_DropsFinalizedOpPoke_If_NonFinalizedAfterUpdate() (gas: 242619) +ScribeOptimisticTest:test_setOpChallengePeriod_FailsIf_OpChallengePeriodIsZero() (gas: 11633) +ScribeOptimisticTest:test_setOpChallengePeriod_IsAuthProtected() (gas: 12393) +ScribeOptimisticTest:test_toll_diss_IsAuthProtected() (gas: 13268) +ScribeOptimisticTest:test_toll_kiss_IsAuthProtected() (gas: 12517) +ScribeOptimisticTest:test_tryReadWithAge_isTollProtected() (gas: 13786) +ScribeOptimisticTest:test_tryRead_isTollProtected() (gas: 13047) +ScribeTest:test_Deployment() (gas: 1185225) +ScribeTest:test_drop_Multiple_IsAuthProtected() (gas: 13643) +ScribeTest:test_drop_Single_IsAuthProtected() (gas: 12648) +ScribeTest:test_latestAnswer_isTollProtected() (gas: 12053) +ScribeTest:test_latestRoundData_isTollProtected() (gas: 13080) +ScribeTest:test_lift_Multiple_FailsIf_ECDSADataInvalid() (gas: 78456) +ScribeTest:test_lift_Multiple_IsAuthProtected() (gas: 15556) +ScribeTest:test_lift_Single_FailsIf_ECDSADataInvalid() (gas: 21117) +ScribeTest:test_lift_Single_FailsIf_FeedIdAlreadyLifted() (gas: 73352) +ScribeTest:test_lift_Single_IsAuthProtected() (gas: 12631) +ScribeTest:test_peek_isTollProtected() (gas: 12337) +ScribeTest:test_peep_isTollProtected() (gas: 12425) +ScribeTest:test_poke_Initial_FailsIf_AgeIsZero() (gas: 181717) +ScribeTest:test_readWithAge_isTollProtected() (gas: 11957) +ScribeTest:test_read_isTollProtected() (gas: 11678) +ScribeTest:test_setBar_FailsIf_BarIsZero() (gas: 11457) +ScribeTest:test_setBar_IsAuthProtected() (gas: 12846) +ScribeTest:test_toll_diss_IsAuthProtected() (gas: 12489) +ScribeTest:test_toll_kiss_IsAuthProtected() (gas: 12156) +ScribeTest:test_tryReadWithAge_isTollProtected() (gas: 12838) +ScribeTest:test_tryRead_isTollProtected() (gas: 12298) \ No newline at end of file diff --git a/.gitignore b/.gitignore index a5677dc..dbce94b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# VSCode settings +.vscode/ + # Compiler files cache/ out/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 86fad36..84a020a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Common Changelog](https://common-changelog.org/). +[2.0.0]: https://github.com/chronicleprotocol/scribe/releases/tag/v2.0.0 [1.2.0]: https://github.com/chronicleprotocol/scribe/releases/tag/v1.2.0 [1.1.0]: https://github.com/chronicleprotocol/scribe/releases/tag/v1.1.0 [1.0.0]: https://github.com/chronicleprotocol/scribe/releases/tag/v1.0.0 +## [2.0.0] - 2023-11-27 + +### Changed + +- **Breaking** Use 1-byte identifier for feeds based on highest-order byte of their addresses instead of their storage array's index ([#23](https://github.com/chronicleprotocol/scribe/pull/23)) +- **Breaking** Change `IScribe` and `IScribeOptimistic` interfaces to account for new feed identification ([#23](https://github.com/chronicleprotocol/scribe/pull/23)) + +### Fixed + +- DOS vector in `ScribeOptimistic::opPoke` making `ScribeOptimistic::opChallenge` economically unprofitable ([#23](https://github.com/chronicleprotocol/scribe/pull/23)) +- Possibility to successfully `opChallenge` a valid `opPoke` via non-default calldata encoding ([#23](https://github.com/chronicleprotocol/scribe/pull/23)) + ## [1.2.0] - 2023-09-29 ### Added diff --git a/README.md b/README.md index ac417e9..47aefdc 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ $ FOUNDRY_PROFILE=intense forge test # Run all tests in intense mode $ forge test --nmt "FuzzDifferentialOracleSuite" # Run only non-differential fuzz tests ``` -Note that in order to run the whole test suite, i.e. including differential fuzz tests, the oracle-suite's [`schnorr`](https://github.com/chronicleprotocol/oracle-suite) binary needs to be present inside the `bin/` directory. +Note that in order to run the whole test suite, i.e. including differential fuzz tests, the oracle-suite's musig [`schnorr`](https://github.com/chronicleprotocol/musig/tree/master/cmd/schnorr) binary needs to be present inside the `bin/` directory. Lint: diff --git a/assets/benchmarks.png b/assets/benchmarks.png index 2211c82..de2dec7 100644 Binary files a/assets/benchmarks.png and b/assets/benchmarks.png differ diff --git a/audits/Cantina@v2.0.0.pdf b/audits/Cantina@v2.0.0.pdf new file mode 100644 index 0000000..6e039a8 Binary files /dev/null and b/audits/Cantina@v2.0.0.pdf differ diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index 0635f69..c6a99ce 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -2,14 +2,16 @@ The benchmark for `Scribe` is based on the `poke()` function while the benchmark for `ScribeOptimistic` being based on the `opPoke()` function. -| `bar` | `Scribe::poke()` | `ScribeOptimistic::opPoke()` | -|-------|--------------------|------------------------------| -| 5 | 79,428 | 66,462 | -| 10 | 106,862 | 66,534 | -| 15 | 131,834 | 66,603 | -| 20 | 158,263 | 66,663 | -| 50 | 315,655 | 67,437 | -| 100 | 577,919 | 68,845 | +| `bar` | `Scribe::poke()` | `ScribeOptimistic::opPoke()` | +| ----- | ---------------- | ---------------------------- | +| 5 | 81,025 | 68,944 | +| 10 | 106,395 | 69,004 | +| 15 | 134,342 | 69,061 | +| 20 | 159,488 | 69,133 | +| 50 | 320,473 | 69,908 | +| 100 | 585,993 | 71,315 | +| 200 | 1,119,535 | 73,759 | +| 255 | 1,411,702 | 74,852 | The following visualization shows the gas usage for different numbers of `bar`: diff --git a/docs/Invariants.md b/docs/Invariants.md index 3c0bad5..fa5441a 100644 --- a/docs/Invariants.md +++ b/docs/Invariants.md @@ -68,67 +68,24 @@ This document specifies invariants of the Scribe and ScribeOptimistic oracle con ## `{Scribe, ScribeOptimistic}::_pubKeys` -* `_pubKeys[0]` is the zero point: +* `_pubKeys`' length is 256: ``` - _pubKeys[0].isZeroPoint() + _pubKeys.length == 256 ``` -* A non-zero public key exists at most once: +* Public keys are stored at the index of their address' first byte: ``` - ∀x ∊ PublicKeys: x.isZeroPoint() ∨ count(x in _pubKeys) <= 1 - ``` - -* Length is strictly monotonically increasing: - ``` - preTx(_pubKeys.length) != postTx(_pubKeys.length) - → preTx(_pubKeys.length) < posTx(_pubKeys.length) - ``` - -* Existing public key may only be deleted, never mutated: - ``` - ∀x ∊ uint: x < _pubKeys.length ⋀ preTx(_pubKeys[x]) != postTx(_pubKeys[x]) - → postTx(_pubKeys[x].isZeroPoint()) - ``` - -* Newly added public key is non-zero: - ``` - preTx(_pubKeys.length) != postTx(_pubKeys.length) - → postTx(!_pubKeys[_pubKeys.length-1].isZeroPoint()) + ∀id ∊ Uint8: _pubKeys[id].isZeroPoint() ∨ (_pubKeys[id].toAddress() >> 152) == id ``` * Only functions `lift` and `drop` may mutate the array's state: ``` - ∀x ∊ uint: preTx(_pubKeys[x]) != postTx(_pubKeys[x]) + ∀id ∊ Uint8: preTx(_pubKeys[id]) != postTx(_pubKeys[id]) → msg.sig ∊ {"lift", "drop"} ``` * Array's state may only be mutated by auth'ed caller: ``` - ∀x ∊ uint: preTx(_pubKeys[x]) != postTx(_pubKeys[x]) - → authed(msg.sender) - ``` - -## `{Scribe, ScribeOptimistic}::_feeds` - -* Image of mapping is `[0, _pubKeys.length)`: - ``` - ∀x ∊ Address: _feeds[x] ∊ [0, _pubKeys.length) - ``` - -* Image of mapping links to feed's public key in `_pubKeys`: - ``` - ∀x ∊ Address: _feeds[x] = y ⋀ y != 0 - → _pubKeys[y].toAddress() == x - ``` - -* Only functions `lift` and `drop` may mutate the mapping's state: - ``` - ∀x ∊ Address: preTx(_feeds[x]) != postTx(_feeds[x]) - → msg.sig ∊ {"lift", "drop"} - ``` - -* Mapping's state may only be mutated by auth'ed caller: - ``` - ∀x ∊ Address: preTx(_feeds[x]) != postTx(_feeds[x]) + ∀id ∊ Uint8: preTx(_pubKeys[id]) != postTx(_pubKeys[id]) → authed(msg.sender) ``` diff --git a/docs/Management.md b/docs/Management.md index 4884f8e..9fcbea0 100644 --- a/docs/Management.md +++ b/docs/Management.md @@ -111,7 +111,7 @@ $ forge script \ Set the following environment variables: -- `FEED_INDEX`: The feed's index +- `FEED_ID`: The feed's id Run: @@ -120,7 +120,7 @@ $ forge script \ --private-key $PRIVATE_KEY \ --broadcast \ --rpc-url $RPC_URL \ - --sig $(cast calldata "drop(address,uint)" $SCRIBE $FEED_INDEX) \ + --sig $(cast calldata "drop(address,uint)" $SCRIBE $FEED_ID) \ -vvv \ script/${SCRIBE_FLAVOUR}.s.sol:${SCRIBE_FLAVOUR}Script ``` diff --git a/docs/Scribe.md b/docs/Scribe.md index 28cad0a..9a29fb9 100644 --- a/docs/Scribe.md +++ b/docs/Scribe.md @@ -50,9 +50,9 @@ For more info, see [`LibSecp256k1::addAffinePoint()`](../src/libs/LibSecp256k1.s The `poke()` function has to receive the set of feeds, i.e. public keys, that participated in the Schnorr multi-signature. -To reduce the calldata load, Scribe does not use type `address`, which uses 20 bytes per feed, but encodes the unique feeds' identifier's byte-wise into a `bytes` type called `signersBlob`. +To reduce the calldata load, Scribe does not use type `address`, which uses 20 bytes per feed, but encodes the feeds' identifier's byte-wise into a `bytes` type called `feedIds`. -For more info, see [`LibSchnorrData.sol`](../src/libs/LibSchnorrData.sol). +A feed's identifier is defined as the highest order byte of the feed's address and can be computed via `uint8(uint(uint160(feedAddress)) >> 152)`. ## Lifting Feeds @@ -60,8 +60,6 @@ Feeds _must_ prove the integrity of their public key by proving the ownership of If public key's would not be verified, the Schnorr signature verification would be vulnerable to rogue-key attacks. For more info, see [`docs/Schnorr.md`](./Schnorr.md#key-aggregation-for-multisignatures). -Also, the number of state-changing `lift()` executions is limited to `type(uint8).max-1`, i.e. 254. After reaching this limit, no further `lift()` calls can be executed. For more info, see [`IScribe.maxFeeds()`](../src/IScribe.sol). - ## Chainlink Compatibility Scribe aims to be partially Chainlink compatible by implementing the most widely, and not deprecated, used functions of the `IChainlinkAggregatorV3` interface. @@ -69,6 +67,7 @@ Scribe aims to be partially Chainlink compatible by implementing the most widely The following `IChainlinkAggregatorV3` functions are provided: - `latestRoundData()` - `decimals()` +- `latestAnswer()` ## Optimistic-Flavored Scribe diff --git a/foundry.toml b/foundry.toml index bcd5f95..0f96259 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,7 +12,7 @@ via_ir = true extra_output_files = ["metadata", "irOptimized"] # Testing -fuzz = { runs = 10 } +fuzz = { runs = 50 } block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT [invariant] diff --git a/script/Scribe.s.sol b/script/Scribe.s.sol index 5fb2b22..516ccd2 100644 --- a/script/Scribe.s.sol +++ b/script/Scribe.s.sol @@ -27,7 +27,7 @@ contract ScribeScript is Script { /// @dev Deploys a new Scribe instance via Greenhouse instance `greenhouse` /// and salt `salt` with `initialAuthed` being the address initially - /// auth'ed. + /// auth'ed. Note that zero address is kissed directly after deployment. function deploy( address greenhouse, bytes32 salt, @@ -43,9 +43,11 @@ contract ScribeScript is Script { address deployed = IGreenhouse(greenhouse).addressOf(salt); require(deployed.code.length == 0, "Salt already used"); - // Plant creation code via greenhouse. + // Plant creation code via greenhouse and kiss zero address. vm.startBroadcast(); + require(msg.sender == initialAuthed, "Deployer must be initial auth'ed"); IGreenhouse(greenhouse).plant(salt, creationCode); + IToll(deployed).kiss(address(0)); vm.stopBroadcast(); console2.log("Deployed at", deployed); @@ -88,8 +90,7 @@ contract ScribeScript is Script { require(!pubKey.isZeroPoint(), "Public key cannot be zero point"); require(pubKey.isOnCurve(), "Public key must be valid secp256k1 point"); - bool isFeed; - (isFeed, /*feedIndex*/ ) = IScribe(self).feeds(pubKey.toAddress()); + bool isFeed = IScribe(self).feeds(pubKey.toAddress()); require(!isFeed, "Public key already lifted"); address recovered = @@ -142,9 +143,7 @@ contract ScribeScript is Script { pubKeys[i].isOnCurve(), "Public key must be valid secp256k1 point" ); - bool isFeed; - (isFeed, /*feedIndex*/ ) = - IScribe(self).feeds(pubKeys[i].toAddress()); + bool isFeed = IScribe(self).feeds(pubKeys[i].toAddress()); require(!isFeed, "Public key already lifted"); } @@ -180,15 +179,13 @@ contract ScribeScript is Script { } } - /// @dev Drops feed with index `feedIndex`. - function drop(address self, uint feedIndex) public { - require(feedIndex != 0, "Feed index cannot be zero"); - + /// @dev Drops feed with id `feedId`. + function drop(address self, uint8 feedId) public { vm.startBroadcast(); - IScribe(self).drop(feedIndex); + IScribe(self).drop(feedId); vm.stopBroadcast(); - console2.log("Dropped", feedIndex); + console2.log("Dropped", feedId); } // -- View Functions @@ -314,13 +311,16 @@ contract ScribeScript is Script { /// script/${SCRIBE_FLAVOUR}.s.sol:${SCRIBE_FLAVOUR}Script /// ``` function deactivate(address self) public { - // Get current feeds' indexes. - uint[] memory feedIndexes; - ( /*feeds*/ , feedIndexes) = IScribe(self).feeds(); + // Get lifted feeds and compute their feed ids. + address[] memory feeds = IScribe(self).feeds(); + uint8[] memory feedIds = new uint8[](feeds.length); + for (uint i; i < feeds.length; i++) { + feedIds[i] = uint8(uint(uint160(feeds[i])) >> 152); + } // Drop all feeds. vm.startBroadcast(); - IScribe(self).drop(feedIndexes); + IScribe(self).drop(feedIds); vm.stopBroadcast(); // Create new random private key. @@ -336,12 +336,9 @@ contract ScribeScript is Script { // Lift feed. vm.startBroadcast(); - uint feedIndex = IScribe(self).lift(feed.pubKey, ecdsaData); + IScribe(self).lift(feed.pubKey, ecdsaData); vm.stopBroadcast(); - // Set feed's assigned feedIndex. - feed.index = uint8(feedIndex); - // Set bar to 1. vm.startBroadcast(); IScribe(self).setBar(uint8(1)); @@ -365,7 +362,7 @@ contract ScribeScript is Script { // Drop feed again. vm.startBroadcast(); - IScribe(self).drop(feed.index); + IScribe(self).drop(feed.id); vm.stopBroadcast(); // Set bar to type(uint8).max. diff --git a/script/ScribeOptimistic.s.sol b/script/ScribeOptimistic.s.sol index 54afd15..9121d16 100644 --- a/script/ScribeOptimistic.s.sol +++ b/script/ScribeOptimistic.s.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.16; import {console2} from "forge-std/console2.sol"; +import {IToll} from "chronicle-std/toll/IToll.sol"; + import {IGreenhouse} from "greenhouse/IGreenhouse.sol"; import {IScribe} from "src/IScribe.sol"; @@ -21,7 +23,8 @@ import {ScribeScript} from "./Scribe.s.sol"; contract ScribeOptimisticScript is ScribeScript { /// @dev Deploys a new ScribeOptimistic instance via Greenhouse instance /// `greenhouse` and salt `salt` with `initialAuthed` being the address - /// initially auth'ed. + /// initially auth'ed. Note that zero address is kissed directly after + /// deployment. function deploy( address greenhouse, bytes32 salt, @@ -37,9 +40,11 @@ contract ScribeOptimisticScript is ScribeScript { address deployed = IGreenhouse(greenhouse).addressOf(salt); require(deployed.code.length == 0, "Salt already used"); - // Plant creation code via greenhouse. + // Plant creation code via greenhouse and kiss zero address. vm.startBroadcast(); + require(msg.sender == initialAuthed, "Deployer must be initial auth'ed"); IGreenhouse(greenhouse).plant(salt, creationCode); + IToll(deployed).kiss(address(0)); vm.stopBroadcast(); console2.log("Deployed at", deployed); diff --git a/script/benchmarks/ScribeBenchmark.s.sol b/script/benchmarks/ScribeBenchmark.s.sol index 8953296..1605d63 100644 --- a/script/benchmarks/ScribeBenchmark.s.sol +++ b/script/benchmarks/ScribeBenchmark.s.sol @@ -107,18 +107,32 @@ contract ScribeBenchmark is Script { scribe.poke(pokeData, schnorrData); } - function _createFeeds(uint amount) + function _createFeeds(uint numberFeeds) internal returns (LibFeed.Feed[] memory) { - uint startPrivKey = 2; - - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](amount); - for (uint i; i < amount; i++) { - feeds[i] = LibFeed.newFeed({ - privKey: startPrivKey + i, - index: uint8(i + 1) - }); + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](numberFeeds); + + // Note to not start with privKey=1. This is because the sum of public + // keys would evaluate to: + // pubKeyOf(1) + pubKeyOf(2) + pubKeyOf(3) + ... + // = pubKeyOf(3) + pubKeyOf(3) + ... + // Note that pubKeyOf(3) would be doubled. Doubling is not supported by + // LibSecp256k1 as this would indicate a double-signing attack. + uint privKey = 2; + uint bloom; + uint ctr; + while (ctr != numberFeeds) { + LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); + + // Check whether feed with id already created, if not create. + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + } + + privKey++; } return feeds; diff --git a/script/benchmarks/ScribeOptimisticBenchmark.s.sol b/script/benchmarks/ScribeOptimisticBenchmark.s.sol index 90c0072..8d436f4 100644 --- a/script/benchmarks/ScribeOptimisticBenchmark.s.sol +++ b/script/benchmarks/ScribeOptimisticBenchmark.s.sol @@ -154,18 +154,32 @@ contract ScribeOptimisticBenchmark is Script { opScribe.opPoke(pokeData, schnorrData, ecdsaData); } - function _createFeeds(uint amount) + function _createFeeds(uint numberFeeds) internal returns (LibFeed.Feed[] memory) { - uint startPrivKey = 2; - - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](amount); - for (uint i; i < amount; i++) { - feeds[i] = LibFeed.newFeed({ - privKey: startPrivKey + i, - index: uint8(i + 1) - }); + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](numberFeeds); + + // Note to not start with privKey=1. This is because the sum of public + // keys would evaluate to: + // pubKeyOf(1) + pubKeyOf(2) + pubKeyOf(3) + ... + // = pubKeyOf(3) + pubKeyOf(3) + ... + // Note that pubKeyOf(3) would be doubled. Doubling is not supported by + // LibSecp256k1 as this would indicate a double-signing attack. + uint privKey = 2; + uint bloom; + uint ctr; + while (ctr != numberFeeds) { + LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); + + // Check whether feed with id already created, if not create. + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + } + + privKey++; } return feeds; diff --git a/script/benchmarks/run.sh b/script/benchmarks/run.sh index e9bcfd3..a916a64 100755 --- a/script/benchmarks/run.sh +++ b/script/benchmarks/run.sh @@ -78,6 +78,8 @@ run_Scribe 15 run_Scribe 20 run_Scribe 50 run_Scribe 100 +run_Scribe 200 +run_Scribe 255 echo "=== Scribe Optimistic Benchmarks (Printing cost of non-initial opPoke())" run_ScribeOptimistic 5 @@ -86,3 +88,5 @@ run_ScribeOptimistic 15 run_ScribeOptimistic 20 run_ScribeOptimistic 50 run_ScribeOptimistic 100 +run_ScribeOptimistic 200 +run_ScribeOptimistic 255 diff --git a/script/benchmarks/visualize.py b/script/benchmarks/visualize.py index 70b3a13..f94e2ca 100644 --- a/script/benchmarks/visualize.py +++ b/script/benchmarks/visualize.py @@ -7,13 +7,13 @@ import matplotlib.pyplot as plt # Bar configuration -x = [5, 10, 15, 20, 50, 100] +x = [5, 10, 15, 20, 50, 100, 200, 255] # Scribe benchmark results received via `run.sh` -scribe = [79428, 106862, 131834, 158263, 315655, 577919] +scribe = [80280, 105070, 132414, 156983, 314455, 574227, 1096599, 1382810] # ScribeOptimistic benchmark results received via `run.sh` -opScribe = [66462, 66534, 66603, 66663, 67437, 68845] +opScribe = [68815, 68887, 68944, 69004, 69791, 71186, 73630, 74735] # Plotting the benchmark data plt.plot(x, scribe, label='Scribe') diff --git a/script/chaincheck/IScribeChaincheck.sol b/script/chaincheck/IScribeChaincheck.sol index 9d2b202..04acf2e 100644 --- a/script/chaincheck/IScribeChaincheck.sol +++ b/script/chaincheck/IScribeChaincheck.sol @@ -24,10 +24,6 @@ import { /** * @notice IScribe's `chaincheck` Integration Test * - * @dev Note that this `chaincheck` has a runtime of and memory consumption of - * ω(2^#feeds). If the script fails with "EVMError: MemoryLimitOOG", - * increase the memory limit via the `--memory-limit` flag. - * * @dev Config Definition: * * ```json @@ -41,10 +37,6 @@ import { * "", * ... * ], - * "feedIndexes": [ - * 0, - * ... - * ], * "feedPublicKeys": { * "xCoordinates": [ * , @@ -88,9 +80,6 @@ contract IScribeChaincheck is Chaincheck { string[] logs; - // Necessary for check_invariant_PubKeysHaveNoLinearRelationship(). - LibPublicKeyVerifier.PublicKeyVerifier pubKeyVerifier; - function setUp(address self_, string memory config_) public virtual @@ -117,23 +106,18 @@ contract IScribeChaincheck is Chaincheck { check_decimals(); // Liveness: - check_stalenessThreshold(); + check_StalenessThreshold(); // Configurations: check_bar(); check_feeds_AllExpectedFeedsAreLifted(); check_feeds_OnlyExpectedFeedsAreLifted(); - check_feeds_AllExpectedFeedIndexesLinkToCorrectFeed(); check_feeds_AllPublicKeysAreLifted(); check_feeds_PublicKeysCorrectlyOrdered(); // Invariants: + check_invariant_IsReadable(); check_invariant_ZeroPublicKeyIsNotLifted(); - // Note that check is disabled due to heavy memory usage making it - // currently unusable in ci. - // @todo: Try to fix. However, problem is NP hard so most likely need - // more resources in ci or utilize some caching strategy. - //check_invariant_PublicKeysHaveNoLinearRelationship(); check_invariant_BarIsNotZero(); check_invariant_ReadFunctionsReturnSameValue(); @@ -149,7 +133,6 @@ contract IScribeChaincheck is Chaincheck { function check_feeds_ConfigSanity() internal { address[] memory feeds = config.readAddressArray(".IScribe.feeds"); - uint[] memory feedIndexes = config.readUintArray(".IScribe.feedIndexes"); uint[] memory feedPublicKeysXCoordinates = config.readUintArray(".IScribe.feedPublicKeys.xCoordinates"); uint[] memory feedPublicKeysYCoordinates = @@ -157,16 +140,6 @@ contract IScribeChaincheck is Chaincheck { uint wantLen = feeds.length; - if (feedIndexes.length != wantLen) { - logs.push( - string.concat( - StdStyle.red( - "Config error: IScribe.feeds.length != IScribe.feedIndexes.length" - ) - ) - ); - } - if (feedPublicKeysXCoordinates.length != wantLen) { logs.push( string.concat( @@ -226,7 +199,7 @@ contract IScribeChaincheck is Chaincheck { // -- Liveness -- - function check_stalenessThreshold() internal { + function check_StalenessThreshold() internal { uint stalenessThreshold = config.readUint(".IScribe.stalenessThreshold"); // Note to make sure address(this) is tolled. @@ -235,16 +208,9 @@ contract IScribeChaincheck is Chaincheck { vm.prank(IAuth(address(self)).authed()[0]); IToll(address(self)).kiss(addrThis); - // Read val and age. - bool ok; - uint val; + // Read age. uint age; - (ok, val, age) = self.tryReadWithAge(); - - // Check whether value is provided at all. - if (!ok) { - logs.push(StdStyle.red("Read failed")); - } + ( /*ok*/ , /*val*/, age) = self.tryReadWithAge(); // Check whether value's age is older than allowed. if (block.timestamp - age > stalenessThreshold) { @@ -291,8 +257,7 @@ contract IScribeChaincheck is Chaincheck { for (uint i; i < wantFeeds.length; i++) { wantFeed = wantFeeds[i]; - bool isFeed; - (isFeed, /*feedIndex*/ ) = self.feeds(wantFeed); + bool isFeed = self.feeds(wantFeed); if (!isFeed) { logs.push( @@ -311,58 +276,26 @@ contract IScribeChaincheck is Chaincheck { address[] memory wantFeeds = config.readAddressArray(".IScribe.feeds"); // Check that only expected feeds are lifted. - address[] memory gotFeeds; - (gotFeeds, /*feedIndexes*/ ) = self.feeds(); + address[] memory gotFeeds = self.feeds(); for (uint i; i < gotFeeds.length; i++) { + bool found = false; + for (uint j; j < wantFeeds.length; j++) { if (gotFeeds[i] == wantFeeds[j]) { - // Feed is expected, break inner loop. - break; - } - - if (j == wantFeeds.length - 1) { - // Feed not found. - logs.push( - string.concat( - StdStyle.red("Unknown feed lifted:"), - " feed=", - vm.toString(gotFeeds[i]) - ) - ); + found = true; + break; // Found feed. Continue with outer loop. } } - } - } - - function check_feeds_AllExpectedFeedIndexesLinkToCorrectFeed() internal { - address[] memory wantFeeds = config.readAddressArray(".IScribe.feeds"); - uint[] memory wantFeedIndexes = - config.readUintArray(".IScribe.feedIndexes"); - // Check that each feed index links to correct feed. - address wantFeed; - uint wantFeedIndex; - for (uint i; i < wantFeeds.length; i++) { - wantFeed = wantFeeds[i]; - wantFeedIndex = wantFeedIndexes[i]; - - bool isFeed; - uint gotFeedIndex; - (isFeed, gotFeedIndex) = self.feeds(wantFeed); - - if (wantFeedIndex != gotFeedIndex) { + if (!found) { + // Feed not found. logs.push( string.concat( - StdStyle.red("Expected feed index does not match:"), + StdStyle.red("Unknown feed lifted:"), " feed=", - vm.toString(wantFeed), - ", expectedIndex=", - vm.toString(wantFeedIndex), - ", actualIndex=", - vm.toString(gotFeedIndex) + vm.toString(gotFeeds[i]) ) ); - continue; } } } @@ -391,8 +324,7 @@ contract IScribeChaincheck is Chaincheck { // Check that each address derived from public key is lifted. for (uint i; i < addrs.length; i++) { - bool isFeed; - (isFeed, /*feedIndex*/ ) = self.feeds(addrs[i]); + bool isFeed = self.feeds(addrs[i]); if (!isFeed) { logs.push( @@ -445,41 +377,29 @@ contract IScribeChaincheck is Chaincheck { // -- Invariants -- - function check_invariant_ZeroPublicKeyIsNotLifted() internal { - bool isFeed; - (isFeed, /*feedIndex*/ ) = - self.feeds(LibSecp256k1.ZERO_POINT().toAddress()); + function check_invariant_IsReadable() internal { + // Note to make sure address(this) is tolled. + // Do not forget to diss after afterwards again! + address addrThis = address(this); + vm.prank(IAuth(address(self)).authed()[0]); + IToll(address(self)).kiss(addrThis); - if (isFeed) { - logs.push( - StdStyle.red("[INVARIANT BROKEN] Zero public key is lifted") - ); + try self.read() {} + catch { + logs.push(StdStyle.red("[INVARIANT BROKEN] Not readable")); } - } - function check_invariant_PublicKeysHaveNoLinearRelationship() internal { - uint[] memory feedPublicKeysXCoordinates = - config.readUintArray(".IScribe.feedPublicKeys.xCoordinates"); - uint[] memory feedPublicKeysYCoordinates = - config.readUintArray(".IScribe.feedPublicKeys.yCoordinates"); + // Note to diss address(this) again. + vm.prank(IAuth(address(self)).authed()[0]); + IToll(address(self)).diss(addrThis); + } - // Make LibSecp256k1.Point types from coordinates. - LibSecp256k1.Point[] memory pubKeys; - pubKeys = new LibSecp256k1.Point[](feedPublicKeysXCoordinates.length); - for (uint i; i < pubKeys.length; i++) { - pubKeys[i] = LibSecp256k1.Point({ - x: feedPublicKeysXCoordinates[i], - y: feedPublicKeysYCoordinates[i] - }); - } + function check_invariant_ZeroPublicKeyIsNotLifted() internal { + bool isFeed = self.feeds(LibSecp256k1.ZERO_POINT().toAddress()); - bool ok; - (ok, /*set1*/, /*set2*/ ) = pubKeyVerifier.verifyPublicKeys(pubKeys); - if (!ok) { + if (isFeed) { logs.push( - StdStyle.red( - "[INVARIANT BROKEN] Public keys have linear relationship" - ) + StdStyle.red("[INVARIANT BROKEN] Zero public key is lifted") ); } } @@ -586,9 +506,7 @@ contract IScribeChaincheck is Chaincheck { function check_IAuth() internal { // Run IAuth chaincheck. string[] memory authLogs; - (, authLogs) = new IAuthChaincheck() - .setUp(address(self), config) - .run(); + (, authLogs) = new IAuthChaincheck().setUp(address(self), config).run(); // Add logs to own logs. for (uint i; i < authLogs.length; i++) { @@ -599,14 +517,12 @@ contract IScribeChaincheck is Chaincheck { /// @dev Checks the IToll module dependency. function check_IToll() internal { // Run IToll chaincheck. - string[] memory authLogs; - (, authLogs) = new ITollChaincheck() - .setUp(address(self), config) - .run(); + string[] memory tollLogs; + (, tollLogs) = new ITollChaincheck().setUp(address(self), config).run(); // Add logs to own logs. - for (uint i; i < authLogs.length; i++) { - logs.push(authLogs[i]); + for (uint i; i < tollLogs.length; i++) { + logs.push(tollLogs[i]); } } diff --git a/script/chaincheck/IScribeOptimisticChaincheck.sol b/script/chaincheck/IScribeOptimisticChaincheck.sol index 41fbdae..f868550 100644 --- a/script/chaincheck/IScribeOptimisticChaincheck.sol +++ b/script/chaincheck/IScribeOptimisticChaincheck.sol @@ -35,10 +35,6 @@ import {IScribeChaincheck} from "./IScribeChaincheck.sol"; * "", * ... * ], - * "feedIndexes": [ - * 0, - * ... - * ], * "feedPublicKeys": { * "xCoordinates": [ * , diff --git a/script/dev/ScribeOptimisticTester.s.sol b/script/dev/ScribeOptimisticTester.s.sol index 42358ce..b2d7b04 100644 --- a/script/dev/ScribeOptimisticTester.s.sol +++ b/script/dev/ScribeOptimisticTester.s.sol @@ -45,27 +45,21 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { // Setup feeds. LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); for (uint i; i < feeds.length; i++) { - feeds[i] = - LibFeed.newFeed({privKey: privKeys[i], index: uint8(i + 1)}); + feeds[i] = LibFeed.newFeed({privKey: privKeys[i]}); vm.label( feeds[i].pubKey.toAddress(), string.concat("Feed #", vm.toString(i + 1)) ); - // Update feed's index. - bool isFeed; - uint feedIndex; - (isFeed, feedIndex) = - IScribe(self).feeds(feeds[i].pubKey.toAddress()); + // Verify feed is lifted. + bool isFeed = IScribe(self).feeds(feeds[i].pubKey.toAddress()); require( isFeed, string.concat( "Private key not feed, privKey=", vm.toString(privKeys[i]) ) ); - - feeds[i].index = uint8(feedIndex); } // Create poke data. @@ -125,27 +119,21 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { // Setup feeds. LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); for (uint i; i < feeds.length; i++) { - feeds[i] = - LibFeed.newFeed({privKey: privKeys[i], index: uint8(i + 1)}); + feeds[i] = LibFeed.newFeed({privKey: privKeys[i]}); vm.label( feeds[i].pubKey.toAddress(), string.concat("Feed #", vm.toString(i + 1)) ); - // Update feed's index. - bool isFeed; - uint feedIndex; - (isFeed, feedIndex) = - IScribe(self).feeds(feeds[i].pubKey.toAddress()); + // Verify feed is lifted. + bool isFeed = IScribe(self).feeds(feeds[i].pubKey.toAddress()); require( isFeed, string.concat( "Private key not feed, privKey=", vm.toString(privKeys[i]) ) ); - - feeds[i].index = uint8(feedIndex); } // Create poke data. @@ -196,7 +184,7 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { /// --private-key $PRIVATE_KEY \ /// --broadcast \ /// --rpc-url $RPC_URL \ - /// --sig $(cast calldata "opChallenge(address,uint128,uint32,bytes32,address,bytes)" $SCRIBE $TEST_POKE_VAL $TEST_POKE_AGE $TEST_SCHNORR_SIGNATURE $TEST_SCHNORR_COMMITMENT $TEST_SCHNORR_SIGNERS_BLOB) \ + /// --sig $(cast calldata "opChallenge(address,uint128,uint32,bytes32,address,bytes)" $SCRIBE $TEST_POKE_VAL $TEST_POKE_AGE $TEST_SCHNORR_SIGNATURE $TEST_SCHNORR_COMMITMENT $TEST_SCHNORR_FEED_IDS) \ /// -vvv \ /// script/dev/ScribeOptimisticTester.s.sol:ScribeOptimisticTesterScript /// ``` @@ -206,7 +194,7 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { uint32 age, bytes32 schnorrSignature, address schnorrCommitment, - bytes memory schnorrSignersBlob + bytes memory schnorrFeedIds ) public { // Construct pokeData and schnorrData. IScribe.PokeData memory pokeData; @@ -216,7 +204,7 @@ contract ScribeOptimisticTesterScript is ScribeTesterScript { IScribe.SchnorrData memory schnorrData; schnorrData.signature = schnorrSignature; schnorrData.commitment = schnorrCommitment; - schnorrData.signersBlob = schnorrSignersBlob; + schnorrData.feedIds = schnorrFeedIds; // Create poke message from pokeData. bytes32 pokeMessage = IScribe(self).constructPokeMessage(pokeData); diff --git a/script/dev/ScribeTester.s.sol b/script/dev/ScribeTester.s.sol index d80f386..9412de4 100644 --- a/script/dev/ScribeTester.s.sol +++ b/script/dev/ScribeTester.s.sol @@ -41,8 +41,7 @@ contract ScribeTesterScript is Script { // Setup feeds. LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); for (uint i; i < feeds.length; i++) { - feeds[i] = - LibFeed.newFeed({privKey: privKeys[i], index: uint8(i + 1)}); + feeds[i] = LibFeed.newFeed({privKey: privKeys[i]}); vm.label( feeds[i].pubKey.toAddress(), @@ -93,27 +92,21 @@ contract ScribeTesterScript is Script { // Setup feeds. LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); for (uint i; i < feeds.length; i++) { - feeds[i] = - LibFeed.newFeed({privKey: privKeys[i], index: uint8(i + 1)}); + feeds[i] = LibFeed.newFeed({privKey: privKeys[i]}); vm.label( feeds[i].pubKey.toAddress(), string.concat("Feed #", vm.toString(i + 1)) ); - // Update feed's index. - bool isFeed; - uint feedIndex; - (isFeed, feedIndex) = - IScribe(self).feeds(feeds[i].pubKey.toAddress()); + // Verify feed is lifted. + bool isFeed = IScribe(self).feeds(feeds[i].pubKey.toAddress()); require( isFeed, string.concat( "Private key not feed, privKey=", vm.toString(privKeys[i]) ) ); - - feeds[i].index = uint8(feedIndex); } // Create poke data. diff --git a/script/dev/invalid-oppoker.sh b/script/dev/invalid-oppoker.sh index e5d3ebc..d8abd6a 100755 --- a/script/dev/invalid-oppoker.sh +++ b/script/dev/invalid-oppoker.sh @@ -16,7 +16,8 @@ PRIVATE_KEY= RPC_URL= SCRIBE= -FEED_PRIVATE_KEYS='[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]' +# Feed private keys taken from script/dev/test-feeds.json. +FEED_PRIVATE_KEYS='[2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,20,21,22]' while true; do # Lift feeds diff --git a/script/dev/test-feeds.json b/script/dev/test-feeds.json new file mode 100644 index 0000000..2045189 --- /dev/null +++ b/script/dev/test-feeds.json @@ -0,0 +1,1024 @@ +{ + "feeds": [ + { + "privKey": 2, + "address": "0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF" + }, + { + "privKey": 3, + "address": "0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69" + }, + { + "privKey": 4, + "address": "0x1efF47bc3a10a45D4B230B5d10E37751FE6AA718" + }, + { + "privKey": 5, + "address": "0xe1AB8145F7E55DC933d51a18c793F901A3A0b276" + }, + { + "privKey": 6, + "address": "0xE57bFE9F44b819898F47BF37E5AF72a0783e1141" + }, + { + "privKey": 7, + "address": "0xd41c057fd1c78805AAC12B0A94a405c0461A6FBb" + }, + { + "privKey": 8, + "address": "0xF1F6619B38A98d6De0800F1DefC0a6399eB6d30C" + }, + { + "privKey": 9, + "address": "0xF7Edc8FA1eCc32967F827C9043FcAe6ba73afA5c" + }, + { + "privKey": 10, + "address": "0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528" + }, + { + "privKey": 11, + "address": "0x3DA8D322CB2435dA26E9C9fEE670f9fB7Fe74E49" + }, + { + "privKey": 12, + "address": "0xDbc23AE43a150ff8884B02Cea117b22D1c3b9796" + }, + { + "privKey": 14, + "address": "0x5A83529ff76Ac5723A87008c4D9B436AD4CA7d28" + }, + { + "privKey": 15, + "address": "0x8735015837bD10e05d9cf5EA43A2486Bf4Be156F" + }, + { + "privKey": 16, + "address": "0xfaE394561e33e242c551d15D4625309EA4c0B97f" + }, + { + "privKey": 17, + "address": "0x252Dae0A4b9d9b80F504F6418acd2d364C0c59cD" + }, + { + "privKey": 18, + "address": "0x79196B90D1E952C5A43d4847CAA08d50b967c34A" + }, + { + "privKey": 19, + "address": "0x4bd1280852Cadb002734647305AFC1db7ddD6Acb" + }, + { + "privKey": 20, + "address": "0x811da72aCA31e56F770Fc33DF0e45fD08720E157" + }, + { + "privKey": 21, + "address": "0x157bFBEcd023fD6384daD2Bded5DAD7e27Bf92E4" + }, + { + "privKey": 22, + "address": "0x37dA28C050E3c0A1c0aC3BE97913EC038783dA4C" + }, + { + "privKey": 23, + "address": "0x3Bc8287F1D872df4217283b7920D363F13Cf39D8" + }, + { + "privKey": 24, + "address": "0xf4e2B0fcbd0DC4b326d8A52B718A7bb43BdBd072" + }, + { + "privKey": 25, + "address": "0x9a5279029e9A2D6E787c5A09CB068AB3D45e209d" + }, + { + "privKey": 26, + "address": "0xc39677F5F47d5fE65ab24e66750e8FCa127c15BE" + }, + { + "privKey": 27, + "address": "0x1dc728786E09F862E39Be1f39dD218EE37feB68D" + }, + { + "privKey": 28, + "address": "0x636CC65783084b9F370789c90F733DBBeb88925D" + }, + { + "privKey": 29, + "address": "0x4a7A7c2E09209dbE44A582cD92b0eDd7129E74be" + }, + { + "privKey": 30, + "address": "0xA56160A359F2EAa66f5c9df5245542B07339A9a6" + }, + { + "privKey": 31, + "address": "0x6b09D6433a379752157fD1a9E537c5CAe5fa3168" + }, + { + "privKey": 32, + "address": "0x32E77DE0D74a5C7AF861aAEd324c6a4c488142a8" + }, + { + "privKey": 33, + "address": "0x093d49D617a10F26915553255Ec3FEE532d2C12F" + }, + { + "privKey": 34, + "address": "0x138854708D8B603c9b7d4d6e55b6d32D40557F4D" + }, + { + "privKey": 35, + "address": "0x7dc0a40D64d72bb4590652B8f5C687bF7F26400c" + }, + { + "privKey": 36, + "address": "0x9358A525CC25aa571af0BCB5B98fBEAb045a5e36" + }, + { + "privKey": 37, + "address": "0xd8E8EA89D71de89214fA39Ba13bA9FCDDc0d9467" + }, + { + "privKey": 38, + "address": "0xb56eD8f48979e1A948AD129199a600d0562cac51" + }, + { + "privKey": 39, + "address": "0xf65Ac7003E905d72c666bFec1DC0960ecC9d0D6e" + }, + { + "privKey": 41, + "address": "0xf2ADB90aa27a3C61a95C50063B20919d811e1476" + }, + { + "privKey": 42, + "address": "0xae3DfFEE97f92db0201d11CB8877C89738353bCE" + }, + { + "privKey": 43, + "address": "0xEB3025e7aC2764040384316b33476E048961a71F" + }, + { + "privKey": 44, + "address": "0x9e3289708Dc5709926A542fCf260fD4B210461F0" + }, + { + "privKey": 45, + "address": "0x6C23faCE014F20B3ebb65aE96D0D7FF32aB94c17" + }, + { + "privKey": 46, + "address": "0xB83B6241f966B1685C8B2fFce3956E21F35B4DcB" + }, + { + "privKey": 48, + "address": "0x673C638147fe91e4277646d86D5AE82f775EeA5C" + }, + { + "privKey": 53, + "address": "0xe6869CC98283aB53E8a1a5312857Ef0bE9d189FE" + }, + { + "privKey": 55, + "address": "0xa1A625AE13b80A9c48b7C0331C83bc4541aC137f" + }, + { + "privKey": 56, + "address": "0xa33C9D26e1E33b84247dEFCA631c1d30FFc76F5d" + }, + { + "privKey": 57, + "address": "0xF9807a8719AE154E74591CB2d452797707faDF73" + }, + { + "privKey": 59, + "address": "0x366c20b40048556e5682e360997537c3715AcA0e" + }, + { + "privKey": 64, + "address": "0xe0dd44773F7657b11019062879D65F3D9862460c" + }, + { + "privKey": 65, + "address": "0x756Be12856A8F44AB22FdBCbD42B70b843377d09" + }, + { + "privKey": 66, + "address": "0x6f4c950442e1Af093BcfF730381E63Ae9171b87a" + }, + { + "privKey": 67, + "address": "0x4d1Bf28514A4451249908E611366Ec967C3d1558" + }, + { + "privKey": 68, + "address": "0xB0142D883494197B02c6ECE84f571D81bd831124" + }, + { + "privKey": 71, + "address": "0x7a601ffA997cEdE6435aeABf4Fa2091f09e149Ec" + }, + { + "privKey": 72, + "address": "0xa92F4b5c4FddCC37E5139873AC28a4A0a42d68df" + }, + { + "privKey": 73, + "address": "0x850Cc185d6Cae4A7fDFB3dd81F977dD1dF7D6503" + }, + { + "privKey": 74, + "address": "0xB1b7c87e8a0Bf2E7fd1a1C582bd353E4C4529341" + }, + { + "privKey": 75, + "address": "0xFF844Fdb49e00776Ad538Db9EA2f9fA98EC0cAF7" + }, + { + "privKey": 76, + "address": "0x1aC6F9601f2F616badcEa8A0a307e1A3C14767A4" + }, + { + "privKey": 77, + "address": "0xC2aA6271409c10deE630e79df90C968989ccF2B7" + }, + { + "privKey": 78, + "address": "0x883d01EaE6eAAC077e126dDb32CD53550966ED76" + }, + { + "privKey": 79, + "address": "0x127688bBc070DD69a4dB8C3BA5d43909E13d8F77" + }, + { + "privKey": 80, + "address": "0x0b54A50c0409Dab2e63c3566324268ED53Ec019a" + }, + { + "privKey": 81, + "address": "0xafd46e3549CC63d7a240D6177D056857679e6F99" + }, + { + "privKey": 83, + "address": "0xAC32DeF421e36b43629f785Fd04523260E7F2b28" + }, + { + "privKey": 84, + "address": "0xFe6032A0810e90D025a3a39dd29844F964ee102c" + }, + { + "privKey": 85, + "address": "0x5CB6F3E6499D1f068B33351D0CaE4B68cDf501Bf" + }, + { + "privKey": 86, + "address": "0x84B743441B7bDF65cB4293126dB4C1B709d7d95e" + }, + { + "privKey": 89, + "address": "0x2853dC9ca40d012969e25360ccE0D9D326b24a86" + }, + { + "privKey": 90, + "address": "0x802271c02F76701929E1ea772e72783D28e4b60f" + }, + { + "privKey": 91, + "address": "0x7bd2aA0726AC3b9E752b120DE8568E90b0423ae4" + }, + { + "privKey": 93, + "address": "0xA72392cD4be285ab6681dA1bF1C45f0B370Cb7B4" + }, + { + "privKey": 94, + "address": "0xcf484269182Ac2388A4bFE6d19FB0687e3534B7f" + }, + { + "privKey": 95, + "address": "0x994907CB80BfD175F9b0B32672CFDE0091368e2E" + }, + { + "privKey": 97, + "address": "0x440db3AB842910D2a39F4e1be9E017C6823Fb658" + }, + { + "privKey": 99, + "address": "0x24D881139Ee639C2A774B4B1851CB7a9d0fce122" + }, + { + "privKey": 100, + "address": "0xd9A284367b6D3e25A91c91b5A430AF2593886EB9" + }, + { + "privKey": 103, + "address": "0x7231C364597f3BfDB72Cf52b197cc59111e71794" + }, + { + "privKey": 104, + "address": "0x043aEd06383F290Ee28FA02794Ec7215CA099683" + }, + { + "privKey": 105, + "address": "0x0c95931d95694B3ef74071241827C09f25d40620" + }, + { + "privKey": 106, + "address": "0x417f3b59eF57C641283C2300fae0f27fe98D518C" + }, + { + "privKey": 107, + "address": "0xD6B931D8d441B1ec98F55F8Ec8ADb532DC140c78" + }, + { + "privKey": 108, + "address": "0x9220625B1a30680387D542E6b5F753786Ca5530e" + }, + { + "privKey": 110, + "address": "0xB961768b578514Debf079017FF78c47b0A6AdBf6" + }, + { + "privKey": 111, + "address": "0x052b91ad9732D1bcE0dDAe15a4545e5c65D02443" + }, + { + "privKey": 112, + "address": "0x8dF64de79608F0aE9e72ECAe3A400582AeD8101C" + }, + { + "privKey": 113, + "address": "0x0e7B23Cd1fdb7ea3CCC80320AB43843a2f193C36" + }, + { + "privKey": 114, + "address": "0xFBBC41289f834A76e4320aC238512035560467Ee" + }, + { + "privKey": 115, + "address": "0x61E1DA6C7B8B211E6e5Dd921EFe27e73Ad226DAc" + }, + { + "privKey": 117, + "address": "0x2ACf0D6fdaC920081A446868B2f09A8dAc797448" + }, + { + "privKey": 118, + "address": "0x1715eb68AfBA4D516ef1e068b55f5093BB4A2f59" + }, + { + "privKey": 119, + "address": "0x58Bab2f728dc4FC227A4C38CaB2ec93B73B4E828" + }, + { + "privKey": 121, + "address": "0xA01CCA6367A84304b6607B76676C66c360b74741" + }, + { + "privKey": 125, + "address": "0xE8C5025c803F3279D94eA55598E147f601929Bf4" + }, + { + "privKey": 127, + "address": "0xB3087F34EDab33A8182BA29ADEa4D739D9831A94" + }, + { + "privKey": 128, + "address": "0xc6A210606f2EE6e64aFB9584DB054F3476a5Cc66" + }, + { + "privKey": 129, + "address": "0xD01C9d93efC83C00B30f768F832182BEfF65696f" + }, + { + "privKey": 130, + "address": "0x00eDf2d16AfbC028FB1e879559b07997Af79539f" + }, + { + "privKey": 131, + "address": "0xF5d722030D17CA01F2813Af9E7be158D7a037997" + }, + { + "privKey": 133, + "address": "0x0DC8B8eF8457b1e45ac277D65ac5987B547Ba775" + }, + { + "privKey": 134, + "address": "0xdE521346f9327a4314A18B2cda96B2a33603177A" + }, + { + "privKey": 135, + "address": "0x69842e12d6F36f9f93F06086B70795bFc7e02745" + }, + { + "privKey": 136, + "address": "0x9b7BdF6ad17d5fC9a168Acaa24495e52A65f3b79" + }, + { + "privKey": 137, + "address": "0xa2d47D2C42009520075cB15f5855052008d0c44D" + }, + { + "privKey": 139, + "address": "0x839D96957F21E82fBCCA0D42a1f12EF6e1CF82E9" + }, + { + "privKey": 141, + "address": "0xA4f8c598927EaB2f1898F8F2d6F8121578De2344" + }, + { + "privKey": 143, + "address": "0x21289CD01f9f58FC44962B6E213a0fbBd015bEb6" + }, + { + "privKey": 146, + "address": "0x64e582C17Ab7c3b90E171795B504cA3C04108501" + }, + { + "privKey": 148, + "address": "0x5FE015779Fb36006B01f9C5A5DbcAA6FFA56F0c0" + }, + { + "privKey": 150, + "address": "0x271D65AF9a5A7B4cD7AF264f251184c2a4b9e7A3" + }, + { + "privKey": 151, + "address": "0xddf44e34ed40c40624C7B9f20a1030b505a4FAC0" + }, + { + "privKey": 154, + "address": "0x7E9961FA09dd52f945F8143844785cF0E51bb4Ce" + }, + { + "privKey": 155, + "address": "0xf33D2f7d96F92d912Ca8418f9d62eB54c1A9889F" + }, + { + "privKey": 156, + "address": "0xEEc566c793A89f388BbABfC0225183a6a95C4263" + }, + { + "privKey": 157, + "address": "0x2001f8CdCdEEF1bbCC188cA59CF04Fb44133D55a" + }, + { + "privKey": 160, + "address": "0x18Eb36d090eEadF82f3454a6DA690fC398d3EBa1" + }, + { + "privKey": 161, + "address": "0xd2431CA38735C2fd438e2cAa23F094191D89675b" + }, + { + "privKey": 168, + "address": "0xE2A09565167D4e3F826ADeC6bEF82B97e0A4383f" + }, + { + "privKey": 171, + "address": "0x07748403082b29a45abD6C124A37E6B14e6B1803" + }, + { + "privKey": 173, + "address": "0x5181bE40152CAaBa8e123a55b7762755D4e8e416" + }, + { + "privKey": 174, + "address": "0x9481Da7766c043EEfeECC9589ee7ADE61316b0ff" + }, + { + "privKey": 175, + "address": "0x42Aba3530DD1cCb1dda27BFaa7C6a832cfDB4446" + }, + { + "privKey": 184, + "address": "0x8eE8813FB9d41cc58EF87D28b36E948B1234e71d" + }, + { + "privKey": 186, + "address": "0x31eB18Dd6f5A8064Ab750eABb281Cf162f43CCD0" + }, + { + "privKey": 190, + "address": "0x3c1638a25aD7e8c2a84B53b661dD1BD048407E8f" + }, + { + "privKey": 191, + "address": "0x2eeDf8a799B73BC02E4664183eB72422C377153B" + }, + { + "privKey": 192, + "address": "0xcdeF6f23a26F960B53468f497967CE74c026AF52" + }, + { + "privKey": 193, + "address": "0x0A2035683FE5587B0B09DB0e757306aD729FE6c1" + }, + { + "privKey": 197, + "address": "0x556330e8d92912cCf133851BA03abD2DB70Da404" + }, + { + "privKey": 200, + "address": "0x5304FB08724D73f2bB5E04C582407c33cDE6c8d3" + }, + { + "privKey": 203, + "address": "0xD5CC10c45fc0f9f956Acd7559F61EdBFeC9F6C3d" + }, + { + "privKey": 204, + "address": "0x381c7A71035BDb42fB5D77523Df2Ff00d9f9Df1b" + }, + { + "privKey": 205, + "address": "0x45CdbEEA730D8212F451A6A8d0Eb5998B04CccCa" + }, + { + "privKey": 207, + "address": "0x598E94EB5E050045272d8417F6AB363bD874D568" + }, + { + "privKey": 211, + "address": "0x1Cd21f00b58894260f7ABeD65ad23DCe3CeA0226" + }, + { + "privKey": 212, + "address": "0x26324733d604aBb6176cF18e4f4a0624CeedDC09" + }, + { + "privKey": 216, + "address": "0x3F588A72d94d0D0986B112C671c2343320a19386" + }, + { + "privKey": 217, + "address": "0x7cFa9eEE1D752da599211BC8a68d0687708DaBFC" + }, + { + "privKey": 219, + "address": "0xC4ad60337B04fC721912531a52a5D77878293FB9" + }, + { + "privKey": 220, + "address": "0xFc5Ba3041F750f9B6820Ce066C153EB396aAC1ff" + }, + { + "privKey": 224, + "address": "0xf084BbaaBEe1a700a8FAa404027DB620A5Aa0059" + }, + { + "privKey": 225, + "address": "0x602d562b4Ef2544f851587619B56F77a9d965d45" + }, + { + "privKey": 227, + "address": "0x11Eb17B20113AE923D72E52870D40BF59A08B40D" + }, + { + "privKey": 230, + "address": "0x161c2E10407e2A87959C0bae1f342C80EaeA28f3" + }, + { + "privKey": 234, + "address": "0x4F81e991f76276A17cA92a1321f37189b1727F77" + }, + { + "privKey": 235, + "address": "0xBa95e317EAd06b55c8B70276FC63904b3339dFa1" + }, + { + "privKey": 237, + "address": "0x73377d6228266393747eFa710017872d6dd5B9A6" + }, + { + "privKey": 241, + "address": "0x7492EbbC1E7F2838Fc7191eDc031581d5712978A" + }, + { + "privKey": 242, + "address": "0xC0Af3981f9C0dfcB8955Fea07a3e4F23806FAB51" + }, + { + "privKey": 243, + "address": "0x8621Dd642245dF371b584b48c081e8863313A70d" + }, + { + "privKey": 249, + "address": "0xA8CE5C40c4aA9278DdEaA418e775985549960E7A" + }, + { + "privKey": 252, + "address": "0x90f022E3ca8453F5E5765cd3054003b544526eec" + }, + { + "privKey": 254, + "address": "0x0311afD3Bc2Ae250D5f9F2706BAE2eF4164d6912" + }, + { + "privKey": 255, + "address": "0x5044a80bD3eff58302e638018534BbDA8896c48A" + }, + { + "privKey": 263, + "address": "0x14a6E94f5c4F109d31ec0ff3cd002561b2525bcc" + }, + { + "privKey": 269, + "address": "0x702B1E972fE11B34148287785d76928F9a9c3A76" + }, + { + "privKey": 270, + "address": "0x222c1424ad90a40B505Be6dF879189668984a9C8" + }, + { + "privKey": 271, + "address": "0xFdb4750B55B1c21E632F140921b9753a26446e20" + }, + { + "privKey": 273, + "address": "0x718811e2d1170db844d0c5de6D276b299f2916a9" + }, + { + "privKey": 279, + "address": "0x7fBdFba6b300A5F54a71C0a3D047BA8F21610e4a" + }, + { + "privKey": 287, + "address": "0x3AD2A5A73385E48e476386ACccEEea79692ABe2f" + }, + { + "privKey": 288, + "address": "0x4Ec254AD8A448b2773D816083efc5441a2fC8ea4" + }, + { + "privKey": 291, + "address": "0x476C88ED464EFD251a8b18Eb84785F7C46807873" + }, + { + "privKey": 292, + "address": "0x57EF22b9b9C791850FE16b9Fcb79407aCD151478" + }, + { + "privKey": 293, + "address": "0xdA5cceAf028127E7Ce75d841295170C0e7Cb4113" + }, + { + "privKey": 296, + "address": "0x0130c3D5e6Bb374aCa10f0026700c671aE82cd1B" + }, + { + "privKey": 297, + "address": "0xC11B8c4B34d627a40dA72b065d3B67002EE5EC9F" + }, + { + "privKey": 299, + "address": "0xAd3EAF2Bf6361a1138Cd301f65FA19BE86E65f74" + }, + { + "privKey": 301, + "address": "0x3577496a436bC4E85842e8061a287D88B1e73523" + }, + { + "privKey": 305, + "address": "0xD3ff90f011b0160D4d37067E037EABd7d2041663" + }, + { + "privKey": 307, + "address": "0x2c9410ACEfF398dF7eD4e1490c5290a3eA3DF4Fb" + }, + { + "privKey": 308, + "address": "0x6Db15e33e66739809758684e431952b572924ebc" + }, + { + "privKey": 309, + "address": "0xb77b1AeA5601396Ee1f9BEAB2A0825a027422f11" + }, + { + "privKey": 313, + "address": "0xbd68a0e6A1dE1ffE12eBB7783d2f8F7973c8969d" + }, + { + "privKey": 315, + "address": "0xdF5b9234F7ee51FDBEDdB29aF433Aec4083e5f18" + }, + { + "privKey": 319, + "address": "0xD77b6f0AebAC54FB80f7c0064aBFb7114ad89478" + }, + { + "privKey": 320, + "address": "0x8A9D78FaC62b156F810beC0717e1c0eA13aeeB75" + }, + { + "privKey": 321, + "address": "0xBc737ac2Cc40ed40dccfd8746CFcEB719c5ACA12" + }, + { + "privKey": 322, + "address": "0x5EAa383ccd66f65736a01e5219E2e0407737062b" + }, + { + "privKey": 326, + "address": "0x6eeD3A9d19916ba5E21a8f7b4d60d6cA0965C974" + }, + { + "privKey": 334, + "address": "0x2F2F681Ee0b1504500c9962435616E32Aa64dEFd" + }, + { + "privKey": 336, + "address": "0x29A3b098572e6C7A53065Cc0E18eF6d9f474f4fE" + }, + { + "privKey": 338, + "address": "0x1BB93559cfB76098A2c54ea963d88609afd4810A" + }, + { + "privKey": 343, + "address": "0x5D231c7C81EE99907BF6B2FD2947Fa4557de219c" + }, + { + "privKey": 348, + "address": "0x2D5cEfE9dBc4D5274ea00C6b870c11d6f0265c7e" + }, + { + "privKey": 353, + "address": "0x23952e6b6aE9A89FDe6204bE4312cc9E4fE0c59a" + }, + { + "privKey": 357, + "address": "0x560697e82e48323b827F26862be60C5b19c1d2CF" + }, + { + "privKey": 364, + "address": "0xEa6ffDCcf9167E754531553D3F7b743fB296ee72" + }, + { + "privKey": 365, + "address": "0x403398d0775A70a233Cb00551422De5d620b6842" + }, + { + "privKey": 372, + "address": "0x08963ADC118C91aed106aE1987bfd53fc3011249" + }, + { + "privKey": 373, + "address": "0x5B3FCD13cf91f5C1a9724116333A570a2CA2116b" + }, + { + "privKey": 374, + "address": "0xc8ff52ecE34a58D1f56ab25A0D50e6350B44e739" + }, + { + "privKey": 379, + "address": "0xaa7625e14fC367C8Cbc2BaDAB9935e477164520f" + }, + { + "privKey": 385, + "address": "0x46FA34F88b8612006f78F4655aeFa624DdD93e19" + }, + { + "privKey": 386, + "address": "0xF8b10dF973Ce20Ed07E70Eb3AFc9C2E35c06225b" + }, + { + "privKey": 390, + "address": "0x78B59e306274Dd4C0AF3195Fc89350a3dC407dE4" + }, + { + "privKey": 394, + "address": "0xb63bCAf2DF4b621E83caE95A1364b8a0ffb40998" + }, + { + "privKey": 400, + "address": "0x101cA1E6e819Fe852B7C1BFF5cA9eE1820F9d86F" + }, + { + "privKey": 406, + "address": "0x3EfA17AAe19619Ea3a061a50a3dbD5A4657d1267" + }, + { + "privKey": 412, + "address": "0x8b7421B86d9fF375F13416FbB57956d63504fDE9" + }, + { + "privKey": 413, + "address": "0xD1A0Da208fDE814c052222FBE9a0acC5F077b7a0" + }, + { + "privKey": 415, + "address": "0xCed1B0433c1347A750e2035a26ebdBd798992Fe7" + }, + { + "privKey": 419, + "address": "0xC7346965bfeCFa8A065D7C0A55FbCD15d8314E0b" + }, + { + "privKey": 432, + "address": "0x8C6134AcfdfE43Ae1F1c8E6d9e2aa201C2246942" + }, + { + "privKey": 437, + "address": "0xEfb3f853E9666F5d34E001eA27aa10C8C6d7dF16" + }, + { + "privKey": 448, + "address": "0xE75721D039E8D1fEAA9e59b19cE344A2B1eA6751" + }, + { + "privKey": 452, + "address": "0x333fe004b28627eA079682F37eB6102657D9b835" + }, + { + "privKey": 476, + "address": "0xBb8A8FE4f12953a5840C6570d5e836079329BE46" + }, + { + "privKey": 483, + "address": "0xCa7B487EF7E9E78A0462d1870C4Bd75de321f32A" + }, + { + "privKey": 487, + "address": "0x8fF0eb618c061e00a3fAB5Fc9B8a0f2F24547d48" + }, + { + "privKey": 488, + "address": "0xbf4CAB99a68355608Bed736B3B945aCd3C1Ee298" + }, + { + "privKey": 490, + "address": "0xC5229a9Aba6247D2B4ab61954735A972D08A74aF" + }, + { + "privKey": 491, + "address": "0x5415c45e539fa1D0E3b81C12e706F4087a04c16C" + }, + { + "privKey": 496, + "address": "0x98f3E259e91391E7ba7d2F2D0E8D60D9c6dB7E84" + }, + { + "privKey": 500, + "address": "0x3447659E07332bb8D6E2E1985FB12e8fD6175645" + }, + { + "privKey": 512, + "address": "0x06d31BD867343e5A9D8A82b99618253b38f63b8c" + }, + { + "privKey": 536, + "address": "0x022c53567A99D26aAbDd033B4Efef258ED6DD76A" + }, + { + "privKey": 538, + "address": "0x48159CF1F76426b2ac5ad8208e1B3E5517438D43" + }, + { + "privKey": 540, + "address": "0x9510BB587234430871d9246C98Ece4a13Fb9116d" + }, + { + "privKey": 549, + "address": "0x6282DE81b2455222C4B15159b36c03F55d4903D6" + }, + { + "privKey": 570, + "address": "0xAB8ECD9c951F7F57b77b3c4578C7e9c26c4eE8D9" + }, + { + "privKey": 572, + "address": "0xB40d73153EA796AF4e5Eef4626942022aaB84175" + }, + { + "privKey": 595, + "address": "0xEDdE91eD94463c10aC9B0001edCA3C068d8E226b" + }, + { + "privKey": 606, + "address": "0xB2AC99BC1140e9F9211843dAb96374414702A64A" + }, + { + "privKey": 608, + "address": "0xCC7EF4957B0816607eBcE0a00FA5861ACf95E463" + }, + { + "privKey": 625, + "address": "0x5275D06Bc9dE3661783331380a810Da55cD31544" + }, + { + "privKey": 637, + "address": "0xe34ac72Ca84deEdF06f8B3fc74114ADbb5a6c507" + }, + { + "privKey": 638, + "address": "0x1934c9b2AAFd1D902987951858584F80bf2CE77f" + }, + { + "privKey": 647, + "address": "0x76da31D0e141AD2f7b7D8A2a3Eb86C387b02D838" + }, + { + "privKey": 654, + "address": "0xdC249431F31b36439071A2a31B132E3775178cB2" + }, + { + "privKey": 657, + "address": "0x9DB78Ef366F0AB9A7B1C0e12b4371C8C922d2e16" + }, + { + "privKey": 670, + "address": "0x778eC180D7286B8fea84a2d42429965F35ec16F6" + }, + { + "privKey": 671, + "address": "0x0FC4530b8F6FE6e6a73453CDE827b9C695a3ebe5" + }, + { + "privKey": 679, + "address": "0xbEc5c09999375F60f91633a30bfED85440B10496" + }, + { + "privKey": 691, + "address": "0xC92Fb2E3F1a8f9861876ee812DfdEdb3e845D13A" + }, + { + "privKey": 702, + "address": "0x89cDb38D3aAc16f35d280bf8e85261F2Cc1CB082" + }, + { + "privKey": 790, + "address": "0x9fBe67E4A55BebC5A91886CF53963d487EbA5B6C" + }, + { + "privKey": 813, + "address": "0xcb16f249831588C81f80da9995867F27C627e3F2" + }, + { + "privKey": 819, + "address": "0x9CaD951a57D174A8F0A425D5Ee6920415165fdAA" + }, + { + "privKey": 823, + "address": "0xa636D0d79DCecc69Ef6b546aCaC88232867BE2E0" + }, + { + "privKey": 825, + "address": "0x6ae34Ce38B5491A4b2Ec31Fcd9Ff69ba4bBA0e45" + }, + { + "privKey": 836, + "address": "0x493c8bC625ce5Ca0B9cdd1eA9D939757721480eE" + }, + { + "privKey": 844, + "address": "0x300D76C7145753f6f2515f150F39a14A6f198F7F" + }, + { + "privKey": 865, + "address": "0x966F91FCA291e85C7d1024EA27f62d28D8fd3Fd7" + }, + { + "privKey": 878, + "address": "0x91add7f40C5447fE8CfB9Fb3c9A8d19fD644630c" + }, + { + "privKey": 903, + "address": "0x8247CDE48C7e7c4f95dfF6647b566a533934D224" + }, + { + "privKey": 916, + "address": "0x438Ca190209A8E7C14A92198C575Eae3f92152D0" + }, + { + "privKey": 947, + "address": "0xE4682E4379706A7a84fF6c967e7C0816251b8CfB" + }, + { + "privKey": 967, + "address": "0x39eB22b26d5Ef4708Bc9e90fa394d35349E7F583" + }, + { + "privKey": 1011, + "address": "0x657551717B4045e2A31bf4f0Db82f5A131510Aff" + }, + { + "privKey": 1028, + "address": "0x1FD03A5a9408f206065fF7A1788A3045C08A0779" + }, + { + "privKey": 1133, + "address": "0x660CD9db3944358B0FCf191b80Afc3005F3032FE" + }, + { + "privKey": 1216, + "address": "0xec470B5aC4ADfa27577967AE0f6A74fD89f9AFB0" + }, + { + "privKey": 1238, + "address": "0x97c004220a1c96F377C58AFc2ebAb589E25455Ef" + } + ] +} diff --git a/script/libs/LibFeed.sol b/script/libs/LibFeed.sol index 46c81b8..6d31346 100644 --- a/script/libs/LibFeed.sol +++ b/script/libs/LibFeed.sol @@ -28,29 +28,21 @@ library LibFeed { Vm(address(uint160(uint(keccak256("hevm cheat code"))))); /// @dev Feed encapsulates a private key, derived public key, and the - /// public keys index in a Scribe instance. + /// corresponding feed id. struct Feed { uint privKey; LibSecp256k1.Point pubKey; - uint8 index; + uint8 id; } - /// @dev Returns a new feed instance with private key `privKey` and feed - /// index 0. Note that 0 is never a valid index! + /// @dev Returns a new feed instance with private key `privKey`. function newFeed(uint privKey) internal returns (Feed memory) { - return newFeed(privKey, 0); - } + LibSecp256k1.Point memory pubKey = privKey.derivePublicKey(); - /// @dev Returns a new feed instance with private key `privKey` and feed - /// index `index` in a Scribe instance. - function newFeed(uint privKey, uint8 index) - internal - returns (Feed memory) - { return Feed({ privKey: privKey, - pubKey: privKey.derivePublicKey(), - index: index + pubKey: pubKey, + id: uint8(uint(uint160(pubKey.toAddress())) >> 152) }); } @@ -77,7 +69,7 @@ library LibFeed { return IScribe.SchnorrData({ signature: bytes32(signature), commitment: commitment, - signersBlob: abi.encodePacked(self.index) + feedIds: abi.encodePacked(self.id) }); } @@ -94,96 +86,16 @@ library LibFeed { } (uint signature, address commitment) = privKeys.signMessage(message); - // Create signersBlob with sorted indexes. - bytes memory signersBlob; - uint8[] memory sortedIndexes = selfs.getIndexesSortedByAddress(); - for (uint i; i < sortedIndexes.length; i++) { - signersBlob = abi.encodePacked(signersBlob, sortedIndexes[i]); - } - - return IScribe.SchnorrData({ - signature: bytes32(signature), - commitment: commitment, - signersBlob: signersBlob - }); - } - - /// @dev Returns a Schnorr multi-signature (aggregated signature) of type - /// IScribe.SchnorrData signing `message` via `selfs`' private keys. - /// Note that SchnorrData's signersBlob is not ordered and the signature - /// therefore unacceptable for Scribe. - function signSchnorr_withoutOrderingSignerIndexes( - Feed[] memory selfs, - bytes32 message - ) internal returns (IScribe.SchnorrData memory) { - // Create multi-signature. - uint[] memory privKeys = new uint[](selfs.length); + // Create blob of feedIds. + bytes memory feedIds; for (uint i; i < selfs.length; i++) { - privKeys[i] = selfs[i].privKey; - } - (uint signature, address commitment) = privKeys.signMessage(message); - - // Create list of signerIndexes. - uint8[] memory signerIndexes = new uint8[](selfs.length); - for (uint i; i < selfs.length; i++) { - signerIndexes[i] = selfs[i].index; - } - - // Create signersBlob. - bytes memory signersBlob; - for (uint i; i < signerIndexes.length; i++) { - signersBlob = abi.encodePacked(signersBlob, signerIndexes[i]); + feedIds = abi.encodePacked(feedIds, selfs[i].id); } return IScribe.SchnorrData({ signature: bytes32(signature), commitment: commitment, - signersBlob: signersBlob + feedIds: feedIds }); } - - /// @dev Returns the list of `selfs` indexes sorted by `selfs`' addresses. - function getIndexesSortedByAddress(Feed[] memory selfs) - internal - pure - returns (uint8[] memory) - { - // Create array of feeds' indexes. - uint8[] memory indexes = new uint8[](selfs.length); - for (uint i; i < selfs.length; i++) { - indexes[i] = selfs[i].index; - } - - // Create array of feeds' addresses. - address[] memory addrs = new address[](selfs.length); - for (uint i; i < selfs.length; i++) { - addrs[i] = selfs[i].pubKey.toAddress(); - } - - // Sort indexes array based on addresses array. - for (uint i = 1; i < selfs.length; i++) { - for ( - uint j = i; - j > 0 && uint160(addrs[j - 1]) > uint160(addrs[j]); - j-- - ) { - // Swap in indexes array. - { - uint8 tmp = indexes[j]; - indexes[j] = indexes[j - 1]; - indexes[j - 1] = tmp; - } - - // Swap in addresses array. - { - address tmp = addrs[j]; - addrs[j] = addrs[j - 1]; - addrs[j - 1] = tmp; - } - } - } - - // Return sorted list of indexes. - return indexes; - } } diff --git a/src/IScribe.sol b/src/IScribe.sol index d78406e..6d1f6ca 100644 --- a/src/IScribe.sol +++ b/src/IScribe.sol @@ -17,7 +17,7 @@ interface IScribe is IChronicle { struct SchnorrData { bytes32 signature; address commitment; - bytes signersBlob; + bytes feedIds; } /// @dev ECDSAData encapsulates an ECDSA signature. @@ -45,13 +45,13 @@ interface IScribe is IChronicle { /// @param bar The bar security parameter. error BarNotReached(uint8 numberSigners, uint8 bar); - /// @notice Thrown if signature signed by non-feed. - /// @param signer The signer's address not being a feed. - error SignerNotFeed(address signer); + /// @notice Thrown if given feed id invalid. + /// @param feedId The invalid feed id. + error InvalidFeedId(uint8 feedId); - /// @notice Thrown if signer indexes are not encoded so that their - /// addresses are in ascending order. - error SignersNotOrdered(); + /// @notice Thrown if double signing attempted. + /// @param feedId The id of the feed attempting to double sign. + error DoubleSigningAttempted(uint8 feedId); /// @notice Thrown if Schnorr signature verification failed. error SchnorrSignatureInvalid(); @@ -65,18 +65,12 @@ interface IScribe is IChronicle { /// @notice Emitted when new feed lifted. /// @param caller The caller's address. /// @param feed The feed address lifted. - /// @param index The feed's index identifier. - event FeedLifted( - address indexed caller, address indexed feed, uint indexed index - ); + event FeedLifted(address indexed caller, address indexed feed); /// @notice Emitted when feed dropped. /// @param caller The caller's address. /// @param feed The feed address dropped. - /// @param index The feed's index identifier. - event FeedDropped( - address indexed caller, address indexed feed, uint indexed index - ); + event FeedDropped(address indexed caller, address indexed feed); /// @notice Emitted when bar updated. /// @param caller The caller's address. @@ -93,12 +87,6 @@ interface IScribe is IChronicle { view returns (bytes32 feedRegistrationMessage); - /// @notice The maximum number of feed lifts supported. - /// @dev Note that the constraint comes from feed's indexes being encoded as - /// uint8 in SchnorrData.signersBlob. - /// @return maxFeeds The maximum number of feed lifts supported. - function maxFeeds() external view returns (uint maxFeeds); - /// @notice Returns the bar security parameter. /// @return bar The bar security parameter. function bar() external view returns (uint8 bar); @@ -138,6 +126,7 @@ interface IScribe is IChronicle { /// @notice Pokes the oracle. /// @dev Expects `pokeData`'s age to be greater than the timestamp of the /// last successful poke. + /// @dev Expects `pokeData`'s age to not be greater than the current time. /// @dev Expects `schnorrData` to prove `pokeData`'s integrity. /// See `isAcceptableSchnorrSignatureNow(bytes32,SchnorrData)(bool)`. /// @param pokeData The PokeData being poked. @@ -150,7 +139,7 @@ interface IScribe is IChronicle { /// currently acceptable for message `message`. /// @dev Note that a valid Schnorr signature is only acceptable if the /// signature was signed by exactly bar many feeds. - /// For more info, see `bar()(uint8)` and `feeds()(address[],uint[])`. + /// For more info, see `bar()(uint8)` and `feeds()(address[])`. /// @dev Note that bar and feeds are configurable, meaning a once acceptable /// Schnorr signature may become unacceptable in the future. /// @param message The message expected to be signed via `schnorrData`. @@ -167,73 +156,66 @@ interface IScribe is IChronicle { /// @dev The message is defined as: /// H(tag ‖ H(wat ‖ pokeData)), where H() is the keccak256 function. /// @param pokeData The pokeData to create the message for. - /// @return Message for `pokeData`. + /// @return pokeMessage Message for `pokeData`. function constructPokeMessage(PokeData calldata pokeData) external view - returns (bytes32); + returns (bytes32 pokeMessage); - /// @notice Returns whether address `who` is a feed and its feed index - /// identifier. + /// @notice Returns whether address `who` is a feed. /// @param who The address to check. /// @return isFeed True if `who` is feed, false otherwise. - /// @return feedIndex Non-zero if `who` is feed, zero otherwise. - function feeds(address who) - external - view - returns (bool isFeed, uint feedIndex); - - /// @notice Returns whether feedIndex `index` maps to a feed and, if so, - /// the feed's address. - /// @param index The feedIndex to check. - /// @return isFeed True if `index` maps to a feed, false otherwise. - /// @return feed Address of the feed with feedIndex `index` if `index` maps - /// to feed, zero-address otherwise. - function feeds(uint index) + function feeds(address who) external view returns (bool isFeed); + + /// @notice Returns whether feed id `feedId` is a feed and, if so, the + /// feed's address. + /// @param feedId The feed id to check. + /// @return isFeed True if `feedId` is a feed, false otherwise. + /// @return feed Address of the feed with id `feedId` if `feedId` is a feed, + /// zero-address otherwise. + function feeds(uint8 feedId) external view returns (bool isFeed, address feed); - /// @notice Returns list of feed addresses and their index identifiers. + /// @notice Returns list of feed addresses. + /// @dev Note that this function has a high gas consumption and is not + /// intended to be called onchain. /// @return feeds List of feed addresses. - /// @return feedIndexes List of feed's indexes. - function feeds() - external - view - returns (address[] memory feeds, uint[] memory feedIndexes); + function feeds() external view returns (address[] memory feeds); /// @notice Lifts public key `pubKey` to being a feed. /// @dev Only callable by auth'ed address. - /// @dev The message expected to be signed by `ecdsaData` is defined as via - /// `feedRegistrationMessage()(bytes32)` function. + /// @dev The message expected to be signed by `ecdsaData` is defined via + /// `feedRegistrationMessage()(bytes32)`. /// @param pubKey The public key of the feed. /// @param ecdsaData ECDSA signed message by the feed's public key. - /// @return The feed index of the newly lifted feed. + /// @return feedId The id of the newly lifted feed. function lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) external - returns (uint); + returns (uint8 feedId); /// @notice Lifts public keys `pubKeys` to being feeds. /// @dev Only callable by auth'ed address. - /// @dev The message expected to be signed by `ecdsaDatas` is defined as via - /// `feedRegistrationMessage()(bytes32)` function. + /// @dev The message expected to be signed by `ecdsaDatas` is defined via + /// `feedRegistrationMessage()(bytes32)`. /// @param pubKeys The public keys of the feeds. /// @param ecdsaDatas ECDSA signed message by the feeds' public keys. - /// @return List of feed indexes of the newly lifted feeds. + /// @return List of feed ids of the newly lifted feeds. function lift( LibSecp256k1.Point[] memory pubKeys, ECDSAData[] memory ecdsaDatas - ) external returns (uint[] memory); + ) external returns (uint8[] memory); - /// @notice Drops feed with index `feedIndex` from being a feed. + /// @notice Drops feed with id `feedId`. /// @dev Only callable by auth'ed address. - /// @param feedIndex The feed index identifier of the feed to drop. - function drop(uint feedIndex) external; + /// @param feedId The feed id to drop. + function drop(uint8 feedId) external; - /// @notice Drops feeds with indexes `feedIndexes` from being feeds. + /// @notice Drops feeds with ids' `feedIds`. /// @dev Only callable by auth'ed address. - /// @param feedIndexes The feed's index identifiers of the feeds to drop. - function drop(uint[] memory feedIndexes) external; + /// @param feedIds The feed ids to drop. + function drop(uint8[] memory feedIds) external; /// @notice Updates the bar security parameters to `bar`. /// @dev Only callable by auth'ed address. diff --git a/src/IScribeOptimistic.sol b/src/IScribeOptimistic.sol index 5bb86f9..9cc3ccb 100644 --- a/src/IScribeOptimistic.sol +++ b/src/IScribeOptimistic.sol @@ -8,8 +8,7 @@ interface IScribeOptimistic is IScribe { /// in challenge period. error InChallengePeriod(); - /// @notice Thrown if opChallenge called while no opPoke exists thats - /// challengeable. + /// @notice Thrown if opChallenge called while no challengeable opPoke exists. error NoOpPokeToChallenge(); /// @notice Thrown if opChallenge called with SchnorrData not matching @@ -19,6 +18,10 @@ interface IScribeOptimistic is IScribe { /// argument. error SchnorrDataMismatch(uint160 gotHash, uint160 wantHash); + /// @notice Thrown if opPoke called with non-feed ECDSA signature. + /// @param signer The ECDSA signature's signer. + error SignerNotFeed(address signer); + /// @notice Emitted when oracles was successfully opPoked. /// @param caller The caller's address. /// @param opFeed The feed that signed the opPoke. @@ -33,25 +36,34 @@ interface IScribeOptimistic is IScribe { /// @notice Emitted when successfully challenged an opPoke. /// @param caller The caller's address. + /// @param schnorrData The schnorrData challenged. /// @param schnorrErr The abi-encoded custom error returned from the failed /// Schnorr signature verification. event OpPokeChallengedSuccessfully( - address indexed caller, bytes schnorrErr + address indexed caller, + IScribe.SchnorrData schnorrData, + bytes schnorrErr ); /// @notice Emitted when unsuccessfully challenged an opPoke. /// @param caller The caller's address. - event OpPokeChallengedUnsuccessfully(address indexed caller); + /// @param schnorrData The schnorrData challenged. + event OpPokeChallengedUnsuccessfully( + address indexed caller, IScribe.SchnorrData schnorrData + ); /// @notice Emitted when ETH reward paid for successfully challenging an /// opPoke. /// @param challenger The challenger to which the reward was send. + /// @param schnorrData The schnorrData challenged. /// @param reward The ETH rewards paid. - event OpChallengeRewardPaid(address indexed challenger, uint reward); + event OpChallengeRewardPaid( + address indexed challenger, IScribe.SchnorrData schnorrData, uint reward + ); /// @notice Emitted when an opPoke dropped. /// @dev opPoke's are dropped if security parameters are updated that could - /// lead to an initially valid opPoke becoming invalid or an opPoke was + /// lead to an initially valid opPoke becoming invalid or if an opPoke /// was successfully challenged. /// @param caller The caller's address. /// @param pokeData The pokeData dropped. @@ -80,6 +92,7 @@ interface IScribeOptimistic is IScribe { /// @notice Optimistically pokes the oracle. /// @dev Expects `pokeData`'s age to be greater than the timestamp of the /// last successful poke. + /// @dev Expects `pokeData`'s age to not be greater than the current time. /// @dev Expects `ecdsaData` to be a signature from a feed. /// @dev Expects `ecdsaData` to prove the integrity of the `pokeData` and /// `schnorrData`. @@ -122,9 +135,9 @@ interface IScribeOptimistic is IScribe { SchnorrData calldata schnorrData ) external view returns (bytes32 opPokeMessage); - /// @notice Returns the feed index of the feed last opPoke'd. - /// @return opFeedIndex Feed index of the feed last opPoke'd. - function opFeedIndex() external view returns (uint8 opFeedIndex); + /// @notice Returns the feed id of the feed last opPoke'd. + /// @return opFeedId Feed id of the feed last opPoke'd. + function opFeedId() external view returns (uint8 opFeedId); /// @notice Returns the opChallengePeriod security parameter. /// @return opChallengePeriod The opChallengePeriod security parameter. diff --git a/src/Scribe.sol b/src/Scribe.sol index b657de2..45dd436 100644 --- a/src/Scribe.sol +++ b/src/Scribe.sol @@ -9,11 +9,10 @@ import {IScribe} from "./IScribe.sol"; import {LibSchnorr} from "./libs/LibSchnorr.sol"; import {LibSecp256k1} from "./libs/LibSecp256k1.sol"; -import {LibSchnorrData} from "./libs/LibSchnorrData.sol"; /** * @title Scribe - * @custom:version 1.2.0 + * @custom:version 2.0.0 * * @notice Efficient Schnorr multi-signature based Oracle */ @@ -21,10 +20,6 @@ contract Scribe is IScribe, Auth, Toll { using LibSchnorr for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.JacobianPoint; - using LibSchnorrData for SchnorrData; - - /// @inheritdoc IScribe - uint public constant maxFeeds = type(uint8).max - 1; /// @inheritdoc IScribe uint8 public constant decimals = 18; @@ -40,20 +35,14 @@ contract Scribe is IScribe, Auth, Toll { /// @inheritdoc IChronicle bytes32 public immutable wat; - /// @dev The storage slot of _pubKeys[0]. - uint internal immutable SLOT_pubKeys; - // -- Storage -- /// @dev Scribe's current value and corresponding age. PokeData internal _pokeData; - /// @dev List of feeds' public keys. - LibSecp256k1.Point[] internal _pubKeys; - - /// @dev Mapping of feeds' addresses to their public key indexes in - /// _pubKeys. - mapping(address => uint) internal _feeds; + /// @dev Statically allocated array of feeds' public keys. + /// Indexed via the public keys address' highest-order byte. + LibSecp256k1.Point[256] internal _pubKeys; /// @inheritdoc IScribe /// @dev Note to have as last in storage to enable downstream contracts to @@ -62,7 +51,10 @@ contract Scribe is IScribe, Auth, Toll { // -- Constructor -- - constructor(address initialAuthed, bytes32 wat_) Auth(initialAuthed) { + constructor(address initialAuthed, bytes32 wat_) + payable + Auth(initialAuthed) + { require(wat_ != 0); // Set wat immutable. @@ -70,17 +62,6 @@ contract Scribe is IScribe, Auth, Toll { // Let initial bar be 2. _setBar(2); - - // Let _pubKeys[0] be the zero point. - _pubKeys.push(LibSecp256k1.ZERO_POINT()); - - // Let SLOT_pubKeys be _pubKeys[0].slot. - uint pubKeysSlot; - assembly ("memory-safe") { - mstore(0x00, _pubKeys.slot) - pubKeysSlot := keccak256(0x00, 0x20) - } - SLOT_pubKeys = pubKeysSlot; } // -- Poke Functionality -- @@ -162,70 +143,66 @@ contract Scribe is IScribe, Auth, Toll { } /// @custom:invariant Reverts iff out of gas. - /// @custom:invariant Runtime is Θ(bar). + /// @custom:invariant Runtime is O(bar). function _verifySchnorrSignature( bytes32 message, SchnorrData calldata schnorrData ) internal view returns (bool, bytes memory) { - // Let signerIndex be the current signer's index read from schnorrData. - uint signerIndex; - // Let signerPubKey be the public key stored for signerIndex. - LibSecp256k1.Point memory signerPubKey; - // Let signer be the address of signerPubKey. - address signer; - // Let lastSigner be the previous processed signer. - address lastSigner; - // Let aggPubKey be the sum of processed signers' public keys. + // Let feedPubKey be the currently processed feed's public key. + LibSecp256k1.Point memory feedPubKey; + // Let feedId be the currently processed feed's id. + uint8 feedId; + // Let aggPubKey be the sum of processed feeds' public keys. // Note that Jacobian coordinates are used. LibSecp256k1.JacobianPoint memory aggPubKey; + // Let bloom be a bloom filter to check for double signing attempts. + uint bloom; - // Fail if number signers unequal to bar. + // Fail if number feeds unequal to bar. // // Note that requiring equality constrains the verification's runtime // from Ω(bar) to Θ(bar). - uint numberSigners = schnorrData.getSignerIndexLength(); - if (numberSigners != bar) { - return (false, _errorBarNotReached(uint8(numberSigners), bar)); + uint numberFeeds = schnorrData.feedIds.length; + if (numberFeeds != bar) { + return (false, _errorBarNotReached(uint8(numberFeeds), bar)); } - // Initiate signer variables with schnorrData's 0's signer index. - signerIndex = schnorrData.getSignerIndex(0); - signerPubKey = _unsafeLoadPubKeyAt(signerIndex); - signer = signerPubKey.toAddress(); + // Initiate feed variables with schnorrData's 0's feed index. + feedId = uint8(schnorrData.feedIds[0]); + feedPubKey = _pubKeys[feedId]; - // Fail if signer not feed. - if (signerPubKey.isZeroPoint()) { - return (false, _errorSignerNotFeed(signer)); + // Fail if feed not lifted. + if (feedPubKey.isZeroPoint()) { + return (false, _errorInvalidFeedId(feedId)); } - // Initiate aggPubKey with value of first signerPubKey. - aggPubKey = signerPubKey.toJacobian(); + // Initiate bloom filter with feedId set. + bloom = 1 << feedId; - // Aggregate remaining encoded signers. - for (uint i = 1; i < bar;) { - // Update Signer Variables. - lastSigner = signer; - signerIndex = schnorrData.getSignerIndex(i); - signerPubKey = _unsafeLoadPubKeyAt(signerIndex); - signer = signerPubKey.toAddress(); + // Initiate aggPubKey with value of first feed's public key. + aggPubKey = feedPubKey.toJacobian(); - // Fail if signer not feed. - if (signerPubKey.isZeroPoint()) { - return (false, _errorSignerNotFeed(signer)); + for (uint8 i = 1; i < numberFeeds;) { + // Update feed variables. + feedId = uint8(schnorrData.feedIds[i]); + feedPubKey = _pubKeys[feedId]; + + // Fail if feed not lifted. + if (feedPubKey.isZeroPoint()) { + return (false, _errorInvalidFeedId(feedId)); } - // Fail if signers not strictly monotonically increasing. - // - // Note that this prevents double signing attacks and enforces - // strict ordering. - if (uint160(lastSigner) >= uint160(signer)) { - return (false, _errorSignersNotOrdered()); + // Fail if double signing attempted. + if (bloom & (1 << feedId) != 0) { + return (false, _errorDoubleSigningAttempted(feedId)); } + // Update bloom filter. + bloom |= 1 << feedId; - // assert(aggPubKey.x != signerPubKey.x); // Indicates rogue-key attack + // assert(aggPubKey.x != feedPubKey.x); // Indicates rogue-key attack - // Add signerPubKey to already aggregated public keys. - aggPubKey.addAffinePoint(signerPubKey); + // Add feedPubKey to already aggregated public keys. + aggPubKey.addAffinePoint(feedPubKey); // forgefmt: disable-next-item unchecked { ++i; } @@ -282,7 +259,7 @@ contract Scribe is IScribe, Auth, Toll { { uint val = _pokeData.val; uint age = _pokeData.age; - return (val != 0, val, age); + return val != 0 ? (true, val, age) : (false, 0, 0); } // - MakerDAO Compatibility @@ -336,66 +313,51 @@ contract Scribe is IScribe, Auth, Toll { // -- Public Read Functionality -- /// @inheritdoc IScribe - function feeds(address who) external view returns (bool, uint) { - uint index = _feeds[who]; - // assert(index != 0 ? !_pubKeys[index].isZeroPoint() : true); - return (index != 0, index); + function feeds(address who) external view returns (bool) { + uint8 feedId = uint8(uint(uint160(who)) >> 152); + + LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; + + return !pubKey.isZeroPoint() && pubKey.toAddress() == who; } /// @inheritdoc IScribe - function feeds(uint index) external view returns (bool, address) { - if (index >= _pubKeys.length) { - return (false, address(0)); - } - - LibSecp256k1.Point memory pubKey = _pubKeys[index]; - if (pubKey.isZeroPoint()) { - return (false, address(0)); - } + function feeds(uint8 feedId) external view returns (bool, address) { + LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; - return (true, pubKey.toAddress()); + return pubKey.isZeroPoint() + ? (false, address(0)) + : (true, pubKey.toAddress()); } /// @inheritdoc IScribe - function feeds() external view returns (address[] memory, uint[] memory) { - // Initiate arrays with upper limit length. - uint upperLimitLength = _pubKeys.length; - address[] memory feedsList = new address[](upperLimitLength); - uint[] memory feedsIndexesList = new uint[](upperLimitLength); - - // Iterate over feeds' public keys. If a public key is non-zero, their - // corresponding address is a feed. - uint ctr; + function feeds() external view returns (address[] memory) { + address[] memory feeds_ = new address[](256); + LibSecp256k1.Point memory pubKey; address feed; - uint feedIndex; - for (uint i; i < upperLimitLength;) { - pubKey = _pubKeys[i]; + uint ctr; + for (uint i; i < 256;) { + pubKey = _pubKeys[uint8(i)]; if (!pubKey.isZeroPoint()) { feed = pubKey.toAddress(); - // assert(feed != address(0)); - - feedIndex = _feeds[feed]; - // assert(feedIndex != 0); - feedsList[ctr] = feed; - feedsIndexesList[ctr] = feedIndex; + feeds_[ctr] = feed; - ctr++; + // forgefmt: disable-next-item + unchecked { ++ctr; } } // forgefmt: disable-next-item unchecked { ++i; } } - // Set length of arrays to number of feeds actually included. assembly ("memory-safe") { - mstore(feedsList, ctr) - mstore(feedsIndexesList, ctr) + mstore(feeds_, ctr) } - return (feedsList, feedsIndexesList); + return feeds_; } // -- Auth'ed Functionality -- @@ -404,7 +366,7 @@ contract Scribe is IScribe, Auth, Toll { function lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) external auth - returns (uint) + returns (uint8) { return _lift(pubKey, ecdsaData); } @@ -413,24 +375,23 @@ contract Scribe is IScribe, Auth, Toll { function lift( LibSecp256k1.Point[] memory pubKeys, ECDSAData[] memory ecdsaDatas - ) external auth returns (uint[] memory) { + ) external auth returns (uint8[] memory) { require(pubKeys.length == ecdsaDatas.length); - uint[] memory indexes = new uint[](pubKeys.length); + uint8[] memory feedIds = new uint8[](pubKeys.length); for (uint i; i < pubKeys.length;) { - indexes[i] = _lift(pubKeys[i], ecdsaDatas[i]); + feedIds[i] = _lift(pubKeys[i], ecdsaDatas[i]); // forgefmt: disable-next-item unchecked { ++i; } } - // Note that indexes contains duplicates iff duplicate pubKeys provided. - return indexes; + return feedIds; } function _lift(LibSecp256k1.Point memory pubKey, ECDSAData memory ecdsaData) internal - returns (uint) + returns (uint8) { address feed = pubKey.toAddress(); // assert(feed != address(0)); @@ -444,44 +405,43 @@ contract Scribe is IScribe, Auth, Toll { ); require(feed == recovered); - uint index = _feeds[feed]; - if (index == 0) { - _pubKeys.push(pubKey); - index = _pubKeys.length - 1; - _feeds[feed] = index; + uint8 feedId = uint8(uint(uint160(feed)) >> 152); - emit FeedLifted(msg.sender, feed, index); + LibSecp256k1.Point memory sPubKey = _pubKeys[feedId]; + if (sPubKey.isZeroPoint()) { + _pubKeys[feedId] = pubKey; - require(index <= maxFeeds); + emit FeedLifted(msg.sender, feed); + } else { + // Note to be idempotent. However, disallow updating an id's feed + // via lifting without dropping the previous feed. + require(feed == sPubKey.toAddress()); } - return index; + return feedId; } /// @inheritdoc IScribe - function drop(uint feedIndex) external auth { - _drop(msg.sender, feedIndex); + function drop(uint8 feedId) external auth { + _drop(msg.sender, feedId); } /// @inheritdoc IScribe - function drop(uint[] memory feedIndexes) external auth { - for (uint i; i < feedIndexes.length;) { - _drop(msg.sender, feedIndexes[i]); + function drop(uint8[] memory feedIds) external auth { + for (uint i; i < feedIds.length;) { + _drop(msg.sender, feedIds[i]); // forgefmt: disable-next-item unchecked { ++i; } } } - function _drop(address caller, uint feedIndex) internal virtual { - require(feedIndex < _pubKeys.length); - address feed = _pubKeys[feedIndex].toAddress(); + function _drop(address caller, uint8 feedId) internal virtual { + LibSecp256k1.Point memory pubKey = _pubKeys[feedId]; + if (!pubKey.isZeroPoint()) { + delete _pubKeys[feedId]; - if (_feeds[feed] != 0) { - emit FeedDropped(caller, feed, _feeds[feed]); - - _feeds[feed] = 0; - _pubKeys[feedIndex] = LibSecp256k1.ZERO_POINT(); + emit FeedDropped(caller, pubKey.toAddress()); } } @@ -501,7 +461,6 @@ contract Scribe is IScribe, Auth, Toll { // -- Internal Helpers -- - /// @dev Halts execution by reverting with `err`. function _revert(bytes memory err) internal pure { // assert(err.length != 0); assembly ("memory-safe") { @@ -511,38 +470,6 @@ contract Scribe is IScribe, Auth, Toll { } } - /// @dev Returns the public key at `_pubKeys[index]`, or zero point if - /// `index` out of bounds. - function _unsafeLoadPubKeyAt(uint index) - internal - view - returns (LibSecp256k1.Point memory) - { - // Push immutable to stack as accessing through assembly not supported. - uint slotPubKeys = SLOT_pubKeys; - - LibSecp256k1.Point memory pubKey; - assembly ("memory-safe") { - // Note that a pubKey consists of two words. - let realIndex := mul(index, 2) - - // Compute slot of _pubKeys[index]. - let slot := add(slotPubKeys, realIndex) - - // Load _pubKeys[index]'s coordinates to stack. - let x := sload(slot) - let y := sload(add(slot, 1)) - - // Store coordinates in pubKey memory location. - mstore(pubKey, x) - mstore(add(pubKey, 0x20), y) - } - // assert(index < _pubKeys.length || pubKey.isZeroPoint()); - - // Note that pubKey is zero if index out of bounds. - return pubKey; - } - function _errorBarNotReached(uint8 got, uint8 want) internal pure @@ -552,17 +479,23 @@ contract Scribe is IScribe, Auth, Toll { return abi.encodeWithSelector(IScribe.BarNotReached.selector, got, want); } - function _errorSignerNotFeed(address signer) + function _errorInvalidFeedId(uint8 feedId) internal pure returns (bytes memory) { - // assert(_feeds[signer] == 0); - return abi.encodeWithSelector(IScribe.SignerNotFeed.selector, signer); + // assert(_pubKeys[feedId].isZeroPoint()); + return abi.encodeWithSelector(IScribe.InvalidFeedId.selector, feedId); } - function _errorSignersNotOrdered() internal pure returns (bytes memory) { - return abi.encodeWithSelector(IScribe.SignersNotOrdered.selector); + function _errorDoubleSigningAttempted(uint8 feedId) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + IScribe.DoubleSigningAttempted.selector, feedId + ); } function _errorSchnorrSignatureInvalid() diff --git a/src/ScribeOptimistic.sol b/src/ScribeOptimistic.sol index 3f154c8..a512cee 100644 --- a/src/ScribeOptimistic.sol +++ b/src/ScribeOptimistic.sol @@ -21,16 +21,13 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { using LibSecp256k1 for LibSecp256k1.Point; using LibSecp256k1 for LibSecp256k1.Point[]; - /// @dev The initial opChallengePeriod set during construction. - uint16 private constant _INITIAL_OP_CHALLENGE_PERIOD = 1 hours; - // -- Storage -- /// @inheritdoc IScribeOptimistic uint16 public opChallengePeriod; /// @inheritdoc IScribeOptimistic - uint8 public opFeedIndex; + uint8 public opFeedId; /// @dev The truncated hash of the schnorrData provided in last opPoke. /// Binds the opFeed to their schnorrData. @@ -50,10 +47,14 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { // -- Constructor and Receive Functionality -- constructor(address initialAuthed, bytes32 wat_) + payable Scribe(initialAuthed, wat_) { // Note to have a non-zero challenge period. - _setOpChallengePeriod(_INITIAL_OP_CHALLENGE_PERIOD); + _setOpChallengePeriod(20 minutes); + + // Set maxChallengeReward to type(uint).max. + _setMaxChallengeRewards(type(uint).max); } receive() external payable {} @@ -98,8 +99,8 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { // -- opPoke Functionality -- /// @dev Optimized function selector: 0x00000000. - /// Note that this function is _not_ defined via the IScribe interface - /// and one should _not_ depend on it. + /// Note that this function is _not_ defined via the IScribeOptimistic + /// interface and one should _not_ depend on it. function opPoke_optimized_397084999( PokeData calldata pokeData, SchnorrData calldata schnorrData, @@ -122,6 +123,15 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { SchnorrData calldata schnorrData, ECDSAData calldata ecdsaData ) internal { + // Revert if schnorrData.feedIds' length is higher than bar's maximum + // value. + // + // Note that this prevents opPoke's with such big schnorrData that it + // becomes economically unprofitable to challenge them. + if (schnorrData.feedIds.length > type(uint8).max) { + revert BarNotReached(type(uint8).max, bar); + } + // Load _opPokeData from storage. PokeData memory opPokeData = _opPokeData; @@ -155,27 +165,25 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { ecdsaData.s ); - // Load signer's index. - uint signerIndex = _feeds[signer]; + // Compute feed id of signer. + uint8 feedId = uint8(uint(uint160(signer)) >> 152); // Revert if signer not feed. - if (signerIndex == 0) { + // assert(_pubKeys[feedId].toAddress() != address(0)); + if (_pubKeys[feedId].toAddress() != signer) { revert SignerNotFeed(signer); } - // Store the signerIndex as opFeedIndex and bind them to their provided + // Store the feed's id as opFeedId and bind them to their provided // schnorrData. - // - // Note that cast is safe as _feed's image is [0, _pubKeys.length) and - // _pubKeys' length is bounded by maxFeeds, i.e. type(uint8).max - 1. - opFeedIndex = uint8(signerIndex); + opFeedId = feedId; _schnorrDataCommitment = uint160( uint( keccak256( abi.encodePacked( schnorrData.signature, schnorrData.commitment, - schnorrData.signersBlob + schnorrData.feedIds ) ) ) @@ -222,7 +230,7 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { abi.encodePacked( schnorrData.signature, schnorrData.commitment, - schnorrData.signersBlob + schnorrData.feedIds ) ) ) @@ -255,20 +263,20 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { delete _opPokeData; } - emit OpPokeChallengedUnsuccessfully(msg.sender); + emit OpPokeChallengedUnsuccessfully(msg.sender, schnorrData); } else { // Drop opFeed and delete invalid _opPokeData. // Note to use address(this) as caller to indicate self-governed // drop of feed. - _drop(address(this), opFeedIndex); + _drop(address(this), opFeedId); // Pay ETH reward to challenger. uint reward = challengeReward(); if (_sendETH(payable(msg.sender), reward)) { - emit OpChallengeRewardPaid(msg.sender, reward); + emit OpChallengeRewardPaid(msg.sender, schnorrData, reward); } - emit OpPokeChallengedSuccessfully(msg.sender, err); + emit OpPokeChallengedSuccessfully(msg.sender, schnorrData, err); } // Return whether challenging was successful. @@ -297,7 +305,7 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { pokeData.age, schnorrData.signature, schnorrData.commitment, - schnorrData.signersBlob + schnorrData.feedIds ) ) ) @@ -349,6 +357,8 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { return (pokeData.val, pokeData.age); } + /// @inheritdoc IChronicle + /// @dev Only callable by toll'ed address. function tryReadWithAge() external view @@ -357,7 +367,9 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { returns (bool, uint, uint) { PokeData memory pokeData = _currentPokeData(); - return (pokeData.val != 0, pokeData.val, pokeData.age); + return pokeData.val != 0 + ? (true, pokeData.val, pokeData.age) + : (false, 0, 0); } // - MakerDAO Compatibility @@ -466,8 +478,8 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { _afterAuthedAction(); } - function _drop(address caller, uint feedIndex) internal override(Scribe) { - super._drop(caller, feedIndex); + function _drop(address caller, uint8 feedId) internal override(Scribe) { + super._drop(caller, feedId); _afterAuthedAction(); } @@ -552,6 +564,10 @@ contract ScribeOptimistic is IScribeOptimistic, Scribe { /// @inheritdoc IScribeOptimistic function setMaxChallengeReward(uint maxChallengeReward_) external auth { + _setMaxChallengeRewards(maxChallengeReward_); + } + + function _setMaxChallengeRewards(uint maxChallengeReward_) internal { if (maxChallengeReward != maxChallengeReward_) { emit MaxChallengeRewardUpdated( msg.sender, maxChallengeReward, maxChallengeReward_ diff --git a/src/libs/LibBytes.sol b/src/libs/LibBytes.sol deleted file mode 100644 index ebf09e0..0000000 --- a/src/libs/LibBytes.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -/** - * @title LibBytes - * - * @notice Library for common byte operations - */ -library LibBytes { - /// @dev Returns the `index`'s byte from `word`. - /// - /// It is the caller's responsibility to ensure `index < 32`! - /// - /// @custom:invariant Uses constant amount of gas. - function getByteAtIndex(uint word, uint index) - internal - pure - returns (uint) - { - uint result; - assembly ("memory-safe") { - result := byte(sub(31, index), word) - } - - // Note that the resulting byte is returned as word. - return result; - } -} diff --git a/src/libs/LibSchnorrData.sol b/src/libs/LibSchnorrData.sol deleted file mode 100644 index af83d31..0000000 --- a/src/libs/LibSchnorrData.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import {IScribe} from "../IScribe.sol"; - -import {LibBytes} from "./LibBytes.sol"; - -/** - * @title LibSchnorrData - * - * @notice Library for working with IScribe.SchnorrData - */ -library LibSchnorrData { - using LibBytes for uint; - - /// @dev Size of a word is 32 bytes, i.e. 256 bits. - uint private constant WORD_SIZE = 32; - - /// @dev Returns the signer index from schnorrData.signersBlob with index - /// `index`. - /// - /// @dev Note that schnorrData.signersBlob is big-endian encoded and - /// counting starts at the highest order byte, i.e. the signer index 0 - /// is the highest order byte of schnorrData.signersBlob. - /// - /// @custom:example SignersBlob encoding via Solidity: - /// - /// ```solidity - /// bytes memory signersBlob; - /// uint8[] memory indexes = someFuncReturningUint8Array(); - /// for (uint i; i < indexes.length; i++) { - /// signersBlob = abi.encodePacked(signersBlob, indexes[i]); - /// } - /// ``` - /// - /// @dev Calldata layout for `schnorrData`: - /// - /// [schnorrData] signature -> schnorrData.signature - /// [schnorrData + 0x20] commitment -> schnorrData.commitment - /// [schnorrData + 0x40] offset(signersBlob) - /// [schnorrData + 0x60] len(signersBlob) -> schnorrData.signersBlob.length - /// [schnorrData + 0x80] signersBlob[0] -> schnorrData.signersBlob[0] - /// ... - /// - /// Note that the `schnorrData` variable holds the offset to the - /// `schnorrData` struct: - /// - /// ```solidity - /// bytes32 signature; - /// assembly { - /// signature := calldataload(schnorrData) - /// } - /// assert(signature == schnorrData.signature) - /// ``` - /// - /// Note that `offset(signersBlob)` is the offset to `signersBlob[0]` - /// from the index `offset(signersBlob)`. - /// - /// @custom:invariant Reverts iff out of gas. - function getSignerIndex( - IScribe.SchnorrData calldata schnorrData, - uint index - ) internal pure returns (uint) { - uint word; - assembly ("memory-safe") { - let wordIndex := mul(div(index, WORD_SIZE), WORD_SIZE) - - // Calldata index for schnorrData.signersBlob[0] is schnorrData's - // offset plus 4 words, i.e. 0x80. - let start := add(schnorrData, 0x80) - - // Note that reading non-existing calldata returns zero. - // Note that overflow is no concern because index's upper limit is - // bounded by bar, which is of type uint8. - word := calldataload(add(start, wordIndex)) - } - - // Unchecked because the subtrahend is guaranteed to be less than or - // equal to 31 due to being a (mod 32) result. - uint byteIndex; - unchecked { - byteIndex = 31 - (index % WORD_SIZE); - } - - return word.getByteAtIndex(byteIndex); - } - - /// @dev Returns the number of signers encoded in schnorrData.signersBlob. - function getSignerIndexLength(IScribe.SchnorrData calldata schnorrData) - internal - pure - returns (uint) - { - uint index; - assembly ("memory-safe") { - // Calldata index for schnorrData.signersBlob.length is - // schnorrData's offset plus 3 words, i.e. 0x60. - index := calldataload(add(schnorrData, 0x60)) - } - return index; - } -} diff --git a/test/EVMTest.sol b/test/EVMTest.sol index 9ba2d8a..8d9fe73 100644 --- a/test/EVMTest.sol +++ b/test/EVMTest.sol @@ -33,10 +33,10 @@ abstract contract EVMTest is Test { uint sSeed ) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Let s ∊ [Q, type(uint).max]. - bytes32 s = bytes32(bound(sSeed, LibSecp256k1.Q(), type(uint).max)); + bytes32 s = bytes32(_bound(sSeed, LibSecp256k1.Q(), type(uint).max)); // Create ECDSA signature. (, bytes32 r,) = vm.sign(privKey, keccak256("scribe")); @@ -51,7 +51,7 @@ abstract contract EVMTest is Test { public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Create ECDSA signature. (,, bytes32 s) = vm.sign(privKey, keccak256("scribe")); diff --git a/test/IScribeOptimisticTest.sol b/test/IScribeOptimisticTest.sol index c2dc784..ef4a233 100644 --- a/test/IScribeOptimisticTest.sol +++ b/test/IScribeOptimisticTest.sol @@ -29,10 +29,16 @@ abstract contract IScribeOptimisticTest is IScribeTest { IScribe.PokeData pokeData ); event OpPokeChallengedSuccessfully( - address indexed caller, bytes schnorrErr + address indexed caller, + IScribe.SchnorrData schnorrData, + bytes schnorrErr + ); + event OpPokeChallengedUnsuccessfully( + address indexed caller, IScribe.SchnorrData schnorrData + ); + event OpChallengeRewardPaid( + address indexed challenger, IScribe.SchnorrData schnorrData, uint reward ); - event OpPokeChallengedUnsuccessfully(address indexed caller); - event OpChallengeRewardPaid(address indexed challenger, uint reward); event OpPokeDataDropped(address indexed caller, IScribe.PokeData pokeData); event OpChallengePeriodUpdated( address indexed caller, @@ -59,11 +65,14 @@ abstract contract IScribeOptimisticTest is IScribeTest { function test_Deployment() public override(IScribeTest) { super.test_Deployment(); - // opFeedIndex not set. - assertEq(opScribe.opFeedIndex(), 0); + // opFeedId set to zero. + assertEq(opScribe.opFeedId(), 0); - // OpChallengePeriod set to 1 hour. - assertEq(opScribe.opChallengePeriod(), 1 hours); + // OpChallengePeriod is non-zero. + assertNotEq(opScribe.opChallengePeriod(), 0); + + // MaxChallengeRewards set to type(uint).max. + assertEq(opScribe.maxChallengeReward(), type(uint).max); } // -- Test: Poke -- @@ -72,7 +81,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { uint val; uint age; - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); // Execute opPoke. IScribe.PokeData memory opPokeData; @@ -135,18 +144,18 @@ abstract contract IScribeOptimisticTest is IScribeTest { } } - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.SchnorrData memory schnorrData; IScribe.ECDSAData memory ecdsaData; uint feedIndex; for (uint i; i < pokeDatas.length; i++) { // Select random feed signing opPoke. - feedIndex = bound(feedIndexSeeds[i], 0, feeds.length - 1); + feedIndex = _bound(feedIndexSeeds[i], 0, feeds.length - 1); // Make sure val is non-zero and age not stale. pokeDatas[i].val = - uint128(bound(pokeDatas[i].val, 1, type(uint128).max)); + uint128(_bound(pokeDatas[i].val, 1, type(uint128).max)); // @todo Weird behaviour if compiled via --via-ir. // See comment in testFuzz_opPoke_FailsIf_AgeIsStale(). @@ -191,11 +200,11 @@ abstract contract IScribeOptimisticTest is IScribeTest { function testFuzz_opPoke_FailsIf_AgeIsStale( IScribe.PokeData memory pokeData ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); vm.assume(pokeData.val != 0); // Let pokeData's age ∊ [1, block.timestamp]. - pokeData.age = uint32(bound(pokeData.age, 1, block.timestamp)); + pokeData.age = uint32(_bound(pokeData.age, 1, block.timestamp)); IScribe.SchnorrData memory schnorrData; schnorrData = feeds.signSchnorr(opScribe.constructPokeMessage(pokeData)); @@ -208,12 +217,12 @@ abstract contract IScribeOptimisticTest is IScribeTest { // Execute opPoke. opScribe.opPoke(pokeData, schnorrData, ecdsaData); - // opPoke'd age is set to block.timestamp. + // opPoke's age is set to block.timestamp. uint lastAge = uint32(block.timestamp); console2.log("lastAge", lastAge); // Set pokeData's age ∊ [0, block.timestamp]. - pokeData.age = uint32(bound(pokeData.age, 0, block.timestamp)); + pokeData.age = uint32(_bound(pokeData.age, 0, block.timestamp)); // Wait until opPokeData finalized. vm.warp(block.timestamp + opScribe.opChallengePeriod()); @@ -242,12 +251,12 @@ abstract contract IScribeOptimisticTest is IScribeTest { function testFuzz_opPoke_FailsIf_AgeIsInTheFuture( IScribe.PokeData memory pokeData ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); vm.assume(pokeData.val != 0); // Let pokeData's age ∊ [block.timestamp+1, type(uint32).max]. pokeData.age = - uint32(bound(pokeData.age, block.timestamp + 1, type(uint32).max)); + uint32(_bound(pokeData.age, block.timestamp + 1, type(uint32).max)); IScribe.SchnorrData memory schnorrData; schnorrData = feeds.signSchnorr(opScribe.constructPokeMessage(pokeData)); @@ -273,7 +282,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { uint rMask, uint sMask ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -302,7 +311,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { ); vm.expectRevert( - abi.encodeWithSelector(IScribe.SignerNotFeed.selector, recovered) + abi.encodeWithSelector( + IScribeOptimistic.SignerNotFeed.selector, recovered + ) ); opScribe.opPoke(pokeData, schnorrData, ecdsaData); } @@ -310,7 +321,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { function testFuzz_opPoke_FailsIf_opPokeDataInChallengePeriodExists( uint warpSeed ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -337,12 +348,42 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.opPoke(pokeData, schnorrData, ecdsaData); } + // See audits/Cantina@v2.0.0.pdf. + function testFuzz_opPoke_FailsIf_BarNotReached_DueTo_GasAttack( + uint feedIdsLengthSeed + ) public { + // Note to stay reasonable with calldata load. + uint feedIdsLength = + _bound(feedIdsLengthSeed, uint(type(uint8).max) + 1, 1_000); + + _liftFeeds(opScribe.bar()); + + IScribe.PokeData memory pokeData; + pokeData.val = 1; + pokeData.age = 1; + + IScribe.SchnorrData memory schnorrData; + for (uint i; i < feedIdsLength; i++) { + schnorrData.feedIds = + abi.encodePacked(schnorrData.feedIds, uint8(0xFF)); + } + + IScribe.ECDSAData memory ecdsaData; + + vm.expectRevert( + abi.encodeWithSelector( + IScribe.BarNotReached.selector, type(uint8).max, opScribe.bar() + ) + ); + opScribe.opPoke(pokeData, schnorrData, ecdsaData); + } + // -- Test: opChallenge -- function testFuzz_opChallenge_opPokeDataValidAndNotStale(uint warpSeed) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -368,7 +409,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { uint balanceBefore = address(this).balance; vm.expectEmit(); - emit OpPokeChallengedUnsuccessfully(address(this)); + emit OpPokeChallengedUnsuccessfully(address(this), schnorrData); // Challenge opPoke. bool opPokeInvalid = opScribe.opChallenge(schnorrData); @@ -394,7 +435,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { function testFuzz_opChallenge_opPokeDataValidButStale(uint warpSeed) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -427,7 +468,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { ); vm.expectEmit(); - emit OpPokeChallengedUnsuccessfully(address(this)); + emit OpPokeChallengedUnsuccessfully(address(this), schnorrData); // Challenge opPoke. bool opPokeInvalid = opScribe.opChallenge(schnorrData); @@ -451,7 +492,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { ) public { vm.assume(schnorrSignatureMask != 0 || schnorrCommitmentMask != 0); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -490,11 +531,12 @@ abstract contract IScribeOptimisticTest is IScribeTest { { // Expect events. vm.expectEmit(); - emit OpChallengeRewardPaid(address(this), 1 ether); + emit OpChallengeRewardPaid(address(this), schnorrData, 1 ether); vm.expectEmit(); emit OpPokeChallengedSuccessfully( address(this), + schnorrData, abi.encodeWithSelector(IScribe.SchnorrSignatureInvalid.selector) ); } @@ -506,7 +548,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { assertTrue(opPokeInvalid); // opFeed dropped. - (bool isFeed,) = opScribe.feeds(feeds[0].pubKey.toAddress()); + bool isFeed = opScribe.feeds(feeds[0].pubKey.toAddress()); assertFalse(isFeed); // 1 ETH reward paid. @@ -526,7 +568,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_opChallenge_FailsIf_InvalidSchnorrDataGiven() public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -552,7 +594,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_opChallenge_FailsIf_CalledSubsequently() public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -575,6 +617,64 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.opChallenge(schnorrData); } + // See audits/Cantina@v2.0.0.pdf. + function test_opChallenge_CalldataEncodingAttack() public { + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); + + IScribe.PokeData memory pokeData; + pokeData.val = 1; + pokeData.age = 1; + assertEq(opScribe.feeds().length, 2, "expected 2 feeds to start with"); + + IScribe.SchnorrData memory schnorrData; + schnorrData = feeds.signSchnorr(opScribe.constructPokeMessage(pokeData)); + + IScribe.ECDSAData memory ecdsaData; + ecdsaData = feeds[0].signECDSA( + opScribe.constructOpPokeMessage(pokeData, schnorrData) + ); + + // Execute valid opPoke. + opScribe.opPoke(pokeData, schnorrData, ecdsaData); + + // Challenge opPoke. + // bytes memory defaultCalldata = abi.encodeCall(opScribe.opChallenge, schnorrData); + // console2.logBytes(defaultCalldata); + // 8928a1f8 // selector + // 0000000000000000000000000000000000000000000000000000000000000020 // struct offset + // fecd661c0731ca99672edb7303a072da3c2b8342e714c2f2a90639b966298958 // signature + // 000000000000000000000000026428bf84a659a2d371be1e705613d89d93f78f // commitment + // 0000000000000000000000000000000000000000000000000000000000000060 // offset(feedIds) + // 0000000000000000000000000000000000000000000000000000000000000002 // length(feedIds) + // 2b68000000000000000000000000000000000000000000000000000000000000 // feedIds + bytes memory customCalldata = ( + hex"8928a1f8" // selector + hex"0000000000000000000000000000000000000000000000000000000000000020" // struct offset + hex"fecd661c0731ca99672edb7303a072da3c2b8342e714c2f2a90639b966298958" // signature + hex"000000000000000000000000026428bf84a659a2d371be1e705613d89d93f78f" // commitment + hex"00000000000000000000000000000000000000000000000000000000000000a0" // offset(feedIds) + hex"0000000000000000000000000000000000000000000000000000000000000002" // injected(feedIds).length + hex"2b2b000000000000000000000000000000000000000000000000000000000000" // injected(feedIds) (double sign) + hex"0000000000000000000000000000000000000000000000000000000000000002" // length(feedIds) + hex"2b68000000000000000000000000000000000000000000000000000000000000" + ); // feedIds + + (bool success, bytes memory retData) = + address(opScribe).call(customCalldata); + if (!success || retData.length != 32) revert(); + bool opPokeInvalid = abi.decode(retData, (bool)); + + // Original PoC: + // assertTrue(opPokeInvalid, "expected opChallenge to return true"); + // assertEq(opScribe.feeds().length, 1, "expected feed to be dropped"); + + // Fixed: + assertFalse(opPokeInvalid, "opChallenge: Calldata encoding attack"); + assertEq( + opScribe.feeds().length, 2, "opChallenge: Calldata encoding attack" + ); + } + // -- Test: Public View Functions -- function testFuzz_challengeReward(uint maxChallengeReward, uint balance) @@ -639,7 +739,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { function test_setOpChallengePeriod_DropsFinalizedOpPoke_If_NonFinalizedAfterUpdate( ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); // Execute opPoke. IScribe.PokeData memory opPokeData; @@ -689,8 +789,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { function _setUpFeedsAndOpPokeOnce(IScribe.PokeData memory pokeData) private + returns (LibFeed.Feed[] memory) { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.SchnorrData memory schnorrData; schnorrData = feeds.signSchnorr(opScribe.constructPokeMessage(pokeData)); @@ -702,6 +803,8 @@ abstract contract IScribeOptimisticTest is IScribeTest { opScribe.constructOpPokeMessage(pokeData, schnorrData) ) ); + + return feeds; } function testFuzz_setOpChallengePeriod_IsAfterAuthedActionProtected( @@ -730,7 +833,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { pokeData.val = 1; pokeData.age = uint32(block.timestamp); - _setUpFeedsAndOpPokeOnce(pokeData); + LibFeed.Feed[] memory feeds = _setUpFeedsAndOpPokeOnce(pokeData); if (opPokeFinalized) { vm.warp(block.timestamp + opScribe.opChallengePeriod()); @@ -739,7 +842,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { emit OpPokeDataDropped(address(this), pokeData); } - opScribe.drop(1); + opScribe.drop(feeds[0].id); } function testFuzz_drop_Multiple_IsAfterAuthedActionProtected( @@ -749,7 +852,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { pokeData.val = 1; pokeData.age = uint32(block.timestamp); - _setUpFeedsAndOpPokeOnce(pokeData); + LibFeed.Feed[] memory feeds = _setUpFeedsAndOpPokeOnce(pokeData); if (opPokeFinalized) { vm.warp(block.timestamp + opScribe.opChallengePeriod()); @@ -758,10 +861,12 @@ abstract contract IScribeOptimisticTest is IScribeTest { emit OpPokeDataDropped(address(this), pokeData); } - uint[] memory feedIndexes = new uint[](1); - feedIndexes[0] = 1; + uint8[] memory feedIds = new uint8[](feeds.length); + for (uint i; i < feeds.length; i++) { + feedIds[i] = feeds[i].id; + } - opScribe.drop(feedIndexes); + opScribe.drop(feedIds); } function testFuzz_setBar_IsAfterAuthedActionProtected(bool opPokeFinalized) @@ -799,9 +904,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { function testFuzz_afterAuthedAction_ProvidesValue_If_MoreThanOncePoked( uint pathSeed ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); - uint path = bound(pathSeed, 0, 6); + uint path = _bound(pathSeed, 0, 6); uint128 otherVal = 1; uint128 wantVal = 2; @@ -1046,9 +1151,13 @@ abstract contract IScribeOptimisticTest is IScribeTest { function _setUp_afterAuthedAction_1() internal - returns (IScribe.PokeData memory, IScribe.PokeData memory) + returns ( + IScribe.PokeData memory, + IScribe.PokeData memory, + LibFeed.Feed[] memory + ) { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData; IScribe.PokeData memory opPokeData = IScribe.PokeData(1, 1); @@ -1063,7 +1172,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { ) ); - return (pokeData, opPokeData); + return (pokeData, opPokeData, feeds); } function test_afterAuthedAction_1_setBar() public { @@ -1079,9 +1188,9 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_1_drop() public { - _setUp_afterAuthedAction_1(); + (,, LibFeed.Feed[] memory feeds) = _setUp_afterAuthedAction_1(); - opScribe.drop(1); + opScribe.drop(feeds[0].id); (bool ok,) = opScribe.tryRead(); assertFalse(ok); @@ -1116,9 +1225,13 @@ abstract contract IScribeOptimisticTest is IScribeTest { function _setUp_afterAuthedAction_2() internal - returns (IScribe.PokeData memory, IScribe.PokeData memory) + returns ( + IScribe.PokeData memory, + IScribe.PokeData memory, + LibFeed.Feed[] memory + ) { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData = IScribe.PokeData(2, 1); opScribe.poke( @@ -1140,11 +1253,11 @@ abstract contract IScribeOptimisticTest is IScribeTest { ) ); - return (pokeData, opPokeData); + return (pokeData, opPokeData, feeds); } function test_afterAuthedAction_2_setBar() public { - (IScribe.PokeData memory pokeData,) = _setUp_afterAuthedAction_2(); + (IScribe.PokeData memory pokeData,,) = _setUp_afterAuthedAction_2(); opScribe.setBar(3); @@ -1158,9 +1271,10 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_2_drop() public { - (IScribe.PokeData memory pokeData,) = _setUp_afterAuthedAction_2(); + (IScribe.PokeData memory pokeData,, LibFeed.Feed[] memory feeds) = + _setUp_afterAuthedAction_2(); - opScribe.drop(1); + opScribe.drop(feeds[0].id); (bool ok, uint val, uint age) = opScribe.tryReadWithAge(); assertTrue(ok); @@ -1172,7 +1286,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_2_setChallengePeriod() public { - (IScribe.PokeData memory pokeData,) = _setUp_afterAuthedAction_2(); + (IScribe.PokeData memory pokeData,,) = _setUp_afterAuthedAction_2(); // Note that _opPokeData still non-finalized after challenge period // update. @@ -1199,9 +1313,13 @@ abstract contract IScribeOptimisticTest is IScribeTest { function _setUp_afterAuthedAction_3() internal - returns (IScribe.PokeData memory, IScribe.PokeData memory) + returns ( + IScribe.PokeData memory, + IScribe.PokeData memory, + LibFeed.Feed[] memory + ) { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory opPokeData = IScribe.PokeData(1, uint32(block.timestamp)); @@ -1218,11 +1336,11 @@ abstract contract IScribeOptimisticTest is IScribeTest { vm.warp(block.timestamp + opScribe.opChallengePeriod() + 1); - return (IScribe.PokeData(0, 0), opPokeData); + return (IScribe.PokeData(0, 0), opPokeData, feeds); } function test_afterAuthedAction_3_setBar() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_3(); + (, IScribe.PokeData memory opPokeData,) = _setUp_afterAuthedAction_3(); opScribe.setBar(3); @@ -1236,9 +1354,10 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_3_drop() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_3(); + (, IScribe.PokeData memory opPokeData, LibFeed.Feed[] memory feeds) = + _setUp_afterAuthedAction_3(); - opScribe.drop(1); + opScribe.drop(feeds[0].id); (bool ok, uint val, uint age) = opScribe.tryReadWithAge(); assertTrue(ok); @@ -1250,7 +1369,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_3_setChallengePeriod() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_3(); + (, IScribe.PokeData memory opPokeData,) = _setUp_afterAuthedAction_3(); // Update challenge period so that _opPokeData still finalized. opScribe.setOpChallengePeriod(1); @@ -1276,9 +1395,13 @@ abstract contract IScribeOptimisticTest is IScribeTest { function _setUp_afterAuthedAction_4() internal - returns (IScribe.PokeData memory, IScribe.PokeData memory) + returns ( + IScribe.PokeData memory, + IScribe.PokeData memory, + LibFeed.Feed[] memory + ) { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(opScribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(opScribe.bar()); IScribe.PokeData memory pokeData = IScribe.PokeData(2, 1); opScribe.poke( @@ -1302,11 +1425,11 @@ abstract contract IScribeOptimisticTest is IScribeTest { vm.warp(block.timestamp + opScribe.opChallengePeriod() + 1); - return (pokeData, opPokeData); + return (pokeData, opPokeData, feeds); } function test_afterAuthedAction_4_setBar() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_4(); + (, IScribe.PokeData memory opPokeData,) = _setUp_afterAuthedAction_4(); opScribe.setBar(3); @@ -1319,9 +1442,10 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_4_drop() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_4(); + (, IScribe.PokeData memory opPokeData, LibFeed.Feed[] memory feeds) = + _setUp_afterAuthedAction_4(); - opScribe.drop(1); + opScribe.drop(feeds[0].id); (bool ok, uint val,) = opScribe.tryReadWithAge(); assertTrue(ok); @@ -1332,7 +1456,7 @@ abstract contract IScribeOptimisticTest is IScribeTest { } function test_afterAuthedAction_4_setChallengePeriod() public { - (, IScribe.PokeData memory opPokeData) = _setUp_afterAuthedAction_4(); + (, IScribe.PokeData memory opPokeData,) = _setUp_afterAuthedAction_4(); // Update challenge period so that _opPokeData still finalized. opScribe.setOpChallengePeriod(1); diff --git a/test/IScribeTest.sol b/test/IScribeTest.sol index 90ec0bf..60e4768 100644 --- a/test/IScribeTest.sol +++ b/test/IScribeTest.sol @@ -23,18 +23,10 @@ abstract contract IScribeTest is Test { bytes32 internal WAT; bytes32 internal FEED_REGISTRATION_MESSAGE; - LibFeed.Feed internal notFeed; - - mapping(address => bool) internal addressFilter; - // Events copied from IScribe. event Poked(address indexed caller, uint128 val, uint32 age); - event FeedLifted( - address indexed caller, address indexed feed, uint indexed index - ); - event FeedDropped( - address indexed caller, address indexed feed, uint indexed index - ); + event FeedLifted(address indexed caller, address indexed feed); + event FeedDropped(address indexed caller, address indexed feed); event BarUpdated(address indexed caller, uint8 oldBar, uint8 newBar); function setUp(address scribe_) internal virtual { @@ -46,37 +38,38 @@ abstract contract IScribeTest is Test { // Toll address(this). IToll(address(scribe)).kiss(address(this)); - - // Create a non-lifted feed instance. - notFeed = LibFeed.newFeed({privKey: 0xdead, index: type(uint8).max}); } - function _createAndLiftFeeds(uint numberFeeds) + function _liftFeeds(uint8 numberFeeds) internal returns (LibFeed.Feed[] memory) { + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](uint(numberFeeds)); + // Note to not start with privKey=1. This is because the sum of public // keys would evaluate to: // pubKeyOf(1) + pubKeyOf(2) + pubKeyOf(3) + ... // = pubKeyOf(3) + pubKeyOf(3) + ... // Note that pubKeyOf(3) would be doubled. Doubling is not supported by // LibSecp256k1 as this would indicate a double-signing attack. - uint startPrivKey = 2; - - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](numberFeeds); - for (uint i; i < numberFeeds; i++) { - feeds[i] = LibFeed.newFeed({ - privKey: startPrivKey + i, - index: uint8(i + 1) - }); - vm.label( - feeds[i].pubKey.toAddress(), - string.concat("Feed #", vm.toString(i + 1)) - ); + uint privKey = 2; + uint bloom; + uint ctr; + while (ctr != numberFeeds) { + LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); + + // Check whether feed with id already created, if not create and + // lift. + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + scribe.lift( + feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE) + ); + } - scribe.lift( - feeds[i].pubKey, feeds[i].signECDSA(FEED_REGISTRATION_MESSAGE) - ); + privKey++; } return feeds; @@ -140,9 +133,8 @@ abstract contract IScribeTest is Test { assertEq(scribe.bar(), 2); // Set of feeds is empty. - (address[] memory feeds_, uint[] memory feedsIndexes) = scribe.feeds(); - assertEq(feeds_.length, 0); - assertEq(feedsIndexes.length, 0); + address[] memory feeds = scribe.feeds(); + assertEq(feeds.length, 0); // read()(uint) fails. try scribe.read() returns (uint) { @@ -196,17 +188,17 @@ abstract contract IScribeTest is Test { assertEq(answeredInRound, 1); // latestAnswer()(int) returns zero. + // Note that latestAnswer()(int) is deprecated. assertEq(scribe.latestAnswer(), int(0)); } // -- Test: Schnorr Verification -- function testFuzz_isAcceptableSchnorrSignatureNow(uint barSeed) public { - // Let bar ∊ [1, scribe.maxFeeds()]. - uint bar = bound(barSeed, 1, scribe.maxFeeds()); - - scribe.setBar(uint8(bar)); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(bar); + // Let bar ∊ [1, 256). + uint8 bar = uint8(_bound(barSeed, 1, 256 - 1)); + scribe.setBar(bar); + LibFeed.Feed[] memory feeds = _liftFeeds(bar); bytes32 message = keccak256("scribe"); @@ -218,20 +210,19 @@ abstract contract IScribeTest is Test { function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_BarNotReached( uint barSeed, - uint numberSignersSeed + uint numberFeedsSeed ) public { - // Let bar ∊ [2, scribe.maxFeeds()]. - uint bar = bound(barSeed, 2, scribe.maxFeeds()); - - // Let numberSigners ∊ [1, bar). - uint numberSigners = bound(numberSignersSeed, 1, bar - 1); + // Let bar ∊ [2, 256). + uint8 bar = uint8(_bound(barSeed, 2, 256 - 1)); + scribe.setBar(bar); + LibFeed.Feed[] memory feeds = _liftFeeds(bar); - scribe.setBar(uint8(bar)); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(bar); + // Let numberFeeds ∊ [1, bar). + uint numberFeeds = _bound(numberFeedsSeed, 1, uint(bar) - 1); assembly ("memory-safe") { - // Set length of feeds list to numberSigners. - mstore(feeds, numberSigners) + // Set length of feeds list to numberFeeds. + mstore(feeds, numberFeeds) } bytes32 message = keccak256("scribe"); @@ -242,37 +233,54 @@ abstract contract IScribeTest is Test { assertFalse(ok); } - function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_SignersNotOrdered( - uint barSeed + function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_DoubleSigningAttempted( + uint barSeed, + uint doubleSignerIndexSeed ) public { - // Let bar ∊ [3, scribe.maxFeeds()]. - uint bar = bound(barSeed, 3, scribe.maxFeeds()); + // Let bar ∊ [2, 256). + uint8 bar = uint8(_bound(barSeed, 2, 256 - 1)); + scribe.setBar(bar); + LibFeed.Feed[] memory feeds = _liftFeeds(bar); - scribe.setBar(uint8(bar)); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(bar); + // Let doubleSignerIndex ∊ [1, bar). + uint doubleSignerIndex = _bound(doubleSignerIndexSeed, 1, uint(bar) - 1); + + // Let random feed double sign. + feeds[0] = feeds[doubleSignerIndex]; bytes32 message = keccak256("scribe"); bool ok = scribe.isAcceptableSchnorrSignatureNow( - message, feeds.signSchnorr_withoutOrderingSignerIndexes(message) + message, feeds.signSchnorr(message) ); assertFalse(ok); } - function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_SignerNotFeed( + function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_InvalidFeedId( uint barSeed, - uint nonSignerIndexSeed + uint privKeySeed, + uint indexSeed ) public { - // Let bar ∊ [1, scribe.maxFeeds()]. - uint bar = bound(barSeed, 1, scribe.maxFeeds()); + // Let bar ∊ [2, 256). + uint8 bar = uint8(_bound(barSeed, 2, 256 - 1)); + scribe.setBar(bar); + LibFeed.Feed[] memory feeds = _liftFeeds(bar); + + // Let privKey ∊ [1, Q). + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); - // Let nonSignerIndex ∊ [0, bar). - uint nonSignerIndex = bound(nonSignerIndexSeed, 0, bar - 1); + // Note to not lift feed. + LibFeed.Feed memory feed = LibFeed.newFeed({privKey: privKey}); - scribe.setBar(uint8(bar)); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(bar); + // Don't run test if bad luck and feed already lifted. + (bool isFeed, /*feedAddr*/ ) = scribe.feeds(feed.id); + if (isFeed) return; - feeds[nonSignerIndex] = notFeed; + // Let index ∊ [0, bar). + uint index = _bound(indexSeed, 0, bar - 1); + + // Let non-lifted feed be the index's signer. + feeds[index] = feed; bytes32 message = keccak256("scribe"); @@ -285,11 +293,10 @@ abstract contract IScribeTest is Test { function testFuzz_isAcceptableSchnorrSignatureNow_FailsIf_SignatureInvalid( uint barSeed ) public { - // Let bar ∊ [1, scribe.maxFeeds()]. - uint bar = bound(barSeed, 1, scribe.maxFeeds()); - - scribe.setBar(uint8(bar)); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(bar); + // Let bar ∊ [1, 256). + uint8 bar = uint8(_bound(barSeed, 1, 256 - 1)); + scribe.setBar(bar); + LibFeed.Feed[] memory feeds = _liftFeeds(bar); bytes32 message = keccak256("scribe"); @@ -308,17 +315,18 @@ abstract contract IScribeTest is Test { // -- Test: Poke -- function testFuzz_poke(IScribe.PokeData[] memory pokeDatas) public { - vm.assume(pokeDatas.length < 50); + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(scribe.bar()); + // Note to stay reasonable in favor of runtime. + vm.assume(pokeDatas.length < 50); uint32 lastPokeTimestamp = 0; IScribe.SchnorrData memory schnorrData; for (uint i; i < pokeDatas.length; i++) { pokeDatas[i].val = - uint128(bound(pokeDatas[i].val, 1, type(uint128).max)); + uint128(_bound(pokeDatas[i].val, 1, type(uint128).max)); pokeDatas[i].age = uint32( - bound(pokeDatas[i].age, lastPokeTimestamp + 1, block.timestamp) + _bound(pokeDatas[i].age, lastPokeTimestamp + 1, block.timestamp) ); schnorrData = @@ -337,7 +345,7 @@ abstract contract IScribeTest is Test { } function test_poke_Initial_FailsIf_AgeIsZero() public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(scribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); IScribe.PokeData memory pokeData; pokeData.val = 1; @@ -355,11 +363,11 @@ abstract contract IScribeTest is Test { function testFuzz_poke_FailsIf_AgeIsStale(IScribe.PokeData memory pokeData) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(scribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); vm.assume(pokeData.val != 0); // Let pokeData's age ∊ [1, block.timestamp]. - pokeData.age = uint32(bound(pokeData.age, 1, block.timestamp)); + pokeData.age = uint32(_bound(pokeData.age, 1, block.timestamp)); IScribe.SchnorrData memory schnorrData; schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData)); @@ -371,7 +379,7 @@ abstract contract IScribeTest is Test { uint currentAge = uint32(block.timestamp); // Set pokeData's age ∊ [0, block.timestamp]. - pokeData.age = uint32(bound(pokeData.age, 0, block.timestamp)); + pokeData.age = uint32(_bound(pokeData.age, 0, block.timestamp)); schnorrData = feeds.signSchnorr(scribe.constructPokeMessage(pokeData)); @@ -387,10 +395,10 @@ abstract contract IScribeTest is Test { function testFuzz_poke_FailsIf_AgeIsInTheFuture( IScribe.PokeData memory pokeData ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(scribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); vm.assume(pokeData.val != 0); - // Let pokeData's age ∊ [block.timestamp+1, type(uint32).max]. + // Let pokeData's age ∊ [block.timestamp + 1, type(uint32).max]. pokeData.age = uint32(bound(pokeData.age, block.timestamp + 1, type(uint32).max)); @@ -410,10 +418,12 @@ abstract contract IScribeTest is Test { function testFuzz_poke_FailsIf_SignatureInvalid( IScribe.PokeData memory pokeData ) public { - LibFeed.Feed[] memory feeds = _createAndLiftFeeds(scribe.bar()); + LibFeed.Feed[] memory feeds = _liftFeeds(scribe.bar()); - vm.assume(pokeData.val != 0); - vm.assume(pokeData.age != 0 && pokeData.age <= uint32(block.timestamp)); + // Let pokeData's val ∊ [1, type(uint128).max]. + // Let pokeData's age ∊ [1, block.timestamp]. + pokeData.val = uint128(_bound(pokeData.val, 1, type(uint128).max)); + pokeData.age = uint32(_bound(pokeData.age, 1, block.timestamp)); // Create schnorrData signing different message. bytes32 message = keccak256("scribe"); @@ -438,150 +448,146 @@ abstract contract IScribeTest is Test { // -- Test: Auth Protected Functions -- function testFuzz_lift_Single(uint privKey) public { - // Bound private key to secp256k1's order, i.e. scalar ∊ [1, Q). - privKey = bound(privKey, 1, LibSecp256k1.Q() - 1); + // Let privKey ∊ [1, Q). + privKey = _bound(privKey, 1, LibSecp256k1.Q() - 1); LibFeed.Feed memory feed = LibFeed.newFeed(privKey); vm.expectEmit(); - emit FeedLifted(address(this), feed.pubKey.toAddress(), 1); + emit FeedLifted(address(this), feed.pubKey.toAddress()); - uint index = + uint feedId = scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - assertEq(index, 1); + assertEq(feedId, feed.id); - // Check via feeds(address)(bool,uint). - bool ok; - (ok, index) = scribe.feeds(feed.pubKey.toAddress()); - assertTrue(ok); - assertEq(index, 1); + // Is idempotent. + scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - // Check via feeds(uint)(bool,address). + // Check via feeds(address)(bool). + bool isFeed = scribe.feeds(feed.pubKey.toAddress()); + assertTrue(isFeed); + + // Check via feeds(uint8)(bool,address). address feedAddr; - (ok, feedAddr) = scribe.feeds(1); - assertTrue(ok); + (isFeed, feedAddr) = scribe.feeds(feed.id); + assertTrue(isFeed); assertEq(feedAddr, feed.pubKey.toAddress()); - // Check via feeds()(address[],uint[]). - address[] memory feeds_; - uint[] memory indexes; - (feeds_, indexes) = scribe.feeds(); - assertEq(feeds_.length, indexes.length); + // Check via feeds()(address[]). + address[] memory feeds_ = scribe.feeds(); assertEq(feeds_.length, 1); assertEq(feeds_[0], feed.pubKey.toAddress()); - assertEq(indexes[0], 1); } function test_lift_Single_FailsIf_ECDSADataInvalid() public { - uint privKeySigner = 1; - uint privKeyFeed = 2; - vm.expectRevert(); scribe.lift( - LibFeed.newFeed(privKeyFeed).pubKey, - LibFeed.newFeed(privKeySigner).signECDSA(FEED_REGISTRATION_MESSAGE) + LibFeed.newFeed({privKey: 1}).pubKey, + LibFeed.newFeed({privKey: 2}).signECDSA(FEED_REGISTRATION_MESSAGE) ); } - function test_lift_Single_FailsIf_MaxFeedsReached() public { - uint maxFeeds = scribe.maxFeeds(); + function test_lift_Single_FailsIf_FeedIdAlreadyLifted() public { + LibFeed.Feed memory feed1 = LibFeed.newFeed({privKey: 22171}); + LibFeed.Feed memory feed2 = LibFeed.newFeed({privKey: 38091}); - // Lift maxFeeds feeds. - LibFeed.Feed memory feed; - for (uint i; i < maxFeeds; i++) { - feed = LibFeed.newFeed(i + 1); - scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - } + // Both feeds have same id. + assertTrue(feed1.id == feed2.id); + + scribe.lift(feed1.pubKey, feed1.signECDSA(FEED_REGISTRATION_MESSAGE)); - feed = LibFeed.newFeed(maxFeeds + 1); vm.expectRevert(); - scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); + scribe.lift(feed2.pubKey, feed2.signECDSA(FEED_REGISTRATION_MESSAGE)); } function testFuzz_lift_Multiple(uint[] memory privKeys) public { - // Bound private keys to secp256k1's order, i.e. scalar ∊ [1, Q). + vm.assume(privKeys.length < 50); + + // Let each privKey ∊ [1, Q). for (uint i; i < privKeys.length; i++) { - privKeys[i] = bound(privKeys[i], 1, LibSecp256k1.Q() - 1); + privKeys[i] = _bound(privKeys[i], 1, LibSecp256k1.Q() - 1); } - // Make feeds. - LibFeed.Feed[] memory feeds_ = new LibFeed.Feed[](privKeys.length); + // Make at most one feed per id. + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); + uint bloom; + uint ctr; for (uint i; i < privKeys.length; i++) { - feeds_[i] = LibFeed.newFeed(privKeys[i]); + LibFeed.Feed memory feed = LibFeed.newFeed(privKeys[i]); + + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + } + } + assembly ("memory-safe") { + mstore(feeds, ctr) } // Make list of public keys. LibSecp256k1.Point[] memory pubKeys = - new LibSecp256k1.Point[](feeds_.length); - for (uint i; i < feeds_.length; i++) { - pubKeys[i] = feeds_[i].pubKey; + new LibSecp256k1.Point[](feeds.length); + for (uint i; i < feeds.length; i++) { + pubKeys[i] = feeds[i].pubKey; } // Make signatures. IScribe.ECDSAData[] memory ecdsaDatas = - new IScribe.ECDSAData[](feeds_.length); - for (uint i; i < feeds_.length; i++) { - ecdsaDatas[i] = feeds_[i].signECDSA(FEED_REGISTRATION_MESSAGE); + new IScribe.ECDSAData[](feeds.length); + for (uint i; i < feeds.length; i++) { + ecdsaDatas[i] = feeds[i].signECDSA(FEED_REGISTRATION_MESSAGE); } - uint indexCtr = 1; - for (uint i; i < feeds_.length; i++) { - // Don't expect event for duplicates. - if (!addressFilter[feeds_[i].pubKey.toAddress()]) { - vm.expectEmit(); - emit FeedLifted( - address(this), feeds_[i].pubKey.toAddress(), indexCtr++ - ); - } - addressFilter[feeds_[i].pubKey.toAddress()] = true; + // Expect events. + for (uint i; i < feeds.length; i++) { + vm.expectEmit(); + emit FeedLifted(address(this), feeds[i].pubKey.toAddress()); } - uint[] memory indexes = scribe.lift(pubKeys, ecdsaDatas); - assertEq(indexes.length, pubKeys.length); - for (uint i; i < indexes.length; i++) { - assertTrue(indexes[i] != 0 && indexes[i] < pubKeys.length + 1); + // Lift feeds and verify returned feed ids. + uint8[] memory feedIds = scribe.lift(pubKeys, ecdsaDatas); + assertEq(feedIds.length, feeds.length); + for (uint i; i < feedIds.length; i++) { + assertEq(feedIds[i], feeds[i].id); } - // Check via feeds(address)(bool,uint) and feeds(uint)(bool,address). - bool ok; - uint index; + // Check via feeds(address)(bool) and feeds(uint8)(bool,address). + bool isFeed; + uint8 feedId; address feedAddr; - for (uint i; i < pubKeys.length; i++) { - (ok, index) = scribe.feeds(pubKeys[i].toAddress()); - assertTrue(ok); - // Note that the indexes are orders based on pubKeys' addresses. - assertTrue(index != 0); - - (ok, feedAddr) = scribe.feeds(index); - assertTrue(ok); - assertEq(pubKeys[i].toAddress(), feedAddr); + for (uint i; i < feeds.length; i++) { + isFeed = scribe.feeds(feeds[i].pubKey.toAddress()); + assertTrue(isFeed); + + feedId = uint8(uint(uint160(feeds[i].pubKey.toAddress())) >> 152); + + (isFeed, feedAddr) = scribe.feeds(feedId); + assertTrue(isFeed); + assertEq(feeds[i].pubKey.toAddress(), feedAddr); } - // Check via feeds()(address[],uint[]). - address[] memory addrs; - (addrs, indexes) = scribe.feeds(); - for (uint i; i < pubKeys.length; i++) { - for (uint j; j < addrs.length; j++) { - // Break inner loop if pubKey's address found in list of feeds. - if (pubKeys[i].toAddress() == addrs[j]) { + // Check via feeds()(address[]). + address[] memory feedAddrs = scribe.feeds(); + for (uint i; i < feeds.length; i++) { + for (uint j; j < feedAddrs.length; j++) { + // Break inner loop if feed's address found in list of feedAddrs. + if (feeds[i].pubKey.toAddress() == feedAddrs[j]) { break; } // Fail if pubKey's address not found in list of feeds. - if (j == addrs.length - 1) { - assertTrue(false); + if (j == feedAddrs.length - 1) { + fail("Expected feed missing in feeds()(address[])"); } } } } function test_lift_Multiple_FailsIf_ECDSADataInvalid() public { - uint privKeySigner = 1; - uint privKeyFeed = 2; - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](2); - feeds[0] = LibFeed.newFeed(privKeySigner); - feeds[1] = LibFeed.newFeed(privKeyFeed); + feeds[0] = LibFeed.newFeed({privKey: 1}); + feeds[1] = LibFeed.newFeed({privKey: 2}); IScribe.ECDSAData[] memory ecdsaDatas = new IScribe.ECDSAData[](2); ecdsaDatas[0] = feeds[0].signECDSA(FEED_REGISTRATION_MESSAGE); @@ -595,33 +601,6 @@ abstract contract IScribeTest is Test { scribe.lift(pubKeys, ecdsaDatas); } - function test_lift_Multiple_FailsIf_MaxFeedsReached() public { - uint maxFeeds = scribe.maxFeeds(); - - // Make feeds. - LibFeed.Feed[] memory feeds = new LibFeed.Feed[](maxFeeds + 1); - for (uint i; i < maxFeeds + 1; i++) { - feeds[i] = LibFeed.newFeed(i + 1); - } - - // Make list of public keys. - LibSecp256k1.Point[] memory pubKeys = - new LibSecp256k1.Point[](maxFeeds + 1); - for (uint i; i < maxFeeds + 1; i++) { - pubKeys[i] = feeds[i].pubKey; - } - - // Make signatures. - IScribe.ECDSAData[] memory ecdsaDatas = - new IScribe.ECDSAData[](maxFeeds + 1); - for (uint i; i < maxFeeds + 1; i++) { - ecdsaDatas[i] = feeds[i].signECDSA(FEED_REGISTRATION_MESSAGE); - } - - vm.expectRevert(); - scribe.lift(pubKeys, ecdsaDatas); - } - function testFuzz_lift_Multiple_FailsIf_ArrayLengthMismatch( LibSecp256k1.Point[] memory pubKeys, IScribe.ECDSAData[] memory ecdsaDatas @@ -633,162 +612,114 @@ abstract contract IScribeTest is Test { } function testFuzz_drop_Single(uint privKey) public { - // Bound private key to secp256k1's order, i.e. scalar ∊ [1, Q). + // Let privKey ∊ [1, Q). privKey = bound(privKey, 1, LibSecp256k1.Q() - 1); LibFeed.Feed memory feed = LibFeed.newFeed(privKey); - uint index = + uint8 feedId = scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - assertEq(index, 1); vm.expectEmit(); - emit FeedDropped(address(this), feed.pubKey.toAddress(), 1); + emit FeedDropped(address(this), feed.pubKey.toAddress()); + + scribe.drop(feedId); - scribe.drop(1); + // Is idempotent. + scribe.drop(feedId); // Check via feeds(address)(bool). - bool ok; - (ok, index) = scribe.feeds(feed.pubKey.toAddress()); - assertFalse(ok); - assertEq(index, 0); + bool isFeed = scribe.feeds(feed.pubKey.toAddress()); + assertFalse(isFeed); // Check via feeds(uint)(bool,address). address feedAddr; - (ok, feedAddr) = scribe.feeds(1); - assertFalse(ok); - assertFalse(feedAddr == feed.pubKey.toAddress()); + (isFeed, feedAddr) = scribe.feeds(feedId); + assertFalse(isFeed); + assertEq(feedAddr, address(0)); - // Check via feeds()(address[],uint[]). - address[] memory feeds_; - uint[] memory indexes; - (feeds_, indexes) = scribe.feeds(); - assertEq(feeds_.length, indexes.length); + // Check via feeds()(address[]). + address[] memory feeds_ = scribe.feeds(); assertEq(feeds_.length, 0); } function testFuzz_drop_Multiple(uint[] memory privKeys) public { - // Bound private keys to secp256k1's order, i.e. scalar ∊ [1, Q). + // Let each privKey ∊ [1, Q). for (uint i; i < privKeys.length; i++) { - privKeys[i] = bound(privKeys[i], 1, LibSecp256k1.Q() - 1); + privKeys[i] = _bound(privKeys[i], 1, LibSecp256k1.Q() - 1); } - // Make feeds. - LibFeed.Feed[] memory feeds_ = new LibFeed.Feed[](privKeys.length); + // Make at most one feed per id. + LibFeed.Feed[] memory feeds = new LibFeed.Feed[](privKeys.length); + uint bloom; + uint ctr; for (uint i; i < privKeys.length; i++) { - feeds_[i] = LibFeed.newFeed(privKeys[i]); + LibFeed.Feed memory feed = LibFeed.newFeed(privKeys[i]); + + if (bloom & (1 << feed.id) == 0) { + bloom |= 1 << feed.id; + + feeds[ctr++] = feed; + } + } + assembly ("memory-safe") { + mstore(feeds, ctr) } // Make list of public keys. LibSecp256k1.Point[] memory pubKeys = - new LibSecp256k1.Point[](feeds_.length); - for (uint i; i < feeds_.length; i++) { - pubKeys[i] = feeds_[i].pubKey; + new LibSecp256k1.Point[](feeds.length); + for (uint i; i < feeds.length; i++) { + pubKeys[i] = feeds[i].pubKey; } // Make signatures. IScribe.ECDSAData[] memory ecdsaDatas = - new IScribe.ECDSAData[](feeds_.length); - for (uint i; i < feeds_.length; i++) { - ecdsaDatas[i] = feeds_[i].signECDSA(FEED_REGISTRATION_MESSAGE); + new IScribe.ECDSAData[](feeds.length); + for (uint i; i < feeds.length; i++) { + ecdsaDatas[i] = feeds[i].signECDSA(FEED_REGISTRATION_MESSAGE); } // Lift feeds. - uint[] memory indexes = scribe.lift(pubKeys, ecdsaDatas); + uint8[] memory feedIds = scribe.lift(pubKeys, ecdsaDatas); // Expect events. - uint indexCtr = 1; - for (uint i; i < pubKeys.length; i++) { + bloom = 0; + ctr = 0; + for (uint i; i < feeds.length; i++) { // Don't expect event for duplicates. - if (!addressFilter[pubKeys[i].toAddress()]) { + if (bloom & (1 << feeds[i].id) == 0) { + bloom |= 1 << feeds[i].id; + vm.expectEmit(); - emit FeedDropped( - address(this), pubKeys[i].toAddress(), indexCtr++ - ); + emit FeedDropped(address(this), feeds[i].pubKey.toAddress()); } - - addressFilter[pubKeys[i].toAddress()] = true; } // Drop feeds. - scribe.drop(indexes); + scribe.drop(feedIds); - // Check via feeds(address)(bool,uint). - bool ok; - uint index; - for (uint i; i < pubKeys.length; i++) { - (ok, index) = scribe.feeds(pubKeys[i].toAddress()); - assertFalse(ok); - assertEq(index, 0); - } + // Is idempotent. + scribe.drop(feedIds); - // Check via feeds()(address[],uint[]). - address[] memory feedAddresses; - uint[] memory feedIndexes; - (feedAddresses, feedIndexes) = scribe.feeds(); - assertEq(feedAddresses.length, feedIndexes.length); - assertEq(feedAddresses.length, 0); - } - - function test_drop_IndexZero() public { - // Does nothing. - scribe.drop(0); - } - - function testFuzz_drop_Single_FailsIf_IndexOutOfBounds(uint index) public { - vm.assume(index != 0); - - vm.expectRevert(); - scribe.drop(index); - } - - function testFuzz_drop_Multiple_FailsIf_IndexOutOfBounds( - uint[] memory indexes - ) public { - vm.assume(indexes.length != 0); - indexes[indexes.length - 1] = 1; - - vm.expectRevert(); - scribe.drop(indexes); - } - - function testFuzz_liftDropLift(uint privKey) public { - // Bound private key to secp256k1's order, i.e. scalar ∊ [1, Q). - privKey = bound(privKey, 1, LibSecp256k1.Q() - 1); - - LibFeed.Feed memory feed = LibFeed.newFeed(privKey); - - bool ok; - uint index; - - index = - scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - assertEq(index, 1); - (ok, index) = scribe.feeds(feed.pubKey.toAddress()); - assertTrue(ok); - assertEq(index, 1); - - scribe.drop(1); - (ok, index) = scribe.feeds(feed.pubKey.toAddress()); - assertFalse(ok); - assertEq(index, 0); + // Check via feeds(address)(bool). + bool isFeed; + for (uint i; i < feeds.length; i++) { + isFeed = scribe.feeds(feeds[i].pubKey.toAddress()); + assertFalse(isFeed); + } - // Note that lifting same feed again leads to an increased index - // nevertheless. - index = - scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - assertEq(index, 2); - (ok, index) = scribe.feeds(feed.pubKey.toAddress()); - assertTrue(ok); - assertEq(index, 2); + // Check via feeds(uint8)(bool,address). + address feedAddr; + for (uint i; i < feeds.length; i++) { + (isFeed, feedAddr) = scribe.feeds(feeds[i].id); + assertFalse(isFeed); + assertEq(feedAddr, address(0)); + } - address[] memory feedAddrs; - uint[] memory feedIndexes; - (feedAddrs, feedIndexes) = scribe.feeds(); - assertEq(feedAddrs.length, 1); - assertEq(feedIndexes.length, 1); - assertEq(feedAddrs[0], feed.pubKey.toAddress()); - assertEq(feedIndexes[0], 2); + // Check via feeds()(address[]). + address[] memory feedAddresses = scribe.feeds(); + assertEq(feedAddresses.length, 0); } function testFuzz_setBar(uint8 bar) public { @@ -870,7 +801,7 @@ abstract contract IScribeTest is Test { IAuth.NotAuthorized.selector, address(0xbeef) ) ); - scribe.drop(new uint[](1)); + scribe.drop(new uint8[](1)); } function test_setBar_IsAuthProtected() public { diff --git a/test/LibBytesTest.sol b/test/LibBytesTest.sol deleted file mode 100644 index 6cbdc44..0000000 --- a/test/LibBytesTest.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import {Test} from "forge-std/Test.sol"; - -import {LibBytes} from "src/libs/LibBytes.sol"; - -abstract contract LibBytesTest is Test { - using LibBytes for uint; - - function testFuzz_getByteAtIndex(uint8 wantByte, uint indexSeed) public { - // Let index ∊ [0, 32). - uint index = bound(indexSeed, 0, 31); - - // Create word with wantByte at byte index. - uint word = uint(wantByte) << (index * 8); - - uint8 gotByte = uint8(word.getByteAtIndex(index)); - - assertEq(wantByte, gotByte); - } - - function test_getByteAtIndex() public { - uint word; - uint index; - uint want; - uint got; - - // Most significant byte. - word = - 0xFF11111111111111111111111111111111111111111111111111111111111111; - index = 31; - want = 0xFF; - got = word.getByteAtIndex(index); - assertEq(want, got); - - // Least significant byte. - word = - 0x11111111111111111111111111111111111111111111111111111111111111FF; - index = 0; - want = 0xFF; - got = word.getByteAtIndex(index); - assertEq(want, got); - - word = - 0x111111111111111111111111111111111111111111111111111111111111AA11; - index = 1; - want = 0xAA; - got = word.getByteAtIndex(index); - assertEq(want, got); - - word = - 0x1111001111111111111111111111111111111111111111111111111111111111; - index = 29; - want = 0x00; - got = word.getByteAtIndex(index); - assertEq(want, got); - } -} diff --git a/test/LibSchnorrDataTest.sol b/test/LibSchnorrDataTest.sol deleted file mode 100644 index ef8c780..0000000 --- a/test/LibSchnorrDataTest.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.16; - -import {Test} from "forge-std/Test.sol"; -import {console2} from "forge-std/console2.sol"; - -import {IScribe} from "src/IScribe.sol"; - -import {LibSchnorrData} from "src/libs/LibSchnorrData.sol"; - -abstract contract LibSchnorrDataTest is Test { - using LibSchnorrData for IScribe.SchnorrData; - - function testFuzz_getSignerIndex(uint lengthSeed, uint indexSeed) public { - // Let length be ∊ [1, type(uint8).max]. - uint length = bound(lengthSeed, 1, type(uint8).max); - - // Let index be ∊ [0, length). - uint index = bound(indexSeed, 0, length - 1); - - bytes memory signersBlob; - for (uint i; i < length; i++) { - if (i == index) { - signersBlob = abi.encodePacked(signersBlob, uint8(0xFF)); - } else { - signersBlob = abi.encodePacked(signersBlob, uint8(1)); - } - } - - IScribe.SchnorrData memory schnorrData; - schnorrData.signersBlob = signersBlob; - - uint got = this.getSignerIndex(schnorrData, index); - assertEq(got, uint8(0xFF)); - } - - function testFuzz_getSingerIndex_ReturnsZeroIfIndexOutOfBounds( - uint lengthSeed, - uint indexSeed - ) public { - // Let length be ∊ [0, type(uint8).max]. - uint length = bound(lengthSeed, 0, type(uint8).max); - - // Let index be ∊ [length, type(uint8).max]. - // Note that index's upper limit is bounded is bounded by bar, which is - // of type uint8. - uint index = bound(indexSeed, length, type(uint8).max); - - bytes memory signersBlob; - for (uint i; i < length; i++) { - signersBlob = abi.encodePacked(signersBlob, uint8(1)); - } - - IScribe.SchnorrData memory schnorrData; - schnorrData.signersBlob = signersBlob; - - uint got = this.getSignerIndex(schnorrData, index); - assertEq(got, uint8(0)); - } - - function testFuzz_getSignerIndexLength(uint lengthSeed) public { - // Let length be ∊ [0, type(uint8).max]. - uint length = bound(lengthSeed, 0, type(uint8).max); - - bytes memory signersBlob; - for (uint i; i < length; i++) { - signersBlob = abi.encodePacked(signersBlob, uint8(1)); - } - - IScribe.SchnorrData memory schnorrData; - schnorrData.signersBlob = signersBlob; - - uint got = this.getSignerIndexLength(schnorrData); - assertEq(got, length); - } - - // -- Optimizations -- - - /// @dev Tests correctness of a possible optimization. - /// - /// Note that the optimization is currently not implemented. - function testFuzzOptimization_getSignerIndex_WordIndexComputation( - uint index - ) public { - // Current implementation: - uint want; - assembly ("memory-safe") { - want := mul(div(index, 32), 32) - } - - // Possible optimization: - uint mask = type(uint).max << 5; - uint got = index & mask; - - assertEq(want, got); - } - - /// @dev Tests correctness of a possible optimization. - /// - /// Note that the optimization is currently not implemented. - function testFuzzOptimization_getSignerIndex_ByteIndexComputation( - uint index - ) public { - // Current implementation: - uint want; - unchecked { - want = 31 - (index % 32); - } - - // Possible optimization: - uint mask = type(uint).max >> (256 - 5); - uint got = (~index) & mask; - - assertEq(want, got); - } - - // -- Executors -- - // - // Used to move memory structs into calldata. - - function getSignerIndex( - IScribe.SchnorrData calldata schnorrData, - uint index - ) public pure returns (uint) { - return schnorrData.getSignerIndex(index); - } - - function getSignerIndexLength(IScribe.SchnorrData calldata schnorrData) - public - pure - returns (uint) - { - return schnorrData.getSignerIndexLength(); - } -} diff --git a/test/LibSchnorrTest.sol b/test/LibSchnorrTest.sol index 25afc77..11f0b44 100644 --- a/test/LibSchnorrTest.sol +++ b/test/LibSchnorrTest.sol @@ -36,7 +36,7 @@ abstract contract LibSchnorrTest is Test { // Note that we allow double signing. uint[] memory privKeys = new uint[](privKeySeeds.length); for (uint i; i < privKeySeeds.length; i++) { - privKeys[i] = bound(privKeySeeds[i], 2, LibSecp256k1.Q() - 1); + privKeys[i] = _bound(privKeySeeds[i], 2, LibSecp256k1.Q() - 1); } // Make list of public key. @@ -79,7 +79,7 @@ abstract contract LibSchnorrTest is Test { bytes32 message ) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Compute pubKey. LibSecp256k1.Point memory pubKey = privKey.derivePublicKey(); @@ -119,7 +119,7 @@ abstract contract LibSchnorrTest is Test { // Note that we allow double signing. uint[] memory privKeys = new uint[](privKeySeeds.length); for (uint i; i < privKeySeeds.length; i++) { - privKeys[i] = bound(privKeySeeds[i], 2, LibSecp256k1.Q() - 1); + privKeys[i] = _bound(privKeySeeds[i], 2, LibSecp256k1.Q() - 1); } // Make list of public key. @@ -166,7 +166,7 @@ abstract contract LibSchnorrTest is Test { vm.assume(signatureMask != 0); // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; @@ -191,7 +191,7 @@ abstract contract LibSchnorrTest is Test { vm.assume(commitmentMask != 0); // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; @@ -216,7 +216,7 @@ abstract contract LibSchnorrTest is Test { vm.assume(messageMask != 0); // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; @@ -242,7 +242,7 @@ abstract contract LibSchnorrTest is Test { vm.assume(pubKeyXMask != 0 || flipParity); // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; @@ -268,7 +268,7 @@ abstract contract LibSchnorrTest is Test { bytes32 message ) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; @@ -290,7 +290,7 @@ abstract contract LibSchnorrTest is Test { bytes32 message ) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Sign message. uint signature; diff --git a/test/LibSecp256k1Test.sol b/test/LibSecp256k1Test.sol index 1c45d24..0ea626f 100644 --- a/test/LibSecp256k1Test.sol +++ b/test/LibSecp256k1Test.sol @@ -17,7 +17,7 @@ abstract contract LibSecp256k1Test is Test { function testFuzzDifferential_toAddress(uint privKeySeed) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); address want = vm.addr(privKey); address got = privKey.derivePublicKey().toAddress(); @@ -43,7 +43,7 @@ abstract contract LibSecp256k1Test is Test { function testFuzz_isOnCurve(uint privKeySeed) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); assertTrue(privKey.derivePublicKey().isOnCurve()); } @@ -56,7 +56,7 @@ abstract contract LibSecp256k1Test is Test { vm.assume(maskX != 0 || maskY != 0); // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); // Compute and mutate point. LibSecp256k1.Point memory p = privKey.derivePublicKey(); @@ -93,7 +93,7 @@ abstract contract LibSecp256k1Test is Test { function testFuzz_toJacobian_toAffine(uint privKeySeed) public { // Let privKey ∊ [1, Q). - uint privKey = bound(privKeySeed, 1, LibSecp256k1.Q() - 1); + uint privKey = _bound(privKeySeed, 1, LibSecp256k1.Q() - 1); LibSecp256k1.Point memory want = privKey.derivePublicKey(); LibSecp256k1.Point memory got = want.toJacobian().toAffine(); diff --git a/test/Runner.t.sol b/test/Runner.t.sol index 4a9cb8d..06d2557 100644 --- a/test/Runner.t.sol +++ b/test/Runner.t.sol @@ -49,15 +49,6 @@ import {LibSchnorrTest as LibSchnorrTest_} from "./LibSchnorrTest.sol"; contract LibSchnorrTest is LibSchnorrTest_ {} -import {LibBytesTest as LibBytesTest_} from "./LibBytesTest.sol"; - -contract LibBytesTest is LibBytesTest_ {} - -import {LibSchnorrDataTest as LibSchnorrDataTest_} from - "./LibSchnorrDataTest.sol"; - -contract LibSchnorrDataTest is LibSchnorrDataTest_ {} - // -- Test: EVM Requirements -- import {EVMTest} from "./EVMTest.sol"; diff --git a/test/inspectable/ScribeInspectable.sol b/test/inspectable/ScribeInspectable.sol index ae85d99..ef5efe7 100644 --- a/test/inspectable/ScribeInspectable.sol +++ b/test/inspectable/ScribeInspectable.sol @@ -14,23 +14,11 @@ contract ScribeInspectable is Scribe { return _pokeData; } - function inspectable_pubKeys() - public - view - returns (LibSecp256k1.Point[] memory) - { - return _pubKeys; - } - - function inspectable_pubKeys(uint index) + function inspectable_pubKeys(uint8 feedId) public view returns (LibSecp256k1.Point memory) { - return _unsafeLoadPubKeyAt(index); - } - - function inspectable_feeds(address addr) public view returns (uint) { - return _feeds[addr]; + return _pubKeys[feedId]; } } diff --git a/test/invariants/FeedSet.sol b/test/invariants/FeedSet.sol index 7beac0f..7cf2e93 100644 --- a/test/invariants/FeedSet.sol +++ b/test/invariants/FeedSet.sol @@ -71,4 +71,8 @@ library LibFeedSet { return s.feeds[seed % s.feeds.length]; } + + function count(FeedSet storage s) internal view returns (uint) { + return s.feeds.length; + } } diff --git a/test/invariants/IScribeInvariantTest.sol b/test/invariants/IScribeInvariantTest.sol index f7dd08b..eba3d85 100644 --- a/test/invariants/IScribeInvariantTest.sol +++ b/test/invariants/IScribeInvariantTest.sol @@ -57,104 +57,37 @@ abstract contract IScribeInvariantTest is Test { function invariant_poke_PokeTimestampsAreStrictlyMonotonicallyIncreasing() public { - // Get scribe's pokeData before the execution. + // Get scribe's pokeData before execution. IScribe.PokeData memory beforePokeData = handler.scribe_lastPokeData(); // Get scribe's current pokeData. IScribe.PokeData memory currentPokeData; currentPokeData = scribe.inspectable_pokeData(); - if (beforePokeData.age != currentPokeData.age) { - assertTrue(beforePokeData.age < currentPokeData.age); - } else { - assertEq(beforePokeData.age, currentPokeData.age); - } + assertTrue(beforePokeData.age <= currentPokeData.age); } - /* - function invariant_poke_PokeTimestampIsOnlyMutatedToCurrentTimestamp() - public - { - // Get scribe's pokeData before the execution. - IScribe.PokeData memory beforePokeData = handler.scribe_lastPokeData(); - - // Get scribe's current pokeData. - IScribe.PokeData memory currentPokeData; - currentPokeData = scribe.inspectable_pokeData(); - - if (beforePokeData.age != currentPokeData.age) { - assertEq(currentPokeData.age, uint32(block.timestamp)); - } - } - */ - // -- PubKeys -- - function invariant_pubKeys_AtIndexZeroIsZeroPoint() public { - assertTrue(scribe.inspectable_pubKeys(0).isZeroPoint()); - } - - mapping(bytes32 => bool) private pubKeyFilter; - - function invariant_pubKeys_NonZeroPubKeyExistsAtMostOnce() public { - LibSecp256k1.Point[] memory pubKeys = scribe.inspectable_pubKeys(); - for (uint i; i < pubKeys.length; i++) { - if (pubKeys[i].isZeroPoint()) continue; + function invariant_pubKeys_IndexedViaFeedId() public { + LibSecp256k1.Point memory pubKey; + uint8 feedId; - bytes32 id = keccak256(abi.encodePacked(pubKeys[i].x, pubKeys[i].y)); + for (uint i; i < 256; i++) { + pubKey = scribe.inspectable_pubKeys(uint8(i)); + feedId = uint8(uint(uint160(pubKey.toAddress())) >> 152); - assertFalse(pubKeyFilter[id]); - pubKeyFilter[id] = true; + assertTrue(pubKey.isZeroPoint() || i == feedId); } } - function invariant_pubKeys_LengthIsStrictlyMonotonicallyIncreasing() + function invariant_pubKeys_CannotIndexOutOfBoundsViaUint8Index() public + view { - uint lastLen = handler.scribe_lastPubKeysLength(); - uint currentLen = scribe.inspectable_pubKeys().length; - - assertTrue(lastLen <= currentLen); - } - - function invariant_pubKeys_ZeroPointIsNeverAddedAsPubKey() public { - uint lastLen = handler.scribe_lastPubKeysLength(); - - LibSecp256k1.Point[] memory pubKeys; - pubKeys = scribe.inspectable_pubKeys(); - - if (lastLen != pubKeys.length) { - assertFalse(pubKeys[pubKeys.length - 1].isZeroPoint()); - } - } - - // -- Feeds -- - - function invariant_feeds_ImageIsZeroToLengthOfPubKeys() public { - address[] memory feedAddrs = handler.ghost_feedAddresses(); - uint pubKeysLen = scribe.inspectable_pubKeys().length; - - for (uint i; i < feedAddrs.length; i++) { - uint index = scribe.inspectable_feeds(feedAddrs[i]); - - assertTrue(index < pubKeysLen); - } - } - - function invariant_feeds_LinkToTheirPublicKeys() public { - address[] memory feedAddrs = handler.ghost_feedAddresses(); - - LibSecp256k1.Point[] memory pubKeys; - pubKeys = scribe.inspectable_pubKeys(); - - LibSecp256k1.Point memory pubKey; - for (uint i; i < feedAddrs.length; i++) { - uint index = scribe.inspectable_feeds(feedAddrs[i]); - - pubKey = pubKeys[index]; - if (!pubKey.isZeroPoint()) { - assertEq(pubKey.toAddress(), feedAddrs[i]); - } + for (uint i; i <= type(uint8).max; i++) { + // Should not revert. + scribe.inspectable_pubKeys(uint8(i)); } } diff --git a/test/invariants/ScribeHandler.sol b/test/invariants/ScribeHandler.sol index 1e6da65..4d7aff9 100644 --- a/test/invariants/ScribeHandler.sol +++ b/test/invariants/ScribeHandler.sol @@ -29,16 +29,13 @@ contract ScribeHandler is CommonBase, StdUtils { IScribe public scribe; IScribe.PokeData internal _scribe_lastPokeData; - uint public scribe_lastPubKeysLength; - uint internal nextPrivKey = 2; - FeedSet internal feedSet; + uint internal _nextPrivKey = 2; + FeedSet internal _feedSet; modifier cacheScribeState() { // forgefmt: disable-next-item _scribe_lastPokeData = ScribeInspectable(address(scribe)).inspectable_pokeData(); - // forgefmt: disable-next-item - scribe_lastPubKeysLength = ScribeInspectable(address(scribe)).inspectable_pubKeys().length; _; } @@ -48,37 +45,38 @@ contract ScribeHandler is CommonBase, StdUtils { // Cache constants. WAT = scribe.wat(); FEED_REGISTRATION_MESSAGE = scribe.feedRegistrationMessage(); - - _ensureBarFeedsLifted(); } function _ensureBarFeedsLifted() internal { uint bar = scribe.bar(); - (address[] memory feeds,) = scribe.feeds(); + address[] memory feeds = scribe.feeds(); if (feeds.length < bar) { // Lift feeds until bar is reached. uint missing = bar - feeds.length; LibFeed.Feed memory feed; - for (uint i; i < missing; i++) { - feed = LibFeed.newFeed(nextPrivKey++); + while (missing != 0) { + feed = LibFeed.newFeed(_nextPrivKey++); + + // Continue if feed's id already lifted. + (bool isFeed,) = scribe.feeds(feed.id); + if (isFeed) continue; - // Lift feed and set its index. - uint index = scribe.lift( + // Otherwise lift feed and add to feedSet. + scribe.lift( feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE) ); - feed.index = uint8(index); + _feedSet.add({feed: feed, lifted: true}); - // Store feed in feedSet. - feedSet.add(feed, true); + missing--; } } } // -- Target Functions -- - function warp(uint seed) external { - uint amount = bound(seed, 1, 1 hours); + function warp(uint seed) external cacheScribeState { + uint amount = _bound(seed, 1, 1 hours); vm.warp(block.timestamp + amount); } @@ -94,7 +92,7 @@ contract ScribeHandler is CommonBase, StdUtils { } // Get set of bar many feeds from feedSet. - LibFeed.Feed[] memory feeds = feedSet.liftedFeeds(scribe.bar()); + LibFeed.Feed[] memory feeds = _feedSet.liftedFeeds(scribe.bar()); // Create pokeData. IScribe.PokeData memory pokeData = IScribe.PokeData({ @@ -122,34 +120,31 @@ contract ScribeHandler is CommonBase, StdUtils { function lift() external cacheScribeState { // Create new feed. - LibFeed.Feed memory feed = LibFeed.newFeed(nextPrivKey++); + LibFeed.Feed memory feed = LibFeed.newFeed(_nextPrivKey++); - // Lift feed and set its index. - uint index = - scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); - feed.index = uint8(index); + // Return if feed's id already lifted. + (bool isFeed,) = scribe.feeds(feed.id); + if (isFeed) return; - // Store feed in feedSet. - feedSet.add(feed, true); + // Lift feed and add to feedSet. + scribe.lift(feed.pubKey, feed.signECDSA(FEED_REGISTRATION_MESSAGE)); + _feedSet.add({feed: feed, lifted: true}); } function drop(uint seed) external cacheScribeState { + if (_feedSet.count() == 0) return; + // Get random feed from feedSet. // Note that feed may not be lifted. - LibFeed.Feed memory feed = LibFeedSet.rand(feedSet, seed); - - // Receive index of feed. Index is zero if not lifted. - (, uint index) = scribe.feeds(feed.pubKey.toAddress()); - - // Drop feed. - scribe.drop(index); + LibFeed.Feed memory feed = _feedSet.rand(seed); - // Mark feed as non-lifted in feedSet. - feedSet.updateLifted(feed, false); + // Drop feed and mark as non-lifted in feedSet. + scribe.drop(feed.id); + _feedSet.updateLifted({feed: feed, lifted: false}); } function setBar(uint barSeed) external cacheScribeState { - uint8 newBar = uint8(bound(barSeed, 0, MAX_BAR)); + uint8 newBar = uint8(_bound(barSeed, 0, MAX_BAR)); // Should revert if newBar is 0. try scribe.setBar(newBar) {} catch {} @@ -166,22 +161,22 @@ contract ScribeHandler is CommonBase, StdUtils { } function ghost_feedAddresses() external view returns (address[] memory) { - address[] memory addrs = new address[](feedSet.feeds.length); + address[] memory addrs = new address[](_feedSet.feeds.length); for (uint i; i < addrs.length; i++) { - addrs[i] = feedSet.feeds[i].pubKey.toAddress(); + addrs[i] = _feedSet.feeds[i].pubKey.toAddress(); } return addrs; } // -- Helpers -- - function _randPokeDataVal(uint seed) internal view returns (uint128) { - uint val = bound(seed, 0, type(uint128).max); + function _randPokeDataVal(uint seed) internal pure returns (uint128) { + uint val = _bound(seed, 0, type(uint128).max); return uint128(val); } function _randPokeDataAge(uint seed) internal view returns (uint32) { - uint age = bound(seed, _scribe_lastPokeData.age + 1, block.timestamp); + uint age = _bound(seed, _scribe_lastPokeData.age + 1, block.timestamp); return uint32(age); } }