-
Notifications
You must be signed in to change notification settings - Fork 123
feat: add input encryption for validators #2342
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
base: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| use miden_node_proto::generated as grpc; | ||
| use miden_node_utils::tracing::miden_instrument; | ||
| use miden_tx::utils::serde::Serializable; | ||
|
|
||
| use super::ValidatorService; | ||
| use crate::COMPONENT; | ||
|
|
||
| #[tonic::async_trait] | ||
| impl grpc::server::validator_api::GetTransactionEncryptionKey for ValidatorService { | ||
| type Input = (); | ||
| type Output = grpc::transaction::TransactionEncryptionKey; | ||
|
|
||
| fn decode(request: ()) -> tonic::Result<Self::Input> { | ||
| Ok(request) | ||
| } | ||
|
|
||
| fn encode(output: Self::Output) -> tonic::Result<grpc::transaction::TransactionEncryptionKey> { | ||
| Ok(output) | ||
| } | ||
|
|
||
| #[miden_instrument( | ||
| target = COMPONENT, | ||
| name = "get_transaction_encryption_key", | ||
| skip_all, | ||
| err, | ||
| )] | ||
| async fn handle( | ||
| &self, | ||
| _input: Self::Input, | ||
| _metadata: &tonic::metadata::MetadataMap, | ||
| _extensions: &tonic::codegen::http::Extensions, | ||
| ) -> tonic::Result<Self::Output> { | ||
| // Built entirely from state fixed at construction, so the endpoint stays available while a | ||
| // backup subscription holds the serve lock. | ||
| Ok(grpc::transaction::TransactionEncryptionKey { | ||
| scheme: self.encryption_key_info.scheme, | ||
| key_id: self.encryption_key_info.key_id.clone(), | ||
| public_key: self.encryption_key_info.public_key.clone(), | ||
| signature: self.encryption_key_attestation.to_bytes(), | ||
|
Comment on lines
+35
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The endpoint should not call
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tackled in cfe6987 |
||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,12 +19,14 @@ use miden_protocol::transaction::{TransactionHeader, TransactionId}; | |
| use tokio::sync::{Semaphore, watch}; | ||
|
|
||
| use crate::db::{find_unvalidated_transactions, load_block_header, load_chain_tip}; | ||
| use crate::{COMPONENT, ValidatorSigner}; | ||
| use crate::signers::TransactionEncryptionKeyInfo; | ||
| use crate::{COMPONENT, TransactionInputDecryptor, ValidatorSigner}; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| mod block_subscription; | ||
| mod get_transaction_encryption_key; | ||
| mod sign_block; | ||
| mod status; | ||
| mod submit_proven_transaction; | ||
|
|
@@ -61,6 +63,10 @@ pub enum ValidatorError { | |
| BlockBackupFailed(#[source] std::io::Error), | ||
| #[error("expected a single-key validator set, got {actual} keys")] | ||
| UnexpectedValidatorSetSize { actual: usize }, | ||
| #[error("no genesis block header exists")] | ||
| NoGenesisHeader, | ||
| #[error("failed to attest the transaction encryption key: {0}")] | ||
| EncryptionKeyAttestationFailed(String), | ||
| } | ||
|
|
||
| // VALIDATOR SERVICE | ||
|
|
@@ -71,6 +77,14 @@ pub enum ValidatorError { | |
| /// Implements the gRPC API for the validator. | ||
| pub(crate) struct ValidatorService { | ||
| signer: ValidatorSigner, | ||
| /// Decryptor for transaction inputs sealed against the shared encryption key. | ||
| #[expect(dead_code, reason = "used by the submit path in a follow-up PR")] | ||
| decryptor: Arc<dyn TransactionInputDecryptor>, | ||
| /// Public metadata of the shared encryption key, fetched once at construction. | ||
| encryption_key_info: TransactionEncryptionKeyInfo, | ||
| /// Signature by this validator's own signing key over the encryption key attestation | ||
| /// commitment, computed once at construction. | ||
| encryption_key_attestation: Signature, | ||
| db: Arc<Database>, | ||
| block_store: BlockStore, | ||
| /// Enforces mutual exclusion between backup block subscriptions and all other RPCs. Regular | ||
|
|
@@ -93,6 +107,7 @@ pub(crate) struct ValidatorService { | |
| impl ValidatorService { | ||
| pub(crate) async fn new( | ||
| signer: ValidatorSigner, | ||
| decryptor: Arc<dyn TransactionInputDecryptor>, | ||
| db: Database, | ||
| block_store: BlockStore, | ||
| initial_chain_tip: u32, | ||
|
|
@@ -121,8 +136,28 @@ impl ValidatorService { | |
| }); | ||
| } | ||
|
|
||
| // Both keys are fixed for the process lifetime, so the attestation is computed once. This | ||
| // also keeps KMS-backed signers to a single signing call. | ||
| let genesis_commitment = db | ||
| .read("load_genesis_header", |tx| load_block_header(tx, BlockNumber::GENESIS)) | ||
| .await | ||
| .map_err(ValidatorError::DatabaseError)? | ||
| .ok_or(ValidatorError::NoGenesisHeader)? | ||
| .commitment(); | ||
| let encryption_key_info = decryptor | ||
| .encryption_key() | ||
| .await | ||
| .map_err(|err| ValidatorError::EncryptionKeyAttestationFailed(err.to_string()))?; | ||
| let encryption_key_attestation = signer | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly I think this can just sign the key metadata returned by the decryptor, rather than enforcing a call to
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in cfe6987 |
||
| .sign_commitment(encryption_key_info.attestation_commitment(genesis_commitment)) | ||
| .await | ||
| .map_err(|err| ValidatorError::EncryptionKeyAttestationFailed(err.to_string()))?; | ||
|
|
||
| Ok(Self { | ||
| signer, | ||
| decryptor, | ||
| encryption_key_info, | ||
| encryption_key_attestation, | ||
| serve_lock: Arc::new(tokio::sync::RwLock::new(())), | ||
| db: db.into(), | ||
| block_store, | ||
|
|
@@ -254,7 +289,7 @@ impl ValidatorService { | |
| )] | ||
| async fn sign_header(&self, header: &BlockHeader) -> Result<Signature, ValidatorError> { | ||
| self.signer | ||
| .sign(header) | ||
| .sign_commitment(header.commitment()) | ||
| .await | ||
| .map_err(|err| ValidatorError::BlockSigningFailed(err.to_string())) | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.