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

Introduce EnclaveID as identifier #1760

Merged
merged 6 commits into from
Jan 30, 2024
Merged

Introduce EnclaveID as identifier #1760

merged 6 commits into from
Jan 30, 2024

Conversation

BedrockSquirrel
Copy link
Collaborator

Why this change is needed

EnclaveID will be the identifier for enclaves that join the protocol.

It is calculated from the enclave's public key generated when it first starts up.

What changes were made as part of this PR

Add method to provide EnclaveID to the host. Have the guardian request the EnclaveID before starting its main loop and use it in its logging (this will be necessary when a host has more than one guardian).

PR checks pre-merging

Please indicate below by ticking the checkbox that you have read and performed the required
PR checks

  • PR checks reviewed and performed

Copy link

coderabbitai bot commented Jan 26, 2024

Warning

Rate Limit Exceeded

@BedrockSquirrel has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 4 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.
Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
Please see our FAQ for further information.

Commits Files that changed from the base of the PR and between 760188d and 7733d67.

Walkthrough

The recent updates introduce a new functionality for managing enclave IDs, enhancing the system's ability to identify and handle enclaves. A unique ID, derived from the hash of the enclave's public key, is now generated and associated with each enclave. This addition is reflected across various components, including the enclave interface, logging mechanisms, and RPC servers, to ensure a cohesive and secure identification process throughout the system.

Changes

