use super::public::PublicKey;
use crate::daemon::error::DaemonError;
use bitcoin::bip32::{ExtendedPrivKey, IntoDerivationPath};
use bitcoin::Network;
use hkd32::mnemonic::{Phrase, Seed};
use rand_core::OsRng;
use secp256k1::Secp256k1;
#[derive(Clone)]
pub struct PrivateKey {
#[allow(missing_docs)]
pub account: u32,
#[allow(missing_docs)]
pub index: u32,
#[allow(missing_docs)]
pub coin_type: u32,
mnemonic: Option<Phrase>,
#[allow(dead_code)]
root_private_key: ExtendedPrivKey,
private_key: ExtendedPrivKey,
}
impl PrivateKey {
pub fn new<C: secp256k1::Signing + secp256k1::Context>(
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, "")
}
pub fn new_seed<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
seed_phrase: &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)
}
pub fn from_words<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
words: &str,
account: u32,
index: u32,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) {
Ok(phrase) => {
PrivateKey::gen_private_key_phrase(secp, phrase, account, index, coin_type, "")
}
Err(_) => Err(DaemonError::Phrasing),
}
}
pub fn from_words_seed<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
words: &str,
seed_pass: &str,
coin_type: u32,
) -> Result<PrivateKey, DaemonError> {
match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) {
Ok(phrase) => {
PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, coin_type, seed_pass)
}
Err(_) => Err(DaemonError::Phrasing),
}
}
pub fn public_key<C: secp256k1::Signing + secp256k1::Context>(
&self,
secp: &Secp256k1<C>,
) -> PublicKey {
let x = self.private_key.private_key.public_key(secp);
PublicKey::from_bitcoin_public_key(&bitcoin::PublicKey::new(x))
}
pub fn raw_key(&self) -> Vec<u8> {
self.private_key.private_key.secret_bytes().to_vec()
}
fn gen_private_key_phrase<C: secp256k1::Signing + secp256k1::Context>(
secp: &Secp256k1<C>,
phrase: Phrase,
account: u32,
index: u32,
coin_type: u32,
seed_phrase: &str,
) -> Result<PrivateKey, DaemonError> {
let seed = phrase.to_seed(seed_phrase);
let root_private_key =
ExtendedPrivKey::new_master(Network::Bitcoin, seed.as_bytes()).unwrap();
let path = format!("m/44'/{coin_type}'/{account}'/0/{index}");
let derivation_path = path.into_derivation_path()?;
let private_key = root_private_key.derive_priv(secp, &derivation_path)?;
Ok(PrivateKey {
account,
index,
coin_type,
mnemonic: Some(phrase),
root_private_key,
private_key,
})
}
pub fn words(&self) -> Option<&str> {
self.mnemonic.as_ref().map(|phrase| phrase.phrase())
}
#[allow(dead_code)]
pub(crate) fn seed(&self, passwd: &str) -> Option<Seed> {
self.mnemonic.as_ref().map(|phrase| phrase.to_seed(passwd))
}
}
#[cfg(test)]
mod tst {
use super::*;
#[test]
pub fn tst_gen_mnemonic() -> Result<(), DaemonError> {
let s = Secp256k1::new();
let coin_type: u32 = 330;
PrivateKey::new(&s, coin_type).map(|_| ())
}
#[test]
pub fn tst_words() -> anyhow::Result<()> {
let coin_type: u32 = 330;
let str_1 = "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius";
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);
match pk.words() {
Some(words) => {
assert_eq!(words, str_1);
Ok(())
}
None => Err(DaemonError::MissingPhrase.into()),
}
}
#[test]
pub fn tst_root_priv_key() -> anyhow::Result<()> {
let coin_type: u32 = 330;
let str_1 = "wonder caution square unveil april art add hover spend smile proud admit modify old copper throw crew happy nature luggage reopen exhibit ordinary napkin";
let secp = Secp256k1::new();
let pk = PrivateKey::from_words(&secp, str_1, 0, 0, coin_type)?;
let root_key = "xprv9s21ZrQH143K2ep3BpYRRMjSqjLHZAPAzxfVVS3NBuGKBVtCrK3C8mE8TcmTjYnLm7SJxdLigDFWGAMnctKxc3p5QKNWXdprcFSQzGzQqTW";
assert_eq!(pk.root_private_key.to_string(), root_key);
let derived_key = "4804e2bdce36d413206ccf47cc4c64db2eff924e7cc9e90339fa7579d2bd9d5b";
assert_eq!(
pk.private_key.private_key.display_secret().to_string(),
derived_key
);
Ok(())
}
#[test]
pub fn tst_words_to_pub() -> anyhow::Result<()> {
let str_1 = "wonder caution square unveil april art add hover spend smile proud admit modify old copper throw crew happy nature luggage reopen exhibit ordinary napkin";
let coin_type: u32 = 330;
let prefix = "terra";
let secp = Secp256k1::new();
let pk = PrivateKey::from_words(&secp, str_1, 0, 0, coin_type)?;
let pub_k = pk.public_key(&secp);
let account = pub_k.account(prefix)?;
assert_eq!(&account, "terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm");
assert_eq!(
&pub_k.operator_address_public_key(prefix)?,
"terravaloperpub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5y7accr"
);
assert_eq!(
&pub_k.application_public_key(prefix)?,
"terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9"
);
Ok(())
}
}