Skip to content

Contract changes for Stateless Batch Poster design #46

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

Draft
wants to merge 4 commits into
base: celestia-feat-stateless-batcher
Choose a base branch
from
Draft
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
175 changes: 175 additions & 0 deletions src/bridge/EspressoSGXTEEVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {
V3QuoteVerifier
} from "@automata-network/dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol";
import {BELE} from "@automata-network/dcap-attestation/contracts/utils/BELE.sol";
import {Header} from "@automata-network/dcap-attestation/contracts/types/CommonStruct.sol";
import {
HEADER_LENGTH,
ENCLAVE_REPORT_LENGTH
} from "@automata-network/dcap-attestation/contracts/types/Constants.sol";
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
import {BytesUtils} from "@automata-network/dcap-attestation/contracts/utils/BytesUtils.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IEspressoSGXTEEVerifier} from "./IEspressoSGXTEEVerifier.sol";

/**
*
* @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
* from automata to verify the quote. Along with some additional verification logic.
*/
contract EspressoSGXTEEVerifier is IEspressoSGXTEEVerifier, Ownable2Step {
using BytesUtils for bytes;

// V3QuoteVerififer contract from automata to verify the quote
V3QuoteVerifier public quoteVerifier;

mapping(bytes32 => bool) public registeredEnclaveHash;
mapping(bytes32 => bool) public registeredEnclaveSigner;
mapping(address => bool) public registeredSigners;

constructor(bytes32 enclaveHash, bytes32 enclaveSigner, address _quoteVerifier) {
quoteVerifier = V3QuoteVerifier(_quoteVerifier);
registeredEnclaveHash[enclaveHash] = true;
registeredEnclaveSigner[enclaveSigner] = true;
}

/*
@notice Verify a quote from the TEE and attest on-chain
The verification is considered successful if the function does not revert.
@param rawQuote The quote from the TEE
@param reportDataHash The hash of the report data
*/
function verify(
bytes calldata rawQuote,
bytes32 reportDataHash
) public view returns (EnclaveReport memory) {
// Parse the header
Header memory header = parseQuoteHeader(rawQuote);

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

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

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

// Check that mrEnclave match
if (!registeredEnclaveHash[localReport.mrEnclave]) {
revert InvalidEnclaveHash();
}

if (!registeredEnclaveSigner[localReport.mrSigner]) {
revert InvalidEnclaveSigner();
}

// 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();
}

return localReport;
}

/*
@notice Register a new signer by verifying a quote from the TEE
@param attestation The attestation from the TEE
@param data which the TEE has attested to
*/
function registerSigner(bytes calldata attestation, bytes calldata data) external {
// Check that the data length is 20 bytes because an address is 20 bytes
if (data.length != 20) {
revert InvalidDataLength();
}
bytes32 paddedSignerAddress = keccak256(data);
// Convert data to address
EnclaveReport memory localReport = verify(attestation, paddedSignerAddress);

if (localReport.reportData.length < 20) {
revert ReportDataTooShort();
}

address signer = address(uint160(bytes20(data[:20])));

// Check if the extracted address is valid
if (signer == address(0)) {
revert InvalidSignerAddress(); // Custom revert if the address is invalid
}
// Mark the signer as registered
registeredSigners[signer] = true;
}

