Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add additional verification to EspressoTEEVerifier #28

Merged
merged 10 commits into from
Nov 28, 2024
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
6 changes: 3 additions & 3 deletions .github/workflows/contract-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ jobs:
no-token-bridge: true
no-l3-token-bridge: true
nitro-contracts-branch: '${{ github.event.pull_request.head.sha || github.sha }}'
nitro-testnode-ref: integration
nitro-testnode-ref: celestia-integration
nitro-testnode-repo: EspressoSystems/nitro-testnode

- name: Install Foundry
Expand Down Expand Up @@ -229,7 +229,7 @@ jobs:
no-token-bridge: true
no-l3-token-bridge: true
nitro-contracts-branch: '${{ github.event.pull_request.head.sha || github.sha }}'
nitro-testnode-ref: integration
nitro-testnode-ref: celestia-integration
nitro-testnode-repo: EspressoSystems/nitro-testnode

- name: Install Foundry
Expand Down Expand Up @@ -275,7 +275,7 @@ jobs:
no-token-bridge: true
no-l3-token-bridge: true
nitro-contracts-branch: '${{ github.event.pull_request.head.sha || github.sha }}'
nitro-testnode-ref: 'integration'
nitro-testnode-ref: 'celestia-integration'
nitro-testnode-repo: EspressoSystems/nitro-testnode

