Skip to content

Commit 6ffebef

Browse files
authored
Feat stateless batcher v1 (#72)
* Support multiple MR_ENCLAVE and MR_SIGNER (#50) * Support multiple MR_ENCLAVE and MR_SIGNER * fix interface * fix interface * fix emit (cherry picked from commit 1b04973) * Support blobs (#65) * Support blobs * update node setup * use abi.encode * add test * test finally passing
1 parent 98026d1 commit 6ffebef

12 files changed

+322
-25
lines changed

.github/workflows/contract-tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ jobs:
7676
exceptions_file: './test/unused-errors/exceptions.txt'
7777

7878
- name: Setup nodejs
79-
uses: actions/setup-node@v2
79+
uses: actions/setup-node@v3
8080
with:
8181
node-version: '18'
8282
cache: 'yarn'
@@ -158,7 +158,7 @@ jobs:
158158
no-token-bridge: true
159159

160160
- name: Setup nodejs
161-
uses: actions/setup-node@v2
161+
uses: actions/setup-node@v3
162162
with:
163163
node-version: 18
164164
cache: 'yarn'

src/bridge/EspressoTEEVerifier.sol

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,18 @@ import {IEspressoTEEVerifier} from "./IEspressoTEEVerifier.sol";
2525
* from automata to verify the quote. Along with some additional verification logic.
2626
*/
2727
contract EspressoTEEVerifier is IEspressoTEEVerifier, Ownable2Step {
28-
event MREnclaveSet(bytes32 indexed mrEnclave);
29-
event MRSignerSet(bytes32 indexed mrSigner);
30-
3128
using BytesUtils for bytes;
3229

3330
// V3QuoteVerififer contract from automata to verify the quote
3431
V3QuoteVerifier public quoteVerifier;
35-
bytes32 public mrEnclave;
36-
bytes32 public mrSigner;
32+
33+
mapping(bytes32 => bool) public mrEnclaves;
34+
mapping(bytes32 => bool) public mrSigners;
3735

3836
constructor(bytes32 _mrEnclave, bytes32 _mrSigner, address _quoteVerifier) {
3937
quoteVerifier = V3QuoteVerifier(_quoteVerifier);
40-
mrEnclave = _mrEnclave;
41-
mrSigner = _mrSigner;
38+
mrEnclaves[_mrEnclave] = true;
39+
mrSigners[_mrSigner] = true;
4240
}
4341

4442
/*
@@ -70,8 +68,7 @@ contract EspressoTEEVerifier is IEspressoTEEVerifier, Ownable2Step {
7068
revert FailedToParseEnclaveReport();
7169
}
7270

73-
// Check that mrEnclave and mrSigner match
74-
if (localReport.mrEnclave != mrEnclave || localReport.mrSigner != mrSigner) {
71+
if (!mrEnclaves[localReport.mrEnclave] || !mrSigners[localReport.mrSigner]) {
7572
revert InvalidMREnclaveOrSigner();
7673
}
7774

@@ -129,16 +126,24 @@ contract EspressoTEEVerifier is IEspressoTEEVerifier, Ownable2Step {
129126
/*
130127
* @dev Set the mrEnclave of the contract
131128
*/
132-
function setMrEnclave(bytes32 _mrEnclave) external onlyOwner {
133-
emit MREnclaveSet(_mrEnclave);
134-
mrEnclave = _mrEnclave;
129+
function setMrEnclave(bytes32 _mrEnclave, bool _isValid) external onlyOwner {
130+
if (_isValid) {
131+
mrEnclaves[_mrEnclave] = true;
132+
} else {
133+
delete mrEnclaves[_mrEnclave];
134+
}
135+
emit MREnclaveSet(_mrEnclave, _isValid);
135136
}
136137

137138
/*
138139
* @dev Set the mrSigner of the contract
139140
*/
140-
function setMrSigner(bytes32 _mrSigner) external onlyOwner {
141-
emit MRSignerSet(_mrSigner);
142-
mrSigner = _mrSigner;
141+
function setMrSigner(bytes32 _mrSigner, bool _isValid) external onlyOwner {
142+
if (_isValid) {
143+
mrSigners[_mrSigner] = true;
144+
} else {
145+
delete mrSigners[_mrSigner];
146+
}
147+
emit MRSignerSet(_mrSigner, _isValid);
143148
}
144149
}

src/bridge/IEspressoTEEVerifier.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ interface IEspressoTEEVerifier {
1616
// This error is thrown when the reportDataHash doesn't match the hash signed by the TEE
1717
error InvalidReportDataHash();
1818

19+
event MREnclaveSet(bytes32 indexed mrEnclave, bool indexed _isValid);
20+
event MRSignerSet(bytes32 indexed mrSigner, bool indexed _isValid);
21+
1922
function verify(bytes calldata rawQuote, bytes32 reportDataHash) external view;
2023

2124
function parseQuoteHeader(bytes calldata rawQuote) external pure returns (Header memory header);
@@ -24,7 +27,10 @@ interface IEspressoTEEVerifier {
2427
bytes memory rawEnclaveReport
2528
) external pure returns (bool success, EnclaveReport memory enclaveReport);
2629

27-
function setMrEnclave(bytes32 _mrEnclave) external;
30+
function mrEnclaves(bytes32 _mrEnclave) external view returns (bool);
31+
function mrSigners(bytes32 _mrSigner) external view returns (bool);
32+
33+
function setMrEnclave(bytes32 _mrEnclave, bool _isValid) external;
2834

29-
function setMrSigner(bytes32 _mrSigner) external;
35+
function setMrSigner(bytes32 _mrSigner, bool _isValid) external;
3036
}

src/bridge/ISequencerInbox.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ interface ISequencerInbox is IDelayedMessageProvider {
202202
uint256 newMessageCount
203203
) external;
204204

205+
function addSequencerL2BatchFromBlobs(
206+
uint256 sequenceNumber,
207+
uint256 afterDelayedMessagesRead,
208+
IGasRefunder gasRefunder,
209+
uint256 prevMessageCount,
210+
uint256 newMessageCount,
211+
bytes memory quote
212+
) external;
213+
205214
// ---------- onlyRollupOrOwner functions ----------
206215

207216
/**

src/bridge/SequencerInbox.sol

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,38 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
437437
IGasRefunder gasRefunder,
438438
uint256 prevMessageCount,
439439
uint256 newMessageCount
440+
) external refundsGas(gasRefunder, reader4844) {
441+
revert Deprecated();
442+
}
443+
444+
function addSequencerL2BatchFromBlobs(
445+
uint256 sequenceNumber,
446+
uint256 afterDelayedMessagesRead,
447+
IGasRefunder gasRefunder,
448+
uint256 prevMessageCount,
449+
uint256 newMessageCount,
450+
bytes memory quote
440451
) external refundsGas(gasRefunder, reader4844) {
441452
if (!isBatchPoster[msg.sender]) revert NotBatchPoster();
453+
454+
bytes32[] memory dataHashes = reader4844.getDataHashes();
455+
if (dataHashes.length == 0) revert MissingDataHashes();
456+
// take keccak2256 hash of all the function arguments and encode packed blob hashes
457+
// except the quote
458+
bytes32 reportDataHash = keccak256(
459+
abi.encode(
460+
sequenceNumber,
461+
afterDelayedMessagesRead,
462+
address(gasRefunder),
463+
prevMessageCount,
464+
newMessageCount,
465+
abi.encode(dataHashes)
466+
)
467+
);
468+
// verify the quote for the batch poster running in the TEE
469+
espressoTEEVerifier.verify(quote, reportDataHash);
470+
emit TEEAttestationQuoteVerified(sequenceNumber);
471+
442472
(
443473
bytes32 dataHash,
444474
IBridge.TimeBounds memory timeBounds,
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.25;
3+
4+
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
5+
import {BELE} from "@automata-network/dcap-attestation/contracts/utils/BELE.sol";
6+
import {Header} from "@automata-network/dcap-attestation/contracts/types/CommonStruct.sol";
7+
import {
8+
IQuoteVerifier
9+
} from "@automata-network/dcap-attestation/contracts/interfaces/IQuoteVerifier.sol";
10+
import {
11+
HEADER_LENGTH,
12+
ENCLAVE_REPORT_LENGTH
13+
} from "@automata-network/dcap-attestation/contracts/types/Constants.sol";
14+
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
15+
import {BytesUtils} from "@automata-network/dcap-attestation/contracts/utils/BytesUtils.sol";
16+
import {IEspressoTEEVerifier} from "../bridge/IEspressoTEEVerifier.sol";
17+
import {
18+
V3QuoteVerifier
19+
} from "@automata-network/dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol";
20+
import "@openzeppelin/contracts/access/Ownable2Step.sol";
21+
22+
/**
23+
*
24+
* @title Verifies quotes from the TEE and attests on-chain
25+
* @notice Contains the logic to verify a quote from the TEE and attest on-chain. It uses the V3QuoteVerifier contract
26+
* to verify the quote. Along with some additional verification logic.
27+
*/
28+
29+
contract EspressoTEEVerifierBlobsMock is IEspressoTEEVerifier {
30+
using BytesUtils for bytes;
31+
32+
// V3QuoteVerififer contract from automata to verify the quote
33+
V3QuoteVerifier public quoteVerifier;
34+
35+
mapping(bytes32 => bool) public mrEnclaves;
36+
mapping(bytes32 => bool) public mrSigners;
37+
38+
constructor() {}
39+
40+
function verify(bytes calldata rawQuote, bytes32 reportDataHash) external view {
41+
// Parse the header
42+
Header memory header = parseQuoteHeader(rawQuote);
43+
44+
// Currently only version 3 is supported
45+
if (header.version != 3) {
46+
revert InvalidHeaderVersion();
47+
}
48+
49+
// Parse enclave quote
50+
uint256 lastIndex = HEADER_LENGTH + ENCLAVE_REPORT_LENGTH;
51+
EnclaveReport memory localReport;
52+
bool success;
53+
(success, localReport) = parseEnclaveReport(rawQuote[HEADER_LENGTH:lastIndex]);
54+
if (!success) {
55+
revert FailedToParseEnclaveReport();
56+
}
57+
58+
// Verify that the reportDataHash if the hash signed by the TEE
59+
// We do not check the signature because `quoteVerifier.verifyQuote` already does that
60+
if (reportDataHash != bytes32(localReport.reportData.substring(0, 32))) {
61+
revert InvalidReportDataHash();
62+
}
63+
}
64+
65+
/*
66+
@notice Parses the enclave report from the quote
67+
@param rawEnclaveReport The raw enclave report from the quote in bytes
68+
@return success True if the enclave report was parsed successfully
69+
@return enclaveReport The parsed enclave report
70+
*/
71+
function parseEnclaveReport(
72+
bytes memory rawEnclaveReport
73+
) public pure returns (bool success, EnclaveReport memory enclaveReport) {
74+
if (rawEnclaveReport.length != ENCLAVE_REPORT_LENGTH) {
75+
return (false, enclaveReport);
76+
}
77+
enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16));
78+
enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4));
79+
enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28));
80+
enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16));
81+
enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32));
82+
enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32));
83+
enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32));
84+
enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96);
85+
enclaveReport.isvProdId = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(256, 2)));
86+
enclaveReport.isvSvn = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(258, 2)));
87+
enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60);
88+
enclaveReport.reportData = rawEnclaveReport.substring(320, 64);
89+
success = true;
90+
}
91+
92+
function parseQuoteHeader(bytes calldata rawQuote) public pure returns (Header memory header) {
93+
header = Header({
94+
version: uint16(BELE.leBytesToBeUint(rawQuote[0:2])),
95+
attestationKeyType: bytes2(rawQuote[2:4]),
96+
teeType: bytes4(uint32(BELE.leBytesToBeUint(rawQuote[4:8]))),
97+
qeSvn: bytes2(rawQuote[8:10]),
98+
pceSvn: bytes2(rawQuote[10:12]),
99+
qeVendorId: bytes16(rawQuote[12:28]),
100+
userData: bytes20(rawQuote[28:48])
101+
});
102+
}
103+
104+
/*
105+
* @dev Set the mrEnclave of the contract
106+
*/
107+
function setMrEnclave(bytes32 _mrEnclave, bool _isValid) external {
108+
if (_isValid) {
109+
mrEnclaves[_mrEnclave] = true;
110+
} else {
111+
delete mrEnclaves[_mrEnclave];
112+
}
113+
emit MREnclaveSet(_mrEnclave, _isValid);
114+
}
115+
116+
/*
117+
* @dev Set the mrSigner of the contract
118+
*/
119+
function setMrSigner(bytes32 _mrSigner, bool _isValid) external {
120+
if (_isValid) {
121+
mrSigners[_mrSigner] = true;
122+
} else {
123+
delete mrSigners[_mrSigner];
124+
}
125+
emit MRSignerSet(_mrSigner, _isValid);
126+
}
127+
}

