Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## Unreleased

- [BREAKING] Updated `miden-protocol` dependencies to use the `next` branch (v0.16). Block and transaction account updates now use the absolute `AccountPatch` representation instead of the relative `AccountDelta`, and the `miden-tx-batch-prover` crate was renamed to `miden-tx-batch` ([#2282](https://github.com/0xMiden/node/pull/2282)).
- Added the `GetTransactionEncryptionKey` endpoint to the validator and RPC APIs, returning the shared transaction encryption key attested by the serving validator's signing key. The shared secret is configured on the validator via `--encryption-key.hex` / `MIDEN_VALIDATOR_ENCRYPTION_KEY` and must be identical across the validator set ([#2342](https://github.com/0xMiden/node/pull/2342)).
- [BREAKING] Renamed the validator signing key options: `--key.hex` / `MIDEN_VALIDATOR_KEY` is now `--signing-key.hex` / `MIDEN_VALIDATOR_SIGNING_KEY`, and `--key.kms-id` / `MIDEN_VALIDATOR_KEY_KMS_ID` is now `--signing-key.kms-id` / `MIDEN_VALIDATOR_SIGNING_KEY_KMS_ID` ([#2342](https://github.com/0xMiden/node/pull/2342)).

Comment thread
bobbinth marked this conversation as resolved.
## v0.15.0 (2026-06-10)

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ miden-node-db = { workspace = true }
miden-node-store = { workspace = true }
miden-node-utils = { features = ["testing"], workspace = true }
miden-protocol = { default-features = true, features = ["testing"], workspace = true }
rand = { workspace = true }
tempfile = { workspace = true }
tokio = { features = ["macros", "rt-multi-thread"], workspace = true }
8 changes: 4 additions & 4 deletions bin/validator/src/commands/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use miden_node_utils::fs::ensure_empty_directory;
use miden_protocol::utils::serde::Serializable;
use miden_validator::{DataDirectory, ValidatorSigner};

use super::ValidatorKey;
use super::ValidatorSigningKey;
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.

// Bootstraps the validator component.
pub async fn bootstrap(
Expand All @@ -17,7 +17,7 @@ pub async fn bootstrap(
data_directory: &Path,
sqlite_connection_pool_size: NonZeroUsize,
genesis_config: Option<&PathBuf>,
validator_key: ValidatorKey,
signing_key: ValidatorSigningKey,
) -> anyhow::Result<()> {
let config = genesis_config
.map(|file_path| {
Expand All @@ -32,7 +32,7 @@ pub async fn bootstrap(
ensure_empty_directory(directory)?;
}

let signer = validator_key.into_signer().await?;
let signer = signing_key.into_signer().await?;
let dirs = DataDirectory::load_bootstrap(
genesis_block_directory.to_path_buf(),
accounts_directory.to_path_buf(),
Expand Down Expand Up @@ -68,7 +68,7 @@ async fn build_and_write_genesis(
.into_unsigned_block()
.context("failed to build the unsigned genesis block")?;
let signature = signer
.sign(unsigned_genesis_block.header())
.sign_commitment(unsigned_genesis_block.header().commitment())
.await
.context("failed to sign the genesis block")?;
let genesis_block = unsigned_genesis_block
Expand Down
167 changes: 103 additions & 64 deletions bin/validator/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,40 @@ mod start;

use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Context;
use clap::Parser;
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::logging::OpenTelemetry;
use miden_node_utils::shutdown::CancellationToken;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
use miden_protocol::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey;
use miden_protocol::utils::serde::Deserializable;
use miden_validator::{DataDirectory, ValidatorSigner};
use miden_validator::{
DataDirectory,
LOG_TARGET,
LocalX25519TransactionInputDecrypter,
TransactionInputDecrypter,
ValidatorSigner,
};

const ENV_DATA_DIRECTORY: &str = "MIDEN_VALIDATOR_DATA_DIRECTORY";
const ENV_LISTEN: &str = "MIDEN_VALIDATOR_LISTEN";
const ENV_KEY: &str = "MIDEN_VALIDATOR_KEY";
const ENV_KMS_KEY_ID: &str = "MIDEN_VALIDATOR_KMS_KEY_ID";
const ENV_SIGNING_KEY: &str = "MIDEN_VALIDATOR_SIGNING_KEY";
const ENV_SIGNING_KEY_KMS_ID: &str = "MIDEN_VALIDATOR_SIGNING_KEY_KMS_ID";
const ENV_ENCRYPTION_KEY: &str = "MIDEN_VALIDATOR_ENCRYPTION_KEY";
const ENV_GENESIS_CONFIG_FILE: &str = "MIDEN_VALIDATOR_GENESIS_CONFIG_FILE";
const ENV_SQLITE_CONNECTION_POOL_SIZE: &str = "MIDEN_VALIDATOR_SQLITE_CONNECTION_POOL_SIZE";

/// A predefined, insecure validator key for development purposes.
pub(crate) const INSECURE_KEY_HEX: &str =
/// A predefined, insecure validator signing key for development purposes.
pub(crate) const INSECURE_SIGNING_KEY_HEX: &str =
"0101010101010101010101010101010101010101010101010101010101010101";

/// A predefined, insecure shared transaction encryption key for development purposes.
pub(crate) const INSECURE_ENCRYPTION_KEY_HEX: &str =
"0202020202020202020202020202020202020202020202020202020202020202";

// VALIDATOR COMMAND
// ================================================================================================

Expand Down Expand Up @@ -56,9 +69,9 @@ pub enum ValidatorCommand {
/// Use the given configuration file to construct the genesis state from.
#[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "GENESIS_CONFIG")]
genesis_config_file: Option<PathBuf>,
/// Configuration for the Validator key used to sign the genesis block.
/// Configuration for the validator signing key used to sign the genesis block.
#[command(flatten)]
validator_key: ValidatorKey,
signing_key: ValidatorSigningKey,
},

/// Applies pending validator database migrations.
Expand Down Expand Up @@ -96,26 +109,40 @@ pub enum ValidatorCommand {
///
/// If not provided, a predefined key is used.
///
/// Cannot be used with `key.kms-id`.
/// Cannot be used with `signing-key.kms-id`.
#[arg(
long = "key.hex",
env = ENV_KEY,
value_name = "VALIDATOR_KEY",
default_value = INSECURE_KEY_HEX,
group = "key"
long = "signing-key.hex",
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.
env = ENV_SIGNING_KEY,
value_name = "VALIDATOR_SIGNING_KEY",
default_value = INSECURE_SIGNING_KEY_HEX,
group = "signing_key"
)]
validator_key: String,
signing_key: String,

/// Key ID for the KMS key used by validator to sign blocks.
///
/// Cannot be used with `key.hex`.
/// Cannot be used with `signing-key.hex`.
#[arg(
long = "key.kms-id",
env = ENV_KMS_KEY_ID,
value_name = "VALIDATOR_KMS_KEY_ID",
group = "key"
long = "signing-key.kms-id",
env = ENV_SIGNING_KEY_KMS_ID,
value_name = "VALIDATOR_SIGNING_KEY_KMS_ID",
group = "signing_key"
)]
kms_key_id: Option<String>,
signing_key_kms_id: Option<String>,

/// Hex-encoded shared secret of the transaction encryption key.
///
/// Unlike the per-validator signing key, this value must be identical across every
/// validator in the set.
///
/// If not provided, a predefined insecure key is used.
Comment thread
SantiagoPittella marked this conversation as resolved.
#[arg(
long = "encryption-key.hex",
env = ENV_ENCRYPTION_KEY,
value_name = "VALIDATOR_ENCRYPTION_KEY",
default_value = INSECURE_ENCRYPTION_KEY_HEX
)]
encryption_key: String,
Comment on lines +143 to +150

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not for this PR, but should we also provide an option to get the key from KMS?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We just need to ensure KMS supports this key type, otherwise we cannot decrypt.

},
}

Expand All @@ -128,15 +155,15 @@ impl ValidatorCommand {
data_directory,
sqlite_connection_pool_size,
genesis_config_file,
validator_key,
signing_key,
} => {
bootstrap::bootstrap(
&genesis_block_directory,
&accounts_directory,
&data_directory,
sqlite_connection_pool_size,
genesis_config_file.as_ref(),
validator_key,
signing_key,
)
.await
},
Expand All @@ -150,38 +177,50 @@ impl ValidatorCommand {
Self::Start {
listen,
grpc_options,
validator_key,
signing_key,
data_directory,
kms_key_id,
signing_key_kms_id,
sqlite_connection_pool_size,
encryption_key,
..
} => {
let address = listen;

if let Some(kms_key_id) = kms_key_id {
let signer = ValidatorSigner::new_kms(kms_key_id).await?;
start::start(
address,
grpc_options,
signer,
data_directory,
sqlite_connection_pool_size,
shutdown,
)
.await
} else {
let signer = SigningKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?;
let signer = ValidatorSigner::new_local(signer);
start::start(
address,
grpc_options,
signer,
data_directory,
sqlite_connection_pool_size,
shutdown,
)
.await
// Unlike the signing key, whose insecure default is caught at startup against the
// chain's committed validator key, nothing cross-checks the encryption key. Warn
// loudly so the default never runs in production unnoticed.
if encryption_key == INSECURE_ENCRYPTION_KEY_HEX {
tracing::warn!(
target: LOG_TARGET,
"Using the predefined, insecure transaction encryption key, configure \
--encryption-key.hex for production deployments"
);
}

let encryption_key_bytes = hex::decode(encryption_key)
.context("failed to decode the encryption key hex")?;
let encryption_key = KeyExchangeKey::read_from_bytes(&encryption_key_bytes)
.context("failed to construct the encryption key")?;
let decrypter: Arc<dyn TransactionInputDecrypter> =
Arc::new(LocalX25519TransactionInputDecrypter::new(encryption_key));

let signer = if let Some(kms_key_id) = signing_key_kms_id {
ValidatorSigner::new_kms(kms_key_id).await?
} else {
let signer = SigningKey::read_from_bytes(hex::decode(signing_key)?.as_ref())?;
ValidatorSigner::new_local(signer)
};

start::start(
address,
grpc_options,
signer,
decrypter,
data_directory,
sqlite_connection_pool_size,
shutdown,
)
.await
},
}
}
Expand All @@ -194,42 +233,42 @@ impl ValidatorCommand {
}
}

// VALIDATOR KEY
// VALIDATOR SIGNING KEY
// ================================================================================================

/// Configuration for the Validator key used to sign blocks.
/// Configuration for the validator signing key used to sign blocks.
#[derive(clap::Args)]
#[group(required = false, multiple = false)]
pub struct ValidatorKey {
pub struct ValidatorSigningKey {
/// Insecure, hex-encoded validator secret key for development and testing purposes.
///
/// If not provided, a predefined key is used.
///
/// Cannot be used with `key.kms-id`.
/// Cannot be used with `signing-key.kms-id`.
#[arg(
long = "key.hex",
env = ENV_KEY,
value_name = "VALIDATOR_KEY",
default_value = INSECURE_KEY_HEX,
long = "signing-key.hex",
env = ENV_SIGNING_KEY,
value_name = "VALIDATOR_SIGNING_KEY",
default_value = INSECURE_SIGNING_KEY_HEX,
)]
pub validator_key: String,
pub signing_key: String,
/// Key ID for the KMS key used by validator to sign blocks.
///
/// Cannot be used with `key.hex`.
/// Cannot be used with `signing-key.hex`.
#[arg(
long = "key.kms-id",
env = ENV_KMS_KEY_ID,
value_name = "VALIDATOR_KMS_KEY_ID",
long = "signing-key.kms-id",
env = ENV_SIGNING_KEY_KMS_ID,
value_name = "VALIDATOR_SIGNING_KEY_KMS_ID",
)]
pub validator_kms_key_id: Option<String>,
pub signing_key_kms_id: Option<String>,
}

