|
1 | 1 | use clap::Parser;
|
2 |
| -use std::path::PathBuf; |
| 2 | +use coins_bip32::{path::DerivationPath, Bip32Error}; |
| 3 | +use eth_keystore::encrypt_key; |
| 4 | +use ethers::{ |
| 5 | + prelude::k256::ecdsa::SigningKey, |
| 6 | + signers::{ |
| 7 | + coins_bip39::{English, Mnemonic}, |
| 8 | + WalletError, |
| 9 | + }, |
| 10 | + utils::secret_key_to_address, |
| 11 | +}; |
| 12 | +use std::{ |
| 13 | + path::{Path, PathBuf}, |
| 14 | + str::FromStr, |
| 15 | +}; |
3 | 16 |
|
4 |
| -use ethers::{prelude::k256::ecdsa::SigningKey, utils::secret_key_to_address}; |
| 17 | +const TEST_MNEMONIC: &str = "test test test test test test test test test test test junk"; |
5 | 18 |
|
6 | 19 | #[derive(Parser, Clone, Debug)]
|
7 | 20 | struct Options {
|
8 |
| - #[clap(long, env = "ESPRESSO_ZKEVM_KEYSTORE_DIR", default_value = "./")] |
9 |
| - keystore_dir: PathBuf, |
10 |
| - |
11 | 21 | #[clap(
|
12 | 22 | long,
|
13 | 23 | env = "ESPRESSO_ZKEVM_KEYSTORE_PASSWORD",
|
14 | 24 | default_value = "testonly"
|
15 | 25 | )]
|
16 | 26 | password: String,
|
17 | 27 |
|
| 28 | + #[clap(long, env = "ESPRESSO_ZKEVM_KEYSTORE_PATH")] |
| 29 | + path: PathBuf, |
| 30 | + |
18 | 31 | #[clap(
|
19 | 32 | long,
|
20 |
| - env = "ESPRESSO_ZKEVM_KEYSTORE_NAME", |
21 |
| - default_value = "aggregator.keystore" |
| 33 | + env = "ESPRESSO_ZKEVM_KEYSTORE_MNEMONIC", |
| 34 | + default_value = TEST_MNEMONIC, |
22 | 35 | )]
|
23 |
| - filename: String, |
| 36 | + mnemonic: String, |
| 37 | + |
| 38 | + #[clap(long, env = "ESPRESSO_ZKEVM_KEYSTORE_INDEX", default_value = "0")] |
| 39 | + index: u32, |
| 40 | +} |
| 41 | + |
| 42 | +fn mnemonic_to_key( |
| 43 | + mnemonic: &Mnemonic<English>, |
| 44 | + derivation_path: &DerivationPath, |
| 45 | +) -> Result<SigningKey, WalletError> { |
| 46 | + let derived_priv_key = mnemonic.derive_key(derivation_path, None /* password */)?; |
| 47 | + let key: &coins_bip32::prelude::SigningKey = derived_priv_key.as_ref(); |
| 48 | + Ok(SigningKey::from_bytes(&key.to_bytes())?) |
| 49 | +} |
| 50 | + |
| 51 | +fn create_key_store( |
| 52 | + path: &Path, |
| 53 | + mnemonic: &str, |
| 54 | + index: u32, |
| 55 | + password: &str, |
| 56 | +) -> Result<(), Bip32Error> { |
| 57 | + let name = path.file_name().unwrap().to_str().unwrap(); |
| 58 | + let dir = path.parent().unwrap().to_path_buf(); |
| 59 | + let mnemonic = Mnemonic::<English>::new_from_phrase(mnemonic).unwrap(); |
| 60 | + let derivation_path = DerivationPath::from_str(&format!("m/44'/60'/0'/0/{}", index)).unwrap(); |
| 61 | + let signer = mnemonic_to_key(&mnemonic, &derivation_path).unwrap(); |
| 62 | + let address = secret_key_to_address(&signer); |
| 63 | + |
| 64 | + let mut rng = rand::thread_rng(); |
| 65 | + let sign_key_bytes = signer.to_bytes(); |
| 66 | + encrypt_key(dir.clone(), &mut rng, sign_key_bytes, password, Some(name)).unwrap(); |
| 67 | + println!( |
| 68 | + "New Keystore with address {:?} created at {:?}", |
| 69 | + address, path |
| 70 | + ); |
| 71 | + |
| 72 | + Ok(()) |
24 | 73 | }
|
25 | 74 |
|
26 | 75 | fn main() {
|
27 | 76 | let opt = Options::parse();
|
28 |
| - let dir = opt.keystore_dir; |
29 |
| - let name = opt.filename; |
30 |
| - let password = opt.password; |
| 77 | + create_key_store(&opt.path, &opt.mnemonic, opt.index, &opt.password).unwrap(); |
| 78 | +} |
31 | 79 |
|
32 |
| - let mut rng = rand::thread_rng(); |
33 |
| - let (secret, _) = eth_keystore::new(dir.clone(), &mut rng, password, Some(&name)).unwrap(); |
34 |
| - let signer = SigningKey::from_bytes(secret.as_slice().into()).unwrap(); |
35 |
| - let address = secret_key_to_address(&signer); |
| 80 | +#[cfg(test)] |
| 81 | +mod tests { |
| 82 | + use super::*; |
| 83 | + use eth_keystore::decrypt_key; |
| 84 | + use ethers::{signers::coins_bip39::English, types::H160, utils::hex}; |
| 85 | + use std::str::FromStr; |
36 | 86 |
|
37 |
| - let mut full_path: PathBuf = dir; |
38 |
| - full_path.push(name); |
| 87 | + #[test] |
| 88 | + fn test_mnemonic_to_key() { |
| 89 | + let expected_address: H160 = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" |
| 90 | + .parse() |
| 91 | + .unwrap(); |
| 92 | + let expected_private_key = |
| 93 | + hex::decode("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") |
| 94 | + .unwrap(); |
39 | 95 |
|
40 |
| - println!( |
41 |
| - "New Keystore with address {:?} created at {}", |
42 |
| - address, |
43 |
| - full_path.display() |
44 |
| - ) |
| 96 | + // Derive known key in memory |
| 97 | + let mnemonic = Mnemonic::<English>::new_from_phrase(TEST_MNEMONIC).unwrap(); |
| 98 | + let derivation_path = |
| 99 | + DerivationPath::from_str(&format!("m/44'/60'/0'/0/{}", 0u32)).unwrap(); |
| 100 | + let signer = mnemonic_to_key(&mnemonic, &derivation_path).unwrap(); |
| 101 | + let signing_key_bytes = signer.to_bytes().to_vec(); |
| 102 | + |
| 103 | + // Check the derived key matches the expected key |
| 104 | + assert_eq!(signing_key_bytes, expected_private_key); |
| 105 | + |
| 106 | + // Check the address of the key matches the expected address of the first account |
| 107 | + let address = secret_key_to_address(&signer); |
| 108 | + assert_eq!(address, expected_address); |
| 109 | + |
| 110 | + // Create a keystore and check the decrypted key matches the key derived in memory |
| 111 | + let name = "test.keystore"; |
| 112 | + let tmpdir = tempfile::tempdir().unwrap(); |
| 113 | + let dir = tmpdir.path().to_path_buf(); |
| 114 | + let path = dir.join(name); |
| 115 | + create_key_store(&path, TEST_MNEMONIC, 0, "testonly").unwrap(); |
| 116 | + |
| 117 | + let decrypted_key_bytes = decrypt_key(path, "testonly").unwrap(); |
| 118 | + assert_eq!(decrypted_key_bytes, expected_private_key); |
| 119 | + } |
45 | 120 | }
|
0 commit comments