/*
@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) {
header = Header({
version: uint16(BELE.leBytesToBeUint(rawQuote[0:2])),
attestationKeyType: bytes2(rawQuote[2:4]),
teeType: bytes4(uint32(BELE.leBytesToBeUint(rawQuote[4:8]))),
qeSvn: bytes2(rawQuote[8:10]),
pceSvn: bytes2(rawQuote[10:12]),
qeVendorId: bytes16(rawQuote[12:28]),
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;
}

function setEnclaveHash(bytes32 enclaveHash, bool valid) external onlyOwner {
registeredEnclaveHash[enclaveHash] = valid;
emit EnclaveHashSet(enclaveHash, valid);
}

function setEnclaveSigner(bytes32 enclaveSigner, bool valid) external onlyOwner {
registeredEnclaveSigner[enclaveSigner] = valid;
emit EnclaveSignerSet(enclaveSigner, valid);
}

function deleteRegisteredSigner(address signer) external onlyOwner {
delete registeredSigners[signer];
}
}
192 changes: 76 additions & 116 deletions src/bridge/EspressoTEEVerifier.sol
Original file line number Diff line number Diff line change
@@ -1,144 +1,104 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {
V3QuoteVerifier
} from "@automata-network/dcap-attestation/contracts/verifiers/V3QuoteVerifier.sol";
import {BELE} from "@automata-network/dcap-attestation/contracts/utils/BELE.sol";
import {Header} from "@automata-network/dcap-attestation/contracts/types/CommonStruct.sol";
import {
IQuoteVerifier
} from "@automata-network/dcap-attestation/contracts/interfaces/IQuoteVerifier.sol";
import {
HEADER_LENGTH,
ENCLAVE_REPORT_LENGTH
} from "@automata-network/dcap-attestation/contracts/types/Constants.sol";
import {EnclaveReport} from "@automata-network/dcap-attestation/contracts/types/V3Structs.sol";
import {BytesUtils} from "@automata-network/dcap-attestation/contracts/utils/BytesUtils.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IEspressoSGXTEEVerifier} from "./IEspressoSGXTEEVerifier.sol";
import {IEspressoTEEVerifier} from "./IEspressoTEEVerifier.sol";

/**
*
* @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
* from automata to verify the quote. Along with some additional verification logic.
*/
contract EspressoTEEVerifier is IEspressoTEEVerifier, Ownable2Step {
event MREnclaveSet(bytes32 indexed mrEnclave);
event MRSignerSet(bytes32 indexed mrSigner);
@title EspressoTEEVerifier
@author Espresso Systems (https://espresso.systems)
@notice This contract is used to resgister a signer which has been attested by the TEE
*/
contract EspressoTEEVerifier is Ownable2Step, IEspressoTEEVerifier {
IEspressoSGXTEEVerifier public espressoSGXTEEVerifier;

using BytesUtils for bytes;

// V3QuoteVerififer contract from automata to verify the quote
V3QuoteVerifier public quoteVerifier;
bytes32 public mrEnclave;
bytes32 public mrSigner;

constructor(bytes32 _mrEnclave, bytes32 _mrSigner, address _quoteVerifier) {
quoteVerifier = V3QuoteVerifier(_quoteVerifier);
mrEnclave = _mrEnclave;
mrSigner = _mrSigner;
constructor(IEspressoSGXTEEVerifier _espressoSGXTEEVerifier) {
espressoSGXTEEVerifier = _espressoSGXTEEVerifier;
}

/*
@notice Verify a quote from the TEE and attest on-chain
The verification is considered successful if the function does not revert.
@param rawQuote The quote from the TEE
@param reportDataHash The hash of the report data
*/
function verify(bytes calldata rawQuote, bytes32 reportDataHash) external view {
// Parse the header
Header memory header = parseQuoteHeader(rawQuote);

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

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

// Parse enclave quote
uint256 lastIndex = HEADER_LENGTH + ENCLAVE_REPORT_LENGTH;
EnclaveReport memory localReport;
(success, localReport) = parseEnclaveReport(rawQuote[HEADER_LENGTH:lastIndex]);
if (!success) {
revert FailedToParseEnclaveReport();
}
/**
@notice This function is used to verify the signature of the user data
@param signature The signature of the user data
@param userDataHash The hash of the user data
*/
function verify(bytes memory signature, bytes32 userDataHash) external view {
Copy link
Member

Choose a reason for hiding this comment

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

How are we planning to extend this for future tee types?

I think this function might want to match on a teeType argument to determine which verifier to check like the registerSigner function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ECDSA signature will be same across all Tee

Copy link
Member

Choose a reason for hiding this comment

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

Yes but this contract will be responsible for checking multiple different potential sub contracts that could have the signer as a verified party correct? Like an instance EspressoTEEVerifier should be capable of verifying with a EspressoSGXTEEVerifier and a hypothetical EspressoNitroTEEVerifier right?

address signer = ECDSA.recover(userDataHash, signature);

// Check that mrEnclave and mrSigner match
if (localReport.mrEnclave != mrEnclave || localReport.mrSigner != mrSigner) {
revert InvalidMREnclaveOrSigner();
if (!espressoSGXTEEVerifier.registeredSigners(signer)) {
revert InvalidSignature();
}
}

// 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();
/* @notice Register a new signer by verifying a quote from the TEE
@param attestation The attestation from the TEE
@param data when registering a signer, data can be passed for each TEE type
which can be any additiona data that is required for registering a signer
@param teeType The type of TEE
*/
function registerSigner(
bytes calldata attestation,
bytes calldata data,
TeeType teeType
) external {
if (teeType == TeeType.SGX) {
espressoSGXTEEVerifier.registerSigner(attestation, data);
return;
}
revert UnsupportedTeeType();
}

/*
@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) {
header = Header({
version: uint16(BELE.leBytesToBeUint(rawQuote[0:2])),
attestationKeyType: bytes2(rawQuote[2:4]),
teeType: bytes4(uint32(BELE.leBytesToBeUint(rawQuote[4:8]))),
qeSvn: bytes2(rawQuote[8:10]),
pceSvn: bytes2(rawQuote[10:12]),
qeVendorId: bytes16(rawQuote[12:28]),
userData: bytes20(rawQuote[28:48])
});
/**
@notice This function retrieves whether a signer is registered or not
@param signer The address of the signer
@param teeType The type of TEE
*/
function registeredSigners(address signer, TeeType teeType) external view returns (bool) {
if (teeType == TeeType.SGX) {
return espressoSGXTEEVerifier.registeredSigners(signer);
}
revert UnsupportedTeeType();
}

/*
@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);
/**
@notice This function retrieves whether an enclave hash is registered or not
@param enclaveHash The hash of the enclave
@param teeType The type of TEE
*/
function registeredEnclaveHashes(
bytes32 enclaveHash,
TeeType teeType
) external view returns (bool) {
if (teeType == TeeType.SGX) {
return espressoSGXTEEVerifier.registeredEnclaveHash(enclaveHash);
}
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;
revert UnsupportedTeeType();
}

/*
* @dev Set the mrEnclave of the contract
/**
@notice This function retrieves whether an enclave signer is registered or not
@param enclaveSigner The enclave signer
@param teeType The type of TEE
*/
function setMrEnclave(bytes32 _mrEnclave) external onlyOwner {
emit MREnclaveSet(_mrEnclave);
mrEnclave = _mrEnclave;

function registeredEnclaveSigners(
bytes32 enclaveSigner,
TeeType teeType
) external view returns (bool) {
if (teeType == TeeType.SGX) {
return espressoSGXTEEVerifier.registeredEnclaveSigner(enclaveSigner);
}
revert UnsupportedTeeType();
}

/*
* @dev Set the mrSigner of the contract
@notice Set the EspressoSGXTEEVerifier
@param _espressoSGXTEEVerifier The address of the EspressoSGXTEEVerifier
*/
function setMrSigner(bytes32 _mrSigner) external onlyOwner {
emit MRSignerSet(_mrSigner);
mrSigner = _mrSigner;
function setEspressoSGXTEEVerifier(
IEspressoSGXTEEVerifier _espressoSGXTEEVerifier
) public onlyOwner {
espressoSGXTEEVerifier = _espressoSGXTEEVerifier;
}
}
Loading
Loading