impl ValidatorKey {
impl ValidatorSigningKey {
pub async fn into_signer(self) -> anyhow::Result<ValidatorSigner> {
if let Some(kms_key_id) = self.validator_kms_key_id {
if let Some(kms_key_id) = self.signing_key_kms_id {
Ok(ValidatorSigner::new_kms(kms_key_id).await?)
} else {
let signer = SigningKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?;
let signer = SigningKey::read_from_bytes(hex::decode(self.signing_key)?.as_ref())?;
Ok(ValidatorSigner::new_local(signer))
}
}
Expand Down
5 changes: 4 additions & 1 deletion bin/validator/src/commands/start.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use std::net::SocketAddr;
use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Context;
use miden_node_utils::clap::GrpcOptionsInternal;
use miden_node_utils::shutdown::CancellationToken;
use miden_validator::{DataDirectory, ValidatorServer, ValidatorSigner};
use miden_validator::{DataDirectory, TransactionInputDecrypter, ValidatorServer, ValidatorSigner};

// Starts the validator component.
pub async fn start(
address: SocketAddr,
grpc_options: GrpcOptionsInternal,
signer: ValidatorSigner,
decrypter: Arc<dyn TransactionInputDecrypter>,
data_directory: PathBuf,
sqlite_connection_pool_size: NonZeroUsize,
shutdown: CancellationToken,
Expand All @@ -22,6 +24,7 @@ pub async fn start(
address,
grpc_options,
signer,
decrypter,
data_directory,
sqlite_connection_pool_size,
}
Expand Down
9 changes: 8 additions & 1 deletion bin/validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ mod tx_validation;

pub use data_directory::DataDirectory;
pub use server::ValidatorServer;
pub use signers::{KmsSigner, ValidatorSigner};
pub use signers::{
KmsSigner,
LocalX25519TransactionInputDecrypter,
TransactionEncryptionKeyInfo,
TransactionInputDecrypter,
ValidatorSigner,
attestation_commitment,
};

// CONSTANTS
// =================================================================================================
Expand Down
7 changes: 6 additions & 1 deletion bin/validator/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::db::{
load_chain_tip,
load_with_pool_size,
};
use crate::{DataDirectory, LOG_TARGET, ValidatorSigner};
use crate::{DataDirectory, LOG_TARGET, TransactionInputDecrypter, ValidatorSigner};

mod validator_service;

Expand All @@ -43,6 +43,10 @@ pub struct ValidatorServer {
/// The signer used to sign blocks.
pub signer: ValidatorSigner,

/// The decrypter for the shared transaction encryption key, used to unseal encrypted
/// transaction inputs.
pub decrypter: std::sync::Arc<dyn TransactionInputDecrypter>,

/// The data directory for the validator component's database files.
pub data_directory: DataDirectory,

Expand Down Expand Up @@ -98,6 +102,7 @@ impl ValidatorServer {
.add_service(validator_api::service(
ValidatorService::new(
self.signer,
self.decrypter,
db,
block_store,
initial_chain_tip,
Expand Down
Loading
Loading