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

Protocol: attest against EnclaveID rather than HostID #1831

Merged
merged 9 commits into from
Mar 18, 2024
24 changes: 12 additions & 12 deletions contracts/generated/ManagementContract/ManagementContract.go

Large diffs are not rendered by default.

40 changes: 22 additions & 18 deletions contracts/src/management/ManagementContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
// Event to log changes to important contract addresses
event ImportantContractAddressUpdated(string key, address newAddress);

mapping(address => string) private attestationRequests;
// mapping of enclaveID to attestation request report
//mapping(address => string) private attestationRequests; todo: do we need to store this mapping?
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have been storing the full attestation report in the contract state, but it's not read at the moment. The enclave gets the data from the transaction calldata currently.

Are there reasons it's a good idea to store these attestation reports on the L1?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's a vestige from the time we were planning to perform the verification on-chain.
Can be removed


// mapping of enclaveID to whether it is attested
mapping(address => bool) private attested;
// TODO - Revisit the decision to store the host addresses in the smart contract.
string[] private hostAddresses; // The addresses of all the Obscuro hosts on the network.
string[] private hostAddresses; // The addresses of all the Ten hosts on the network.

// In the near-term it is convenient to have an accessible source of truth for important contract addresses
// TODO - this is probably not appropriate long term but currently useful for testnets. Look to remove.
Expand Down Expand Up @@ -78,44 +81,45 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
// solc-ignore-next-line unused-param
function AddRollup(Structs.MetaRollup calldata r, string calldata _rollupData, Structs.HeaderCrossChainData calldata crossChainData) public {
// TODO How to ensure the sender without hashing the calldata ?
// bytes32 derp = keccak256(abi.encodePacked(ParentHash, AggregatorID, L1Block, Number, rollupData));
// bytes32 derp = keccak256(abi.encodePacked(ParentHash, EnclaveID, L1Block, Number, rollupData));

// TODO: Add a check that ensures the cross messages are coming from the correct fork using block hashes.

// revert if the AggregatorID is not attested
require(attested[r.AggregatorID], "aggregator not attested");
// revert if the EnclaveID is not attested
require(attested[r.EnclaveID], "enclave not attested");

AppendRollup(r);
pushCrossChainMessages(crossChainData);
}