- name: Install Foundry
Expand Down
2 changes: 1 addition & 1 deletion lib/automata-dcap-attestation
Submodule automata-dcap-attestation updated 47 files
+0 −21 .env.example
+1 −1 .github/workflows/build.yml
+16 −11 .github/workflows/slither.yml
+3 −0 .gitmodules
+61 −30 README.md
+19 −19 broadcast/AttestationScript.s.sol/1398243/configVerifier-latest.json
+56 −0 broadcast/AttestationScript.s.sol/1398243/configureZk-latest.json
+25 −28 broadcast/AttestationScript.s.sol/1398243/deployEntrypoint-latest.json
+56 −0 broadcast/DeployRouter.s.sol/1398243/setAuthorizedCaller-latest.json
+60 −0 broadcast/DeployRouter.s.sol/1398243/updateConfig-latest.json
+19 −18 broadcast/DeployV3.s.sol/1398243/run-latest.json
+20 −19 broadcast/DeployV4.s.sol/1398243/run-latest.json
+171 −0 contracts/AttestationEntrypointBase.sol
+0 −100 contracts/AutomataDcapAttestation.sol
+42 −0 contracts/AutomataDcapAttestationFee.sol
+81 −55 contracts/PCCSRouter.sol
+68 −0 contracts/bases/FeeManagerBase.sol
+17 −24 contracts/bases/QuoteVerifierBase.sol
+14 −76 contracts/bases/X509ChainBase.sol
+9 −4 contracts/bases/tcb/TCBInfoV3Base.sol
+0 −41 contracts/interfaces/IAttestation.sol
+6 −0 contracts/interfaces/IPCCSRouter.sol
+15 −3 contracts/interfaces/IQuoteVerifier.sol
+1 −0 contracts/types/CommonStruct.sol
+1 −1 contracts/types/Constants.sol
+4 −0 contracts/utils/BELE.sol
+12 −4 contracts/utils/P256Verifier.sol
+18 −12 contracts/verifiers/V3QuoteVerifier.sol
+31 −20 contracts/verifiers/V4QuoteVerifier.sol
+26 −0 env/.testnet.env.example
+9 −7 forge-script/AttestationScript.s.sol
+0 −24 forge-script/DeployRisc0Verifier.s.sol
+18 −0 forge-script/DeployRouter.s.sol
+61 −0 forge-script/utils/P256Configuration.sol
+13 −5 forge-script/verifiers/DeployV3.s.sol
+13 −5 forge-script/verifiers/DeployV4.s.sol
+106 −0 forge-test/AutomataDcapAttestationFeeTest.t.sol
+122 −22 forge-test/AutomataDcapAttestationTest.t.sol
+10 −20 forge-test/utils/PCCSSetupBase.sol
+2 −2 forge-test/utils/RiscZeroSetup.sol
+11 −0 forge-test/utils/succinct/Groth16Setup.sol
+11 −0 forge-test/utils/succinct/PlonkSetup.sol
+5 −2 foundry.toml
+1 −1 lib/automata-on-chain-pccs
+1 −1 lib/forge-std
+1 −1 lib/risc0-ethereum
+1 −0 lib/sp1-contracts
2 changes: 1 addition & 1 deletion lib/forge-std
Submodule forge-std updated 2 files
+99 −64 src/Vm.sol
+2 −2 test/Vm.t.sol
18 changes: 14 additions & 4 deletions scripts/deployEspressoTEEVerifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ import { deployContract } from './deploymentUtils'
async function main() {
const [deployer] = await ethers.getSigners()

const pccsRouterAddress = process.env.PCCS_ROUTER_ADDRESS as string
if (!pccsRouterAddress) {
throw new Error('PCCS_ROUTER_ADDRESS not set')
const v3QuoteVerifier = process.env.V3_QUOTE_VERIFIER_ADDRESS
if (!v3QuoteVerifier) {
throw new Error('V3_QUOTE_VERIFIER_ADDRESS not set')
}

const mrEnclave = process.env.MR_ENCLAVE
if (!mrEnclave) {
throw new Error('MR_ENCLAVE not set')
}

const mrSigner = process.env.MR_SIGNER
if (!mrSigner) {
throw new Error('MR_SIGNER not set')
}

const esperssoTEEVerifier = await deployContract(
'EspressoTEEVerifier',
deployer,
[pccsRouterAddress],
[mrEnclave, mrSigner, v3QuoteVerifier],
true
)
console.log(
Expand Down
117 changes: 99 additions & 18 deletions src/bridge/EspressoTEEVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,90 @@ import {
ENCLAVE_REPORT_LENGTH
} from "@automata-network/dcap-attestation/contracts/types/Constants.sol";
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
import {
V3QuoteVerifier
} from "@automata-network/dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol";
import {BytesUtils} from "@automata-network/dcap-attestation/contracts/utils/BytesUtils.sol";
import {Ownable} from "solady/auth/Ownable.sol";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a particular reason we're not using the openzeppelin Ownable here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Automata uses this library for ownable. Apparently is more gas efficient


/**
*
* @title Verifies quotes from the TEE and attests on-chain
* @notice Contains the logic to verify a quote from the TEE and attest on-chain. It uses the V3QuoteVerifier contract
* to verify the quote. Along with some additional verification logic.
* from automata to verify the quote. Along with some additional verification logic.
*/
contract EspressoTEEVerifier is Ownable {
using BytesUtils for bytes;

// We only support version 3 for now
error InvalidHeaderVersion();
// This error is thrown when the automata verification fails
error InvalidQuote();
// This error is thrown when the enclave report fails to parse
error FailedToParseEnclaveReport();
// This error is thrown when the mrEnclave and mrSigner don't match
error InvalidMREnclaveOrSigner();
// This error is thrown when the reportDataHash doesn't match the hash signed by the TEE
error InvalidReportDataHash();

contract EspressoTEEVerifier is V3QuoteVerifier {
constructor(address _router) V3QuoteVerifier(_router) {}
// V3QuoteVerififer contract from automata to verify the quote
V3QuoteVerifier public quoteVerifier;
bytes32 public mrEnclave;
bytes32 public mrSigner;

/**
constructor(bytes32 _mrEnclave, bytes32 _mrSigner, address _quoteVerifier) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The reason I am accepting the V3QuoteVerifier contract here is because automata changed how they manage their contracts. Now each Verifier contract should be registered with the PCCSRouter

quoteVerifier = V3QuoteVerifier(_quoteVerifier);
mrEnclave = _mrEnclave;
mrSigner = _mrSigner;
_initializeOwner(msg.sender);
}

/*
@notice Verify a quote from the TEE and attest on-chain
@param rawQuote The quote from the TEE
@return success True if the quote was verified and attested on-chain
*/
function verify(bytes calldata rawQuote) external view returns (bool success) {
@param reportDataHash The hash of the report data
*/
function verify(bytes calldata rawQuote, bytes32 reportDataHash) external {
// Parse the header
Header memory header = _parseQuoteHeader(rawQuote);
Header memory header = parseQuoteHeader(rawQuote);

// Currently only version 3 is supported
if (header.version != 3) {
return false;
revert InvalidHeaderVersion();
}

(success, ) = this.verifyQuote(header, rawQuote);
// Verify the quote
(bool success, ) = quoteVerifier.verifyQuote(header, rawQuote);
if (!success) {
return false;
revert InvalidQuote();
}

// Parse enclave quote
// // Parse enclave quote
uint256 offset = HEADER_LENGTH + ENCLAVE_REPORT_LENGTH;
EnclaveReport memory localReport;
(success, localReport) = parseEnclaveReport(rawQuote[HEADER_LENGTH:offset]);
if (!success) {
return false;
revert FailedToParseEnclaveReport();
}

return true;
// Check that mrEnclave and mrSigner match
if (localReport.mrEnclave != mrEnclave || localReport.mrSigner != mrSigner) {
revert InvalidMREnclaveOrSigner();
}

// TODO: Use the parsed enclave report (localReport) to do other verifications
// Verify that the reportDataHash if the hash signed by the TEE
// We do not check the signature because `quoteVerifier.verifyQuote` already does that
if (reportDataHash != bytes32(localReport.reportData.substring(0, 32))) {
revert InvalidReportDataHash();
}
}

function _parseQuoteHeader(
bytes calldata rawQuote
) private pure returns (Header memory header) {
/*
@notice Parses the header from the quote
@param rawQuote The raw quote in bytes
@return header The parsed header
*/
function parseQuoteHeader(bytes calldata rawQuote) public pure returns (Header memory header) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didnt see any harm in making it public and I think it might turn out helpful for debugging in the future. Its a pure function so takes very less gas as well.

bytes2 attestationKeyType = bytes2(rawQuote[2:4]);
bytes2 qeSvn = bytes2(rawQuote[8:10]);
bytes2 pceSvn = bytes2(rawQuote[10:12]);
Expand All @@ -73,4 +113,45 @@ contract EspressoTEEVerifier is V3QuoteVerifier {
userData: bytes20(rawQuote[28:48])
});
}

/*
@notice Parses the enclave report from the quote
@param rawEnclaveReport The raw enclave report from the quote in bytes
@return success True if the enclave report was parsed successfully
@return enclaveReport The parsed enclave report
*/
function parseEnclaveReport(
bytes memory rawEnclaveReport
) public pure returns (bool success, EnclaveReport memory enclaveReport) {
if (rawEnclaveReport.length != ENCLAVE_REPORT_LENGTH) {
return (false, enclaveReport);
}
enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16));
enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4));
enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28));
enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16));
enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32));
enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32));
enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32));
enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96);
enclaveReport.isvProdId = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(256, 2)));
enclaveReport.isvSvn = uint16(BELE.leBytesToBeUint(rawEnclaveReport.substring(258, 2)));
enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60);
enclaveReport.reportData = rawEnclaveReport.substring(320, 64);
success = true;
}

/*
* @dev Set the mrEnclave of the contract
*/
function setMrEnclave(bytes32 _mrEnclave) external onlyOwner {
mrEnclave = _mrEnclave;
}

/*
* @dev Set the mrSigner of the contract
*/
function setMrSigner(bytes32 _mrSigner) external onlyOwner {
mrSigner = _mrSigner;
}
}
3 changes: 3 additions & 0 deletions src/bridge/ISequencerInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ interface ISequencerInbox is IDelayedMessageProvider {
/// @dev a keyset was invalidated
event InvalidateKeyset(bytes32 indexed keysetHash);

/// @dev a TEE attestation quote was verified
event TEEAttestationQuoteVerified(uint256 indexed seqMessageIndex);

function totalDelayedMessagesRead() external view returns (uint256);

function bridge() external view returns (IBridge);
Expand Down
86 changes: 57 additions & 29 deletions src/bridge/SequencerInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import {
NativeTokenMismatch,
BadMaxTimeVariation,
Deprecated,
InvalidCelestiaBatch,
InvalidTEEAttestationQuote
InvalidCelestiaBatch
} from "../libraries/Error.sol";
import "./IBridge.sol";
import "./IInboxBase.sol";
Expand Down Expand Up @@ -182,10 +181,11 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
__LEGACY_MAX_TIME_VARIATION.futureSeconds = 0;
}

