Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(protocol): extract SgxVerifierBase to be reused #18231

Merged
merged 2 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions packages/protocol/contracts/layer1/based/ITaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change function order


/// @notice Gets the details of a block.
/// @param _blockId Index of the block.
/// @return blk_ The block.
Expand All @@ -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);
}
}
10 changes: 2 additions & 8 deletions packages/protocol/contracts/layer1/provers/ProverSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -157,4 +151,4 @@ contract ProverSet is EssentialContract, IERC1271 {
function tkoToken() internal view virtual returns (address) {
return resolve(LibStrings.B_TAIKO_TOKEN, false);
}
}
}
172 changes: 172 additions & 0 deletions packages/protocol/contracts/layer1/verifiers/SgxVerifierBase.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion packages/protocol/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 13 additions & 11 deletions packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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
);

Expand Down Expand Up @@ -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();
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -435,4 +437,4 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase {

vm.stopPrank();
}
}
}