Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion script/Interactions.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
pragma solidity 0.8.28;

import {Script, console} from "forge-std/Script.sol";
import {BlockBuilderPolicy, WorkloadId} from "../src/BlockBuilderPolicy.sol";
import {BlockBuilderPolicy} from "../src/BlockBuilderPolicy.sol";
import {WorkloadId} from "../src/interfaces/IBlockBuilderPolicy.sol";
import {FlashtestationRegistry} from "../src/FlashtestationRegistry.sol";
import {IFlashtestationRegistry} from "../src/interfaces/IFlashtestationRegistry.sol";
import {DeploymentUtils} from "./utils/DeploymentUtils.sol";
Expand Down
195 changes: 50 additions & 145 deletions src/BlockBuilderPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,8 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {FlashtestationRegistry} from "./FlashtestationRegistry.sol";

/// @notice WorkloadID uniquely identifies a TEE workload. A workload is roughly equivalent to a version of an application's
/// code, can be reproduced from source code, and is derived from a combination of the TEE's measurement registers.
/// The TDX platform provides several registers that capture cryptographic hashes of code, data, and configuration
/// loaded into the TEE's environment. This means that whenever a TEE device changes anything about its compute stack
/// (e.g. user code, firmware, OS, etc), the workloadID will change.
/// See the [Flashtestation's specification](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#workload-identity-derivation) for more details
type WorkloadId is bytes32;

/**
* @notice Metadata associated with a workload
* @dev Used to track the source code used to build the TEE image identified by the workloadId
*/
struct WorkloadMetadata {
/// @notice The Git commit hash of the source code repository
string commitHash;
/// @notice An array of URLs pointing to the source code repository
string[] sourceLocators;
}
import {IFlashtestationRegistry} from "./interfaces/IFlashtestationRegistry.sol";
import {IBlockBuilderPolicy, WorkloadId} from "./interfaces/IBlockBuilderPolicy.sol";

