diff --git a/moksha-cli/src/main.rs b/moksha-cli/src/main.rs index 4b71e6bd..bb7d232f 100644 --- a/moksha-cli/src/main.rs +++ b/moksha-cli/src/main.rs @@ -4,7 +4,6 @@ use moksha_core::primitives::{ CurrencyUnit, PaymentMethod, PostMeltOnchainResponse, PostMintQuoteBolt11Response, PostMintQuoteOnchainResponse, }; -use moksha_wallet::deterministic_secrets; use moksha_wallet::http::CrossPlatformHttpClient; use num_format::{Locale, ToFormattedString}; use std::io::Write; @@ -27,38 +26,25 @@ struct Opts { #[derive(Subcommand, Clone)] enum Command { /// Mint tokens - Mint { - amount: u64, - }, + Mint { amount: u64 }, /// Pay Lightning invoice - Pay { - invoice: String, - }, + Pay { invoice: String }, /// Pay Bitcoin on chain - PayOnchain { - address: String, - amount: u64, - }, + PayOnchain { address: String, amount: u64 }, /// Send tokens - Send { - amount: u64, - }, + Send { amount: u64 }, /// Receive tokens - Receive { - token: String, - }, + Receive { token: String }, /// Show local balance Balance, /// Show version and configuration Info, - - Seed, } #[tokio::main] @@ -96,25 +82,6 @@ async fn main() -> anyhow::Result<()> { })?; match cli.command { - Command::Seed => { - // FIXME - // let master = deterministic_secrets::generate_master_key().unwrap(); - // let first = deterministic_secrets::derive_private_key( - // master, - // 0, - // 0, - // deterministic_secrets::DerivationType::Secret, - // ); - // println!("Seed: {}", master); - // println!("first: {:?}", first); - // let second = deterministic_secrets::derive_private_key( - // master, - // 0, - // 1, - // deterministic_secrets::DerivationType::Secret, - // ); - // println!("second: {:?}", second); - } Command::Info => { let wallet_version = env!("CARGO_PKG_VERSION"); println!( diff --git a/moksha-wallet/src/deterministic_secrets.rs b/moksha-wallet/src/deterministic_secrets.rs deleted file mode 100644 index 4a053bf6..00000000 --- a/moksha-wallet/src/deterministic_secrets.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::error::MokshaWalletError; -use std::str::FromStr; - -use bip32::{Seed, XPrv}; -use bip39::Mnemonic; -use rand::Rng; - -pub enum DerivationType { - Secret = 0, - Blinding = 1, -} - -pub fn derive_private_key( - seed_words: &str, - keyset_id: u32, - counter: u32, - secret_or_blinding: DerivationType, -) -> Result { - let secret_or_blinding = secret_or_blinding as u32; - let derivation_path = format!("m/129372'/0'/{keyset_id}'/{counter}'/{secret_or_blinding}"); - let mnemonic = Mnemonic::from_str(seed_words)?; - let seed = Seed::new(mnemonic.to_seed("")); - let derivation_path = bip32::DerivationPath::from_str(&derivation_path)?; - let key = XPrv::derive_from_path(seed, &derivation_path)?; - Ok(hex::encode(key.private_key().to_bytes())) -} - -pub fn generate_mnemonic() -> Result { - let mut rng = rand::thread_rng(); - let entropy: [u8; 16] = rng.gen(); // 16 bytes for 12 words mnemonic - Mnemonic::from_entropy(&entropy) -} - -pub fn convert_hex_to_int(keyset_id_hex: &str) -> anyhow::Result { - let bytes = hex::decode(keyset_id_hex)?; - let bytes_array: [u8; 8] = bytes[0..8].try_into()?; - let num = u64::from_be_bytes(bytes_array); - Ok((num % (2u64.pow(31) - 1)) as u32) -} - -#[cfg(test)] -mod tests { - - use super::{convert_hex_to_int, derive_private_key, DerivationType}; - - #[test] - fn test_keyset_id_conversion() -> anyhow::Result<()> { - let int_value = convert_hex_to_int("009a1f293253e41e")?; - assert_eq!(864559728, int_value); - Ok(()) - } - - #[test] - fn test_() -> anyhow::Result<()> { - let mn = super::generate_mnemonic()?; - let words = mn.word_iter().collect::>(); - - println!("{:?}", words); - - let phrase = - "half depart obvious quality work element tank gorilla view sugar picture humble"; - - let secrets = [ - "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae", - "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270", - "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8", - "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf", - "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0", - ]; - - for (i, secret) in secrets.iter().enumerate() { - let key = derive_private_key(phrase, 864559728, i as u32, DerivationType::Secret)?; - assert_eq!(secret.to_owned(), key); - } - - let blinding_factors = [ - "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679", - "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248", - "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899", - "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29", - "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9", - ]; - - for (i, factor) in blinding_factors.iter().enumerate() { - let key = derive_private_key(phrase, 864559728, i as u32, DerivationType::Blinding)?; - assert_eq!(factor.to_owned(), key); - } - Ok(()) - } -} diff --git a/moksha-wallet/src/error.rs b/moksha-wallet/src/error.rs index 38e9ff04..de296990 100644 --- a/moksha-wallet/src/error.rs +++ b/moksha-wallet/src/error.rs @@ -68,4 +68,7 @@ pub enum MokshaWalletError { #[error("Bip39Error {0}")] Bip39(#[from] bip39::Error), + + #[error("Secp256k1 {0}")] + Secp256k1(#[from] secp256k1::Error), } diff --git a/moksha-wallet/src/lib.rs b/moksha-wallet/src/lib.rs index 8365c943..543ec49f 100644 --- a/moksha-wallet/src/lib.rs +++ b/moksha-wallet/src/lib.rs @@ -1,8 +1,8 @@ pub mod btcprice; pub mod client; pub mod config_path; -pub mod deterministic_secrets; pub mod error; pub mod http; pub mod localstore; +pub mod secret; pub mod wallet; diff --git a/moksha-wallet/src/secret.rs b/moksha-wallet/src/secret.rs new file mode 100644 index 00000000..d1de9061 --- /dev/null +++ b/moksha-wallet/src/secret.rs @@ -0,0 +1,113 @@ +use crate::error::MokshaWalletError; +use std::str::FromStr; + +use bip32::{Seed, XPrv}; +use bip39::Mnemonic; +use rand::Rng; +use secp256k1::SecretKey; + +enum DerivationType { + Secret = 0, + Blinding = 1, +} + +pub struct DeterministicSecret { + pub seed: Seed, +} + +impl DeterministicSecret { + pub fn from_seed_words(seed_words: &str) -> Result { + let mnemonic = Mnemonic::from_str(seed_words)?; + let seed = Seed::new(mnemonic.to_seed("")); + Ok(Self { seed }) + } + + pub fn from_random_seed() -> Result { + let mut rng = rand::thread_rng(); + let entropy: [u8; 16] = rng.gen(); // 16 bytes for 12 words mnemonic + let mnemonic = Mnemonic::from_entropy(&entropy)?; + let seed = Seed::new(mnemonic.to_seed("")); + Ok(Self { seed }) + } + + fn derive_private_key( + &self, + keyset_id: u32, + counter: u32, + secret_or_blinding: DerivationType, + ) -> Result, MokshaWalletError> { + let secret_or_blinding = secret_or_blinding as u32; + let derivation_path = format!("m/129372'/0'/{keyset_id}'/{counter}'/{secret_or_blinding}"); + let derivation_path = bip32::DerivationPath::from_str(&derivation_path)?; + let key = XPrv::derive_from_path(&self.seed, &derivation_path)?; + Ok(key.private_key().to_bytes().to_vec()) + } + + pub fn derive_secret(&self, keyset_id: u32, counter: u32) -> Result { + let key = self.derive_private_key(keyset_id, counter, DerivationType::Secret)?; + Ok(hex::encode(key)) + } + + pub fn derive_blinding_factor( + &self, + keyset_id: u32, + counter: u32, + ) -> Result { + let key = self.derive_private_key(keyset_id, counter, DerivationType::Blinding)?; + Ok(SecretKey::from_slice(&key)?) + } +} + +pub fn convert_hex_to_int(keyset_id_hex: &str) -> anyhow::Result { + let bytes = hex::decode(keyset_id_hex)?; + let bytes_array: [u8; 8] = bytes[0..8].try_into()?; + let num = u64::from_be_bytes(bytes_array); + Ok((num % (2u64.pow(31) - 1)) as u32) +} + +#[cfg(test)] +mod tests { + + use super::{convert_hex_to_int, DeterministicSecret}; + + #[test] + fn test_keyset_id_conversion() -> anyhow::Result<()> { + let int_value = convert_hex_to_int("009a1f293253e41e")?; + assert_eq!(864559728, int_value); + Ok(()) + } + + #[test] + fn test_secret_derivation() -> anyhow::Result<()> { + let phrase = + "half depart obvious quality work element tank gorilla view sugar picture humble"; + let deterministic_secret = DeterministicSecret::from_seed_words(phrase)?; + + let secrets = [ + "485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae", + "8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270", + "bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8", + "59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf", + "576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0", + ]; + + for (i, secret) in secrets.iter().enumerate() { + let key = deterministic_secret.derive_secret(864559728, i as u32)?; + assert_eq!(secret.to_owned(), key); + } + + let blinding_factors = [ + "ad00d431add9c673e843d4c2bf9a778a5f402b985b8da2d5550bf39cda41d679", + "967d5232515e10b81ff226ecf5a9e2e2aff92d66ebc3edf0987eb56357fd6248", + "b20f47bb6ae083659f3aa986bfa0435c55c6d93f687d51a01f26862d9b9a4899", + "fb5fca398eb0b1deb955a2988b5ac77d32956155f1c002a373535211a2dfdc29", + "5f09bfbfe27c439a597719321e061e2e40aad4a36768bb2bcc3de547c9644bf9", + ]; + + for (i, factor) in blinding_factors.iter().enumerate() { + let key = deterministic_secret.derive_blinding_factor(864559728, i as u32)?; + assert_eq!(factor.to_owned(), hex::encode(&key[..])); + } + Ok(()) + } +}