Skip to content

Commit fe80792

Browse files
committed
first draft of AllowList.registerTEEService function
1 parent b2b7cfc commit fe80792

File tree

7 files changed

+227
-191
lines changed

7 files changed

+227
-191
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@ docs/
1212

1313
# Dotenv file
1414
.env
15+
16+
# Cursor.ai rules
17+
.cursor/

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,16 @@ This will perform a simple test to see if onchain verification of a tdx attestat
5050

5151
## TODOs
5252

53-
- [] Implement TEE Device Registry
53+
- [X] Implement TEE Device Registry
5454
- [] Implement Flashtestation transaction verification
55-
- [] Implement TEE Device Deregistration
5655

5756
## Open Questions
5857
- Should it be Upgradeable?
59-
Pros:
60-
Cons:
61-
Should we use Automata's DAO contracts for endorsement/collateral management, or implement our own?
62-
Pros:
58+
Pros:
59+
- very simple to account for changes to the Automata DCAP Attestation contract, contract bugs, contract upgrades
60+
- Doesn't really impact the trust model, because we already expect to have some Security Council of Unichain + Flashbots in the beginning that manages which workloadIDs to trust (via the setting of Policies)
6361
Cons:
62+
- trust model now relies on owner (probably a security council of Unichain + Flashbots) to remain not collude. If they do collude, they can upgrade the contract to emit a malicious `Registered` event and trick users into incorrectly trusting that blocks are being verified by a trusted TEE
6463

6564

6665
## Foundry

foundry.toml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ gas_limit = "3000000000"
1111
fuzz_runs = 10_000
1212
bytecode_hash = "none"
1313

14-
additional_compiler_profiles = [
15-
{ name = "test", via_ir = false }
16-
]
17-
18-
compilation_restrictions = [
19-
{ paths = "test/**", via_ir = false }
20-
]
21-
2214
[profile.debug]
2315
via_ir = false
2416
optimizer_runs = 200

script/AllowList.s.sol

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import {TD10ReportBody} from "automata-dcap-attestation/contracts/types/V4Struct
88
import {TD_REPORT10_LENGTH} from "automata-dcap-attestation/contracts/types/Constants.sol";
99

