eigen_cli/
lib.rs

1#![doc(
2    html_logo_url = "https://github.com/Layr-Labs/eigensdk-rs/assets/91280922/bd13caec-3c00-4afc-839a-b83d2890beb5",
3    issue_tracker_base_url = "https://github.com/Layr-Labs/eigensdk-rs/issues/"
4)]
5pub mod args;
6pub mod bls;
7mod convert;
8pub mod eigen_address;
9mod generate;
10mod operator_id;
11
12use eth_keystore::KeystoreError;
13use tokio::runtime::Runtime;
14
15use crate::eigen_address::ContractAddresses;
16use alloy::contract::Error as ContractError;
17use alloy::transports::RpcError;
18use alloy::transports::TransportErrorKind;
19use args::{Commands, EigenKeyCommand};
20use ark_serialize::SerializationError;
21use bls::BlsKeystore;
22use colored::*;
23use convert::store;
24use eigen_crypto_bls::error::BlsError;
25pub use generate::KeyGenerator;
26use operator_id::derive_operator_id;
27use rust_bls_bn254::{errors::KeystoreError as BlsKeystoreError, mnemonics::Mnemonic};
28use thiserror::Error;
29
30/// Possible errors raised while executing a CLI command
31#[derive(Error, Debug)]
32pub enum EigenCliError {
33    #[error("address error")]
34    EigenAddressCliError(EigenAddressCliError),
35    #[error("key error")]
36    EigenKeyCliError(EigenKeyCliError),
37}
38
39/// Possible errors raised while trying to get contract addresses
40#[derive(Error, Debug)]
41pub enum EigenAddressCliError {
42    #[error("contract error")]
43    ContractError(ContractError),
44    #[error("RPC error")]
45    RpcError(RpcError<TransportErrorKind>),
46}
47
48/// Possible errors raised while executing egnkey commands
49#[derive(Error, Debug)]
50pub enum EigenKeyCliError {
51    #[error("file error")]
52    FileError(std::io::Error),
53    #[error("keystore error")]
54    KeystoreError(KeystoreError),
55    #[error("BLS error")]
56    BLSError(BlsError),
57    #[error("serialization error")]
58    SerializationError(SerializationError),
59    #[error("Bls Keystore error")]
60    EigenBlsKeyStoreError(EigenBlsKeyStoreError),
61}
62
63impl From<EigenBlsKeyStoreError> for EigenKeyCliError {
64    fn from(e: EigenBlsKeyStoreError) -> EigenKeyCliError {
65        EigenKeyCliError::EigenBlsKeyStoreError(e)
66    }
67}
68
69/// BlsKeyStore errors
70#[derive(Error, Debug)]
71pub enum EigenBlsKeyStoreError {
72    #[error("Invalid secret key : Failed to decode key to hex {0} ")]
73    InvalidSecretKeyFromHexError(String),
74
75    #[error("KeyStore Error : {0}")]
76    BlsKeystoreError(String),
77}
78
79impl From<BlsKeystoreError> for EigenBlsKeyStoreError {
80    fn from(e: BlsKeystoreError) -> EigenBlsKeyStoreError {
81        EigenBlsKeyStoreError::BlsKeystoreError(e.to_string())
82    }
83}
84
85impl From<hex::FromHexError> for EigenBlsKeyStoreError {
86    fn from(e: hex::FromHexError) -> EigenBlsKeyStoreError {
87        EigenBlsKeyStoreError::InvalidSecretKeyFromHexError(e.to_string())
88    }
89}
90
91/// Executes an `egnkey` subcommand.
92///
93/// # Arguments
94///
95/// * `subcommand` - An egnkey subcommand which can be `generate`, `convert` or `derive-operator-id`.
96///
97/// # Errors
98///
99/// - If the subcommand execution fails (`EigenKeyCliError`).
100pub fn execute_egnkey_subcommand(subcommand: EigenKeyCommand) -> Result<(), EigenKeyCliError> {
101    match subcommand {
102        EigenKeyCommand::Generate {
103            key_type,
104            num_keys,
105            output_dir,
106        } => KeyGenerator::from(key_type).generate(num_keys, output_dir),
107
108        EigenKeyCommand::ConvertECDSA {
109            private_key,
110            output_file,
111            password,
112        } => store(private_key.into(), output_file, password)
113            .map_err(EigenKeyCliError::KeystoreError),
114
115        EigenKeyCommand::DeriveOperatorId { private_key } => {
116            let operator_id =
117                derive_operator_id(private_key).map_err(EigenKeyCliError::BLSError)?;
118            println!("{}", operator_id);
119            Ok(())
120        }
121
122        EigenKeyCommand::BlsConvert {
123            key_type,
124            secret_key,
125            output_path,
126            password,
127        } => {
128            BlsKeystore::from(key_type).new_keystore(
129                secret_key,
130                output_path,
131                password.as_deref(),
132            )?;
133            Ok(())
134        }
135        EigenKeyCommand::CreateNewMnemonicFromDefaultWordList { language } => {
136            let word_list = language.try_from().1;
137            let mnemonic = Mnemonic::get_mnemonic_without_word_path(word_list, None).unwrap();
138
139            println!("New mnemonic generated : {}", mnemonic);
140            println!("{}", "Please store it safely!".red().bold());
141            Ok(())
142        }
143        EigenKeyCommand::CreateNewMnemonicFromPath { language, path } => {
144            let language_string = language.try_from().0;
145
146            let mnemonic = Mnemonic::get_mnemonic(language_string, &path, None).unwrap();
147            println!("New mnemonic generated : {}", mnemonic);
148            println!("{}", "Please store it safely!".red().bold());
149            Ok(())
150        }
151    }
152}
153
154/// Executes a CLI command.
155///
156/// # Arguments
157///
158/// * `command` - A CLI command which can be `Commands::EigenAddress` or `Commands::EigenKey`
159///
160/// # Errors
161///
162/// - If the command execution fails (`EigenCliError`).
163pub fn execute_command(command: Commands) -> Result<(), EigenCliError> {
164    match command {
165        Commands::EigenAddress {
166            service_manager,
167            registry_coordinator,
168            rpc_url,
169        } => {
170            let rt = Runtime::new().unwrap();
171            let addresses = rt.block_on(async {
172                ContractAddresses::get_addresses(service_manager, registry_coordinator, rpc_url)
173                    .await
174                    .map_err(EigenCliError::EigenAddressCliError)
175            })?;
176            println!("{}", serde_json::to_string_pretty(&addresses).unwrap());
177            Ok(())
178        }
179        Commands::EigenKey { subcommand } => {
180            execute_egnkey_subcommand(subcommand).map_err(EigenCliError::EigenKeyCliError)?;
181            Ok(())
182        }
183    }
184}
185
186#[cfg(test)]
187mod test {
188    use crate::args::{BlsKeystoreType, EigenKeyCommand, MnemonicLanguage};
189    use crate::convert::store;
190    use crate::eigen_address::ContractAddresses;
191    use crate::{
192        args::{Commands, KeyType},
193        execute_command,
194        generate::{KeyGenerator, DEFAULT_KEY_FOLDER, PASSWORD_FILE, PRIVATE_KEY_HEX_FILE},
195        operator_id::derive_operator_id,
196    };
197    use alloy::primitives::Address;
198    use eigen_testing_utils::anvil::start_anvil_container;
199    use eigen_testing_utils::anvil_constants::{
200        get_registry_coordinator_address, get_service_manager_address,
201    };
202    use eigen_testing_utils::test_data::TestData;
203    use eth_keystore::decrypt_key;
204    use k256::SecretKey;
205    use rstest::rstest;
206    use rust_bls_bn254::keystores::base_keystore::Keystore;
207    use serde::Deserialize;
208    use std::fs;
209    use tempfile::tempdir;
210
211    #[rstest]
212    #[case(BlsKeystoreType::Scrypt)]
213    #[case(BlsKeystoreType::Pbkdf2)]
214    fn test_blskeystore_generation(#[case] keystore_type: BlsKeystoreType) {
215        let output_dir = tempdir().unwrap();
216        let output_path = output_dir.path().join("keystore.json");
217        let key = "12248929636257230549931416853095037629726205319386239410403476017439825112537";
218        let subcommand = EigenKeyCommand::BlsConvert {
219            key_type: keystore_type,
220            secret_key: key.to_string(),
221            output_path: output_path.to_str().unwrap().to_string(),
222            password: Some("testpassword".to_string()),
223        };
224
225        let command = Commands::EigenKey { subcommand };
226
227        execute_command(command).unwrap();
228
229        let keystore_instance = Keystore::from_file(output_path.to_str().unwrap()).unwrap();
230        let decrypted_key = keystore_instance.decrypt("testpassword").unwrap();
231        let fr_key: String = decrypted_key.iter().map(|&value| value as char).collect();
232        assert_eq!(fr_key, key);
233    }
234
235    #[rstest]
236    #[case(MnemonicLanguage::English)]
237    #[case(MnemonicLanguage::Italian)]
238    #[case(MnemonicLanguage::ChineseSimplified)]
239    #[case(MnemonicLanguage::ChineseTraditional)]
240    #[case(MnemonicLanguage::Spanish)]
241    #[case(MnemonicLanguage::Korean)]
242    #[case(MnemonicLanguage::Portuguese)]
243    #[case(MnemonicLanguage::Czech)]
244    fn test_new_mnemonic_from_default_word_list(#[case] language: MnemonicLanguage) {
245        let subcommand = EigenKeyCommand::CreateNewMnemonicFromDefaultWordList { language };
246
247        let command = Commands::EigenKey { subcommand };
248
249        execute_command(command).unwrap();
250    }
251
252    #[test]
253    fn test_egnkey_derive_operator_id() {
254        let private_key =
255            "13710126902690889134622698668747132666439281256983827313388062967626731803599"
256                .to_string();
257        let operator_id = derive_operator_id(private_key).unwrap();
258        let expected_operator_id =
259            "48beccce16ccdf8000c13d5af5f91c7c3dac6c47b339d993d229af1500dbe4a9".to_string();
260        assert_eq!(expected_operator_id, operator_id);
261    }
262
263    #[test]
264    fn test_convert_ecdsa() {
265        let private_key = KeyGenerator::random_ecdsa_key();
266        let password = KeyGenerator::generate_random_password();
267        let file = "key.json".to_string();
268        let path = "./key.json".to_string();
269
270        store(private_key.clone(), Some(file), Some(password.clone())).unwrap();
271        let decrypted_key = decrypt_key(path.clone(), password).unwrap();
272        std::fs::remove_file(path).unwrap();
273
274        assert_eq!(private_key, decrypted_key);
275    }
276
277    #[derive(Deserialize, Debug)]
278    struct Input {
279        service_manager_address: Address,
280        rpc_url: String,
281    }
282
283    #[tokio::test]
284    async fn test_egn_addrs_with_service_manager_flag() {
285        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
286        let test_data = TestData::new(Input {
287            service_manager_address: get_service_manager_address(http_endpoint.clone()).await,
288            rpc_url: http_endpoint.clone(),
289        });
290        let expected_addresses: ContractAddresses = serde_json::from_str(&format!(
291            r#"{{
292                "avs": {{
293                    "bls-apk-registry": "0xfd471836031dc5108809d173a067e8486b9047a3",
294                    "index-registry": "0x1429859428c0abc9c2c47c8ee9fbaf82cfa0f20f",
295                    "registry-coordinator": "0x7bc06c482dead17c0e297afbc32f6e63d3846650",
296                    "service-manager": "0xcd8a1c3ba11cf5ecfa6267617243239504a98d90",
297                    "stake-registry": "0x2bdcc0de6be1f7d2ee689a0342d76f52e8efaba3"
298                }},
299                "eigenlayer": {{
300                    "delegation-manager": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9",
301                    "allocation-manager": "0x8a791620dd6260079bf849dc5567adc3f2fdc318",
302                    "strategy-manager": "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853"
303                }},
304                "network": {{
305                    "chain-id": "31337",
306                    "rpc-url": "{http_endpoint}"
307                }}
308            }}"#,
309        ))
310        .unwrap();
311        let addresses = ContractAddresses::get_addresses(
312            Some(test_data.input.service_manager_address),
313            None,
314            test_data.input.rpc_url,
315        )
316        .await
317        .unwrap();
318
319        assert_eq!(expected_addresses, addresses);
320    }
321
322    #[tokio::test]
323    async fn test_egn_addrs_with_registry_coordinator_flag() {
324        let (_container, http_endpoint, _ws_endpoint) = start_anvil_container().await;
325        let registry_coordinator_address =
326            get_registry_coordinator_address(http_endpoint.clone()).await;
327
328        // When no service manager is provided, the service manager address is set to 0
329        let expected_addresses: ContractAddresses = serde_json::from_str(&format!(
330            r#"{{
331                "avs": {{
332                    "bls-apk-registry": "0xfd471836031dc5108809d173a067e8486b9047a3",
333                    "index-registry": "0x1429859428c0abc9c2c47c8ee9fbaf82cfa0f20f",
334                    "registry-coordinator": "0x7bc06c482dead17c0e297afbc32f6e63d3846650",
335                    "service-manager": "0x0000000000000000000000000000000000000000",
336                    "stake-registry": "0x2bdcc0de6be1f7d2ee689a0342d76f52e8efaba3"
337                }},
338                "eigenlayer": {{
339                    "delegation-manager": "0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9",
340                    "allocation-manager": "0x8a791620dd6260079bf849dc5567adc3f2fdc318",
341                    "strategy-manager": "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853"
342                }},
343                "network": {{
344                    "chain-id": "31337",
345                    "rpc-url": "{http_endpoint}"
346                }}
347            }}"#,
348        ))
349        .unwrap();
350        let addresses = ContractAddresses::get_addresses(
351            None,
352            Some(registry_coordinator_address),
353            http_endpoint,
354        )
355        .await
356        .unwrap();
357
358        assert_eq!(expected_addresses, addresses);
359    }
360
361    #[rstest]
362    #[case(KeyType::Ecdsa)]
363    #[case(KeyType::Bls)]
364    fn test_generate_key(#[case] key_type: KeyType) {
365        let output_dir = tempdir().unwrap();
366        let output_path = output_dir.path();
367        let subcommand = EigenKeyCommand::Generate {
368            key_type: key_type.clone(),
369            num_keys: 1,
370            output_dir: output_path.to_str().map(String::from),
371        };
372        let command = Commands::EigenKey { subcommand };
373
374        execute_command(command).unwrap();
375
376        let private_key_hex = fs::read_to_string(output_path.join(PRIVATE_KEY_HEX_FILE)).unwrap();
377        let password = fs::read_to_string(output_path.join(PASSWORD_FILE)).unwrap();
378        let key_name = match key_type {
379            KeyType::Ecdsa => "ecdsa",
380            KeyType::Bls => "bls",
381        };
382        let key_path = output_path
383            .join(DEFAULT_KEY_FOLDER)
384            .join(format!("1.{}.key.json", key_name));
385
386        let decrypted_bytes = decrypt_key(key_path, password).unwrap();
387        let decrypted_private_key = SecretKey::from_slice(&decrypted_bytes).unwrap().to_bytes();
388
389        let private_key = hex::decode(private_key_hex).unwrap();
390
391        assert_eq!(private_key, decrypted_private_key.as_slice());
392    }
393}