From 8a1b84a331282b6b31a0742e7ce50dde1ca8681c Mon Sep 17 00:00:00 2001 From: Mirko von Leipzig <48352201+Mirko-von-Leipzig@users.noreply.github.com> Date: Wed, 6 May 2026 13:34:17 +0200 Subject: [PATCH] Remove BlockSigner trait --- bin/genesis/src/main.rs | 10 ++-- bin/node/src/commands/validator.rs | 47 +++++++---------- bin/stress-test/src/seeding/mod.rs | 7 ++- crates/block-producer/src/server/tests.rs | 6 +-- crates/rpc/src/tests.rs | 13 ++--- crates/store/src/db/tests.rs | 20 ++++--- crates/store/src/genesis/config/mod.rs | 12 ++--- crates/store/src/genesis/config/tests.rs | 23 +++++---- crates/store/src/genesis/mod.rs | 63 ++++++++++++++++------- crates/utils/src/lib.rs | 1 - crates/utils/src/signer.rs | 36 ------------- crates/validator/src/server/tests.rs | 16 +++--- crates/validator/src/signers/kms.rs | 15 ++---- crates/validator/src/signers/mod.rs | 40 +++++++------- 14 files changed, 146 insertions(+), 163 deletions(-) delete mode 100644 crates/utils/src/signer.rs diff --git a/bin/genesis/src/main.rs b/bin/genesis/src/main.rs index 0b66588273..4244ee6b13 100644 --- a/bin/genesis/src/main.rs +++ b/bin/genesis/src/main.rs @@ -205,17 +205,17 @@ mod tests { /// Parses the generated genesis.toml, builds a genesis block, and asserts the bridge account /// is included with nonce=1. - async fn assert_valid_genesis_block(dir: &Path) { + fn assert_valid_genesis_block(dir: &Path) { let bridge_id = AccountFile::read(dir.join("bridge.mac")).unwrap().account.id(); let config = GenesisConfig::read_toml_file(&dir.join("genesis.toml")).unwrap(); let signer = SecretKey::read_from_bytes(&[0x01; 32]).unwrap(); - let (state, _) = config.into_state(signer).unwrap(); + let (state, _) = config.into_state(signer.public_key()).unwrap(); let bridge = state.accounts.iter().find(|a| a.id() == bridge_id).unwrap(); assert_eq!(bridge.nonce(), ONE); - state.into_block().await.expect("genesis block should build"); + state.into_block(&signer).expect("genesis block should build"); } #[tokio::test] @@ -229,7 +229,7 @@ mod tests { let ger = AccountFile::read(dir.path().join("ger_manager.mac")).unwrap(); assert_eq!(ger.auth_secret_keys.len(), 1); - assert_valid_genesis_block(dir.path()).await; + assert_valid_genesis_block(dir.path()); } #[tokio::test] @@ -249,6 +249,6 @@ mod tests { let ger = AccountFile::read(dir.path().join("ger_manager.mac")).unwrap(); assert!(ger.auth_secret_keys.is_empty()); - assert_valid_genesis_block(dir.path()).await; + assert_valid_genesis_block(dir.path()); } } diff --git a/bin/node/src/commands/validator.rs b/bin/node/src/commands/validator.rs index 8e1e9fdf9f..ce80c97c83 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/node/src/commands/validator.rs @@ -6,7 +6,6 @@ use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::clap::GrpcOptionsInternal; use miden_node_utils::fs::ensure_empty_directory; use miden_node_utils::grpc::UrlExt; -use miden_node_utils::signer::BlockSigner; use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::serde::{Deserializable, Serializable}; @@ -196,28 +195,14 @@ impl ValidatorCommand { // Bootstrap with KMS key or local key. let signer = validator_key.into_signer().await?; - match signer { - ValidatorSigner::Kms(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, - ValidatorSigner::Local(signer) => { - build_and_write_genesis( - config, - signer, - accounts_directory, - genesis_block_directory, - data_directory, - ) - .await - }, - } + build_and_write_genesis( + config, + signer, + accounts_directory, + genesis_block_directory, + data_directory, + ) + .await } } @@ -225,13 +210,13 @@ impl ValidatorCommand { /// to disk, and initializes the validator's database with the genesis block as the chain tip. async fn build_and_write_genesis( config: GenesisConfig, - signer: impl BlockSigner, + signer: ValidatorSigner, accounts_directory: &Path, genesis_block_directory: &Path, data_directory: &Path, ) -> anyhow::Result<()> { // Build genesis state with the provided signer. - let (genesis_state, secrets) = config.into_state(signer)?; + let (genesis_state, secrets) = config.into_state(signer.public_key())?; // Write account secret files. for item in secrets.as_account_files(&genesis_state) { @@ -247,8 +232,16 @@ async fn build_and_write_genesis( } // Build the signed genesis block. - let genesis_block = - genesis_state.into_block().await.context("failed to build the genesis block")?; + let unsigned_genesis_block = genesis_state + .into_unsigned_block() + .context("failed to build the unsigned genesis block")?; + let signature = signer + .sign(unsigned_genesis_block.header()) + .await + .context("failed to sign the genesis block")?; + let genesis_block = unsigned_genesis_block + .into_block(signature) + .context("failed to build the genesis block")?; // Serialize and write the genesis block to disk. let block_bytes = genesis_block.inner().to_bytes(); diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index f1da81554f..28b70b68aa 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -119,11 +119,10 @@ pub async fn seed_store( let asset_faucet_ids = benchmark_faucets.iter().map(Account::id).collect::>(); let fee_params = FeeParameters::new(faucet.id(), 0).unwrap(); let signer = EcdsaSecretKey::new(); - let genesis_state = GenesisState::new(benchmark_faucets, fee_params, 1, 1, signer.clone()); + let genesis_state = GenesisState::new(benchmark_faucets, fee_params, 1, 1, signer.public_key()); let genesis_block = genesis_state .clone() - .into_block() - .await + .into_block(&signer) .expect("genesis block should be created"); Store::bootstrap(genesis_block, &data_directory).expect("store should bootstrap"); @@ -135,7 +134,7 @@ pub async fn seed_store( let accounts_filepath = data_directory.join(ACCOUNTS_FILENAME); let data_directory = miden_node_store::DataDirectory::load(data_directory).expect("data directory should exist"); - let genesis_header = genesis_state.into_block().await.unwrap().into_inner(); + let genesis_header = genesis_state.into_block(&signer).unwrap().into_inner(); let metrics = generate_blocks( num_accounts, public_accounts_percentage, diff --git a/crates/block-producer/src/server/tests.rs b/crates/block-producer/src/server/tests.rs index c63613ce02..73055deff5 100644 --- a/crates/block-producer/src/server/tests.rs +++ b/crates/block-producer/src/server/tests.rs @@ -130,11 +130,11 @@ async fn start_store( store_addr: std::net::SocketAddr, data_directory: &std::path::Path, ) -> runtime::Runtime { - let genesis_state = GenesisState::new(vec![], test_fee_params(), 1, 1, random_secret_key()); + let signer = random_secret_key(); + let genesis_state = GenesisState::new(vec![], test_fee_params(), 1, 1, signer.public_key()); let genesis_block = genesis_state .clone() - .into_block() - .await + .into_block(&signer) .expect("genesis block should be created"); Store::bootstrap(genesis_block, data_directory).expect("store should bootstrap"); diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index a1e85e7737..f892316d85 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -526,12 +526,12 @@ async fn start_store(store_listener: TcpListener) -> (Runtime, TempDir, Word, So let config = GenesisConfig::default(); let signer = SecretKey::new(); - let (genesis_state, _) = config.into_state(signer).unwrap(); + let (genesis_state, _) = config.into_state(signer.public_key()).unwrap(); let genesis_block = genesis_state .clone() - .into_block() - .await + .into_block(&signer) .expect("genesis block should be created"); + let genesis_commitment = genesis_block.inner().header().commitment(); Store::bootstrap(genesis_block, data_directory.path()).expect("store should bootstrap"); let dir = data_directory.path().to_path_buf(); let store_addr = @@ -562,12 +562,7 @@ async fn start_store(store_listener: TcpListener) -> (Runtime, TempDir, Word, So .await .expect("store should start serving"); }); - ( - store_runtime, - data_directory, - genesis_state.into_block().await.unwrap().inner().header().commitment(), - store_addr, - ) + (store_runtime, data_directory, genesis_commitment, store_addr) } /// Shuts down the store runtime properly to allow `RocksDB` to flush before the temp directory is diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 738889cddd..903bd3a847 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -1953,9 +1953,10 @@ async fn genesis_with_account_assets() { .build_existing() .unwrap(); + let signer = random_secret_key(); let genesis_state = - GenesisState::new(vec![account], test_fee_params(), 1, 0, random_secret_key()); - let genesis_block = genesis_state.into_block().await.unwrap(); + GenesisState::new(vec![account], test_fee_params(), 1, 0, signer.public_key()); + let genesis_block = genesis_state.into_block(&signer).unwrap(); crate::db::Db::bootstrap(":memory:".into(), genesis_block).unwrap(); } @@ -2008,9 +2009,10 @@ async fn genesis_with_account_storage_map() { .build_existing() .unwrap(); + let signer = random_secret_key(); let genesis_state = - GenesisState::new(vec![account], test_fee_params(), 1, 0, random_secret_key()); - let genesis_block = genesis_state.into_block().await.unwrap(); + GenesisState::new(vec![account], test_fee_params(), 1, 0, signer.public_key()); + let genesis_block = genesis_state.into_block(&signer).unwrap(); crate::db::Db::bootstrap(":memory:".into(), genesis_block).unwrap(); } @@ -2061,9 +2063,10 @@ async fn genesis_with_account_assets_and_storage() { .build_existing() .unwrap(); + let signer = random_secret_key(); let genesis_state = - GenesisState::new(vec![account], test_fee_params(), 1, 0, random_secret_key()); - let genesis_block = genesis_state.into_block().await.unwrap(); + GenesisState::new(vec![account], test_fee_params(), 1, 0, signer.public_key()); + let genesis_block = genesis_state.into_block(&signer).unwrap(); crate::db::Db::bootstrap(":memory:".into(), genesis_block).unwrap(); } @@ -2152,14 +2155,15 @@ async fn genesis_with_multiple_accounts() { .build_existing() .unwrap(); + let signer = random_secret_key(); let genesis_state = GenesisState::new( vec![account1, account2, account3], test_fee_params(), 1, 0, - random_secret_key(), + signer.public_key(), ); - let genesis_block = genesis_state.into_block().await.unwrap(); + let genesis_block = genesis_state.into_block(&signer).unwrap(); crate::db::Db::bootstrap(":memory:".into(), genesis_block).unwrap(); } diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 6c70a6f387..d3398f7053 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -6,7 +6,6 @@ use std::str::FromStr; use indexmap::IndexMap; use miden_node_utils::crypto::get_rpo_random_coin; -use miden_node_utils::signer::BlockSigner; use miden_protocol::account::auth::{AuthScheme, AuthSecretKey}; use miden_protocol::account::{ Account, @@ -23,6 +22,7 @@ use miden_protocol::account::{ }; use miden_protocol::asset::{FungibleAsset, TokenSymbol}; use miden_protocol::block::FeeParameters; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey; use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey as RpoSecretKey; use miden_protocol::errors::TokenSymbolError; use miden_protocol::{Felt, ONE}; @@ -141,10 +141,10 @@ impl GenesisConfig { /// /// Also returns the set of secrets for the generated accounts. #[expect(clippy::too_many_lines)] - pub fn into_state( + pub fn into_state( self, - signer: S, - ) -> Result<(GenesisState, AccountSecrets), GenesisConfigError> { + validator_key: PublicKey, + ) -> Result<(GenesisState, AccountSecrets), GenesisConfigError> { let GenesisConfig { version, timestamp, @@ -335,7 +335,7 @@ impl GenesisConfig { accounts: all_accounts, version, timestamp, - block_signer: signer, + validator_key, }, AccountSecrets { secrets }, )) @@ -529,7 +529,7 @@ impl AccountSecrets { /// and the index in pub fn as_account_files( &self, - genesis_state: &GenesisState, + genesis_state: &GenesisState, ) -> impl Iterator> + '_ { let account_lut = IndexMap::::from_iter( genesis_state.accounts.iter().map(|account| (account.id(), account.clone())), diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index acc1b94465..0463396431 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -27,7 +27,8 @@ fn parsing_yields_expected_default_values() -> TestResult { let config_path = write_toml_file(temp_dir.path(), sample_content); let gcfg = GenesisConfig::read_toml_file(&config_path)?; - let (state, _secrets) = gcfg.into_state(SecretKey::new())?; + let signer = SecretKey::new(); + let (state, _secrets) = gcfg.into_state(signer.public_key())?; let _ = state; // faucets always precede wallet accounts let native_faucet = state.accounts[0].clone(); @@ -70,14 +71,15 @@ fn parsing_yields_expected_default_values() -> TestResult { #[miden_node_test_macro::enable_logging] async fn genesis_accounts_have_nonce_one() -> TestResult { let gcfg = GenesisConfig::default(); - let (state, secrets) = gcfg.into_state(SecretKey::new()).unwrap(); + let signer = SecretKey::new(); + let (state, secrets) = gcfg.into_state(signer.public_key()).unwrap(); let mut iter = secrets.as_account_files(&state); let AccountFileWithName { account_file: status_quo, .. } = iter.next().unwrap().unwrap(); assert!(iter.next().is_none()); assert_eq!(status_quo.account.nonce(), ONE); - let _block = state.into_block().await?; + let _block = state.into_block(&signer)?; Ok(()) } @@ -134,7 +136,8 @@ path = "test_account.mac" let gcfg = GenesisConfig::read_toml_file(&config_path)?; // Convert to state and verify the account is included - let (state, _secrets) = gcfg.into_state(SecretKey::new())?; + let signer = SecretKey::new(); + let (state, _secrets) = gcfg.into_state(signer.public_key())?; assert!(state.accounts.iter().any(|a| a.id() == account_id)); Ok(()) @@ -192,7 +195,8 @@ verification_base_fee = 0 let gcfg = GenesisConfig::read_toml_file(&config_path)?; // Convert to state and verify the native faucet is included - let (state, secrets) = gcfg.into_state(SecretKey::new())?; + let signer = SecretKey::new(); + let (state, secrets) = gcfg.into_state(signer.public_key())?; assert!(state.accounts.iter().any(|a| a.id() == faucet_id)); // No secrets should be generated for file-loaded native faucet @@ -251,7 +255,7 @@ verification_base_fee = 0 let gcfg = GenesisConfig::read_toml_file(&config_path)?; // into_state should fail with NativeFaucetNotFungible error when loading the file - let result = gcfg.into_state(SecretKey::new()); + let result = gcfg.into_state(SecretKey::new().public_key()); assert!(result.is_err()); let err = result.unwrap_err(); assert!( @@ -284,7 +288,7 @@ path = "does_not_exist.mac" let gcfg = GenesisConfig::read_toml_file(&config_path).unwrap(); // into_state should fail with AccountFileRead error when loading the file - let result = gcfg.into_state(SecretKey::new()); + let result = gcfg.into_state(SecretKey::new().public_key()); assert!(result.is_err()); let err = result.unwrap_err(); assert!( @@ -303,7 +307,8 @@ async fn parsing_agglayer_sample_with_account_files() -> TestResult { .join("src/genesis/config/samples/02-with-account-files.toml"); let gcfg = GenesisConfig::read_toml_file(&sample_path)?; - let (state, secrets) = gcfg.into_state(SecretKey::new())?; + let signer = SecretKey::new(); + let (state, secrets) = gcfg.into_state(signer.public_key())?; // Should have 4 accounts: // 1. Native faucet (MIDEN) - built from parameters @@ -355,7 +360,7 @@ async fn parsing_agglayer_sample_with_account_files() -> TestResult { assert_eq!(secrets.secrets.len(), 1, "Only native faucet should generate a secret"); // Verify the genesis state can be converted to a block - let block = state.into_block().await?; + let block = state.into_block(&signer)?; // Verify that non-private accounts (Public and Network) get full Delta details. for update in block.inner().body().updated_accounts() { diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 6c4624fb00..041c42b561 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -1,4 +1,3 @@ -use miden_node_utils::signer::BlockSigner; use miden_protocol::Word; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{Account, AccountDelta}; @@ -13,6 +12,7 @@ use miden_protocol::block::{ FeeParameters, ProvenBlock, }; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks}; use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage, Smt}; use miden_protocol::errors::AccountError; @@ -26,18 +26,45 @@ pub mod config; /// Represents the state at genesis, which will be used to derive the genesis block. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct GenesisState { +pub struct GenesisState { pub accounts: Vec, pub fee_parameters: FeeParameters, pub version: u32, pub timestamp: u32, - pub block_signer: S, + pub validator_key: PublicKey, } /// A type-safety wrapper ensuring that genesis block data can only be created from /// [`GenesisState`] or validated from a [`ProvenBlock`] via [`GenesisBlock::try_from`]. pub struct GenesisBlock(ProvenBlock); +/// A genesis block with all data except the validator signature. +pub struct UnsignedGenesisBlock { + header: BlockHeader, + body: BlockBody, + block_proof: BlockProof, +} + +impl UnsignedGenesisBlock { + pub fn header(&self) -> &BlockHeader { + &self.header + } + + pub fn into_block(self, signature: Signature) -> anyhow::Result { + anyhow::ensure!( + signature.verify(self.header.commitment(), self.header.validator_key()), + "genesis block signature verification failed", + ); + + Ok(GenesisBlock(ProvenBlock::new_unchecked( + self.header, + self.body, + signature, + self.block_proof, + ))) + } +} + impl GenesisBlock { pub fn inner(&self) -> &ProvenBlock { &self.0 @@ -69,27 +96,25 @@ impl TryFrom for GenesisBlock { } } -impl GenesisState { +impl GenesisState { pub fn new( accounts: Vec, fee_parameters: FeeParameters, version: u32, timestamp: u32, - signer: S, + validator_key: PublicKey, ) -> Self { Self { accounts, fee_parameters, version, timestamp, - block_signer: signer, + validator_key, } } -} -impl GenesisState { - /// Returns the block header and the account SMT. - pub async fn into_block(self) -> anyhow::Result { + /// Builds the unsigned genesis block. + pub fn into_unsigned_block(self) -> anyhow::Result { let accounts: Vec = self .accounts .iter() @@ -140,7 +165,7 @@ impl GenesisState { empty_block_note_tree.root(), Word::empty(), TransactionKernel.to_commitment(), - self.block_signer.public_key(), + self.validator_key, self.fee_parameters, self.timestamp, ); @@ -154,13 +179,13 @@ impl GenesisState { let block_proof = BlockProof::new_dummy(); - // Sign and assert verification for sanity (no mismatch between frontend and backend signing - // impls). - let signature = self.block_signer.sign(&header).await?; - assert!(signature.verify(header.commitment(), &self.block_signer.public_key())); - // SAFETY: Header and accounts should be valid by construction. - // No notes or nullifiers are created at genesis, which is consistent with the above empty - // block note tree root and empty nullifier tree root. - Ok(GenesisBlock(ProvenBlock::new_unchecked(header, body, signature, block_proof))) + Ok(UnsignedGenesisBlock { header, body, block_proof }) + } + + /// Builds and signs the genesis block with a local secret key. + pub fn into_block(self, signer: &SecretKey) -> anyhow::Result { + let unsigned_block = self.into_unsigned_block()?; + let signature = signer.sign(unsigned_block.header().commitment()); + unsigned_block.into_block(signature) } } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index a2c4a82d1c..ea9e60c5ae 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -10,7 +10,6 @@ pub mod limiter; pub mod logging; pub mod lru_cache; pub mod panic; -pub mod signer; pub mod tracing; pub trait ErrorReport: std::error::Error { diff --git a/crates/utils/src/signer.rs b/crates/utils/src/signer.rs deleted file mode 100644 index 00dbe3ebc3..0000000000 --- a/crates/utils/src/signer.rs +++ /dev/null @@ -1,36 +0,0 @@ -use core::convert::Infallible; -use core::error; - -use miden_protocol::block::BlockHeader; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; - -// BLOCK SIGNER -// ================================================================================================ - -/// Trait which abstracts the signing of block headers with ECDSA signatures. -/// -/// Production-level implementations will involve some sort of secure remote backend. The trait also -/// allows for testing with local and ephemeral signers. -pub trait BlockSigner { - type Error: error::Error + Send + Sync + 'static; - fn sign( - &self, - header: &BlockHeader, - ) -> impl Future> + Send; - fn public_key(&self) -> PublicKey; -} - -// SECRET KEY BLOCK SIGNER -// ================================================================================================ - -impl BlockSigner for SecretKey { - type Error = Infallible; - - async fn sign(&self, header: &BlockHeader) -> Result { - Ok(self.sign(header.commitment())) - } - - fn public_key(&self) -> PublicKey { - self.public_key() - } -} diff --git a/crates/validator/src/server/tests.rs b/crates/validator/src/server/tests.rs index e87b821e21..7ce8efdb4c 100644 --- a/crates/validator/src/server/tests.rs +++ b/crates/validator/src/server/tests.rs @@ -29,8 +29,10 @@ impl TestValidator { async fn new() -> Self { let signer = ValidatorSigner::new_local(random_secret_key()); - let genesis_state = GenesisState::new(vec![], test_fee_params(), 1, 0, random_secret_key()); - let genesis_block = genesis_state.into_block().await.unwrap(); + let genesis_signer = random_secret_key(); + let genesis_state = + GenesisState::new(vec![], test_fee_params(), 1, 0, genesis_signer.public_key()); + let genesis_block = genesis_state.into_block(&genesis_signer).unwrap(); let genesis_header = genesis_block.inner().header().clone(); let dir = tempfile::tempdir().unwrap(); @@ -234,9 +236,10 @@ async fn commitment_mismatch_rejected() { // Build a valid ProposedBlock on a *different* genesis so its prev_block_commitment // won't match the validator's actual chain tip. + let other_genesis_signer = random_secret_key(); let other_genesis_state = - GenesisState::new(vec![], test_fee_params(), 1, 1, random_secret_key()); - let other_genesis_block = other_genesis_state.into_block().await.unwrap(); + GenesisState::new(vec![], test_fee_params(), 1, 1, other_genesis_signer.public_key()); + let other_genesis_block = other_genesis_state.into_block(&other_genesis_signer).unwrap(); let other_genesis_header = other_genesis_block.inner().header().clone(); let mismatched_block = empty_block(&other_genesis_header, &PartialBlockchain::default()); @@ -261,9 +264,10 @@ async fn replacement_commitment_mismatch_rejected() { // Build a replacement block at the same height but using a *different* genesis so its // prev_block_commitment won't match the validator's actual parent of the chain tip. + let other_genesis_signer = random_secret_key(); let other_genesis_state = - GenesisState::new(vec![], test_fee_params(), 1, 1, random_secret_key()); - let other_genesis_block = other_genesis_state.into_block().await.unwrap(); + GenesisState::new(vec![], test_fee_params(), 1, 1, other_genesis_signer.public_key()); + let other_genesis_block = other_genesis_state.into_block(&other_genesis_signer).unwrap(); let other_genesis_header = other_genesis_block.inner().header().clone(); let mismatched_replacement = empty_block(&other_genesis_header, &PartialBlockchain::default()); diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index e84576ac1e..f9a5f47b2d 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,8 +1,7 @@ use aws_sdk_kms::error::SdkError; use aws_sdk_kms::operation::sign::SignError; use aws_sdk_kms::types::SigningAlgorithmSpec; -use miden_node_utils::signer::BlockSigner; -use miden_protocol::block::BlockHeader; +use miden_protocol::Word; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; use miden_protocol::crypto::hash::keccak::Keccak256; use miden_protocol::utils::serde::{DeserializationError, Serializable}; @@ -74,18 +73,14 @@ impl KmsSigner { let pub_key = PublicKey::from_der(spki_der)?; Ok(Self { key_id, pub_key, client }) } -} - -impl BlockSigner for KmsSigner { - type Error = KmsSignerError; - async fn sign(&self, header: &BlockHeader) -> Result { + pub async fn sign(&self, commitment: Word) -> Result { // The Validator produces Ethereum-style ECDSA (secp256k1) signatures over Keccak-256 // digests. AWS KMS does not support SHA-3 hashing for ECDSA keys // (ECC_SECG_P256K1 being the corresponding AWS key-spec), so we pre-hash the // message and pass MessageType::Digest. KMS signs the provided 32-byte digest // verbatim. - let msg = header.commitment().to_bytes(); + let msg = commitment.to_bytes(); let digest = Keccak256::hash(&msg); // Request signature from KMS backend. @@ -109,14 +104,14 @@ impl BlockSigner for KmsSigner { .map_err(KmsSignerError::SignatureFormatError)?; // Check the returned signature. - if sig.verify(header.commitment(), &self.pub_key) { + if sig.verify(commitment, &self.pub_key) { Ok(sig) } else { Err(KmsSignerError::InvalidSignature) } } - fn public_key(&self) -> PublicKey { + pub fn public_key(&self) -> PublicKey { self.pub_key.clone() } } diff --git a/crates/validator/src/signers/mod.rs b/crates/validator/src/signers/mod.rs index 6e6092350f..e4c2192b3e 100644 --- a/crates/validator/src/signers/mod.rs +++ b/crates/validator/src/signers/mod.rs @@ -1,8 +1,7 @@ mod kms; pub use kms::KmsSigner; -use miden_node_utils::signer::BlockSigner; use miden_protocol::block::BlockHeader; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{SecretKey, Signature}; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; // VALIDATOR SIGNER // ================================================================================================= @@ -28,26 +27,27 @@ impl ValidatorSigner { Self::Local(secret_key) } + /// Returns the public key corresponding to the configured signer. + pub fn public_key(&self) -> PublicKey { + match self { + Self::Kms(signer) => signer.public_key(), + Self::Local(signer) => signer.public_key(), + } + } + /// Signs a block header using the configured signer. pub async fn sign(&self, header: &BlockHeader) -> anyhow::Result { - match self { - Self::Kms(signer) => { - let sig = signer.sign(header).await?; - Ok(sig) - }, - Self::Local(signer) => { + let commitment = header.commitment(); + let signature = match self { + Self::Kms(signer) => signer.sign(commitment).await?, + Self::Local(signer) => tokio::task::spawn_blocking({ let signer = signer.clone(); - let header = header.clone(); - let sig = tokio::task::spawn_blocking(move || { - tokio::runtime::Builder::new_current_thread() - .build() - .expect("failed to build tokio runtime") - .block_on(::sign(&signer, &header)) - }) - .await - .unwrap_or_else(|e| std::panic::resume_unwind(e.into_panic()))?; - Ok(sig) - }, - } + move || signer.sign(commitment) + }) + .await + .unwrap_or_else(|e| std::panic::resume_unwind(e.into_panic())), + }; + + Ok(signature) } }