From e02ab694a3c55eb6eb44cd699aad1a6559daf92b Mon Sep 17 00:00:00 2001 From: Joe Clapis Date: Mon, 21 Jul 2025 13:17:40 -0400 Subject: [PATCH 1/2] Added Nimbus loader support --- config.example.toml | 8 +-- crates/common/src/signer/loader.rs | 53 ++++++++++++++++++- .../keystore.json | 1 + .../keystore.json | 1 + 4 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 tests/data/keystores/nimbus-keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4/keystore.json create mode 100644 tests/data/keystores/nimbus-keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9/keystore.json diff --git a/config.example.toml b/config.example.toml index ab8b90e1..8ed5b139 100644 --- a/config.example.toml +++ b/config.example.toml @@ -212,19 +212,21 @@ jwt_auth_fail_timeout_seconds = 300 [signer.local.loader] # File: path to the keys file key_path = "./tests/data/keys.example.json" -# ValidatorsDir: format of the keystore (lighthouse, prysm, teku or lodestar) +# ValidatorsDir: format of the keystore (lighthouse, prysm, teku, lodestar, or nimbus) # format = "lighthouse" # ValidatorsDir: full path to the keys directory -# For lighthouse, it's de path to the directory where the `/voting-keystore.json` directories are located. +# For lighthouse, it's the path to the directory where the `` directories are located, under each of which is a `voting-keystore.json` file. # For prysm, it's the path to the `all-accounts.keystore.json` file. # For teku, it's the path to the directory where all `.json` files are located. # For lodestar, it's the path to the directory where all `.json` files are located. +# For nimbus, it's the path to the directory where the `` directories are located, under each of which is a `keystore.json` file. # keys_path = "" # ValidatorsDir: full path to the secrets file/directory -# For lighthouse, it's de path to the directory where the `.json` files are located. +# For lighthouse, it's the path to the directory where the `` files are located. # For prysm, it's the path to the file containing the wallet decryption password. # For teku, it's the path to the directory where all `.txt` files are located. # For lodestar, it's the path to the file containing the decryption password. +# For nimbus, it's the path to the directory where the `` files are located. # secrets_path = "" # Configuration for how the Signer module should store proxy delegations. Supported types of store are: # - File: store keys and delegations from a plain text file (unsafe, use only for testing purposes) diff --git a/crates/common/src/signer/loader.rs b/crates/common/src/signer/loader.rs index d678795e..4fb9adb1 100644 --- a/crates/common/src/signer/loader.rs +++ b/crates/common/src/signer/loader.rs @@ -47,6 +47,8 @@ pub enum ValidatorKeysFormat { Lodestar, #[serde(alias = "prysm")] Prysm, + #[serde(alias = "nimbus")] + Nimbus, } impl SignerLoader { @@ -85,6 +87,7 @@ impl SignerLoader { load_from_lodestar_format(keys_path, secrets_path) } ValidatorKeysFormat::Prysm => load_from_prysm_format(keys_path, secrets_path), + ValidatorKeysFormat::Nimbus => load_from_nimbus_format(keys_path, secrets_path), }; } }) @@ -272,6 +275,42 @@ fn load_from_prysm_format( Ok(signers) } +fn load_from_nimbus_format( + keys_path: PathBuf, + secrets_path: PathBuf, +) -> eyre::Result> { + let paths: Vec<_> = + fs::read_dir(&keys_path)?.map(|res| res.map(|e| e.path())).collect::>()?; + + let signers = paths + .into_par_iter() + .filter_map(|path| { + if !path.is_dir() { + return None + } + + let maybe_pubkey = path.file_name().and_then(|d| d.to_str())?; + let Ok(pubkey) = BlsPublicKey::from_hex(maybe_pubkey) else { + warn!("Invalid pubkey: {}", maybe_pubkey); + return None + }; + + let ks_path = keys_path.join(maybe_pubkey).join("keystore.json"); + let pw_path = secrets_path.join(pubkey.to_string()); + + match load_one(ks_path, pw_path) { + Ok(signer) => Some(signer), + Err(e) => { + warn!("Failed to load signer for pubkey: {}, err: {}", pubkey, e); + None + } + } + }) + .collect(); + + Ok(signers) +} + fn load_one(ks_path: PathBuf, pw_path: PathBuf) -> eyre::Result { let keystore = Keystore::from_json_file(ks_path).map_err(|_| eyre!("failed reading json"))?; let password = fs::read(pw_path.clone()) @@ -303,7 +342,7 @@ mod tests { use super::{load_from_lighthouse_format, load_from_lodestar_format, FileKey}; use crate::signer::{ - loader::{load_from_prysm_format, load_from_teku_format}, + loader::{load_from_nimbus_format, load_from_prysm_format, load_from_teku_format}, BlsPublicKey, BlsSigner, }; @@ -399,4 +438,16 @@ mod tests { hex!("b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9") ))); } + + #[test] + fn test_load_nimbus() { + let result = load_from_nimbus_format( + "../../tests/data/keystores/nimbus-keys".into(), + "../../tests/data/keystores/secrets".into(), + ); + + assert!(result.is_ok()); + + test_correct_load(result.unwrap()); + } } diff --git a/tests/data/keystores/nimbus-keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4/keystore.json b/tests/data/keystores/nimbus-keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4/keystore.json new file mode 100644 index 00000000..72b13cad --- /dev/null +++ b/tests/data/keystores/nimbus-keys/0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4/keystore.json @@ -0,0 +1 @@ +{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"0ded1a0ed9d0d5aa9c41ac1a6be6d9943835f9ccbe1081869af74925611a4687"},"message":""},"checksum":{"function":"sha256","params":{},"message":"b1de458543b0532666e8f24e679f93ed6f168fd09de1da7c3f4f79b7fa2f2412"},"cipher":{"function":"aes-128-ctr","params":{"iv":"3ca34eb318e53a4c7e545571d8d0c7af"},"message":"acc6c222eea80974107b5a9bf824c8156edaad944f0d444a1aab4cc2118cecc5"}},"description":"0x883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","pubkey":"883827193f7627cd04e621e1e8d56498362a52b2a30c9a1c72036eb935c4278dee23d38a24d2f7dda62689886f0c39f4","path":"","uuid":"61c06c9c-b0bc-4022-9bf8-a2f250d4e751","version":4} \ No newline at end of file diff --git a/tests/data/keystores/nimbus-keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9/keystore.json b/tests/data/keystores/nimbus-keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9/keystore.json new file mode 100644 index 00000000..ba717c1c --- /dev/null +++ b/tests/data/keystores/nimbus-keys/0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9/keystore.json @@ -0,0 +1 @@ +{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"2154bba4d5999c6069442db5b499b2b27b6c2f54f36490e51163934dd4fb412e"},"message":""},"checksum":{"function":"sha256","params":{},"message":"1db4975098c97905f1dd9a9207cab0a9af7e16bebdab700ee08efb51e068017f"},"cipher":{"function":"aes-128-ctr","params":{"iv":"2265a3b57110b46c08295e53379165b5"},"message":"3bd312cc34cebfdd890c9704752191ed93ecd562bb62d2d8ceb4ff945b58b790"}},"description":"0xb3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","pubkey":"b3a22e4a673ac7a153ab5b3c17a4dbef55f7e47210b20c0cbb0e66df5b36bb49ef808577610b034172e955d2312a61b9","path":"","uuid":"a8457299-739d-42fb-a0f6-961020f22b8e","version":4} \ No newline at end of file From cb8fa71f4a9fde01a35142c42f031fb0e34b3b07 Mon Sep 17 00:00:00 2001 From: Joe Clapis Date: Tue, 22 Jul 2025 14:27:05 -0400 Subject: [PATCH 2/2] Added Nimbus to the key loader docs --- docs/docs/get_started/configuration.md | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/docs/get_started/configuration.md b/docs/docs/get_started/configuration.md index 5dd46329..c3f49712 100644 --- a/docs/docs/get_started/configuration.md +++ b/docs/docs/get_started/configuration.md @@ -52,7 +52,7 @@ keys_path = "/path/to/keys" secrets_path = "/path/to.secrets" ``` -We currently support Lighthouse, Prysm, Teku and Lodestar's keystores so it's easier to load the keys. We're working on adding support for additional keystores. These are the expected file structures for each format: +We currently support Lighthouse, Prysm, Teku, Lodestar, and Nimbus's keystores so it's easier to load the keys. We're working on adding support for additional keystores. These are the expected file structures for each format:
Lighthouse @@ -175,6 +175,37 @@ We currently support Lighthouse, Prysm, Teku and Lodestar's keystores so it's ea :::
+
+ Nimbus + + #### File structure: + ``` + ├── keys + │   ├── + │   │   └── keystore.json + │   └── + │   └── keystore.json + └── secrets +    ├── +    └── + ``` + + #### Config: + ```toml + [pbs] + ... + with_signer = true + + [signer] + port = 20000 + + [signer.local.loader] + format = "nimbus" + keys_path = "keys" + secrets_path = "secrets" + ``` +
+ ### Proxy keys store Proxy keys can be used to sign transactions with a different key than the one used to sign the block. Proxy keys are generated by the Signer module and authorized by the validator key. Each module have their own proxy keys, that can be BLS or ECDSA.