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#[derive(Error, Debug)]
32pub enum EigenCliError {
33 #[error("address error")]
34 EigenAddressCliError(EigenAddressCliError),
35 #[error("key error")]
36 EigenKeyCliError(EigenKeyCliError),
37}
38
39#[derive(Error, Debug)]
41pub enum EigenAddressCliError {
42 #[error("contract error")]
43 ContractError(ContractError),
44 #[error("RPC error")]
45 RpcError(RpcError<TransportErrorKind>),
46}
47
48#[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#[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
91pub 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
154pub 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 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}