1010
contract AllowListScript is Script {
11-
AllowList public registry;
12-
AutomataDcapAttestationFee public attestationFee;
11+
AllowList public allowlist;
1312

1413
function setUp() public {}
1514

@@ -19,31 +18,12 @@ contract AllowListScript is Script {
1918
function run() public {
2019
vm.startBroadcast();
2120

22-
address initialOwner;
23-
try vm.envAddress("REGISTRY_INITIAL_OWNER") returns (address owner) {
24-
initialOwner = owner;
25-
} catch {
26-
console.log("Warning: REGISTRY_INITIAL_OWNER not found in environment variables, using msg.sender");
27-
initialOwner = msg.sender;
28-
}
21+
allowlist = new AllowList(ETHEREUM_SEPOLIA_ATTESTATION_FEE_ADDRESS);
2922

30-
console.log("REGISTRY_INITIAL_OWNER:", initialOwner);
31-
32-
registry = new AllowList(initialOwner);
33-
34-
// Deploy AutomataDcapAttestationFee with deployer as owner
35-
console.log("msg.sender:", msg.sender);
36-
attestationFee = new AutomataDcapAttestationFee(msg.sender);
37-
38-
// Example: Call verifyQuoteWithAttestationFee with a sample quote
23+
// Example: Call registerTEEService with a sample quote
3924
bytes memory sampleQuote = vm.readFileBinary("test/quote.raw");
4025

41-
(bool success, bytes memory output) =
42-
registry.verifyQuoteWithAttestationFee(address(ETHEREUM_SEPOLIA_ATTESTATION_FEE_ADDRESS), sampleQuote);
43-
44-
if (!success) {
45-
console.log(string(output));
46-
}
26+
allowlist.registerTEEService(sampleQuote);
4727

4828
vm.stopBroadcast();
4929
}

src/AllowList.sol

Lines changed: 83 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,61 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.28;
33

4-
import "solmate/src/auth/Owned.sol";
54
import {AutomataDcapAttestationFee} from "../lib/automata-dcap-attestation/evm/contracts/AutomataDcapAttestationFee.sol";
65
import {TD10ReportBody} from "automata-dcap-attestation/contracts/types/V4Structs.sol";
7-
import {TD_REPORT10_LENGTH, TDX_TEE} from "automata-dcap-attestation/contracts/types/Constants.sol";
8-
import {BytesUtils} from "@automata-network/on-chain-pccs/utils/BytesUtils.sol";
6+
import {QuoteParser, WorkloadId} from "./utils/QuoteParser.sol";
97

108
/**
119
* @title AllowList
1210
* @dev A contract for managing trusted execution environment (TEE) identities and configurations
13-
* using Intel DCAP attestation
11+
* using Automata's Intel DCAP attestation
1412
*/
15-
contract AllowList is Owned {
16-
using BytesUtils for bytes;
13+
contract AllowList {
1714

18-
// This is the number of bytes in the Output struct that come before the quoteBody.
19-
// See the Output struct definition in the Automata DCAP Attestation repo:
20-
// https://github.com/automata-network/automata-dcap-attestation/blob/evm-v1.0.0/evm/contracts/types/CommonStruct.sol#L113
21-
// 13 bytes = quoteVersion (2 bytes) + tee (4 byte) + tcbStatus (1 byte) + fmspcBytes (6 bytes)
22-
uint256 public constant SERIALIZED_OUTPUT_OFFSET = 13;
15+
// Constants
2316

2417
// Maximum size for byte arrays to prevent DoS attacks
2518
uint256 public constant MAX_BYTES_SIZE = 20 * 1024; // 20KB limit
2619

27-
// The TDX version of the quote Flashtestation's accepts
28-
uint256 public constant ACCEPTED_TDX_VERSION = 4;
20+
// Structs
2921

3022
// TEE identity and status tracking
31-
struct TEEDevice {
32-
bytes32 publicKey; // Public key of the TEE device
33-
uint64 lastActiveTime; // Timestamp of last activity
34-
bool isActive; // Whether the device is currently active
23+
struct RegisteredTEE {
24+
uint64 registeredAt; // The most recent timestamp that the TEE last registered with a valid quote
25+
WorkloadId workloadId; // The workloadID of the TEE device
26+
bytes rawQuote; // The raw quote from the TEE device, which is stored to allow for future quote re-verification
3527
}
3628

37-
// Mapping from TEE identity to device information
38-
mapping(bytes32 => TEEDevice) public teeDevices;
29+
// Storage Variables
3930

40-
// State variables
41-
string[] public instanceDomainNames;
31+
// The address of the Automata DCAP Attestation contract, which verifies TEE quotes.
32+
// This is deployed by Automata, and once set on the AllowList, it cannot be changed
33+
address public attestationContract;
4234

43-
// Notes config and secrets locations
44-
string[] public storageBackends;
45-
// Maps config hash to config data and secrets for onchain DA
46-
mapping(bytes32 => bytes) public artifacts;
47-
// Maps identity to config hash
48-
mapping(bytes32 => bytes32) public identityConfigMap;
35+
// Used to track registered TEEs by the Ethereum address in the quote that originally registered them
36+
mapping(address => RegisteredTEE) public registeredTEEs;
37+
38+
// Used to track registered TEEs by their (ethAddress, workloadId) pair,
39+
// which is needed for when
40+
mapping(WorkloadId => RegisteredTEE) public registeredTEEsByWorkloadId;
4941

5042
// Events
51-
event InstanceDomainRegistered(string domain, address registrar);
52-
event StorageBackendSet(string location, address setter);
53-
event StorageBackendRemoved(string location, address remover);
54-
event ArtifactAdded(bytes32 configHash, address adder);
55-
event IdentityConfigSet(bytes32 identity, bytes32 configHash, address setter);
5643

44+
event TEEServiceRegistered(address ethAddress, WorkloadId workloadId, bytes rawQuote, bool alreadyExists);
45+
46+
// Errors
47+
48+
error InvalidQuote(bytes output);
5749
error ByteSizeExceeded(uint256 size);
50+
error TEEServiceAlreadyRegistered(address ethAddress, WorkloadId workloadId);
5851

5952
/**
60-
* @notice Constructor to set the the governance address, which
61-
* is the only address that can register and de-register TEE devices
62-
* and wipe the registry in case of a compromise
63-
* @param governance The address of the governance contract
53+
* Constructor to set the the Automata DCAP Attestation contract, which verifies TEE quotes
54+
* @param _attestationContract The address of the attestation contract
6455
*/
65-
constructor(address governance) Owned(governance) {}
56+
constructor(address _attestationContract) {
57+
attestationContract = _attestationContract;
58+
}
6659

6760
/**
6861
* @dev Modifier to check if input bytes size is within limits
@@ -73,114 +66,77 @@ contract AllowList is Owned {
7366
_;
7467
}
7568

76-
function registerTEE(bytes memory quote) external onlyOwner {
77-
// TODO: Implement
78-
}
79-
80-
function deregisterTEE(bytes memory quote) external onlyOwner {
81-
// TODO: Implement
82-
}
83-
84-
function verifyFlashestationTransaction(bytes memory attestationTransaction) external {
85-
// TODO: Implement
86-
// 1. check signature against live builder keys
87-
// 2. update liveness
88-
}
89-
90-
function _updateLiveness() internal {
91-
// TODO: Implement
92-
}
93-
9469
/**
95-
* @notice Verifies a quote using AutomataDcapAttestationFee
96-
* @dev TODO: move this logic into registerTEE
97-
* @param attestationFeeContract The address of the AutomataDcapAttestationFee contract
98-
* @param quote The DCAP quote to verify
70+
* @notice Registers a TEE workload with a specific Ethereum address in the AllowList
71+
* @notice The TEE must be registered with a quote whose validity is verified by the attestationContract
72+
* @dev In order to mitigate DoS attacks, the quote must be less than 20KB
73+
* @param rawQuote The raw quote from the TEE device. Must be a V4 TDX quote
9974
*/
100-
function verifyQuoteWithAttestationFee(address attestationFeeContract, bytes calldata quote)
101-
external
102-
limitBytesSize(quote)
103-
returns (bool, bytes memory)
104-
{
75+
function registerTEEService(bytes calldata rawQuote) external limitBytesSize(rawQuote) {
10576
(bool success, bytes memory output) =
106-
AutomataDcapAttestationFee(attestationFeeContract).verifyAndAttestOnChain(quote);
77+
AutomataDcapAttestationFee(attestationContract).verifyAndAttestOnChain(rawQuote);
10778

108-
if (success) {
109-
// check that quote is v4 and for TDX, otherwise in the next
110-
// step the output will not have the byte length we expect and we'll fail
111-
// to parse it, returning a unhelpful error message
112-
checkTEEVersion(output);
113-
checkTEEType(output);
79+
if (!success) {
80+
revert InvalidQuote(output);
81+
}
82+
// at this point we know this quote was generated by an update-to-date TEE, but we still
83+
// need to make sure we can parse the workloadId and ethereum public key from the quote
11484

115-
// now we can safely decode the output into the TDX report body, from which we can extract
116-
// the ethereum public key and compute the workloadID
117-
TD10ReportBody memory td10ReportBodyStruct = parseTD10ReportBody(output);
85+
// now we can safely decode the output into the TDX report body, from which we can extract
86+
// the ethereum public key and compute the workloadID
87+
TD10ReportBody memory td10ReportBodyStruct = QuoteParser.parseTD10ReportBody(output);
11888

89+
// extract the ethereum public key from the quote
90+
address ethAddress = QuoteParser.extractEthereumAddress(td10ReportBodyStruct);
11991

120-
// TODO do flashtestations protocol, so far we've only verified that the quote is valid
121-
}
92+
// extract the workloadId from the quote
93+
WorkloadId workloadId = QuoteParser.extractWorkloadId(td10ReportBodyStruct);
12294

123-
return (success, output);
95+
// Register the address in the allowlist with the raw quote for future quote re-verification
96+
bool previouslyRegistered = addAddress(workloadId, ethAddress, rawQuote);
97+
98+
emit TEEServiceRegistered(ethAddress, workloadId, rawQuote, previouslyRegistered);
12499
}
125100

126101
/**
127-
* @notice Parses a TD10ReportBody which contains all the data we need for
128-
* registering a TEE, using the serializedOutput bytes generated by Automata's verification logic
129-
* @param serializedOutput The serializedOutput bytes generated by Automata's verification logic
130-
* @return report The parsed TD10ReportBody
131-
* @dev Taken from Automata's DCAP Attestation repo:
132-
* https://github.com/automata-network/automata-dcap-attestation/blob/evm-v1.0.0/evm/contracts/verifiers/V4QuoteVerifier.sol#L309
102+
* @notice Adds a TEE to the allowlist
103+
* @dev It's possible that a TEE has already registered with this ethereum address, but with a different workloadId.
104+
* This is expected if the TEE gets restarted or upgraded and generates a new workloadId.
105+
* It's also possible that the ethereum address and workloadId are the same, but the quote
106+
* is different. This is expected if Intel releases a new set of DCAP Endorsements (i.e.
107+
* a new TCB), in which case the quotes the TEE generates will be different.
108+
* In both cases, we need to update the allowlist with the new quote.
109+
* @param workloadId The workloadId of the TEE
110+
* @param ethAddress The Ethereum address of the TEE
111+
* @param rawQuote The raw quote from the TEE device
112+
* @return previouslyRegistered Whether the TEE was previously registered
133113
*/
134-
function parseTD10ReportBody(bytes memory serializedOutput)
114+
function addAddress(WorkloadId workloadId, address ethAddress, bytes calldata rawQuote)
135115
internal
136-
pure
137-
returns (TD10ReportBody memory report)
116+
returns (bool previouslyRegistered)
138117
{
139-
bytes memory rawReportBody = output.substring(SERIALIZED_OUTPUT_OFFSET, TD_REPORT10_LENGTH);
140-
141-
// note: because of the call to .substring above, we know that the length of rawReportBody is
142-
// exactly TD_REPORT10_LENGTH, so we can safely call substring without checking the length
143-
144-
if (success) {
145-
report.teeTcbSvn = bytes16(rawReportBody.substring(0, 16));
146-
report.mrSeam = rawReportBody.substring(16, 48);
147-
report.mrsignerSeam = rawReportBody.substring(64, 48);
148-
report.seamAttributes = bytes8(rawReportBody.substring(112, 8));
149-
report.tdAttributes = bytes8(rawReportBody.substring(120, 8));
150-
report.xFAM = bytes8(rawReportBody.substring(128, 8));
151-
report.mrTd = rawReportBody.substring(136, 48);
152-
report.mrConfigId = rawReportBody.substring(184, 48);
153-
report.mrOwner = rawReportBody.substring(232, 48);
154-
report.mrOwnerConfig = rawReportBody.substring(280, 48);
155-
report.rtMr0 = rawReportBody.substring(328, 48);
156-
report.rtMr1 = rawReportBody.substring(376, 48);
157-
report.rtMr2 = rawReportBody.substring(424, 48);
158-
report.rtMr3 = rawReportBody.substring(472, 48);
159-
report.reportData = rawReportBody.substring(520, 64);
118+
// check if the TEE is already registered with the same ethereum address and workloadId,
119+
// in which case this call is a no-op and possibly being called because of user error
120+
if (
121+
WorkloadId.unwrap(registeredTEEs[ethAddress].workloadId) == WorkloadId.unwrap(workloadId)
122+
&& keccak256(registeredTEEs[ethAddress].rawQuote) == keccak256(rawQuote)
123+
) {
124+
revert TEEServiceAlreadyRegistered(ethAddress, workloadId);
160125
}
161-
}
162126

163-
/**
164-
* Parses and checks that the uint16 version from the quote header is one we accept
165-
* @param rawReportBody The rawQuote bytes generated by Automata's verification logic
166-
* @dev Automata currently only supports V3 and V4 TDX quotes
167-
*/
168-
function checkTEEVersion(bytes memory rawReportBody) internal pure {
169-
version = uint16(rawReportBody.substring(0, 2)); // uint16 is 2 bytes
170-
if (version != ACCEPTED_TDX_VERSION) {
171-
revert InvalidTEEVersion(version);
127+
// this is a nice-to-have that signals to the user that the TEE was previously registered,
128+
// but it's not strictly necessary
129+
if (registeredTEEs[ethAddress].registeredAt > 0) {
130+
previouslyRegistered = true;
172131
}
132+
133+
registeredTEEs[ethAddress] =
134+
RegisteredTEE({registeredAt: uint64(block.timestamp), workloadId: workloadId, rawQuote: rawQuote});
173135
}
174136

175-
/**
176-
* Parses and checks that the bytes4 tee type from the quote header is of type TDX
177-
* @param rawReportBody The rawQuote bytes generated by Automata's verification logic
178-
* @dev Automata currently only supports SGX and TDX TEE types
179-
*/
180-
function checkTEEType(bytes memory rawReportBody) internal pure {
181-
teeType = bytes4(rawReportBody.substring(2, 6)); // 4 bytes
182-
if (teeType != TDX_TEE) {
183-
revert InvalidTEEType(teeType);
184-
}
137+
function verifyFlashestationTransaction(bytes memory attestationTransaction) external {
138+
// TODO: Implement
139+
// 1. check signature against live builder keys
140+
// 2. update liveness
185141
}
186142
}

0 commit comments

Comments
 (0)