Skip to content

Commit

Permalink
12 words mnemonic (#486)
Browse files Browse the repository at this point in the history
* Added 12 words mnemonic change

* Changelog

* nits

* nits

* Using bip39 rng

* Changelog and function name

---------

Co-authored-by: cyberhoward <cyberhoward@protonmail.com>
  • Loading branch information
Kayanski and CyberHoward authored Sep 11, 2024
1 parent 434d31d commit dd7238f
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 34 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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
Expand Down
3 changes: 1 addition & 2 deletions cw-orch-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -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 }
Expand Down
61 changes: 29 additions & 32 deletions cw-orch-daemon/src/keys/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Phrase>,
/// The mnemonic (12, 15, 18, 21 or 24 words) used to generate this private key
mnemonic: Option<bip39::Mnemonic>,
#[allow(dead_code)]
/// This is used for testing
root_private_key: Xpriv,
Expand All @@ -39,19 +40,20 @@ impl PrivateKey {
secp: &Secp256k1<C>,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
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<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
seed_phrase: &str,
passphrase: &str,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
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<C: secp256k1::Signing + secp256k1::Context>(
Expand All @@ -61,28 +63,24 @@ impl PrivateKey {
index: u32,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
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<C: secp256k1::Signing + secp256k1::Context>(
/// for private key recovery with passphrase
pub fn from_words_with_passphrase<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
words: &str,
seed_pass: &str,
passphrase: &str,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
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),
}
Expand Down Expand Up @@ -168,15 +166,14 @@ impl PrivateKey {
// Generate private key from Phrase
fn gen_private_key_phrase<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
phrase: Phrase,
phrase: bip39::Mnemonic,
account: u32,
index: u32,
coin_type: u32,
seed_phrase: &str,
passphrase: &str,
) -> Result<PrivateKey, DaemonError> {
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)
}
Expand Down Expand Up @@ -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<String> {
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<Seed> {
pub(crate) fn seed(&self, passwd: &str) -> Option<[u8; 64]> {
self.mnemonic.as_ref().map(|phrase| phrase.to_seed(passwd))
}
}
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit dd7238f

Please sign in to comment.