diff --git a/cmd/ethrex/build_l2.rs b/cmd/ethrex/build_l2.rs
index 67615013405..82f1e053c1e 100644
--- a/cmd/ethrex/build_l2.rs
+++ b/cmd/ethrex/build_l2.rs
@@ -107,6 +107,10 @@ pub fn download_script() {
&Path::new("../../crates/l2/contracts/src/l1/Router.sol"),
"Router",
),
+ (
+ &Path::new("../../crates/l2/contracts/src/l1/Timelock.sol"),
+ "Timelock",
+ ),
];
for (path, name) in l1_contracts {
compile_contract_to_bytecode(
@@ -196,6 +200,7 @@ fn write_empty_bytecode_files(output_contracts_path: &Path) {
"UpgradeableSystemContract",
"SequencerRegistry",
"OnChainProposerBased",
+ "Timelock",
];
for name in &contract_names {
diff --git a/cmd/ethrex/l2/command.rs b/cmd/ethrex/l2/command.rs
index a4413e96021..87e92eba02d 100644
--- a/cmd/ethrex/l2/command.rs
+++ b/cmd/ethrex/l2/command.rs
@@ -95,6 +95,8 @@ impl L2Command {
.sequencer_opts
.committer_opts
.on_chain_proposer_address = Some(contract_addresses.on_chain_proposer_address);
+ l2_options.sequencer_opts.committer_opts.timelock_address =
+ contract_addresses.timelock_address;
l2_options.sequencer_opts.watcher_opts.bridge_address =
Some(contract_addresses.bridge_address);
println!("Initializing L2");
diff --git a/cmd/ethrex/l2/deployer.rs b/cmd/ethrex/l2/deployer.rs
index 71f151ac367..c62cd10948b 100644
--- a/cmd/ethrex/l2/deployer.rs
+++ b/cmd/ethrex/l2/deployer.rs
@@ -533,6 +533,13 @@ const SEQUENCER_REGISTRY_BYTECODE: &[u8] = include_bytes!(concat!(
"/contracts/solc_out/SequencerRegistry.bytecode"
));
+/// Bytecode of the Timelock contract.
+/// This is generated by the [build script](./build.rs).
+const TIMELOCK_BYTECODE: &[u8] = include_bytes!(concat!(
+ env!("OUT_DIR"),
+ "/contracts/solc_out/Timelock.bytecode"
+));
+
/// Bytecode of the SP1Verifier contract.
/// This is generated by the [build script](./build.rs).
const SP1_VERIFIER_BYTECODE: &[u8] = include_bytes!(concat!(
@@ -540,10 +547,10 @@ const SP1_VERIFIER_BYTECODE: &[u8] = include_bytes!(concat!(
"/contracts/solc_out/SP1Verifier.bytecode"
));
-const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE_BASED: &str = "initialize(bool,address,bool,bool,bool,bool,address,address,address,address,bytes32,bytes32,bytes32,bytes32,address,uint256)";
-const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE: &str = "initialize(bool,address,bool,bool,bool,bool,address,address,address,address,bytes32,bytes32,bytes32,bytes32,address[],uint256)";
+const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE_BASED: &str = "initialize(bool,address,bool,bool,bool,bool,address,address,address,address,bytes32,bytes32,bytes32,bytes32,address,uint256,address)";
+const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE: &str = "initialize(bool,address,bool,bool,bool,bool,address,address,address,address,bytes32,bytes32,bytes32,bytes32,uint256,address)";
+const INITIALIZE_TIMELOCK_SIGNATURE: &str = "initialize(uint256,address[],address,address,address)";
-const INITIALIZE_BRIDGE_ADDRESS_SIGNATURE: &str = "initializeBridgeAddress(address)";
const TRANSFER_OWNERSHIP_SIGNATURE: &str = "transferOwnership(address)";
const ACCEPT_OWNERSHIP_SIGNATURE: &str = "acceptOwnership()";
const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address,address,uint256,address, uint256)";
@@ -565,6 +572,7 @@ pub struct ContractAddresses {
pub sequencer_registry_address: Address,
pub aligned_aggregator_address: Address,
pub router: Option
,
+ pub timelock_address: Option,
}
pub async fn deploy_l1_contracts(
@@ -716,6 +724,41 @@ async fn deploy_contracts(
Default::default()
};
+ let (timelock_deployment, timelock_address) = if !opts.deploy_based_contracts {
+ info!("Deploying Timelock");
+
+ let timelock_deployment = deploy_with_proxy_from_bytecode_no_wait(
+ deployer,
+ eth_client,
+ TIMELOCK_BYTECODE,
+ &salt,
+ Overrides {
+ nonce: Some(nonce),
+ gas_limit: Some(TRANSACTION_GAS_LIMIT),
+ max_fee_per_gas: Some(gas_price),
+ max_priority_fee_per_gas: Some(gas_price),
+ ..Default::default()
+ },
+ )
+ .await?;
+
+ nonce += 2;
+
+ info!(
+ "Timelock deployed:\n Proxy -> address={:#x}, tx_hash={:#x}\n Impl -> address={:#x}, tx_hash={:#x}",
+ timelock_deployment.proxy_address,
+ timelock_deployment.proxy_tx_hash,
+ timelock_deployment.implementation_address,
+ timelock_deployment.implementation_tx_hash,
+ );
+ (
+ Some(timelock_deployment.clone()),
+ Some(timelock_deployment.proxy_address),
+ )
+ } else {
+ (None, None)
+ };
+
info!("Deploying OnChainProposer");
trace!("Attempting to deploy OnChainProposer contract");
@@ -854,13 +897,15 @@ async fn deploy_contracts(
_ => Address::zero(),
};
+ let tdx_controller_address =
+ timelock_address.unwrap_or(on_chain_proposer_deployment.proxy_address);
+
// if it's a required proof type, but no address has been specified, deploy it.
let tdx_verifier_address = match opts.tdx_verifier_address {
Some(addr) if opts.tdx => addr,
None if opts.tdx => {
info!("Deploying TDXVerifier (if tdx_deploy_verifier is true)");
- let tdx_verifier_address =
- deploy_tdx_contracts(opts, on_chain_proposer_deployment.proxy_address)?;
+ let tdx_verifier_address = deploy_tdx_contracts(opts, tdx_controller_address)?;
info!(address = %format!("{tdx_verifier_address:#x}"), "TDXVerifier deployed");
tdx_verifier_address
@@ -888,7 +933,7 @@ async fn deploy_contracts(
tdx_verifier_address = ?tdx_verifier_address,
"Contracts deployed"
);
- let receipts = vec![
+ let mut receipts = vec![
on_chain_proposer_deployment.implementation_tx_hash,
on_chain_proposer_deployment.proxy_tx_hash,
bridge_deployment.implementation_tx_hash,
@@ -900,6 +945,11 @@ async fn deploy_contracts(
router_deployment.proxy_tx_hash,
];
+ if let Some(timelock_deployment) = timelock_deployment {
+ receipts.push(timelock_deployment.implementation_tx_hash);
+ receipts.push(timelock_deployment.proxy_tx_hash);
+ }
+
Ok((
ContractAddresses {
on_chain_proposer_address: on_chain_proposer_deployment.proxy_address,
@@ -910,6 +960,7 @@ async fn deploy_contracts(
sequencer_registry_address: sequencer_registry_deployment.proxy_address,
aligned_aggregator_address,
router: opts.router.or(Some(router_deployment.proxy_address)),
+ timelock_address,
},
receipts,
))
@@ -1033,13 +1084,55 @@ async fn initialize_contracts(
let deployer_address = get_address_from_secret_key(&opts.private_key.secret_bytes())
.map_err(DeployerError::InternalError)?;
+ if let Some(timelock_address) = contract_addresses.timelock_address {
+ info!("Initializing Timelock");
+ let initialize_tx_hash = {
+ let deployer = Signer::Local(LocalSigner::new(opts.private_key));
+ let deployer_nonce = eth_client
+ .get_nonce(deployer_address, BlockIdentifier::Tag(BlockTag::Pending))
+ .await?;
+ let calldata_values = vec![
+ Value::Uint(U256::from(30)), // TODO: Make minDelay parametrizable. For now this is for testing purposes.
+ Value::Array(vec![
+ // sequencers
+ Value::Address(opts.committer_l1_address),
+ Value::Address(opts.proof_sender_l1_address),
+ ]),
+ Value::Address(opts.on_chain_proposer_owner), // owner
+ Value::Address(opts.on_chain_proposer_owner), // securityCouncil
+ Value::Address(contract_addresses.on_chain_proposer_address), // onChainProposer
+ ];
+ let timelock_initialization_calldata =
+ encode_calldata(INITIALIZE_TIMELOCK_SIGNATURE, &calldata_values)?;
+
+ initialize_contract_no_wait(
+ timelock_address,
+ timelock_initialization_calldata,
+ &deployer,
+ eth_client,
+ Overrides {
+ nonce: Some(deployer_nonce),
+ gas_limit: Some(TRANSACTION_GAS_LIMIT),
+ max_fee_per_gas: Some(gas_price),
+ max_priority_fee_per_gas: Some(gas_price),
+ ..Default::default()
+ },
+ )
+ .await?
+ };
+ info!(tx_hash = %format!("{initialize_tx_hash:#x}"), "Timelock initialized");
+ tx_hashes.push(initialize_tx_hash);
+ } else {
+ info!("Skipping Timelock initialization (based enabled)");
+ }
+
info!("Initializing OnChainProposer");
if opts.deploy_based_contracts {
// Initialize OnChainProposer with Based config and SequencerRegistry
let calldata_values = vec![
Value::Bool(opts.validium),
- Value::Address(deployer_address),
+ Value::Address(opts.on_chain_proposer_owner),
Value::Bool(opts.risc0),
Value::Bool(opts.sp1),
Value::Bool(opts.tdx),
@@ -1054,6 +1147,7 @@ async fn initialize_contracts(
Value::FixedBytes(genesis.compute_state_root().0.to_vec().into()),
Value::Address(contract_addresses.sequencer_registry_address),
Value::Uint(genesis.config.chain_id.into()),
+ Value::Address(contract_addresses.bridge_address),
];
trace!(calldata_values = ?calldata_values, "OnChainProposer initialization calldata values");
@@ -1149,7 +1243,9 @@ async fn initialize_contracts(
// Initialize only OnChainProposer without Based config
let calldata_values = vec![
Value::Bool(opts.validium),
- Value::Address(deployer_address),
+ Value::Address(contract_addresses.timelock_address.ok_or(
+ DeployerError::InternalError("Timelock address missing".to_string()),
+ )?),
Value::Bool(opts.risc0),
Value::Bool(opts.sp1),
Value::Bool(opts.tdx),
@@ -1162,11 +1258,8 @@ async fn initialize_contracts(
Value::FixedBytes(risc0_vk),
Value::FixedBytes(commit_hash.0.to_vec().into()),
Value::FixedBytes(genesis.compute_state_root().0.to_vec().into()),
- Value::Array(vec![
- Value::Address(opts.committer_l1_address),
- Value::Address(opts.proof_sender_l1_address),
- ]),
Value::Uint(genesis.config.chain_id.into()),
+ Value::Address(contract_addresses.bridge_address),
];
trace!(calldata_values = ?calldata_values, "OnChainProposer initialization calldata values");
let on_chain_proposer_initialization_calldata =
@@ -1196,108 +1289,6 @@ async fn initialize_contracts(
tx_hashes.push(initialize_tx_hash);
}
- let initialize_bridge_address_tx_hash = {
- let initializer_nonce = eth_client
- .get_nonce(
- initializer.address(),
- BlockIdentifier::Tag(BlockTag::Pending),
- )
- .await?;
- let calldata_values = vec![Value::Address(contract_addresses.bridge_address)];
- let on_chain_proposer_initialization_calldata =
- encode_calldata(INITIALIZE_BRIDGE_ADDRESS_SIGNATURE, &calldata_values)?;
-
- initialize_contract_no_wait(
- contract_addresses.on_chain_proposer_address,
- on_chain_proposer_initialization_calldata,
- initializer,
- eth_client,
- Overrides {
- nonce: Some(initializer_nonce),
- gas_limit: Some(TRANSACTION_GAS_LIMIT),
- max_fee_per_gas: Some(gas_price),
- max_priority_fee_per_gas: Some(gas_price),
- ..Default::default()
- },
- )
- .await?
- };
-
- info!(
- tx_hash = %format!("{initialize_bridge_address_tx_hash:#x}"),
- "OnChainProposer bridge address initialized"
- );
-
- tx_hashes.push(initialize_bridge_address_tx_hash);
-
- if opts.on_chain_proposer_owner != initializer.address() {
- let initializer_nonce = eth_client
- .get_nonce(
- initializer.address(),
- BlockIdentifier::Tag(BlockTag::Pending),
- )
- .await?;
- let transfer_ownership_tx_hash = {
- let owner_transfer_calldata = encode_calldata(
- TRANSFER_OWNERSHIP_SIGNATURE,
- &[Value::Address(opts.on_chain_proposer_owner)],
- )?;
-
- initialize_contract_no_wait(
- contract_addresses.on_chain_proposer_address,
- owner_transfer_calldata,
- initializer,
- eth_client,
- Overrides {
- nonce: Some(initializer_nonce),
- gas_limit: Some(TRANSACTION_GAS_LIMIT),
- max_fee_per_gas: Some(gas_price),
- max_priority_fee_per_gas: Some(gas_price),
- ..Default::default()
- },
- )
- .await?
- };
-
- tx_hashes.push(transfer_ownership_tx_hash);
-
- if let Some(owner_pk) = opts.on_chain_proposer_owner_pk {
- let signer = Signer::Local(LocalSigner::new(owner_pk));
- let owner_nonce = eth_client
- .get_nonce(signer.address(), BlockIdentifier::Tag(BlockTag::Pending))
- .await?;
- let accept_ownership_calldata = encode_calldata(ACCEPT_OWNERSHIP_SIGNATURE, &[])?;
- let accept_tx = build_generic_tx(
- eth_client,
- TxType::EIP1559,
- contract_addresses.on_chain_proposer_address,
- opts.on_chain_proposer_owner,
- accept_ownership_calldata.into(),
- Overrides {
- nonce: Some(owner_nonce),
- gas_limit: Some(TRANSACTION_GAS_LIMIT),
- max_fee_per_gas: Some(gas_price),
- max_priority_fee_per_gas: Some(gas_price),
- ..Default::default()
- },
- )
- .await?;
- let accept_tx_hash = send_generic_transaction(eth_client, accept_tx, &signer).await?;
- tx_hashes.push(accept_tx_hash);
-
- info!(
- transfer_tx_hash = %format!("{transfer_ownership_tx_hash:#x}"),
- accept_tx_hash = %format!("{accept_tx_hash:#x}"),
- "OnChainProposer ownership transfered"
- );
- } else {
- info!(
- transfer_tx_hash = %format!("{transfer_ownership_tx_hash:#x}"),
- "OnChainProposer ownership transfered but not accepted yet"
- );
- }
- }
-
info!("Initializing CommonBridge");
let initialize_tx_hash = {
let initializer_nonce = eth_client
@@ -1581,6 +1572,9 @@ fn write_contract_addresses_to_env(
"ETHREX_COMMITTER_ON_CHAIN_PROPOSER_ADDRESS={:#x}",
contract_addresses.on_chain_proposer_address
)?;
+ if let Some(timelock_address) = contract_addresses.timelock_address {
+ writeln!(writer, "ETHREX_TIMELOCK_ADDRESS={:#x}", timelock_address)?;
+ }
writeln!(
writer,
"ETHREX_WATCHER_BRIDGE_ADDRESS={:#x}",
diff --git a/cmd/ethrex/l2/options.rs b/cmd/ethrex/l2/options.rs
index 34642f8ca53..73938cb2b63 100644
--- a/cmd/ethrex/l2/options.rs
+++ b/cmd/ethrex/l2/options.rs
@@ -172,6 +172,7 @@ impl TryFrom for SequencerConfig {
.committer_opts
.on_chain_proposer_address
.ok_or(SequencerOptionsError::NoOnChainProposerAddress)?,
+ timelock_address: opts.committer_opts.timelock_address,
first_wake_up_time_ms: opts.committer_opts.first_wake_up_time_ms.unwrap_or(0),
commit_time_ms: opts.committer_opts.commit_time_ms,
batch_gas_limit: opts.committer_opts.batch_gas_limit,
@@ -605,6 +606,13 @@ pub struct CommitterOptions {
required_unless_present = "dev"
)]
pub on_chain_proposer_address: Option,
+ #[arg(
+ long = "l1.timelock-address",
+ value_name = "ADDRESS",
+ env = "ETHREX_TIMELOCK_ADDRESS",
+ help_heading = "L1 Committer options"
+ )]
+ pub timelock_address: Option,
#[arg(
long = "committer.commit-time",
default_value = "60000",
@@ -648,6 +656,7 @@ impl Default for CommitterOptions {
)
.ok(),
on_chain_proposer_address: None,
+ timelock_address: None,
commit_time_ms: 60000,
batch_gas_limit: None,
first_wake_up_time_ms: None,
@@ -675,6 +684,7 @@ impl CommitterOptions {
self.on_chain_proposer_address = self
.on_chain_proposer_address
.or(defaults.on_chain_proposer_address);
+ self.timelock_address = self.timelock_address.or(defaults.timelock_address);
self.batch_gas_limit = self.batch_gas_limit.or(defaults.batch_gas_limit);
self.first_wake_up_time_ms = self
.first_wake_up_time_ms
diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol
index 885c41a9691..5edeba82b60 100644
--- a/crates/l2/contracts/src/l1/OnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/OnChainProposer.sol
@@ -11,8 +11,6 @@ import {ICommonBridge} from "./interfaces/ICommonBridge.sol";
import {IRiscZeroVerifier} from "./interfaces/IRiscZeroVerifier.sol";
import {ISP1Verifier} from "./interfaces/ISP1Verifier.sol";
import {ITDXVerifier} from "./interfaces/ITDXVerifier.sol";
-import "./interfaces/ICommonBridge.sol";
-import "./interfaces/IOnChainProposer.sol";
import "../l2/interfaces/ICommonBridgeL2.sol";
/// @title OnChainProposer contract.
@@ -69,7 +67,7 @@ contract OnChainProposer is
/// @dev This is crucial for ensuring that only subsequents batches are committed in the contract.
uint256 public lastCommittedBatch;
- /// @dev The sequencer addresses that are authorized to commit and verify batches.
+ /// @dev Deprecated variable. This is managed inside the Timelock.
mapping(address _authorizedAddress => bool)
public authorizedSequencerAddresses;
@@ -82,7 +80,7 @@ contract OnChainProposer is
/// @dev Deprecated variable.
bytes32 public SP1_VERIFICATION_KEY;
- /// @notice Indicates whether the contract operates in validium mode.Add commentMore actions
+ /// @notice Indicates whether the contract operates in validium mode.
/// @dev This value is immutable and can only be set during contract deployment.
bool public VALIDIUM;
@@ -112,24 +110,17 @@ contract OnChainProposer is
mapping(bytes32 commitHash => mapping(uint8 verifierId => bytes32 vk))
public verificationKeys;
- modifier onlySequencer() {
- require(
- authorizedSequencerAddresses[msg.sender],
- "000" // OnChainProposer: caller is not the sequencer
- );
- _;
- }
-
/// @notice Initializes the contract.
/// @dev This method is called only once after the contract is deployed.
+ /// @dev The owner is expected to be the Timelock contract.
/// @dev It sets the bridge address.
- /// @param owner the address of the owner who can perform upgrades.
+ /// @param timelock_owner the Timelock address that can perform upgrades.
/// @param alignedProofAggregator the address of the alignedProofAggregatorService contract.
/// @param r0verifier the address of the risc0 groth16 verifier.
/// @param sp1verifier the address of the sp1 groth16 verifier.
function initialize(
bool _validium,
- address owner,
+ address timelock_owner,
bool requireRisc0Proof,
bool requireSp1Proof,
bool requireTdxProof,
@@ -142,24 +133,19 @@ contract OnChainProposer is
bytes32 risc0Vk,
bytes32 commitHash,
bytes32 genesisStateRoot,
- address[] calldata sequencerAddresses,
- uint256 chainId
+ uint256 chainId,
+ address bridge
) public initializer {
VALIDIUM = _validium;
- // Risc0 constants
REQUIRE_RISC0_PROOF = requireRisc0Proof;
- RISC0_VERIFIER_ADDRESS = r0verifier;
-
- // SP1 constants
REQUIRE_SP1_PROOF = requireSp1Proof;
- SP1_VERIFIER_ADDRESS = sp1verifier;
-
- // TDX constants
REQUIRE_TDX_PROOF = requireTdxProof;
+
+ RISC0_VERIFIER_ADDRESS = r0verifier;
+ SP1_VERIFIER_ADDRESS = sp1verifier;
TDX_VERIFIER_ADDRESS = tdxverifier;
- // Aligned Layer constants
ALIGNED_MODE = aligned;
ALIGNEDPROOFAGGREGATOR = alignedProofAggregator;
@@ -178,34 +164,33 @@ contract OnChainProposer is
verificationKeys[commitHash][SP1_VERIFIER_ID] = sp1Vk;
verificationKeys[commitHash][RISC0_VERIFIER_ID] = risc0Vk;
- batchCommitments[0] = BatchCommitmentInfo(
- genesisStateRoot,
- bytes32(0),
- bytes32(0),
- bytes32(0),
- bytes32(0),
- 0,
- new ICommonBridge.BalanceDiff[](0),
- commitHash,
- new ICommonBridge.L2MessageRollingHash[](0)
+ BatchCommitmentInfo storage commitment = batchCommitments[0];
+ commitment.newStateRoot = genesisStateRoot;
+ commitment.blobKZGVersionedHash = bytes32(0);
+ commitment.processedPrivilegedTransactionsRollingHash = bytes32(0);
+ commitment.withdrawalsLogsMerkleRoot = bytes32(0);
+ commitment.lastBlockHash = bytes32(0);
+ commitment.nonPrivilegedTransactions = 0;
+ commitment.balanceDiffs = new ICommonBridge.BalanceDiff[](0);
+ commitment.commitHash = commitHash;
+ commitment
+ .l2InMessageRollingHashes = new ICommonBridge.L2MessageRollingHash[](
+ 0
);
- for (uint256 i = 0; i < sequencerAddresses.length; i++) {
- authorizedSequencerAddresses[sequencerAddresses[i]] = true;
- }
-
CHAIN_ID = chainId;
- OwnableUpgradeable.__Ownable_init(owner);
- }
-
- /// @inheritdoc IOnChainProposer
- function initializeBridgeAddress(address bridge) public onlyOwner {
require(
bridge != address(0),
"001" // OnChainProposer: bridge is the zero address
);
+ require(
+ bridge != address(this),
+ "000" // OnChainProposer: bridge is the contract address
+ );
BRIDGE = bridge;
+
+ OwnableUpgradeable.__Ownable_init(timelock_owner);
}
/// @inheritdoc IOnChainProposer
@@ -249,7 +234,7 @@ contract OnChainProposer is
bytes32 commitHash,
ICommonBridge.BalanceDiff[] calldata balanceDiffs,
ICommonBridge.L2MessageRollingHash[] calldata l2MessageRollingHashes
- ) external override onlySequencer whenNotPaused {
+ ) external override onlyOwner whenNotPaused {
// TODO: Refactor validation
require(
batchNumber == lastCommittedBatch + 1,
@@ -357,7 +342,7 @@ contract OnChainProposer is
//tdx
bytes calldata tdxPublicValues,
bytes memory tdxSignature
- ) external override onlySequencer whenNotPaused {
+ ) external override onlyOwner whenNotPaused {
require(
!ALIGNED_MODE,
"008" // Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead.
@@ -507,7 +492,7 @@ contract OnChainProposer is
bytes[] calldata publicInputsList,
bytes32[][] calldata sp1MerkleProofsList,
bytes32[][] calldata risc0MerkleProofsList
- ) external override onlySequencer whenNotPaused {
+ ) external override onlyOwner whenNotPaused {
require(
ALIGNED_MODE,
"00h" // Batch verification should be done via smart contract verifiers. Call verifyBatch() instead.
@@ -774,7 +759,7 @@ contract OnChainProposer is
/// @inheritdoc IOnChainProposer
function revertBatch(
uint256 batchNumber
- ) external override onlySequencer whenPaused {
+ ) external override onlyOwner whenPaused {
require(
batchNumber >= lastVerifiedBatch,
"010" // OnChainProposer: can't revert verified batch
diff --git a/crates/l2/contracts/src/l1/Timelock.sol b/crates/l2/contracts/src/l1/Timelock.sol
new file mode 100644
index 00000000000..6af92bf1f4d
--- /dev/null
+++ b/crates/l2/contracts/src/l1/Timelock.sol
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: MIT
+pragma solidity =0.8.31;
+
+import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
+import {IOnChainProposer} from "./interfaces/IOnChainProposer.sol";
+import {ICommonBridge} from "./interfaces/ICommonBridge.sol";
+import {ITimelock} from "./interfaces/ITimelock.sol";
+
+/// @title Timelock contract.
+/// @author LambdaClass
+/// @notice The Timelock contract is the owner of the OnChainProposer contract, it gates access to it by managing roles
+/// and adding delay to specific operations for some roles (e.g. updating the contract, in order to provide an exit window).
+contract Timelock is TimelockControllerUpgradeable, UUPSUpgradeable, ITimelock {
+ /// @notice Role identifier for sequencers.
+ /// @dev Accounts with this role can commit and verify batches.
+ bytes32 public constant SEQUENCER = keccak256("SEQUENCER");
+
+ /// @notice Role identifier for the Security Council.
+ /// @dev Accounts with this role can manage roles and bypass the timelock delay.
+ bytes32 public constant SECURITY_COUNCIL = keccak256("SECURITY_COUNCIL");
+
+ /// @notice The OnChainProposer contract controlled by this timelock.
+ IOnChainProposer public onChainProposer;
+
+ /// @dev Restricts calls to the timelock itself.
+ modifier onlySelf() {
+ if (msg.sender != address(this)) {
+ revert TimelockCallerNotSelf();
+ }
+ _;
+ }
+
+ /// @notice Disables the parent initialize function to prevent accidental misuse.
+ function initialize(
+ uint256,
+ address[] memory,
+ address[] memory,
+ address
+ ) public pure override(TimelockControllerUpgradeable) {
+ revert TimelockUseCustomInitialize();
+ }
+
+ /// @notice Initializes the timelock contract.
+ /// @dev Called once after proxy deployment.
+ /// @param minDelay The minimum delay (in seconds) for scheduled operations.
+ /// @param sequencers Accounts that can commit and verify batches.
+ /// @param governance The account that can propose and execute operations, respecting the delay.
+ /// @param securityCouncil The Security Council account that can manage roles and bypass the delay.
+ /// @param _onChainProposer The deployed `OnChainProposer` contract address.
+ function initialize(
+ uint256 minDelay,
+ address[] memory sequencers,
+ address governance,
+ address securityCouncil,
+ address _onChainProposer
+ ) public override(ITimelock) initializer {
+ for (uint256 i = 0; i < sequencers.length; ++i) {
+ _grantRole(SEQUENCER, sequencers[i]);
+ }
+
+ _grantRole(SECURITY_COUNCIL, securityCouncil);
+
+ address[] memory governanceAccounts = new address[](1);
+ governanceAccounts[0] = governance;
+
+ TimelockControllerUpgradeable.__TimelockController_init(
+ minDelay,
+ governanceAccounts, // proposers
+ governanceAccounts, // executors
+ securityCouncil // admin
+ );
+
+ onChainProposer = IOnChainProposer(_onChainProposer);
+ }
+
+ /// @notice Returns whether an address has the sequencer role.
+ /// @dev This matches the legacy OnChainProposer mapping used by TDXVerifier.
+ function isSequencer(address addr) external view returns (bool) {
+ return hasRole(SEQUENCER, addr);
+ }
+
+ // NOTE: In the future commit and verify will have timelock logic incorporated in case there are any zkVM bugs and we want to avoid applying the changes in the L1. Probably the Security Council would act upon those changes.
+ /// @custom:access Restricted to accounts with the `SEQUENCER` role.
+ function commitBatch(
+ uint256 batchNumber,
+ bytes32 newStateRoot,
+ bytes32 withdrawalsLogsMerkleRoot,
+ bytes32 processedPrivilegedTransactionsRollingHash,
+ bytes32 lastBlockHash,
+ uint256 nonPrivilegedTransactions,
+ bytes32 commitHash,
+ ICommonBridge.BalanceDiff[] calldata balanceDiffs,
+ ICommonBridge.L2MessageRollingHash[] calldata l2MessageRollingHashes
+ ) external onlyRole(SEQUENCER) {
+ onChainProposer.commitBatch(
+ batchNumber,
+ newStateRoot,
+ withdrawalsLogsMerkleRoot,
+ processedPrivilegedTransactionsRollingHash,
+ lastBlockHash,
+ nonPrivilegedTransactions,
+ commitHash,
+ balanceDiffs,
+ l2MessageRollingHashes
+ );
+ }
+
+ /// @custom:access Restricted to accounts with the `SEQUENCER` role.
+ function verifyBatch(
+ uint256 batchNumber,
+ bytes memory risc0BlockProof,
+ bytes calldata risc0Journal,
+ bytes calldata sp1PublicValues,
+ bytes memory sp1ProofBytes,
+ bytes calldata tdxPublicValues,
+ bytes memory tdxSignature
+ ) external onlyRole(SEQUENCER) {
+ onChainProposer.verifyBatch(
+ batchNumber,
+ risc0BlockProof,
+ risc0Journal,
+ sp1PublicValues,
+ sp1ProofBytes,
+ tdxPublicValues,
+ tdxSignature
+ );
+ }
+
+ /// @custom:access Restricted to accounts with the `SEQUENCER` role.
+ function verifyBatchesAligned(
+ uint256 firstBatchNumber,
+ bytes[] calldata publicInputsList,
+ bytes32[][] calldata sp1MerkleProofsList,
+ bytes32[][] calldata risc0MerkleProofsList
+ ) external onlyRole(SEQUENCER) {
+ onChainProposer.verifyBatchesAligned(
+ firstBatchNumber,
+ publicInputsList,
+ sp1MerkleProofsList,
+ risc0MerkleProofsList
+ );
+ }
+
+ /// @notice Executes an operation immediately, bypassing the timelock delay.
+ /// @dev Intended for emergency use by the Security Council.
+ /// @param target The address to call.
+ /// @param value The ETH value to send with the call.
+ /// @param data The calldata to forward to `target`.
+ function emergencyExecute(
+ address target,
+ uint256 value,
+ bytes calldata data
+ ) external payable onlyRole(SECURITY_COUNCIL) {
+ _execute(target, value, data);
+ emit EmergencyExecution(target, value, data);
+ }
+
+ /// @notice Allow timelock itself to upgrade the contract in order to respect min time.
+ /// @param newImplementation the address of the new implementation
+ function _authorizeUpgrade(
+ address newImplementation
+ ) internal virtual override onlySelf {}
+}
diff --git a/crates/l2/contracts/src/l1/based/OnChainProposer.sol b/crates/l2/contracts/src/l1/based/OnChainProposer.sol
index f9905d5a0dd..cd78131f0d3 100644
--- a/crates/l2/contracts/src/l1/based/OnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/based/OnChainProposer.sol
@@ -135,7 +135,8 @@ contract OnChainProposer is
bytes32 commitHash,
bytes32 genesisStateRoot,
address sequencer_registry,
- uint256 chainId
+ uint256 chainId,
+ address bridge
) public initializer {
VALIDIUM = _validium;
@@ -199,16 +200,6 @@ contract OnChainProposer is
OwnableUpgradeable.__Ownable_init(owner);
- emit VerificationKeyUpgraded("SP1", commitHash, sp1Vk);
- emit VerificationKeyUpgraded("RISC0", commitHash, risc0Vk);
- }
-
- /// @inheritdoc IOnChainProposer
- function initializeBridgeAddress(address bridge) public onlyOwner {
- require(
- BRIDGE == address(0),
- "OnChainProposer: bridge already initialized"
- );
require(
bridge != address(0),
"OnChainProposer: bridge is the zero address"
@@ -218,6 +209,9 @@ contract OnChainProposer is
"OnChainProposer: bridge is the contract address"
);
BRIDGE = bridge;
+
+ emit VerificationKeyUpgraded("SP1", commitHash, sp1Vk);
+ emit VerificationKeyUpgraded("RISC0", commitHash, risc0Vk);
}
/// @inheritdoc IOnChainProposer
diff --git a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol
index ee3b78bb6b0..74483b61e0e 100644
--- a/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/based/interfaces/IOnChainProposer.sol
@@ -34,13 +34,6 @@ interface IOnChainProposer {
bytes32 newVerificationKey
);
- /// @notice Set the bridge address for the first time.
- /// @dev This method is separated from initialize because both the CommonBridge
- /// and the OnChainProposer need to know the address of the other. This solves
- /// the circular dependency while allowing to initialize the proxy with the deploy.
- /// @param bridge the address of the bridge contract.
- function initializeBridgeAddress(address bridge) external;
-
/// @notice Upgrades the SP1 verification key that represents the sequencer's code.
/// @param commitHash git commit hash that produced the verifier keys for this batch.
/// @param new_vk new verification key for SP1 verifier
diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
index 51d12e32fea..6d4ebec4b23 100644
--- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
@@ -40,13 +40,6 @@ interface IOnChainProposer {
bytes32 newVerificationKey
);
- /// @notice Set the bridge address for the first time.
- /// @dev This method is separated from initialize because both the CommonBridge
- /// and the OnChainProposer need to know the address of the other. This solves
- /// the circular dependency while allowing to initialize the proxy with the deploy.
- /// @param bridge the address of the bridge contract.
- function initializeBridgeAddress(address bridge) external;
-
/// @notice Upgrades the SP1 verification key that represents the sequencer's code.
/// @param new_vk new verification key for SP1 verifier
/// @param commit_hash git commit hash that produced the new verification key
diff --git a/crates/l2/contracts/src/l1/interfaces/ITimelock.sol b/crates/l2/contracts/src/l1/interfaces/ITimelock.sol
new file mode 100644
index 00000000000..7679aacbcf2
--- /dev/null
+++ b/crates/l2/contracts/src/l1/interfaces/ITimelock.sol
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: MIT
+pragma solidity =0.8.31;
+
+import {IOnChainProposer} from "./IOnChainProposer.sol";
+import {ICommonBridge} from "./ICommonBridge.sol";
+
+/// @title Interface for the Timelock contract.
+/// @author LambdaClass
+/// @notice Timelock owner for OnChainProposer, manages roles and forwards sequencer actions.
+interface ITimelock {
+ /// @notice Emitted when the Security Council executes a call bypassing the delay.
+ /// @param target The address that was called.
+ /// @param value The ETH value that was sent.
+ /// @param data The calldata that was forwarded to `target`.
+ event EmergencyExecution(address indexed target, uint256 value, bytes data);
+
+ // @notice Used for functions that can only be called by the Timelock itself.
+ error TimelockCallerNotSelf();
+
+ // @notice Used for other initialize() from contracts that the Timelock inherits from
+ error TimelockUseCustomInitialize();
+
+ /// @notice The OnChainProposer contract controlled by this timelock.
+ function onChainProposer() external view returns (IOnChainProposer);
+
+ /// @notice Initializes the timelock contract.
+ /// @dev Called once after proxy deployment.
+ /// @param minDelay The minimum delay (in seconds) for scheduled operations.
+ /// @param sequencers Accounts that can commit and verify batches.
+ /// @param governance The account that can propose and execute operations, respecting the delay.
+ /// @param securityCouncil The Security Council account that can manage roles and bypass the delay.
+ /// @param _onChainProposer The deployed `OnChainProposer` contract address.
+ function initialize(
+ uint256 minDelay,
+ address[] memory sequencers,
+ address governance,
+ address securityCouncil,
+ address _onChainProposer
+ ) external;
+
+ /// @notice Returns whether an address has the sequencer role.
+ function isSequencer(address addr) external view returns (bool);
+
+ /// @notice Commits a batch through the timelock.
+ function commitBatch(
+ uint256 batchNumber,
+ bytes32 newStateRoot,
+ bytes32 withdrawalsLogsMerkleRoot,
+ bytes32 processedPrivilegedTransactionsRollingHash,
+ bytes32 lastBlockHash,
+ uint256 nonPrivilegedTransactions,
+ bytes32 commitHash,
+ ICommonBridge.BalanceDiff[] calldata balanceDiffs,
+ ICommonBridge.L2MessageRollingHash[] calldata l2MessageRollingHashes
+ ) external;
+
+ /// @notice Verifies a single batch through the timelock.
+ function verifyBatch(
+ uint256 batchNumber,
+ bytes memory risc0BlockProof,
+ bytes calldata risc0Journal,
+ bytes calldata sp1PublicValues,
+ bytes memory sp1ProofBytes,
+ bytes calldata tdxPublicValues,
+ bytes memory tdxSignature
+ ) external;
+
+ /// @notice Verifies multiple batches through the timelock using aligned proofs.
+ function verifyBatchesAligned(
+ uint256 firstBatchNumber,
+ bytes[] calldata publicInputsList,
+ bytes32[][] calldata sp1MerkleProofsList,
+ bytes32[][] calldata risc0MerkleProofsList
+ ) external;
+
+ /// @notice Executes an operation immediately, bypassing the timelock delay.
+ function emergencyExecute(
+ address target,
+ uint256 value,
+ bytes calldata data
+ ) external payable;
+}
diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs
index 349afaf97a7..537175c3fcf 100644
--- a/crates/l2/sdk/src/sdk.rs
+++ b/crates/l2/sdk/src/sdk.rs
@@ -388,7 +388,7 @@ pub const CREATE2DEPLOYER_ADDRESS: Address = H160([
0x1a, 0xe9, 0xbe, 0xf2,
]);
-#[derive(Default)]
+#[derive(Default, Clone)]
pub struct ProxyDeployment {
pub proxy_address: Address,
pub proxy_tx_hash: H256,
diff --git a/crates/l2/sequencer/configs.rs b/crates/l2/sequencer/configs.rs
index 159ae164239..f641bfffa14 100644
--- a/crates/l2/sequencer/configs.rs
+++ b/crates/l2/sequencer/configs.rs
@@ -32,6 +32,7 @@ pub struct BlockProducerConfig {
#[derive(Clone, Debug)]
pub struct CommitterConfig {
pub on_chain_proposer_address: Address,
+ pub timelock_address: Option,
pub first_wake_up_time_ms: u64,
pub commit_time_ms: u64,
pub batch_gas_limit: Option,
diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs
index bebde0f3f77..684da2e4bb6 100644
--- a/crates/l2/sequencer/l1_committer.rs
+++ b/crates/l2/sequencer/l1_committer.rs
@@ -101,6 +101,7 @@ pub struct L1Committer {
eth_client: EthClient,
blockchain: Arc,
on_chain_proposer_address: Address,
+ timelock_address: Option,
store: Store,
rollup_store: StoreRollup,
commit_time_ms: u64,
@@ -192,6 +193,7 @@ impl L1Committer {
eth_client,
blockchain,
on_chain_proposer_address: committer_config.on_chain_proposer_address,
+ timelock_address: committer_config.timelock_address,
store,
rollup_store,
commit_time_ms: committer_config.commit_time_ms,
@@ -1184,6 +1186,15 @@ impl L1Committer {
CommitterError::ConversionError("Failed to convert gas_price to a u64".to_owned())
})?;
+ let target_address = if !self.based {
+ self.timelock_address
+ .ok_or(CommitterError::UnexpectedError(
+ "Timelock address is not set".to_string(),
+ ))?
+ } else {
+ self.on_chain_proposer_address
+ };
+
// Validium: EIP1559 Transaction.
// Rollup: EIP4844 Transaction -> For on-chain Data Availability.
let tx = if !self.validium {
@@ -1201,7 +1212,7 @@ impl L1Committer {
build_generic_tx(
&self.eth_client,
TxType::EIP4844,
- self.on_chain_proposer_address,
+ target_address,
self.signer.address(),
calldata.into(),
Overrides {
@@ -1221,7 +1232,7 @@ impl L1Committer {
build_generic_tx(
&self.eth_client,
TxType::EIP1559,
- self.on_chain_proposer_address,
+ target_address,
self.signer.address(),
calldata.into(),
Overrides {
diff --git a/crates/l2/sequencer/l1_proof_sender.rs b/crates/l2/sequencer/l1_proof_sender.rs
index d68a220b932..9098e079a18 100644
--- a/crates/l2/sequencer/l1_proof_sender.rs
+++ b/crates/l2/sequencer/l1_proof_sender.rs
@@ -68,6 +68,7 @@ pub struct L1ProofSender {
eth_client: EthClient,
signer: ethrex_l2_rpc::signer::Signer,
on_chain_proposer_address: Address,
+ timelock_address: Option,
needed_proof_types: Vec,
proof_send_interval_ms: u64,
sequencer_state: SequencerState,
@@ -124,6 +125,7 @@ impl L1ProofSender {
eth_client,
signer: cfg.signer.clone(),
on_chain_proposer_address: committer_cfg.on_chain_proposer_address,
+ timelock_address: committer_cfg.timelock_address,
needed_proof_types,
proof_send_interval_ms: cfg.proof_send_interval_ms,
sequencer_state,
@@ -397,13 +399,13 @@ impl L1ProofSender {
let calldata = encode_calldata(VERIFY_FUNCTION_SIGNATURE, &calldata_values)?;
- let send_verify_tx_result = send_verify_tx(
- calldata,
- &self.eth_client,
- self.on_chain_proposer_address,
- &self.signer,
- )
- .await;
+ // Based won't have timelock address until we implement it on it. For the meantime if it's None (only happens in based) we use the OCP
+ let target_address = self
+ .timelock_address
+ .unwrap_or(self.on_chain_proposer_address);
+
+ let send_verify_tx_result =
+ send_verify_tx(calldata, &self.eth_client, target_address, &self.signer).await;
if let Err(EthClientError::EstimateGasError(EstimateGasError::RPCError(error))) =
send_verify_tx_result.as_ref()
diff --git a/crates/l2/sequencer/l1_proof_verifier.rs b/crates/l2/sequencer/l1_proof_verifier.rs
index 6c5d2192bf1..4621fb870f1 100644
--- a/crates/l2/sequencer/l1_proof_verifier.rs
+++ b/crates/l2/sequencer/l1_proof_verifier.rs
@@ -59,6 +59,7 @@ struct L1ProofVerifier {
beacon_urls: Vec,
l1_signer: Signer,
on_chain_proposer_address: Address,
+ timelock_address: Option,
proof_verify_interval_ms: u64,
network: Network,
rollup_store: StoreRollup,
@@ -96,6 +97,7 @@ impl L1ProofVerifier {
network: aligned_cfg.network.clone(),
l1_signer: proof_coordinator_cfg.signer,
on_chain_proposer_address: committer_cfg.on_chain_proposer_address,
+ timelock_address: committer_cfg.timelock_address,
proof_verify_interval_ms: aligned_cfg.aligned_verifier_interval_ms,
rollup_store,
sp1_vk,
@@ -238,13 +240,13 @@ impl L1ProofVerifier {
let calldata = encode_calldata(ALIGNED_VERIFY_FUNCTION_SIGNATURE, &calldata_values)?;
- let send_verify_tx_result = send_verify_tx(
- calldata,
- &self.eth_client,
- self.on_chain_proposer_address,
- &self.l1_signer,
- )
- .await;
+ // Based won't have timelock address until we implement it on it. For the meantime if it's None (only happens in based) we use the OCP
+ let target_address = self
+ .timelock_address
+ .unwrap_or(self.on_chain_proposer_address);
+
+ let send_verify_tx_result =
+ send_verify_tx(calldata, &self.eth_client, target_address, &self.l1_signer).await;
if let Err(EthClientError::EstimateGasError(EstimateGasError::RPCError(error))) =
send_verify_tx_result.as_ref()
diff --git a/crates/l2/tee/contracts/Makefile b/crates/l2/tee/contracts/Makefile
index b6d6afe09e7..2755c193e9e 100644
--- a/crates/l2/tee/contracts/Makefile
+++ b/crates/l2/tee/contracts/Makefile
@@ -42,7 +42,7 @@ lib/openzeppelin-contracts:
solc_out/TDXVerifier.bin: src/TDXVerifier.sol lib/openzeppelin-contracts
mkdir -p solc_out
- solc src/TDXVerifier.sol --bin --allow-paths lib/ -o solc_out/ --overwrite
+ solc src/TDXVerifier.sol --bin --allow-paths lib/,../../.. --base-path . --include-path ../../ @openzeppelin/=lib/openzeppelin-contracts/ -o solc_out/ --overwrite
deploy: solc_out/TDXVerifier.bin
$(eval CONTRACT_BIN := $(shell cat solc_out/TDXVerifier.bin))
diff --git a/crates/l2/tee/contracts/src/TDXVerifier.sol b/crates/l2/tee/contracts/src/TDXVerifier.sol
index c8f9bdac9fe..e752271ffa4 100644
--- a/crates/l2/tee/contracts/src/TDXVerifier.sol
+++ b/crates/l2/tee/contracts/src/TDXVerifier.sol
@@ -1,42 +1,45 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
-import "../lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
-import "../lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
+import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
+import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
+import {ITimelock} from "../../../contracts/src/l1/interfaces/ITimelock.sol";
interface IAttestation {
- function verifyAndAttestOnChain(bytes calldata rawQuote)
- external
- payable
- returns (bool success, bytes memory output);
-}
-
-interface IOnChainProposer {
- function authorizedSequencerAddresses(address addr) external returns (bool isAuthorized);
+ function verifyAndAttestOnChain(
+ bytes calldata rawQuote
+ ) external payable returns (bool success, bytes memory output);
}
contract TDXVerifier {
IAttestation public quoteVerifier = IAttestation(address(0));
- IOnChainProposer public onChainProposer = IOnChainProposer(address(0));
+ ITimelock public timelock = ITimelock(address(0));
address public authorizedSignature = address(0);
bool public isDevMode = false;
- bytes public RTMR0 = hex'4f3d617a1c89bd9a89ea146c15b04383b7db7318f41a851802bba8eace5a6cf71050e65f65fd50176e4f006764a42643';
- bytes public RTMR1 = hex'53827a034d1e4c7f13fd2a12aee4497e7097f15a04794553e12fe73e2ffb8bd57585e771951115a13ec4d7e6bc193038';
- bytes public RTMR2 = hex'2ca1a728ff13c36195ad95e8f725bf00d7f9c5d6ed730fb8f50cccad692ab81aefc83d594819375649be934022573528';
- bytes public MRTD = hex'91eb2b44d141d4ece09f0c75c2c53d247a3c68edd7fafe8a3520c942a604a407de03ae6dc5f87f27428b2538873118b7';
+ bytes public RTMR0 =
+ hex"4f3d617a1c89bd9a89ea146c15b04383b7db7318f41a851802bba8eace5a6cf71050e65f65fd50176e4f006764a42643";
+ bytes public RTMR1 =
+ hex"53827a034d1e4c7f13fd2a12aee4497e7097f15a04794553e12fe73e2ffb8bd57585e771951115a13ec4d7e6bc193038";
+ bytes public RTMR2 =
+ hex"2ca1a728ff13c36195ad95e8f725bf00d7f9c5d6ed730fb8f50cccad692ab81aefc83d594819375649be934022573528";
+ bytes public MRTD =
+ hex"91eb2b44d141d4ece09f0c75c2c53d247a3c68edd7fafe8a3520c942a604a407de03ae6dc5f87f27428b2538873118b7";
/// @notice Initializes the contract
/// @param _dcap DCAP contract.
- /// @param _ocp OnChainProposer contract, used for permission checks
+ /// @param _timelock Timelock contract, used for permission checks
/// @param _isDevMode Disables quote verification
- constructor(address _dcap, address _ocp, bool _isDevMode) {
+ constructor(address _dcap, address _timelock, bool _isDevMode) {
require(_dcap != address(0), "TDXVerifier: DCAP address can't be null");
- require(_ocp != address(0), "TDXVerifier: OnChainPropser address can't be null");
+ require(
+ _timelock != address(0),
+ "TDXVerifier: Timelock address can't be null"
+ );
quoteVerifier = IAttestation(_dcap);
- onChainProposer = IOnChainProposer(_ocp);
+ timelock = ITimelock(_timelock);
isDevMode = _isDevMode;
}
@@ -48,19 +51,23 @@ contract TDXVerifier {
bytes calldata payload,
bytes memory signature
) external view {
- require(authorizedSignature != address(0), "TDXVerifier: authorized signer not registered");
+ require(
+ authorizedSignature != address(0),
+ "TDXVerifier: authorized signer not registered"
+ );
bytes32 signedHash = MessageHashUtils.toEthSignedMessageHash(payload);
- require(ECDSA.recover(signedHash, signature) == authorizedSignature, "TDXVerifier: invalid signature");
+ require(
+ ECDSA.recover(signedHash, signature) == authorizedSignature,
+ "TDXVerifier: invalid signature"
+ );
}
/// @notice Registers the quote
/// @dev The data required to verify the quote must be loaded to the PCCS contracts beforehand
/// @param quote The TDX quote, which includes the address being registered
- function register(
- bytes calldata quote
- ) external {
+ function register(bytes calldata quote) external {
require(
- onChainProposer.authorizedSequencerAddresses(msg.sender),
+ timelock.isSequencer(msg.sender),
"TDXVerifier: only sequencer can update keys"
);
// TODO: only allow the owner to update the key, to avoid DoS
@@ -68,25 +75,44 @@ contract TDXVerifier {
authorizedSignature = _getAddress(quote, 0);
return;
}
- (bool success, bytes memory report) = quoteVerifier.verifyAndAttestOnChain(quote);
+ (bool success, bytes memory report) = quoteVerifier
+ .verifyAndAttestOnChain(quote);
require(success, "TDXVerifier: quote verification failed");
_validateReport(report);
authorizedSignature = _getAddress(report, 533);
}
- function _validateReport(bytes memory report) view internal {
- require(_rangeEquals(report, 0, hex'0004'), "TDXVerifier: Unsupported quote version");
+ function _validateReport(bytes memory report) internal view {
+ require(
+ _rangeEquals(report, 0, hex"0004"),
+ "TDXVerifier: Unsupported quote version"
+ );
require(report[2] == 0x81, "TDXVerifier: Quote is not of type TDX");
require(report[6] == 0, "TDXVerifier: TCB_STATUS != OK");
- require(uint8(report[133]) & 15 == 0, "TDXVerifier: debug attributes are set");
+ require(
+ uint8(report[133]) & 15 == 0,
+ "TDXVerifier: debug attributes are set"
+ );
require(_rangeEquals(report, 149, MRTD), "TDXVerifier: MRTD mismatch");
- require(_rangeEquals(report, 341, RTMR0), "TDXVerifier: RTMR0 mismatch");
- require(_rangeEquals(report, 389, RTMR1), "TDXVerifier: RTMR1 mismatch");
- require(_rangeEquals(report, 437, RTMR2), "TDXVerifier: RTMR2 mismatch");
+ require(
+ _rangeEquals(report, 341, RTMR0),
+ "TDXVerifier: RTMR0 mismatch"
+ );
+ require(
+ _rangeEquals(report, 389, RTMR1),
+ "TDXVerifier: RTMR1 mismatch"
+ );
+ require(
+ _rangeEquals(report, 437, RTMR2),
+ "TDXVerifier: RTMR2 mismatch"
+ );
// RTMR3 is ignored
}
- function _getAddress(bytes memory report, uint256 offset) pure public returns (address) {
+ function _getAddress(
+ bytes memory report,
+ uint256 offset
+ ) public pure returns (address) {
uint256 addr;
for (uint8 i = 0; i < 20; i++) {
addr = (addr << 8) | uint8(report[offset + i]);
@@ -94,7 +120,11 @@ contract TDXVerifier {
return address(uint160(addr));
}
- function _rangeEquals(bytes memory report, uint256 offset, bytes memory other) pure internal returns (bool) {
+ function _rangeEquals(
+ bytes memory report,
+ uint256 offset,
+ bytes memory other
+ ) internal pure returns (bool) {
for (uint256 i; i < other.length; i++) {
if (report[offset + i] != other[i]) return false;
}