/**
* @notice Cached workload information for gas optimization
Expand All @@ -49,12 +32,18 @@ struct CachedWorkload {
* changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address
* is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest
*/
contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeable, EIP712Upgradeable {
contract BlockBuilderPolicy is
Initializable,
UUPSUpgradeable,
OwnableUpgradeable,
EIP712Upgradeable,
IBlockBuilderPolicy
{
using ECDSA for bytes32;

// ============ EIP-712 Constants ============

/// @notice EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function
/// @inheritdoc IBlockBuilderPolicy
bytes32 public constant VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH =
keccak256("VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)");

Expand All @@ -79,14 +68,14 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
/// @notice Mapping from workloadId to its metadata (commit hash and source locators)
/// @dev This is only updateable by governance (i.e. the owner) of the Policy contract
/// Adding and removing a workload is O(1).
/// This means the critical `isAllowedPolicy` function is O(1) since we can directly check if a workloadId exists
/// This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists
/// in the mapping
mapping(bytes32 => WorkloadMetadata) public approvedWorkloads;
mapping(bytes32 => WorkloadMetadata) private approvedWorkloads;

/// @notice Address of the FlashtestationRegistry contract that verifies TEE quotes
/// @inheritdoc IBlockBuilderPolicy
address public registry;

/// @notice Tracks nonces for EIP-712 signatures to prevent replay attacks
/// @inheritdoc IBlockBuilderPolicy
mapping(address => uint256) public nonces;

/// @notice Cache of computed workloadIds to avoid expensive recomputation
Expand All @@ -97,43 +86,8 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
/// @dev This reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads)
uint256[46] __gap;

// ============ Errors ============

error InvalidRegistry();
error WorkloadAlreadyInPolicy();
error WorkloadNotInPolicy();
error UnauthorizedBlockBuilder(address caller); // the teeAddress is not associated with a valid TEE workload
error InvalidNonce(uint256 expected, uint256 provided);
error EmptyCommitHash();
error EmptySourceLocators();

// ============ Events ============

event WorkloadAddedToPolicy(WorkloadId workloadId);
event WorkloadRemovedFromPolicy(WorkloadId workloadId);
event RegistrySet(address registry);
/// @notice Emitted when a block builder proof is successfully verified
/// @param caller The address that called the verification function (TEE address)
/// @param workloadId The workload identifier of the TEE
/// @param blockNumber The block number when the verification occurred
/// @param version The flashtestation protocol version used
/// @param blockContentHash The hash of the block content
/// @param commitHash The git commit hash associated with the workload
event BlockBuilderProofVerified(
address caller,
WorkloadId workloadId,
uint256 blockNumber,
uint8 version,
bytes32 blockContentHash,
string commitHash
);

/**
* @notice Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract
* @param _initialOwner The address of the initial owner of the contract
* @param _registry The address of the registry contract
*/
function initialize(address _initialOwner, address _registry) external initializer {
/// @inheritdoc IBlockBuilderPolicy
function initialize(address _initialOwner, address _registry) external override initializer {
__Ownable_init(_initialOwner);
__EIP712_init("BlockBuilderPolicy", "1");
require(_registry != address(0), InvalidRegistry());
Expand All @@ -146,37 +100,18 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
/// @param newImplementation The address of the new implementation contract
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

/// @notice Verify a block builder proof with a Flashtestation Transaction
/// @param version The version of the flashtestation's protocol used to generate the block builder proof
/// @param blockContentHash The hash of the block content
/// @notice This function will only succeed if the caller is a registered TEE-controlled address from an attested TEE
/// and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`)
/// @notice The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version.
/// See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/77fc19f785eeeb9b4eb5fb08463bc556dec2c837/specs/flashtestations.md) for more details
/// @dev If you do not want to deal with the operational difficulties of keeping your TEE-controlled
/// addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs
/// more gas, but allows any EOA to submit a block builder proof on behalf of a TEE
function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external {
/// @inheritdoc IBlockBuilderPolicy
function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external override {
_verifyBlockBuilderProof(msg.sender, version, blockContentHash);
}

/// @notice Verify a block builder proof with a Flashtestation Transaction using EIP-712 signatures
/// @notice This function allows any EOA to submit a block builder proof on behalf of a TEE
/// @notice The TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address
/// whose associated workload is approved under this policy
/// @dev This function is useful if you do not want to deal with the operational difficulties of keeping your
/// TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost
/// more gas than the non-EIP-712 verifyBlockBuilderProof function
/// @param version The version of the flashtestation's protocol used to generate the block builder proof
/// @param blockContentHash The hash of the block content
/// @param nonce The nonce to use for the EIP-712 signature
/// @param eip712Sig The EIP-712 signature of the verification message
/// @inheritdoc IBlockBuilderPolicy
function permitVerifyBlockBuilderProof(
uint8 version,
bytes32 blockContentHash,
uint256 nonce,
bytes calldata eip712Sig
) external {
) external override {
// Get the TEE address from the signature
bytes32 digest = getHashedTypeDataV4(computeStructHash(version, blockContentHash, nonce));
address teeAddress = digest.recover(eip712Sig);
Expand Down Expand Up @@ -211,19 +146,15 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
// onchain. We rely on the TEE workload to correctly compute this hash according to the
// specified version of the calculation method.

string memory commitHash = approvedWorkloads[WorkloadId.unwrap(workloadId)].commitHash;
emit BlockBuilderProofVerified(teeAddress, workloadId, block.number, version, blockContentHash, commitHash);
bytes32 workloadKey = WorkloadId.unwrap(workloadId);
string memory commitHash = approvedWorkloads[workloadKey].commitHash;
emit BlockBuilderProofVerified(teeAddress, workloadKey, block.number, version, blockContentHash, commitHash);
}

/// @notice Check if this TEE-controlled address has registered a valid TEE workload with the registry, and
/// if the workload is approved under this policy
/// @param teeAddress The TEE-controlled address
/// @return allowed True if the TEE is using an approved workload in the policy
/// @return workloadId The workloadId of the TEE that is using an approved workload in the policy, or 0 if
/// the TEE is not using an approved workload in the policy
function isAllowedPolicy(address teeAddress) public view returns (bool allowed, WorkloadId) {
/// @inheritdoc IBlockBuilderPolicy
function isAllowedPolicy(address teeAddress) public view override returns (bool allowed, WorkloadId) {
// Get full registration data and compute workload ID
(, FlashtestationRegistry.RegisteredTEE memory registration) =
(, IFlashtestationRegistry.RegisteredTEE memory registration) =
FlashtestationRegistry(registry).getRegistration(teeAddress);

// Invalid Registrations means the attestation used to register the TEE is no longer valid
Expand Down Expand Up @@ -288,14 +219,11 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
}
}

/// @notice Application specific mapping of registration data to a workload identifier
/// @dev Think of the workload identifier as the version of the application for governance.
/// The workloadId verifiably maps to a version of source code that builds the TEE VM image
/// @param registration The registration data from a TEE device
/// @return The computed workload identifier
function workloadIdForTDRegistration(FlashtestationRegistry.RegisteredTEE memory registration)
/// @inheritdoc IBlockBuilderPolicy
function workloadIdForTDRegistration(IFlashtestationRegistry.RegisteredTEE memory registration)
public
pure
override
returns (WorkloadId)
{
// We expect FPU and SSE xfam bits to be set, and anything else should be handled by explicitly allowing the workloadid
Expand All @@ -321,29 +249,10 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
);
}

/// @notice Add a workload to a policy (governance only)
/// @notice Only the owner of this contract can add workloads to the policy
/// and it is the responsibility of the owner to ensure that the workload is valid
/// otherwise the address associated with this workload has full power to do anything
/// who's authorization is based on this policy
/// @dev The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy
/// to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance,
/// op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead
/// we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever
/// it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so
/// we can also assume the owner will not add a commit hash that is not associated with the workloadId. If
/// the owner did act maliciously, this can easily be determined offchain by an honest actor building the
/// TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the
/// workloadId stored on the policy that is associated with the commit hash. If the workloadId is different,
/// this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a
/// source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image
/// identified by the workloadId.
/// @param workloadId The workload identifier
/// @param commitHash The 40-character hexadecimal commit hash of the git repository
/// whose source code is used to build the TEE image identified by the workloadId
/// @param sourceLocators An array of URIs pointing to the source code
/// @inheritdoc IBlockBuilderPolicy
function addWorkloadToPolicy(WorkloadId workloadId, string calldata commitHash, string[] calldata sourceLocators)
external
override
onlyOwner
{
require(bytes(commitHash).length > 0, EmptyCommitHash());
Expand All @@ -357,12 +266,11 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
// Store the workload metadata
approvedWorkloads[workloadKey] = WorkloadMetadata({commitHash: commitHash, sourceLocators: sourceLocators});

emit WorkloadAddedToPolicy(workloadId);
emit WorkloadAddedToPolicy(workloadKey);
}

/// @notice Remove a workload from a policy (governance only)
/// @param workloadId The workload identifier
function removeWorkloadFromPolicy(WorkloadId workloadId) external onlyOwner {
/// @inheritdoc IBlockBuilderPolicy
function removeWorkloadFromPolicy(WorkloadId workloadId) external override onlyOwner {
bytes32 workloadKey = WorkloadId.unwrap(workloadId);

// Check if workload exists
Expand All @@ -371,39 +279,36 @@ contract BlockBuilderPolicy is Initializable, UUPSUpgradeable, OwnableUpgradeabl
// Remove the workload metadata
delete approvedWorkloads[workloadKey];

emit WorkloadRemovedFromPolicy(workloadId);
emit WorkloadRemovedFromPolicy(workloadKey);
}

/// @notice Get the metadata for a workload
/// @param workloadId The workload identifier to query
/// @return The metadata associated with the workload
function getWorkloadMetadata(WorkloadId workloadId) external view returns (WorkloadMetadata memory) {
/// @inheritdoc IBlockBuilderPolicy
function getWorkloadMetadata(WorkloadId workloadId) external view override returns (WorkloadMetadata memory) {
return approvedWorkloads[WorkloadId.unwrap(workloadId)];
}

/// @notice Computes the digest for the EIP-712 signature
/// @param structHash The struct hash for the EIP-712 signature
/// @return The digest for the EIP-712 signature
/// @inheritdoc IBlockBuilderPolicy
function getHashedTypeDataV4(bytes32 structHash) public view returns (bytes32) {
return _hashTypedDataV4(structHash);
}

/// @notice Computes the struct hash for the EIP-712 signature
/// @param version The version of the flashtestation's protocol
/// @param blockContentHash The hash of the block content
/// @param nonce The nonce to use for the EIP-712 signature
/// @return The struct hash for the EIP-712 signature
/// @inheritdoc IBlockBuilderPolicy
function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce) public pure returns (bytes32) {
return keccak256(abi.encode(VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH, version, blockContentHash, nonce));
}

/**
* @notice Returns the domain separator for the EIP-712 signature
* @dev This is useful for when both onchain and offchain users want to compute the domain separator
* for the EIP-712 signature, and then use it to verify the signature
* @return The domain separator for the EIP-712 signature
*/
/// @inheritdoc IBlockBuilderPolicy
function domainSeparator() external view returns (bytes32) {
return _domainSeparatorV4();
}

/// @inheritdoc IBlockBuilderPolicy
function getApprovedWorkloads(bytes32 workloadId)
external
view
override
returns (string memory commitHash, string[] memory sourceLocators)
{
return (approvedWorkloads[workloadId].commitHash, approvedWorkloads[workloadId].sourceLocators);
}
}
Loading