From 93095b3f6716d5b28cc57a9eb977b71c3f43d2e6 Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 11:21:18 +0100 Subject: [PATCH 1/6] fix(yubihsm): refuse a secp256k1 priv_validator key instead of panicking --- src/commands/yubihsm/keys/import.rs | 10 +++++++- tests/cli/yubihsm.rs | 4 ++++ tests/cli/yubihsm/keys.rs | 2 +- tests/cli/yubihsm/keys/import.rs | 24 +++++++++++++++++++ .../priv_validator_secp256k1_mock.json | 12 ++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/support/priv_validator_secp256k1_mock.json diff --git a/src/commands/yubihsm/keys/import.rs b/src/commands/yubihsm/keys/import.rs index 00e96eea..6adf7976 100644 --- a/src/commands/yubihsm/keys/import.rs +++ b/src/commands/yubihsm/keys/import.rs @@ -172,9 +172,17 @@ impl ImportCommand { }) .priv_key; + // `PrivValidatorKey` also accepts secp256k1, so this is reachable with a + // legitimate key file let seed = match private_key { PrivateKey::Ed25519(pk) => pk, - _ => unreachable!(), + _ => { + status_err!( + "unsupported key type in `{}`: an Ed25519 consensus key is required", + self.path.display() + ); + process::exit(1); + } }; let label = object::Label::from(self.label.as_ref().map(|l| l.as_ref()).unwrap_or("")); diff --git a/tests/cli/yubihsm.rs b/tests/cli/yubihsm.rs index 3ea86847..bf3d5663 100644 --- a/tests/cli/yubihsm.rs +++ b/tests/cli/yubihsm.rs @@ -10,6 +10,10 @@ pub const KMS_GNO_PREFIX_CONFIG_PATH: &str = "tests/support/kms_yubihsm_mock_gno #[allow(dead_code)] pub const PRIV_VALIDATOR_CONFIG_PATH: &str = "tests/support/priv_validator_mock.json"; +/// A priv_validator key file holding a secp256k1 key rather than an Ed25519 one +#[allow(dead_code)] +pub const SECP256K1_PRIV_VALIDATOR_PATH: &str = "tests/support/priv_validator_secp256k1_mock.json"; + // This test requires USB access to a YubiHSM2 #[cfg(not(feature = "yubihsm-mock"))] mod detect; diff --git a/tests/cli/yubihsm/keys.rs b/tests/cli/yubihsm/keys.rs index a27e8821..21dc0d12 100644 --- a/tests/cli/yubihsm/keys.rs +++ b/tests/cli/yubihsm/keys.rs @@ -4,7 +4,7 @@ mod generate; mod import; mod list; -pub use super::{KMS_CONFIG_PATH, PRIV_VALIDATOR_CONFIG_PATH}; +pub use super::{KMS_CONFIG_PATH, PRIV_VALIDATOR_CONFIG_PATH, SECP256K1_PRIV_VALIDATOR_PATH}; use crate::cli; #[test] diff --git a/tests/cli/yubihsm/keys/import.rs b/tests/cli/yubihsm/keys/import.rs index 6e0a7272..22791e1a 100644 --- a/tests/cli/yubihsm/keys/import.rs +++ b/tests/cli/yubihsm/keys/import.rs @@ -21,3 +21,27 @@ fn keys_import_priv_validator_test() { let message = str::from_utf8(&out.stderr).unwrap().trim().to_owned(); assert!(message.contains("key 0x0001")); } + +/// A priv_validator key file can hold a secp256k1 key, which cannot be imported as +/// an Ed25519 consensus key: that has to be an error the operator can act on, not +/// a panic +#[test] +fn keys_import_rejects_a_secp256k1_priv_validator() { + #[allow(unused_mut)] + let mut args = vec!["yubihsm", "keys", "import"]; + + #[cfg(feature = "yubihsm-mock")] + args.extend_from_slice(&["-c", super::KMS_CONFIG_PATH]); + args.extend_from_slice(&["-t", "json"]); + args.extend_from_slice(&["-i", "1"]); // key ID + args.extend_from_slice(&[super::SECP256K1_PRIV_VALIDATOR_PATH]); + + let out = cli::run(args.as_slice()); + let message = str::from_utf8(&out.stderr).unwrap(); + + assert_eq!(out.status.code(), Some(1), "{message}"); + assert!( + message.contains("Ed25519 consensus key is required"), + "{message}" + ); +} diff --git a/tests/support/priv_validator_secp256k1_mock.json b/tests/support/priv_validator_secp256k1_mock.json new file mode 100644 index 00000000..6bd53c25 --- /dev/null +++ b/tests/support/priv_validator_secp256k1_mock.json @@ -0,0 +1,12 @@ +{ + "comment": "TEST KEY ONLY: DO **NOT** USE!! secp256k1, secret = 32 bytes of 0x01", + "address": "0000000000000000000000000000000000000000", + "pub_key": { + "type": "tendermint/PubKeySecp256k1", + "value": "AxuExVZ7EmRAmV0+1aq6BWXXHhg0YEgZ/5wX9enV3QeP" + }, + "priv_key": { + "type": "tendermint/PrivKeySecp256k1", + "value": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=" + } +} From 1a5a7b16e50c827c8ab286c31bdcfd8aa30c27ba Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 11:21:18 +0100 Subject: [PATCH 2/6] docs: update the verification counts --- FORK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FORK.md b/FORK.md index 82d5c890..d0f1db37 100644 --- a/FORK.md +++ b/FORK.md @@ -192,7 +192,7 @@ Signing is unaffected either way. - `cargo fmt --all -- --check` — clean. - `cargo clippy --all-features --all-targets` — clean. -- `cargo test --all-features -- --test-threads 1` — 68 unit + 23 integration tests +- `cargo test --all-features -- --test-threads 1` — 68 unit + 24 integration tests pass (10 ignored, all hardware-dependent). - The published image is pulled and checked on every release: it reports its own version, keeps `tmkms` as the entrypoint, and links the USB libraries the From b342aa96b5465179e528d4ebfb28a8c7aeff4665 Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 11:57:57 +0100 Subject: [PATCH 3/6] test(yubihsm): check the import error names the key file --- tests/cli/yubihsm/keys/import.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/cli/yubihsm/keys/import.rs b/tests/cli/yubihsm/keys/import.rs index 22791e1a..b205e61f 100644 --- a/tests/cli/yubihsm/keys/import.rs +++ b/tests/cli/yubihsm/keys/import.rs @@ -44,4 +44,8 @@ fn keys_import_rejects_a_secp256k1_priv_validator() { message.contains("Ed25519 consensus key is required"), "{message}" ); + assert!( + message.contains(super::SECP256K1_PRIV_VALIDATOR_PATH), + "{message}" + ); } From fcf34f2ec74788ca09d467b793f5ff0d1939c421 Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 11:59:30 +0100 Subject: [PATCH 4/6] test(yubihsm): build the import arguments in one place --- tests/cli/yubihsm/keys/import.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/cli/yubihsm/keys/import.rs b/tests/cli/yubihsm/keys/import.rs index b205e61f..848e6384 100644 --- a/tests/cli/yubihsm/keys/import.rs +++ b/tests/cli/yubihsm/keys/import.rs @@ -3,8 +3,8 @@ use crate::cli; use std::str; -#[test] -fn keys_import_priv_validator_test() { +/// Arguments to import the given priv_validator key file as key 0x0001 +fn import_json_args(path: &str) -> Vec<&str> { #[allow(unused_mut)] let mut args = vec!["yubihsm", "keys", "import"]; @@ -12,7 +12,13 @@ fn keys_import_priv_validator_test() { args.extend_from_slice(&["-c", super::KMS_CONFIG_PATH]); args.extend_from_slice(&["-t", "json"]); args.extend_from_slice(&["-i", "1"]); // key ID - args.extend_from_slice(&[super::PRIV_VALIDATOR_CONFIG_PATH]); + args.push(path); + args +} + +#[test] +fn keys_import_priv_validator_test() { + let args = import_json_args(super::PRIV_VALIDATOR_CONFIG_PATH); let out = cli::run_successfully(args.as_slice()); @@ -27,14 +33,7 @@ fn keys_import_priv_validator_test() { /// a panic #[test] fn keys_import_rejects_a_secp256k1_priv_validator() { - #[allow(unused_mut)] - let mut args = vec!["yubihsm", "keys", "import"]; - - #[cfg(feature = "yubihsm-mock")] - args.extend_from_slice(&["-c", super::KMS_CONFIG_PATH]); - args.extend_from_slice(&["-t", "json"]); - args.extend_from_slice(&["-i", "1"]); // key ID - args.extend_from_slice(&[super::SECP256K1_PRIV_VALIDATOR_PATH]); + let args = import_json_args(super::SECP256K1_PRIV_VALIDATOR_PATH); let out = cli::run(args.as_slice()); let message = str::from_utf8(&out.stderr).unwrap(); From e1bee0c1a476e8f32c5f72b53533124e6dfeddb7 Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 12:02:39 +0100 Subject: [PATCH 5/6] test(yubihsm): drop an allow that suppresses nothing --- tests/cli/yubihsm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/cli/yubihsm.rs b/tests/cli/yubihsm.rs index bf3d5663..9a459256 100644 --- a/tests/cli/yubihsm.rs +++ b/tests/cli/yubihsm.rs @@ -11,7 +11,6 @@ pub const KMS_GNO_PREFIX_CONFIG_PATH: &str = "tests/support/kms_yubihsm_mock_gno pub const PRIV_VALIDATOR_CONFIG_PATH: &str = "tests/support/priv_validator_mock.json"; /// A priv_validator key file holding a secp256k1 key rather than an Ed25519 one -#[allow(dead_code)] pub const SECP256K1_PRIV_VALIDATOR_PATH: &str = "tests/support/priv_validator_secp256k1_mock.json"; // This test requires USB access to a YubiHSM2 From 891734dcaa37640a910de3f8287b8ebdf228f615 Mon Sep 17 00:00:00 2001 From: aeddi Date: Wed, 4 Nov 2026 12:02:53 +0100 Subject: [PATCH 6/6] test(yubihsm): give the secp256k1 fixture its derived address --- tests/support/priv_validator_secp256k1_mock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/support/priv_validator_secp256k1_mock.json b/tests/support/priv_validator_secp256k1_mock.json index 6bd53c25..2279a223 100644 --- a/tests/support/priv_validator_secp256k1_mock.json +++ b/tests/support/priv_validator_secp256k1_mock.json @@ -1,6 +1,6 @@ { "comment": "TEST KEY ONLY: DO **NOT** USE!! secp256k1, secret = 32 bytes of 0x01", - "address": "0000000000000000000000000000000000000000", + "address": "79B000887626B294A914501A4CD226B58B235983", "pub_key": { "type": "tendermint/PubKeySecp256k1", "value": "AxuExVZ7EmRAmV0+1aq6BWXXHhg0YEgZ/5wX9enV3QeP"