function initialize(
IBridge bridge_,
ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_
) external onlyDelegated {
/**
Deprecated because we created another `initialize` function that accepts the `EspressoTEEVerifier` contract
address as a parameter which is used by the `SequencerInbox` contract to verify the TEE attestation quote.
*/
function initialize(IBridge, ISequencerInbox.MaxTimeVariation calldata) external onlyDelegated {
revert Deprecated();
}

Expand Down Expand Up @@ -349,13 +349,17 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
revert Deprecated();
}

/**
Deprecated because we added a new method with TEE attestation quote
to verify that the batch is posted by the batch poster running in TEE.
*/
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
uint256,
bytes calldata,
uint256,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
uint256,
uint256
) external refundsGas(gasRefunder, IReader4844(address(0))) {
revert Deprecated();
}
Expand All @@ -373,10 +377,20 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
if (msg.sender != tx.origin) revert NotOrigin();
if (!isBatchPoster[msg.sender]) revert NotBatchPoster();

bool success = espressoTEEVerifier.verify(quote);
if (!success) {
revert InvalidTEEAttestationQuote();
}
// take keccak2256 hash of all the function arguments except the quote
bytes32 reportDataHash = keccak256(
abi.encode(
sequenceNumber,
data,
afterDelayedMessagesRead,
address(gasRefunder),
prevMessageCount,
newMessageCount
)
);
// verify the quote for the batch poster running in the TEE
espressoTEEVerifier.verify(quote, reportDataHash);
emit TEEAttestationQuoteVerified(sequenceNumber);

(bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash(
data,
Expand Down Expand Up @@ -480,26 +494,30 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
}
}

/**
Deprecated because we added a new method with TEE attestation quote
to verify that the batch is posted by the batch poster running in TEE.
*/
function addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
uint256,
bytes calldata,
uint256,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
uint256,
uint256
) external override refundsGas(gasRefunder, IReader4844(address(0))) {
revert Deprecated();
}

/*
* addSequencerL2Batch is called by either the rollup admin or batch poster
* running in TEE to add a new L2 batch to the rollup
* @param sequenceNumber - the sequence number of the L2 batch
* @param data - the data of the L2 batch
* running in TEE to add a new batch
* @param sequenceNumber - the sequence number of the batch
* @param data - the data of the batch
* @param afterDelayedMessagesRead - the number of delayed messages read by the sequencer
* @param gasRefunder - the gas refunder contract
* @param prevMessageCount - the number of messages in the previous L2 batch
* @param newMessageCount - the number of messages in the new L2 batch
* @param prevMessageCount - the number of messages in the previous batch
* @param newMessageCount - the number of messages in the new batch
* @param quote - the atttestation quote from the TEE
*/
function addSequencerL2Batch(
Expand All @@ -516,10 +534,20 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox
// Only check the attestation quote if the batch has been posted by the
// batch poster
if (isBatchPoster[msg.sender]) {
bool success = espressoTEEVerifier.verify(quote);
if (!success) {
revert InvalidTEEAttestationQuote();
}
// take keccak2256 hash of all the function arguments except the quote
bytes32 reportDataHash = keccak256(
abi.encode(
sequenceNumber,
data,
afterDelayedMessagesRead,
address(gasRefunder),
prevMessageCount,
newMessageCount
)
);
// verify the quote for the batch poster running in the TEE
espressoTEEVerifier.verify(quote, reportDataHash);
emit TEEAttestationQuoteVerified(sequenceNumber);
}
(bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash(
data,
Expand Down
3 changes: 0 additions & 3 deletions src/libraries/Error.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,3 @@ error BadMaxTimeVariation();

/// @dev Thrown when Blobstream verification fails for a Celestia Data Root
error InvalidCelestiaBatch();

/// @dev Thrown when the TEE Attestation quote is invalid
error InvalidTEEAttestationQuote();
5 changes: 4 additions & 1 deletion src/mocks/EspressoTEEVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ pragma solidity ^0.8.0;
contract EspressoTEEVerifierMock {
constructor() {}

function verify(bytes calldata rawQuote) external view returns (bool success) {
function verify(
bytes calldata rawQuote,
bytes32 reportDataHash
) external view returns (bool success) {
return (true);
}
}
Loading
Loading