diff --git a/CHANGELOG.md b/CHANGELOG.md index 267d88259..5f32389e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ### Breaking +- Added Support for more mnemonic lengths (at least 24 and 12). This is breaking because of how the mnemonic words are stored and retrieved (`words` method on `PrivateKey`) + ## 0.25.0 - `is_test` Added for Daemon Builders, when set to `true` will use temporary file for state diff --git a/cw-orch-daemon/Cargo.toml b/cw-orch-daemon/Cargo.toml index 371dc0215..d2f327527 100644 --- a/cw-orch-daemon/Cargo.toml +++ b/cw-orch-daemon/Cargo.toml @@ -48,7 +48,6 @@ tokio = { workspace = true, features = ["full"] } tonic = { workspace = true, features = ["tls"] } reqwest = { version = "0.12.5" } base64 = { version = "0.22.1" } -hkd32 = { version = "0.7.0", features = ["bip39", "mnemonic", "bech32"] } rand_core = { version = "0.6.4", default-features = false } ed25519-dalek = { version = "2", features = ["serde"] } eyre = { version = "0.6" } @@ -57,7 +56,7 @@ chrono = { version = "0.4" } base16 = { version = "0.2.1" } ring = { version = "0.17.8" } dirs = "5.0.1" - +bip39 = { version = "2.0.0", features = ["rand"] } # Injective dependencies ethers-signers = { version = "2.0.14", optional = true } diff --git a/cw-orch-daemon/src/keys/private.rs b/cw-orch-daemon/src/keys/private.rs index ddae0c3b9..6335ce344 100644 --- a/cw-orch-daemon/src/keys/private.rs +++ b/cw-orch-daemon/src/keys/private.rs @@ -11,9 +11,10 @@ use bitcoin::{ }; use cosmrs::tx::SignerPublicKey; use cw_orch_core::log::local_target; -use hkd32::mnemonic::{Phrase, Seed}; use prost_types::Any; -use rand_core::OsRng; +use rand_core::{OsRng, RngCore}; + +pub const DEFAULT_MNEMONIC_WORD_COUNT: usize = 24; /// The Private key structure that is used to generate signatures and public keys /// WARNING: No Security Audit has been performed @@ -25,8 +26,8 @@ pub struct PrivateKey { pub index: u32, #[allow(missing_docs)] pub coin_type: u32, - /// The 24 words used to generate this private key - mnemonic: Option, + /// The mnemonic (12, 15, 18, 21 or 24 words) used to generate this private key + mnemonic: Option, #[allow(dead_code)] /// This is used for testing root_private_key: Xpriv, @@ -39,19 +40,20 @@ impl PrivateKey { secp: &Secp256k1, coin_type: u32, ) -> Result { - let phrase = hkd32::mnemonic::Phrase::random(OsRng, hkd32::mnemonic::Language::English); - - PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, coin_type, "") + Self::new_seed(secp, "", coin_type) } /// generate a new private key with a seed phrase pub fn new_seed( secp: &Secp256k1, - seed_phrase: &str, + passphrase: &str, coin_type: u32, ) -> Result { - let phrase = hkd32::mnemonic::Phrase::random(OsRng, hkd32::mnemonic::Language::English); - - PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, coin_type, seed_phrase) + match bip39::Mnemonic::generate(DEFAULT_MNEMONIC_WORD_COUNT) { + Ok(mnemonic) => { + PrivateKey::gen_private_key_phrase(secp, mnemonic, 0, 0, coin_type, passphrase) + } + Err(_) => Err(DaemonError::Phrasing), + } } /// for private key recovery. This is also used by wallet routines to re-hydrate the structure pub fn from_words( @@ -61,28 +63,24 @@ impl PrivateKey { index: u32, coin_type: u32, ) -> Result { - if words.split(' ').count() != 24 { - return Err(DaemonError::WrongLength); - } - - match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) { - Ok(phrase) => { - PrivateKey::gen_private_key_phrase(secp, phrase, account, index, coin_type, "") + match bip39::Mnemonic::parse_in_normalized(bip39::Language::English, words) { + Ok(mnemonic) => { + PrivateKey::gen_private_key_phrase(secp, mnemonic, account, index, coin_type, "") } Err(_) => Err(DaemonError::Phrasing), } } - /// for private key recovery with seed phrase - pub fn from_words_seed( + /// for private key recovery with passphrase + pub fn from_words_with_passphrase( secp: &Secp256k1, words: &str, - seed_pass: &str, + passphrase: &str, coin_type: u32, ) -> Result { - match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) { + match bip39::Mnemonic::parse_in_normalized(bip39::Language::English, words) { Ok(phrase) => { - PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, coin_type, seed_pass) + PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, coin_type, passphrase) } Err(_) => Err(DaemonError::Phrasing), } @@ -168,15 +166,14 @@ impl PrivateKey { // Generate private key from Phrase fn gen_private_key_phrase( secp: &Secp256k1, - phrase: Phrase, + phrase: bip39::Mnemonic, account: u32, index: u32, coin_type: u32, - seed_phrase: &str, + passphrase: &str, ) -> Result { - let seed = phrase.to_seed(seed_phrase); - let mut private_key = - Self::gen_private_key_raw(secp, seed.as_bytes(), account, index, coin_type)?; + let seed = phrase.to_seed(passphrase); + let mut private_key = Self::gen_private_key_raw(secp, &seed, account, index, coin_type)?; private_key.mnemonic = Some(phrase); Ok(private_key) } @@ -206,14 +203,14 @@ impl PrivateKey { } /// the words used to generate this private key - pub fn words(&self) -> Option<&str> { - self.mnemonic.as_ref().map(|phrase| phrase.phrase()) + pub fn words(&self) -> Option { + self.mnemonic.as_ref().map(|phrase| phrase.to_string()) } /// used for testing /// could potentially be used to recreate the private key instead of words #[allow(dead_code)] - pub(crate) fn seed(&self, passwd: &str) -> Option { + pub(crate) fn seed(&self, passwd: &str) -> Option<[u8; 64]> { self.mnemonic.as_ref().map(|phrase| phrase.to_seed(passwd)) } } @@ -251,7 +248,7 @@ mod tst { let seed_1 = "a2ae8846397b55d266af35acdbb18ba1d005f7ddbdd4ca7a804df83352eaf373f274ba0dc8ac1b2b25f19dfcb7fa8b30a240d2c6039d88963defc2f626003b2f"; let s = Secp256k1::new(); let pk = PrivateKey::from_words(&s, str_1, 0, 0, coin_type)?; - assert_eq!(hex::encode(pk.seed("").unwrap().as_bytes()), seed_1); + assert_eq!(hex::encode(pk.seed("").unwrap()), seed_1); match pk.words() { Some(words) => { assert_eq!(words, str_1);