Skip to content

Commit 2d344de

Browse files
authored
Merge pull request #227 from EspressoSystems/ma/mnemonic-to-keystore
Create keystore from mnemonic
2 parents e88b5d7 + fb5f4bc commit 2d344de

File tree

3 files changed

+104
-28
lines changed

3 files changed

+104
-28
lines changed

Cargo.lock

+5-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

keygen/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ edition = "2021"
66

77
[dependencies]
88
clap = { version = "4.3.9", features = ["derive", "env"] }
9+
coins-bip32 = "0.8.7"
910
eth-keystore = { version = "0.5.0", features = ["geth-compat"] }
1011
ethers = "2.0.7"
1112
rand = "0.8.5"
13+
tempfile = "3.8.0"

keygen/src/main.rs

+97-22
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,120 @@
11
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+
};
316

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";
518

619
#[derive(Parser, Clone, Debug)]
720
struct Options {
8-
#[clap(long, env = "ESPRESSO_ZKEVM_KEYSTORE_DIR", default_value = "./")]
9-
keystore_dir: PathBuf,
10-
1121
#[clap(
1222
long,
1323
env = "ESPRESSO_ZKEVM_KEYSTORE_PASSWORD",
1424
default_value = "testonly"
1525
)]
1626
password: String,
1727

28+
#[clap(long, env = "ESPRESSO_ZKEVM_KEYSTORE_PATH")]
29+
path: PathBuf,
30+
1831
#[clap(
1932
long,
20-
env = "ESPRESSO_ZKEVM_KEYSTORE_NAME",
21-
default_value = "aggregator.keystore"
33+
env = "ESPRESSO_ZKEVM_KEYSTORE_MNEMONIC",
34+
default_value = TEST_MNEMONIC,
2235
)]
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(())
2473
}
2574

2675
fn main() {
2776
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+
}
3179

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;
3686

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();
3995

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+
}
45120
}

0 commit comments

Comments
 (0)