src/mocks/Reader4844.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.4;
3+
4+
import {IReader4844} from "../libraries/IReader4844.sol";
5+
6+
contract Reader4844 is IReader4844 {
7+
uint256 public constant BLOB_BASE_FEE = 100;
8+
9+
function getBlobBaseFee() external view returns (uint256) {
10+
return BLOB_BASE_FEE;
11+
}
12+
13+
function getDataHashes() external view returns (bytes32[] memory) {
14+
bytes32[] memory dataHashes = new bytes32[](1); // Fixed size (e.g., 1 element)
15+
dataHashes[0] = 0x014e8e17947683a76729b8efd62f59785227e0011c4ace32d7887589acd46ee7; // Assign hash
16+
return dataHashes;
17+
}
18+
}

test/contract/sequencerInbox.spec.4844.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,13 +434,14 @@ describe('SequencerInbox', async () => {
434434
sequencerInbox.address,
435435
['0x0142', '0x0143'],
436436
sequencerInbox.interface.encodeFunctionData(
437-
'addSequencerL2BatchFromBlobs',
437+
'addSequencerL2BatchFromBlobs(uint256,uint256,address,uint256,uint256,bytes)',
438438
[
439439
sequenceNumber,
440440
afterDelayedMessagesRead,
441441
gasRefunder.address,
442442
subMessageCount,
443443
subMessageCount.add(1),
444+
'0x'
444445
]
445446
)
446447
)

test/foundry/EspressoTEEVerifier.t.sol

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,21 @@ contract EspressoTEEVerifierTest is Test {
100100

101101
function testSetMrEnclave() public {
102102
vm.startPrank(adminTEE);
103-
bytes32 newMrEnclave = bytes32(hex"01");
104-
espressoTEEVerifier.setMrEnclave(newMrEnclave);
105-
assertEq(espressoTEEVerifier.mrEnclave(), newMrEnclave);
103+
bytes32 newMrEnclave = bytes32(hex"0123456789abcdef");
104+
espressoTEEVerifier.setMrEnclave(newMrEnclave, false);
105+
assertEq(espressoTEEVerifier.mrEnclaves(newMrEnclave), false);
106+
espressoTEEVerifier.setMrEnclave(newMrEnclave, true);
107+
assertEq(espressoTEEVerifier.mrEnclaves(newMrEnclave), true);
106108
vm.stopPrank();
107109
}
108110

109111
function testSetMrSigner() public {
110112
vm.startPrank(adminTEE);
111113
bytes32 newMrSigner = bytes32(hex"01");
112-
espressoTEEVerifier.setMrSigner(newMrSigner);
113-
assertEq(espressoTEEVerifier.mrSigner(), newMrSigner);
114+
espressoTEEVerifier.setMrSigner(newMrSigner, false);
115+
assertEq(espressoTEEVerifier.mrSigners(newMrSigner), false);
116+
espressoTEEVerifier.setMrSigner(newMrSigner, true);
117+
assertEq(espressoTEEVerifier.mrSigners(newMrSigner), true);
114118
vm.stopPrank();
115119
}
116120

0 commit comments

Comments
 (0)