// InitializeNetworkSecret kickstarts the network secret, can only be called once
// solc-ignore-next-line unused-param
function InitializeNetworkSecret(address _aggregatorID, bytes calldata _initSecret, string memory _hostAddress, string calldata _genesisAttestation) public {
require(!networkSecretInitialized);
function InitializeNetworkSecret(address _enclaveID, bytes calldata _initSecret, string memory _hostAddress, string calldata _genesisAttestation) public {
require(!networkSecretInitialized, "network secret already initialized");

// network can no longer be initialized
networkSecretInitialized = true;

// aggregator is now on the list of attested aggregators and its host address is available
attested[_aggregatorID] = true;
// enclave is now on the list of attested enclaves (and its host address is published for p2p)
attested[_enclaveID] = true;
hostAddresses.push(_hostAddress);
}

// Aggregators can request the Network Secret given an attestation request report
// Enclaves can request the Network Secret given an attestation request report
function RequestNetworkSecret(string calldata requestReport) public {
// todo (@matt) should we store the request report against enclaveID?
// Attestations should only be allowed to produce once ?
attestationRequests[msg.sender] = requestReport;
// attestationRequests[msg.sender] = requestReport;
}

// Attested node will pickup on Network Secret Request
// and if valid will respond with the Network Secret
// An attested enclave will pickup the Network Secret Request
// and, if valid, will respond with the Network Secret
// and mark the requesterID as attested
// @param verifyAttester Whether to ask the attester to complete a challenge (signing a hash) to prove their identity.
function RespondNetworkSecret(address attesterID, address requesterID, bytes memory attesterSig, bytes memory responseSecret, string memory hostAddress, bool verifyAttester) public {
// only attested aggregators can respond to Network Secret Requests
bool isAggAttested = attested[attesterID];
require(isAggAttested);
// only attested enclaves can respond to Network Secret Requests
bool isEnclAttested = attested[attesterID];
require(isEnclAttested, "responding attester is not attested");

if (verifyAttester) {

Expand All @@ -127,9 +131,9 @@ contract ManagementContract is Initializable, OwnableUpgradeable {
address recoveredAddrSignedCalculated = ECDSA.recover(calculatedHashSigned, attesterSig);

require(recoveredAddrSignedCalculated == attesterID, "calculated address and attesterID dont match");
}
}

// mark the requesterID aggregator as an attested aggregator and store its host address
// mark the requesterID enclave as an attested enclave and store its host address
attested[requesterID] = true;
// TODO - Consider whether to remove duplicates.
hostAddresses.push(hostAddress);
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/management/Structs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as MessageBusStructs from "../messaging/Structs.sol";
interface Structs {
struct MetaRollup{
bytes32 Hash;
address AggregatorID;
address EnclaveID;
uint256 LastSequenceNumber;
}

Expand Down
3 changes: 2 additions & 1 deletion go/common/enclave.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ type BlockSubmissionResponse struct {
// ProducedSecretResponse contains the data to publish to L1 in response to a secret request discovered while processing an L1 block
type ProducedSecretResponse struct {
Secret []byte
RequesterID gethcommon.Address
RequesterID gethcommon.Address // enclaveID of the enclave that requested the secret
AttesterID gethcommon.Address // enclaveID of the enclave that produced the secret
HostAddress string
}

Expand Down
4 changes: 2 additions & 2 deletions go/common/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ func (b *BatchHeader) UnmarshalJSON(data []byte) error {
// RollupHeader is a public / plaintext struct that holds common properties of rollups.
// All these fields are processed by the Management contract
type RollupHeader struct {
Coinbase common.Address
CompressionL1Head L1BlockHash // the l1 block that the sequencer considers canonical at the time when this rollup is created
EnclaveID common.Address // the enclave that signed the rollup
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need the enclaveId here?
It should be derive-able from the signature, afaict:
sig -> pub key -> enclaveId

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah agree this doesn't seem quite right.

My understanding (or rather chatGPT's understanding 😅) is that having the R and S signature values and the original hash isn't enough to derive the public key.

If we had the signature bytes (the format with a 'recoveryID' extra byte on the end) we would be able to recover it. Should we be including that in the header instead of just the R and S maybe?

CompressionL1Head L1BlockHash // the l1 block that the sequencer considers canonical at the time when this rollup is created

CrossChainMessages []MessageBus.StructsCrossChainMessage `json:"crossChainMessages"`

Expand Down
10 changes: 6 additions & 4 deletions go/common/rpc/converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import (
// Protobuf message classes.

func ToAttestationReportMsg(report *common.AttestationReport) generated.AttestationReportMsg {
return generated.AttestationReportMsg{Report: report.Report, PubKey: report.PubKey, Owner: report.Owner.Bytes(), HostAddress: report.HostAddress}
return generated.AttestationReportMsg{Report: report.Report, PubKey: report.PubKey, EnclaveID: report.EnclaveID.Bytes(), HostAddress: report.HostAddress}
}

func FromAttestationReportMsg(msg *generated.AttestationReportMsg) *common.AttestationReport {
return &common.AttestationReport{
Report: msg.Report,
PubKey: msg.PubKey,
Owner: gethcommon.BytesToAddress(msg.Owner),
EnclaveID: gethcommon.BytesToAddress(msg.EnclaveID),
HostAddress: msg.HostAddress,
}
}
Expand All @@ -46,6 +46,7 @@ func ToSecretRespMsg(responses []*common.ProducedSecretResponse) []*generated.Se
msg := generated.SecretResponseMsg{
Secret: resp.Secret,
RequesterID: resp.RequesterID.Bytes(),
AttesterID: resp.AttesterID.Bytes(),
HostAddress: resp.HostAddress,
}
respMsgs[i] = &msg
Expand All @@ -61,6 +62,7 @@ func FromSecretRespMsg(secretResponses []*generated.SecretResponseMsg) []*common
r := common.ProducedSecretResponse{
Secret: msgResp.Secret,
RequesterID: gethcommon.BytesToAddress(msgResp.RequesterID),
AttesterID: gethcommon.BytesToAddress(msgResp.AttesterID),
HostAddress: msgResp.HostAddress,
}
respList[i] = &r
Expand Down Expand Up @@ -223,7 +225,7 @@ func ToRollupHeaderMsg(header *common.RollupHeader) *generated.RollupHeaderMsg {
CompressionL1Head: header.CompressionL1Head.Bytes(),
R: header.R.Bytes(),
S: header.S.Bytes(),
Coinbase: header.Coinbase.Bytes(),
EnclaveID: header.EnclaveID.Bytes(),
CrossChainMessages: ToCrossChainMsgs(header.CrossChainMessages),
LastBatchSeqNo: header.LastBatchSeqNo,
}
Expand Down Expand Up @@ -256,7 +258,7 @@ func FromRollupHeaderMsg(header *generated.RollupHeaderMsg) *common.RollupHeader
CompressionL1Head: gethcommon.BytesToHash(header.CompressionL1Head),
R: r.SetBytes(header.R),
S: s.SetBytes(header.S),
Coinbase: gethcommon.BytesToAddress(header.Coinbase),
EnclaveID: gethcommon.BytesToAddress(header.EnclaveID),
CrossChainMessages: FromCrossChainMsgs(header.CrossChainMessages),
LastBatchSeqNo: header.LastBatchSeqNo,
}
Expand Down
Loading
Loading