File(s) Change Summary
go/common/enclave.go, go/enclave/enclave.go, go/enclave/rpc_server.go, go/host/enclave/guardian.go, go/host/rpc/.../enclave_client.go Introduced a new method EnclaveID() to generate and manage enclave IDs, updated structures and RPC handling to incorporate the new ID system.
go/common/log/log.go Added a constant EnclaveIDKey for logging purposes.
go/common/types.go Added EnclaveID as an alias for common.Hash to represent the enclave's ID.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 26d7db4 and 8a32a8b.
Files ignored due to path filters (3)
  • go/common/rpc/generated/enclave.pb.go is excluded by: !**/*.pb.go, !**/generated/**
  • go/common/rpc/generated/enclave.proto is excluded by: !**/generated/**
  • go/common/rpc/generated/enclave_grpc.pb.go is excluded by: !**/*.pb.go, !**/generated/**
Files selected for processing (7)
  • go/common/enclave.go (1 hunks)
  • go/common/log/log.go (1 hunks)
  • go/common/types.go (1 hunks)
  • go/enclave/enclave.go (4 hunks)
  • go/enclave/rpc_server.go (1 hunks)
  • go/host/enclave/guardian.go (3 hunks)
  • go/host/rpc/enclaverpc/enclave_client.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • go/common/types.go
Additional comments: 10
go/common/log/log.go (1)
  • 24-24: The addition of the EnclaveIDKey constant is consistent with the naming conventions and context of existing log keys. This change is straightforward and does not introduce any issues.
go/common/enclave.go (1)
  • 48-50: The addition of the EnclaveID() method to the Enclave interface is correctly implemented. The method signature is consistent with the interface's design, returning both an EnclaveID and a SystemError, which aligns with the error handling pattern used throughout the interface.
go/enclave/rpc_server.go (1)
  • 110-117: The implementation of the EnclaveID method in the RPCServer struct correctly retrieves the enclave's ID from the underlying enclave interface. Error handling and logging are appropriately implemented, following the established patterns in the file.
go/host/rpc/enclaverpc/enclave_client.go (1)
  • 149-161: The implementation of the EnclaveID method in the Client type correctly initiates an RPC call to retrieve the enclave ID, with appropriate timeout handling and error checking. The method returns the expected EnclaveID type and a SystemError, aligning with the interface's design.
go/host/enclave/guardian.go (2)
  • 41-41: The introduction of _emptyEnclaveID as a global variable is a good practice for representing an uninitialized or default state of EnclaveID. This approach simplifies comparisons and checks throughout the code.
  • 78-78: Adding enclaveID to the Guardian struct aligns with the PR's objective to track and utilize the unique identifier of enclaves. This field is essential for distinguishing between different enclaves, especially in systems managing multiple enclaves.
go/enclave/enclave.go (4)
  • 90-90: The enclaveID field in the enclaveImpl struct is correctly added to support the new EnclaveID functionality.
  • 172-173: The calculation of enclaveID using gethcrypto.Keccak256 on the serialized public key is a secure and appropriate method for generating a unique identifier based on the enclave's cryptographic identity.
  • 271-271: Assigning the calculated enclaveID to the enclaveImpl struct's enclaveID field is correctly done, ensuring that the enclave's identifier is stored and accessible.
  • 907-909: The addition of the EnclaveID() method to the enclaveImpl struct is correctly implemented, providing a way to retrieve the enclave's identifier.

Comment on lines 101 to 117
if g.enclaveID != _emptyEnclaveID {
return errors.New("guardian already started")
}

// Identify the enclave before starting (the enclave generates its ID immediately at startup)
// (retry until we get the enclave ID or the host is stopping)
for g.enclaveID == _emptyEnclaveID && !g.hostInterrupter.IsStopping() {
enclID, err := g.enclaveClient.EnclaveID()
if err != nil {
g.logger.Warn("could not get enclave ID", log.ErrKey, err)
time.Sleep(_retryInterval)
continue
}
g.enclaveID = enclID
// include the enclave ID in guardian log messages (for multi-enclave nodes)
g.logger = g.logger.New(log.EnclaveIDKey, g.enclaveID)
g.logger.Info("Starting guardian process.")
Copy link

Choose a reason for hiding this comment

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

The logic added to the Start method for retrieving and setting the EnclaveID is well-implemented. The use of a loop with a retry mechanism (lines 107-113) ensures that the Guardian can successfully obtain the EnclaveID from the enclave client, even if initial attempts fail. Additionally, the inclusion of the enclaveID in log messages (line 116) enhances the traceability of actions related to specific enclaves. However, consider adding a maximum retry limit or a timeout to prevent an indefinite loop in scenarios where the EnclaveID cannot be retrieved due to persistent issues.

+ var maxRetries = 5 // or another appropriate limit
+ var retries = 0
  for g.enclaveID == _emptyEnclaveID && !g.hostInterrupter.IsStopping() && retries < maxRetries {
      ...
+     retries++
  }
+ if retries == maxRetries {
+     return errors.New("failed to obtain enclave ID after maximum retries")
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if g.enclaveID != _emptyEnclaveID {
return errors.New("guardian already started")
}
// Identify the enclave before starting (the enclave generates its ID immediately at startup)
// (retry until we get the enclave ID or the host is stopping)
for g.enclaveID == _emptyEnclaveID && !g.hostInterrupter.IsStopping() {
enclID, err := g.enclaveClient.EnclaveID()
if err != nil {
g.logger.Warn("could not get enclave ID", log.ErrKey, err)
time.Sleep(_retryInterval)
continue
}
g.enclaveID = enclID
// include the enclave ID in guardian log messages (for multi-enclave nodes)
g.logger = g.logger.New(log.EnclaveIDKey, g.enclaveID)
g.logger.Info("Starting guardian process.")
if g.enclaveID != _emptyEnclaveID {
return errors.New("guardian already started")
}
// Identify the enclave before starting (the enclave generates its ID immediately at startup)
// (retry until we get the enclave ID or the host is stopping)
+ var maxRetries = 5 // or another appropriate limit
+ var retries = 0
for g.enclaveID == _emptyEnclaveID && !g.hostInterrupter.IsStopping() && retries < maxRetries {
enclID, err := g.enclaveClient.EnclaveID()
if err != nil {
g.logger.Warn("could not get enclave ID", log.ErrKey, err)
time.Sleep(_retryInterval)
+ retries++
continue
}
g.enclaveID = enclID
// include the enclave ID in guardian log messages (for multi-enclave nodes)
g.logger = g.logger.New(log.EnclaveIDKey, g.enclaveID)
g.logger.Info("Starting guardian process.")
+ if retries == maxRetries {
+ return errors.New("failed to obtain enclave ID after maximum retries")
+ }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 8a32a8b and c8a7778.
Files selected for processing (1)
  • go/host/enclave/guardian.go (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • go/host/enclave/guardian.go

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between c8a7778 and f475262.
Files selected for processing (2)
  • go/common/types.go (1 hunks)
  • go/enclave/enclave.go (4 hunks)
Files skipped from review as they are similar to previous changes (2)
  • go/common/types.go
  • go/enclave/enclave.go

Copy link
Collaborator

@tudor-malene tudor-malene left a comment

Choose a reason for hiding this comment

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

Looks good.
Minor comments and a small refactoring suggestion

go/common/rpc/generated/enclave.proto Outdated Show resolved Hide resolved
go/host/enclave/guardian.go Outdated Show resolved Hide resolved
@@ -87,6 +87,7 @@ type enclaveImpl struct {

enclaveKey *ecdsa.PrivateKey // this is a key specific to this enclave, which is included in the Attestation. Used for signing rollups and for encryption of the shared secret.
Copy link
Collaborator

Choose a reason for hiding this comment

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

feels like we should group these fields and the logic into a struct that only has that responsibility.

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 good shout. Is the new commit the sort of thing you had in mind?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 4

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between f475262 and 760188d.
Files ignored due to path filters (1)
  • go/common/rpc/generated/enclave.proto is excluded by: !**/generated/**
Files selected for processing (6)
  • go/enclave/crypto/enclave_key.go (1 hunks)
  • go/enclave/enclave.go (9 hunks)
  • go/enclave/nodetype/sequencer.go (5 hunks)
  • go/enclave/storage/interfaces.go (1 hunks)
  • go/enclave/storage/storage.go (1 hunks)
  • go/host/enclave/guardian.go (2 hunks)
Files skipped from review as they are similar to previous changes (1)
  • go/host/enclave/guardian.go
Additional comments: 12
go/enclave/crypto/enclave_key.go (2)
  • 12-18: The function GenerateEnclaveKey correctly generates a new enclave key using Ethereum's crypto library and wraps it with NewEnclaveKey. This encapsulation ensures that the enclave key, along with its derived enclave ID, is properly initialized.
  • 30-51: The EnclaveKey struct and its associated methods (PrivateKey, PublicKey, EnclaveID, PublicKeyBytes) are correctly implemented, providing encapsulated access to the enclave's cryptographic materials and the derived enclave ID. This encapsulation promotes good security practices by controlling access to sensitive information.
go/enclave/storage/interfaces.go (1)
  • 117-118: The update to EnclaveKeyStorage interface to use *crypto.EnclaveKey for both storing and retrieving enclave keys aligns with the objective of enhancing the management of enclave keys. This change ensures that the enclave key and its associated data, such as the enclave ID, are handled together, improving the system's overall design coherence.
go/enclave/nodetype/sequencer.go (4)
  • 57-57: The replacement of enclavePrivateKey with enclaveKey of type *crypto.EnclaveKey in the sequencer struct is a significant improvement. It ensures that the sequencer has access to both the private key and the enclave ID, facilitating more secure and identifiable operations.
  • 63-69: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [66-78]

The NewSequencer function's signature change to accept *crypto.EnclaveKey is correctly implemented. This change is necessary to accommodate the updated sequencer struct and ensures that the sequencer is initialized with the correct type of key.

  • 425-425: The signBatch method's update to use s.enclaveKey.PrivateKey() for signing batches is correct and necessary due to the changes in the sequencer struct. This ensures that the method continues to function correctly with the new key management approach.
  • 435-435: Similarly, the signRollup method's update to use s.enclaveKey.PrivateKey() for signing rollups is correctly implemented. This consistency in using the crypto.EnclaveKey type across the sequencer's operations is crucial for maintaining the integrity of the signing process.
go/enclave/enclave.go (5)
  • 87-87: The enclaveKey field has been modified to use the *crypto.EnclaveKey type. Ensure that all references and operations on enclaveKey throughout the codebase are updated to reflect this change.
  • 858-861: The method Attestation checks if enclaveKey is nil before proceeding. This is a good practice for null safety. However, ensure that there are no scenarios where enclaveKey could be nil during normal operation, as this would indicate a more significant issue in the enclave initialization process.
  • 879-879: The method GenerateSecret uses crypto.EncryptSecret which is expected to handle the encryption securely. Verify that the encryption standards (e.g., AES-GCM) and key management practices are up to the current security standards.
  • 892-892: In InitEnclave, the secret is decrypted with crypto.DecryptSecret. Ensure that error handling around decryption is robust, especially handling decryption failures due to incorrect keys or tampered data.
  • 904-906: The addition of the EnclaveID method is correctly implemented and follows the changes described. This method provides a straightforward way to retrieve the EnclaveID, enhancing the code's readability and maintainability.

Comment on lines +20 to +28
func NewEnclaveKey(privKey *ecdsa.PrivateKey) *EnclaveKey {
pubKey := gethcrypto.CompressPubkey(&privKey.PublicKey)
enclaveID := common.EnclaveID(pubKey[gethcommon.HashLength-gethcommon.AddressLength:])
return &EnclaveKey{
privateKey: privKey,
publicKeyBytes: pubKey,
enclaveID: enclaveID,
}
}
Copy link

Choose a reason for hiding this comment

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

The NewEnclaveKey function correctly generates the enclave ID from the public key's bytes. However, the slicing operation to derive the enclave ID from the public key bytes assumes the length of the public key bytes and the constants from gethcommon will align correctly. This could be fragile if underlying assumptions change. Consider adding a check to ensure pubKey has the expected length before slicing.

+ if len(pubKey) < gethcommon.HashLength {
+   return nil, errors.New("public key bytes length is less than expected")
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func NewEnclaveKey(privKey *ecdsa.PrivateKey) *EnclaveKey {
pubKey := gethcrypto.CompressPubkey(&privKey.PublicKey)
enclaveID := common.EnclaveID(pubKey[gethcommon.HashLength-gethcommon.AddressLength:])
return &EnclaveKey{
privateKey: privKey,
publicKeyBytes: pubKey,
enclaveID: enclaveID,
}
}
func NewEnclaveKey(privKey *ecdsa.PrivateKey) *EnclaveKey {
pubKey := gethcrypto.CompressPubkey(&privKey.PublicKey)
if len(pubKey) < gethcommon.HashLength {
return nil, errors.New("public key bytes length is less than expected")
}
enclaveID := common.EnclaveID(pubKey[gethcommon.HashLength-gethcommon.AddressLength:])
return &EnclaveKey{
privateKey: privKey,
publicKeyBytes: pubKey,
enclaveID: enclaveID,
}
}

Comment on lines +464 to +469
func (s *storageImpl) StoreEnclaveKey(enclaveKey *crypto.EnclaveKey) error {
defer s.logDuration("StoreEnclaveKey", measure.NewStopwatch())
if enclaveKey == nil {
return errors.New("enclaveKey cannot be nil")
}
keyBytes := gethcrypto.FromECDSA(enclaveKey)
keyBytes := gethcrypto.FromECDSA(enclaveKey.PrivateKey())
Copy link

Choose a reason for hiding this comment

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

The StoreEnclaveKey method correctly serializes the private key part of crypto.EnclaveKey for storage. This approach is secure as it only stores the private key, which is necessary for enclave operations. However, ensure that the key is stored securely and consider encrypting the key before storage for enhanced security.

Comment on lines +475 to +485
func (s *storageImpl) GetEnclaveKey() (*crypto.EnclaveKey, error) {
defer s.logDuration("GetEnclaveKey", measure.NewStopwatch())
keyBytes, err := enclavedb.FetchConfig(s.db.GetSQLDB(), enclaveKeyKey)
if err != nil {
return nil, err
}
enclaveKey, err := gethcrypto.ToECDSA(keyBytes)
ecdsaKey, err := gethcrypto.ToECDSA(keyBytes)
if err != nil {
return nil, fmt.Errorf("unable to construct ECDSA private key from enclave key bytes - %w", err)
}
return enclaveKey, nil
return crypto.NewEnclaveKey(ecdsaKey), nil
Copy link

Choose a reason for hiding this comment

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

The GetEnclaveKey method correctly retrieves and constructs an *crypto.EnclaveKey from stored bytes. This method ensures that the enclave key can be reconstructed accurately for use in the system. However, consider adding error handling for cases where the stored key might not exist or is corrupted.

Comment on lines 165 to 175
}
}

serializedEnclavePubKey := gethcrypto.CompressPubkey(&enclaveKey.PublicKey)
logger.Info(fmt.Sprintf("Generated public key %s", gethcommon.Bytes2Hex(serializedEnclavePubKey)))
serializedEnclavePubKey := gethcrypto.CompressPubkey(enclaveKey.PublicKey())
pubKeyHash := gethcrypto.Keccak256(serializedEnclavePubKey)
// the enclave ID is the address for its keypair (like eth addresses, the last 20 bytes of the hash of the public key)
enclaveID := pubKeyHash[gethcommon.HashLength-gethcommon.AddressLength:]
logger.Info(fmt.Sprintf("Generated public key. EnclaveID=%s, publicKey=%s", enclaveID, gethcommon.Bytes2Hex(serializedEnclavePubKey)))

obscuroKey := crypto.GetObscuroKey(logger)
rpcEncryptionManager := rpc.NewEncryptionManager(ecies.ImportECDSA(obscuroKey))
Copy link

Choose a reason for hiding this comment

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

📝 NOTE
This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [158-171]

The logic for generating the EnclaveID based on the public key's hash is correct and follows the Ethereum address generation pattern. However, consider encapsulating this logic within the crypto package or a method on the EnclaveKey type to improve modularity and reusability.

- serializedEnclavePubKey := gethcrypto.CompressPubkey(enclaveKey.PublicKey())
- pubKeyHash := gethcrypto.Keccak256(serializedEnclavePubKey)
- enclaveID := pubKeyHash[gethcommon.HashLength-gethcommon.AddressLength:]
+ enclaveID := enclaveKey.GenerateEnclaveID()

And in crypto package:

func (k *EnclaveKey) GenerateEnclaveID() common.EnclaveID {
    serializedPubKey := gethcrypto.CompressPubkey(k.PublicKey())
    pubKeyHash := gethcrypto.Keccak256(serializedPubKey)
    return pubKeyHash[gethcommon.HashLength-gethcommon.AddressLength:]
}

Copy link
Collaborator

@tudor-malene tudor-malene left a comment

Choose a reason for hiding this comment

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

lgtm

@BedrockSquirrel BedrockSquirrel merged commit b69bb7f into main Jan 30, 2024
2 checks passed
@BedrockSquirrel BedrockSquirrel deleted the matt/enclave-id branch January 30, 2024 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants