From c26774b7c9d9e4a51c2189b2de824cb7bc2d9098 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Fri, 11 Oct 2024 11:36:13 +0800 Subject: [PATCH 1/2] Extract TaikoL1TestBase --- .../contracts/layer1/based/ITaikoL1.sol | 28 +-- .../contracts/layer1/provers/ProverSet.sol | 10 +- .../layer1/verifiers/SgxVerifierBase.sol | 172 ++++++++++++++++++ packages/protocol/foundry.toml | 2 +- .../test/layer1/verifiers/SgxVerifier.t.sol | 24 +-- 5 files changed, 202 insertions(+), 34 deletions(-) create mode 100644 packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol diff --git a/packages/protocol/contracts/layer1/based/ITaikoL1.sol b/packages/protocol/contracts/layer1/based/ITaikoL1.sol index 91119d4787a..dcdd5ae1987 100644 --- a/packages/protocol/contracts/layer1/based/ITaikoL1.sol +++ b/packages/protocol/contracts/layer1/based/ITaikoL1.sol @@ -56,6 +56,19 @@ interface ITaikoL1 { /// @param _pause True to pause, false to unpause. function pauseProving(bool _pause) external; + /// @notice Deposits Taiko token to be used as bonds. + /// @param _amount The amount of Taiko token to deposit. + function depositBond(uint256 _amount) external; + + /// @notice Withdraws Taiko tokens. + /// @param _amount Amount of Taiko tokens to withdraw. + function withdrawBond(uint256 _amount) external; + + /// @notice Gets the prover that actually proved a verified block. + /// @param _blockId Index of the block. + /// @return The prover's address. If the block is not verified yet, address(0) will be returned. + function getVerifiedBlockProver(uint64 _blockId) external view returns (address); + /// @notice Gets the details of a block. /// @param _blockId Index of the block. /// @return blk_ The block. @@ -73,20 +86,7 @@ interface ITaikoL1 { view returns (TaikoData.TransitionState memory); - /// @notice Deposits Taiko token to be used as bonds. - /// @param _amount The amount of Taiko token to deposit. - function depositBond(uint256 _amount) external; - - /// @notice Withdraws Taiko tokens. - /// @param _amount Amount of Taiko tokens to withdraw. - function withdrawBond(uint256 _amount) external; - - /// @notice Gets the prover that actually proved a verified block. - /// @param _blockId Index of the block. - /// @return The prover's address. If the block is not verified yet, address(0) will be returned. - function getVerifiedBlockProver(uint64 _blockId) external view returns (address); - /// @notice Gets the configuration of the TaikoL1 contract. /// @return Config struct containing configuration parameters. function getConfig() external pure returns (TaikoData.Config memory); -} +} \ No newline at end of file diff --git a/packages/protocol/contracts/layer1/provers/ProverSet.sol b/packages/protocol/contracts/layer1/provers/ProverSet.sol index e7ea82ee562..1ba2729250d 100644 --- a/packages/protocol/contracts/layer1/provers/ProverSet.sol +++ b/packages/protocol/contracts/layer1/provers/ProverSet.sol @@ -81,13 +81,7 @@ contract ProverSet is EssentialContract, IERC1271 { } /// @notice Propose a Taiko block. - function proposeBlockV2( - bytes calldata _params, - bytes calldata _txList - ) - external - onlyProver - { + function proposeBlockV2(bytes calldata _params, bytes calldata _txList) external onlyProver { ITaikoL1(taikoL1()).proposeBlockV2(_params, _txList); } @@ -157,4 +151,4 @@ contract ProverSet is EssentialContract, IERC1271 { function tkoToken() internal view virtual returns (address) { return resolve(LibStrings.B_TAIKO_TOKEN, false); } -} +} \ No newline at end of file diff --git a/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol b/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol new file mode 100644 index 00000000000..7746ef4019f --- /dev/null +++ b/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "src/shared/common/EssentialContract.sol"; +import "src/shared/common/LibStrings.sol"; +import "../automata-attestation/interfaces/IAttestation.sol"; +import "../automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; + + +/// @title SgxVerifierBase +/// @dev Please see references below: +/// - Reference #1: https://ethresear.ch/t/2fa-zk-rollups-using-sgx/14462 +/// - Reference #2: https://github.com/gramineproject/gramine/discussions/1579 +/// @custom:security-contact security@taiko.xyz +abstract contract SgxVerifierBase is EssentialContract { + /// @dev Each public-private key pair (Ethereum address) is generated within + /// the SGX program when it boots up. The off-chain remote attestation + /// ensures the validity of the program hash and has the capability of + /// bootstrapping the network with trustworthy instances. + struct Instance { + address addr; + uint64 validSince; + } + + /// @notice The expiry time for the SGX instance. + uint64 public constant INSTANCE_EXPIRY = 365 days; + + /// @notice A security feature, a delay until an instance is enabled when using onchain RA + /// verification + uint64 public constant INSTANCE_VALIDITY_DELAY = 0; + + /// @dev For gas savings, we shall assign each SGX instance with an id that when we need to + /// set a new pub key, just write storage once. + /// Slot 1. + uint256 public nextInstanceId; + + /// @dev One SGX instance is uniquely identified (on-chain) by it's ECDSA public key + /// (or rather ethereum address). Once that address is used (by proof verification) it has to be + /// overwritten by a new one (representing the same instance). This is due to side-channel + /// protection. Also this public key shall expire after some time + /// (for now it is a long enough 6 months setting). + /// Slot 2. + mapping(uint256 instanceId => Instance instance) public instances; + + /// @dev One address shall be registered (during attestation) only once, otherwise it could + /// bypass this contract's expiry check by always registering with the same attestation and + /// getting multiple valid instanceIds. While during proving, it is technically possible to + /// register the old addresses, it is less of a problem, because the instanceId would be the + /// same for those addresses and if deleted - the attestation cannot be reused anyways. + /// Slot 3. + mapping(address instanceAddress => bool alreadyAttested) public addressRegistered; + + uint256[47] private __gap; + + /// @notice Emitted when a new SGX instance is added to the registry, or replaced. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + /// @param replaced The address of the SGX instance that was replaced. If it is the first + /// instance, this value is zero address. + /// @param validSince The time since the instance is valid. + event InstanceAdded( + uint256 indexed id, address indexed instance, address indexed replaced, uint256 validSince + ); + + /// @notice Emitted when an SGX instance is deleted from the registry. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + event InstanceDeleted(uint256 indexed id, address indexed instance); + + error SGX_ALREADY_ATTESTED(); + error SGX_INVALID_ATTESTATION(); + error SGX_INVALID_INSTANCE(); + error SGX_INVALID_PROOF(); + error SGX_RA_NOT_SUPPORTED(); + + /// @notice Register an SGX instance after the attestation is verified + /// @param _attestation The parsed attestation quote. + /// @return The respective instanceId + function registerInstance(V3Struct.ParsedV3QuoteStruct calldata _attestation) + external + returns (uint256) + { + address automataDcapAttestation = resolve(LibStrings.B_AUTOMATA_DCAP_ATTESTATION, true); + + if (automataDcapAttestation == address(0)) { + revert SGX_RA_NOT_SUPPORTED(); + } + + (bool verified,) = IAttestation(automataDcapAttestation).verifyParsedQuote(_attestation); + + if (!verified) revert SGX_INVALID_ATTESTATION(); + + address[] memory addresses = new address[](1); + addresses[0] = address(bytes20(_attestation.localEnclaveReport.reportData)); + + return _addInstances(addresses, false)[0]; + } + + /// @notice Adds trusted SGX instances to the registry. + /// @param _instances The address array of trusted SGX instances. + /// @return The respective instanceId array per addresses. + function addInstances(address[] calldata _instances) + external + onlyOwner + returns (uint256[] memory) + { + return _addInstances(_instances, true); + } + + /// @notice Deletes SGX instances from the registry. + /// @param _ids The ids array of SGX instances. + function deleteInstances(uint256[] calldata _ids) + external + onlyFromOwnerOrNamed(LibStrings.B_SGX_WATCHDOG) + { + for (uint256 i; i < _ids.length; ++i) { + uint256 idx = _ids[i]; + + if (instances[idx].addr == address(0)) revert SGX_INVALID_INSTANCE(); + + emit InstanceDeleted(idx, instances[idx].addr); + + delete instances[idx]; + } + } + + function _addInstances( + address[] memory _instances, + bool instantValid + ) + internal + returns (uint256[] memory ids) + { + ids = new uint256[](_instances.length); + + uint64 validSince = uint64(block.timestamp); + + if (!instantValid) { + validSince += INSTANCE_VALIDITY_DELAY; + } + + for (uint256 i; i < _instances.length; ++i) { + if (addressRegistered[_instances[i]]) revert SGX_ALREADY_ATTESTED(); + + addressRegistered[_instances[i]] = true; + + if (_instances[i] == address(0)) revert SGX_INVALID_INSTANCE(); + + instances[nextInstanceId] = Instance(_instances[i], validSince); + ids[i] = nextInstanceId; + + emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince); + + ++nextInstanceId; + } + } + + function _replaceInstance(uint256 id, address oldInstance, address newInstance) internal { + // Replacing an instance means, it went through a cooldown (if added by on-chain RA) so no + // need to have a cooldown + instances[id] = Instance(newInstance, uint64(block.timestamp)); + emit InstanceAdded(id, newInstance, oldInstance, block.timestamp); + } + + function _isInstanceValid(uint256 id, address instance) internal view returns (bool) { + if (instance == address(0)) return false; + if (instance != instances[id].addr) return false; + return instances[id].validSince <= block.timestamp + && block.timestamp <= instances[id].validSince + INSTANCE_EXPIRY; + } +} diff --git a/packages/protocol/foundry.toml b/packages/protocol/foundry.toml index 7012fe4b58d..d22f925e5d4 100644 --- a/packages/protocol/foundry.toml +++ b/packages/protocol/foundry.toml @@ -21,7 +21,7 @@ remappings = [ "@p256-verifier/contracts/=node_modules/p256-verifier/src/", "src/=contracts/", "test/=test/", - "script/=script/" + "script/=script/", ] # Do not change the block_gas_limit value, TaikoL2.t.sol depends on it. diff --git a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol index 5631423fddb..72d8984c72a 100644 --- a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol +++ b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import "src/layer1/verifiers/SgxVerifierBase.sol"; import "../automata-attestation/common/AttestationBase.t.sol"; import "../based/TaikoL1TestBase.sol"; + contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { address internal SGX_Y = vm.addr(0x9b1bb8cb3bdb539d0d1f03951d27f167f2d5443e7ef0d7ce745cd4ec619d3dd7); @@ -38,9 +40,9 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { instances[1] = Bob; vm.expectEmit(true, true, true, true); - emit SgxVerifier.InstanceAdded(startInstance, instances[0], address(0), block.timestamp); + emit SgxVerifierBase.InstanceAdded(startInstance, instances[0], address(0), block.timestamp); vm.expectEmit(true, true, true, true); - emit SgxVerifier.InstanceAdded(startInstance + 1, instances[1], address(0), block.timestamp); + emit SgxVerifierBase.InstanceAdded(startInstance + 1, instances[1], address(0), block.timestamp); // `addInstances()` uint256[] memory ids = sv.addInstances(instances); @@ -66,11 +68,11 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { instances2[1] = David; vm.expectEmit(true, true, true, true); - emit SgxVerifier.InstanceAdded( + emit SgxVerifierBase.InstanceAdded( startInstance + 2, instances2[0], address(0), block.timestamp ); vm.expectEmit(true, true, true, true); - emit SgxVerifier.InstanceAdded( + emit SgxVerifierBase.InstanceAdded( startInstance + 3, instances2[1], address(0), block.timestamp ); @@ -103,7 +105,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { instances[1] = address(0); // `addInstances()` - vm.expectRevert(SgxVerifier.SGX_INVALID_INSTANCE.selector); + vm.expectRevert(SgxVerifierBase.SGX_INVALID_INSTANCE.selector); sv.addInstances(instances); vm.stopPrank(); @@ -117,7 +119,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { instances[1] = Alice; // invalid as duplicate instance // `addInstances()` - vm.expectRevert(SgxVerifier.SGX_ALREADY_ATTESTED.selector); + vm.expectRevert(SgxVerifierBase.SGX_ALREADY_ATTESTED.selector); sv.addInstances(instances); } @@ -161,7 +163,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.prank(Bob, Bob); sv.registerInstance(v3quote); - vm.expectRevert(SgxVerifier.SGX_ALREADY_ATTESTED.selector); + vm.expectRevert(SgxVerifierBase.SGX_ALREADY_ATTESTED.selector); vm.prank(Carol, Carol); sv.registerInstance(v3quote); } @@ -218,7 +220,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.warp(block.timestamp + 5); vm.expectEmit(true, true, true, true); - emit SgxVerifier.InstanceAdded(id, newInstance, KNOWN_ADDRESS, block.timestamp); + emit SgxVerifierBase.InstanceAdded(id, newInstance, KNOWN_ADDRESS, block.timestamp); // `verifyProof()` sv.verifyProof(ctx, transition, proof); @@ -294,7 +296,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { }); // `verifyProof()` - vm.expectRevert(SgxVerifier.SGX_INVALID_PROOF.selector); + vm.expectRevert(SgxVerifierBase.SGX_INVALID_PROOF.selector); sv.verifyProof(ctx, transition, proof); } @@ -375,7 +377,7 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { TaikoData.TierProof memory proof = TaikoData.TierProof({ tier: 0, data: data }); // `verifyProof()` - vm.expectRevert(SgxVerifier.SGX_INVALID_INSTANCE.selector); + vm.expectRevert(SgxVerifierBase.SGX_INVALID_INSTANCE.selector); sv.verifyProof(ctx, transition, proof); vm.stopPrank(); @@ -435,4 +437,4 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.stopPrank(); } -} +} \ No newline at end of file From 62a47465cb9b06c222e77b04fdc83d6d72c0adda Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Fri, 11 Oct 2024 11:38:51 +0800 Subject: [PATCH 2/2] fmt --- packages/protocol/contracts/layer1/based/ITaikoL1.sol | 2 +- packages/protocol/contracts/layer1/provers/ProverSet.sol | 2 +- .../contracts/layer1/verifiers/SgxVerifierBase.sol | 1 - packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol | 7 ++++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/protocol/contracts/layer1/based/ITaikoL1.sol b/packages/protocol/contracts/layer1/based/ITaikoL1.sol index dcdd5ae1987..5ea7923c370 100644 --- a/packages/protocol/contracts/layer1/based/ITaikoL1.sol +++ b/packages/protocol/contracts/layer1/based/ITaikoL1.sol @@ -89,4 +89,4 @@ interface ITaikoL1 { /// @notice Gets the configuration of the TaikoL1 contract. /// @return Config struct containing configuration parameters. function getConfig() external pure returns (TaikoData.Config memory); -} \ No newline at end of file +} diff --git a/packages/protocol/contracts/layer1/provers/ProverSet.sol b/packages/protocol/contracts/layer1/provers/ProverSet.sol index 1ba2729250d..7de671f5a74 100644 --- a/packages/protocol/contracts/layer1/provers/ProverSet.sol +++ b/packages/protocol/contracts/layer1/provers/ProverSet.sol @@ -151,4 +151,4 @@ contract ProverSet is EssentialContract, IERC1271 { function tkoToken() internal view virtual returns (address) { return resolve(LibStrings.B_TAIKO_TOKEN, false); } -} \ No newline at end of file +} diff --git a/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol b/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol index 7746ef4019f..f3510a0484f 100644 --- a/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol +++ b/packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol @@ -7,7 +7,6 @@ import "src/shared/common/LibStrings.sol"; import "../automata-attestation/interfaces/IAttestation.sol"; import "../automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; - /// @title SgxVerifierBase /// @dev Please see references below: /// - Reference #1: https://ethresear.ch/t/2fa-zk-rollups-using-sgx/14462 diff --git a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol index 72d8984c72a..2e1b115de9e 100644 --- a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol +++ b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol @@ -5,7 +5,6 @@ import "src/layer1/verifiers/SgxVerifierBase.sol"; import "../automata-attestation/common/AttestationBase.t.sol"; import "../based/TaikoL1TestBase.sol"; - contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { address internal SGX_Y = vm.addr(0x9b1bb8cb3bdb539d0d1f03951d27f167f2d5443e7ef0d7ce745cd4ec619d3dd7); @@ -42,7 +41,9 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.expectEmit(true, true, true, true); emit SgxVerifierBase.InstanceAdded(startInstance, instances[0], address(0), block.timestamp); vm.expectEmit(true, true, true, true); - emit SgxVerifierBase.InstanceAdded(startInstance + 1, instances[1], address(0), block.timestamp); + emit SgxVerifierBase.InstanceAdded( + startInstance + 1, instances[1], address(0), block.timestamp + ); // `addInstances()` uint256[] memory ids = sv.addInstances(instances); @@ -437,4 +438,4 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.stopPrank(); } -} \ No newline at end of file +}