diff --git a/moksha-core/src/dhke.rs b/moksha-core/src/dhke.rs index aa83a713..268ea4d9 100644 --- a/moksha-core/src/dhke.rs +++ b/moksha-core/src/dhke.rs @@ -89,6 +89,7 @@ impl Dhke { .ok_or(MokshaCoreError::NoValidPointFound) } + // FIXME: use SecretKey instead of &[u8] for blinding factor pub fn step1_alice( &self, secret_msg: impl Into, diff --git a/moksha-wallet/src/localstore/mod.rs b/moksha-wallet/src/localstore/mod.rs index 1f5ff92b..19a9e3e8 100644 --- a/moksha-wallet/src/localstore/mod.rs +++ b/moksha-wallet/src/localstore/mod.rs @@ -124,8 +124,33 @@ pub trait LocalStore { ) -> Result<(), MokshaWalletError>; async fn get_proofs(&self, tx: &mut RexieTransaction) -> Result; - async fn get_keysets(&self) -> Result, MokshaWalletError>; - async fn add_keyset(&self, keyset: &WalletKeyset) -> Result<(), MokshaWalletError>; + async fn get_keysets( + &self, + _tx: &mut RexieTransaction, + ) -> Result, MokshaWalletError>; + + async fn upsert_keyset( + &self, + _tx: &mut RexieTransaction, + keyset: &WalletKeyset, + ) -> Result<(), MokshaWalletError>; + + async fn update_keyset_last_index( + &self, + _tx: &mut RexieTransaction, + keyset: &WalletKeyset, + ) -> Result<(), MokshaWalletError>; + + async fn add_seed( + &self, + _tx: &mut RexieTransaction, + seed_words: &str, + ) -> Result<(), MokshaWalletError>; + + async fn get_seed( + &self, + _tx: &mut RexieTransaction, + ) -> Result, MokshaWalletError>; } #[cfg(test)] diff --git a/moksha-wallet/src/localstore/rexie.rs b/moksha-wallet/src/localstore/rexie.rs index 1a08e5fa..1f72368b 100644 --- a/moksha-wallet/src/localstore/rexie.rs +++ b/moksha-wallet/src/localstore/rexie.rs @@ -110,19 +110,41 @@ impl LocalStore for RexieLocalStore { Ok(()) } - async fn get_keysets(&self) -> std::result::Result, MokshaWalletError> { - // FIXME todo implement - Ok(vec![WalletKeyset { - id: "id".to_string(), - mint_url: "mint_url".to_string(), - }]) + async fn get_keysets( + &self, + _tx: &mut RexieTransaction, + ) -> std::result::Result, MokshaWalletError> { + todo!() } - async fn add_keyset( + async fn upsert_keyset( &self, + _tx: &mut RexieTransaction, _keyset: &WalletKeyset, ) -> std::result::Result<(), MokshaWalletError> { - // FIXME todo implement - Ok(()) + todo!() + } + + async fn update_keyset_last_index( + &self, + _tx: &mut RexieTransaction, + _keyset: &WalletKeyset, + ) -> std::result::Result<(), MokshaWalletError> { + todo!() + } + + async fn add_seed( + &self, + _tx: &mut RexieTransaction, + _seed_words: &str, + ) -> std::result::Result<(), MokshaWalletError> { + todo!() + } + + async fn get_seed( + &self, + _tx: &mut RexieTransaction, + ) -> std::result::Result, MokshaWalletError> { + todo!() } } diff --git a/moksha-wallet/src/secret.rs b/moksha-wallet/src/secret.rs index d573f624..6156bf3d 100644 --- a/moksha-wallet/src/secret.rs +++ b/moksha-wallet/src/secret.rs @@ -15,6 +15,14 @@ pub struct DeterministicSecret { pub seed: Seed, } +impl Clone for DeterministicSecret { + fn clone(&self) -> Self { + Self { + seed: Seed::new(*self.seed.as_bytes()), + } + } +} + impl DeterministicSecret { pub fn from_seed_words(seed_words: &str) -> Result { let mnemonic = Mnemonic::from_str(seed_words)?; @@ -55,6 +63,21 @@ impl DeterministicSecret { Ok(hex::encode(key)) } + pub fn derive_range( + &self, + keyset_id: u32, + start: u32, + length: u32, + ) -> Result, MokshaWalletError> { + Ok((start..start + length) + .map(|i| { + let key = self.derive_secret(keyset_id, i).unwrap(); + let blinding_factor = self.derive_blinding_factor(keyset_id, i).unwrap(); + (key, blinding_factor) + }) + .collect::>()) + } + pub fn derive_blinding_factor( &self, keyset_id: u32, diff --git a/moksha-wallet/src/wallet.rs b/moksha-wallet/src/wallet.rs index cc353139..a2ba85a4 100644 --- a/moksha-wallet/src/wallet.rs +++ b/moksha-wallet/src/wallet.rs @@ -20,7 +20,7 @@ use crate::{ error::MokshaWalletError, http::CrossPlatformHttpClient, localstore::{LocalStore, WalletKeyset}, - secret::DeterministicSecret, + secret::{self, DeterministicSecret}, }; use lightning_invoice::Bolt11Invoice as LNInvoice; use std::str::FromStr; @@ -37,6 +37,7 @@ where dhke: Dhke, localstore: L, mint_url: Url, + secret: DeterministicSecret, } pub struct WalletBuilder @@ -111,6 +112,7 @@ where let wallet_keyset = WalletKeyset::new(&m.id, &mint_url, &m.unit, 0, public_keys, true); localstore.upsert_keyset(&mut tx, &wallet_keyset).await?; } + let seed = localstore.get_seed(&mut tx).await?.expect("seed not found"); tx.commit().await?; // FIXME store all keysets @@ -134,6 +136,7 @@ where key_response.clone(), localstore, mint_url, + DeterministicSecret::from_seed_words(&seed)?, )) } } @@ -159,6 +162,7 @@ where key_response: KeyResponse, localstore: L, mint_url: Url, + secret: DeterministicSecret, ) -> Self { Self { client, @@ -167,6 +171,7 @@ where dhke: Dhke::new(), localstore, mint_url, + secret, } } @@ -426,6 +431,37 @@ where Ok(melt_response) } + async fn create_secrets( + &self, + amount: u32, + ) -> Result, MokshaWalletError> { + let keyset_id = self.keyset_id.clone().id; + let keyset_id_int = secret::convert_hex_to_int(&keyset_id).unwrap(); // FIXME + + let mut tx = self.localstore.begin_tx().await?; + let all_keysets = self.localstore.get_keysets(&mut tx).await?; + let keyset = all_keysets + .iter() + .find(|k| k.keyset_id == keyset_id) + .expect("keyset not found"); + + let start_index = (keyset.last_index + 1) as u32; + let secret_range = self + .secret + .derive_range(keyset_id_int, start_index, amount)?; + + self.localstore + .update_keyset_last_index( + &mut tx, + &WalletKeyset { + last_index: (start_index + amount) as u64, + ..keyset.clone() + }, + ) + .await?; + Ok(secret_range) + } + pub async fn split_tokens( &self, tokens: &TokenV3, @@ -433,13 +469,17 @@ where ) -> Result<(TokenV3, TokenV3), MokshaWalletError> { let total_token_amount = tokens.total_amount(); let first_amount: Amount = (total_token_amount - splt_amount.0).into(); - let first_secrets = first_amount.split().create_secrets(); + let first_secrets = self + .create_secrets(first_amount.split().len() as u32) + .await?; let first_outputs = self.create_blinded_messages(first_amount, &first_secrets)?; // ############################################################################ let second_amount = splt_amount.clone(); - let second_secrets = second_amount.split().create_secrets(); + let second_secrets = self + .create_secrets(second_amount.split().len() as u32) + .await?; let second_outputs = self.create_blinded_messages(second_amount, &second_secrets)?; let mut total_outputs = vec![]; @@ -463,6 +503,8 @@ where let secrets = [first_secrets, second_secrets].concat(); let outputs = [first_outputs, second_outputs].concat(); + let secrets = secrets.into_iter().map(|(s, _)| s).collect::>(); + let proofs = self .create_proofs_from_blinded_signatures(split_result.signatures, secrets, outputs)? .proofs(); @@ -530,13 +572,42 @@ where quote_id: String, ) -> Result { let split_amount = amount.split(); - let secrets = split_amount.create_secrets(); + + // FIXME cleanup code + let keyset_id = self.keyset_id.clone().id; + let keyset_id_int = secret::convert_hex_to_int(&keyset_id).unwrap(); // FIXME + + let mut tx = self.localstore.begin_tx().await?; + let all_keysets = self.localstore.get_keysets(&mut tx).await?; + let keyset = all_keysets + .iter() + .find(|k| k.keyset_id == keyset_id) + .expect("keyset not found"); + + let start_index = (keyset.last_index + 1) as u32; + let secret_range = + self.secret + .derive_range(keyset_id_int, start_index, split_amount.len() as u32)?; + + self.localstore + .update_keyset_last_index( + &mut tx, + &WalletKeyset { + last_index: (start_index + split_amount.len() as u32) as u64, + ..keyset.clone() + }, + ) + .await?; + tx.commit().await?; let blinded_messages = split_amount .into_iter() - .zip(secrets.clone()) - .map(|(amount, secret)| { - let (b_, alice_secret_key) = self.dhke.step1_alice(secret, None).unwrap(); // FIXME + .zip(secret_range) + .map(|(amount, (secret, blinding_factor))| { + let (b_, alice_secret_key) = self + .dhke + .step1_alice(&secret, Some(&blinding_factor.secret_bytes())) + .unwrap(); // FIXME ( BlindedMessage { amount, @@ -544,9 +615,10 @@ where id: self.keyset_id.clone().id, }, alice_secret_key, + secret, ) }) - .collect::>(); + .collect::>(); let signatures = match payment_method { PaymentMethod::Bolt11 => { @@ -558,7 +630,7 @@ where blinded_messages .clone() .into_iter() - .map(|(msg, _)| msg) + .map(|(msg, _, _)| msg) .collect::>(), ) .await?; @@ -573,7 +645,7 @@ where blinded_messages .clone() .into_iter() - .map(|(msg, _)| msg) + .map(|(msg, _, _)| msg) .collect::>(), ) .await?; @@ -584,17 +656,10 @@ where // step 3: unblind signatures let current_keyset_id = self.keyset_id.clone().id; // FIXME - let private_keys = blinded_messages - .clone() - .into_iter() - .map(|(_, secret)| secret) - .collect::>(); - let proofs = signatures .iter() - .zip(private_keys) - .zip(secrets) - .map(|((p, priv_key), secret)| { + .zip(blinded_messages) + .map(|(p, (_, priv_key, secret))| { let key = self .keyset .keys @@ -620,15 +685,18 @@ where fn create_blinded_messages( &self, amount: Amount, - secrets: &[String], + secrets_factors: &Vec<(String, SecretKey)>, ) -> Result, MokshaWalletError> { let split_amount = amount.split(); Ok(split_amount .into_iter() - .zip(secrets) - .map(|(amount, secret)| { - let (b_, alice_secret_key) = self.dhke.step1_alice(secret, None).unwrap(); // FIXME + .zip(secrets_factors) + .map(|(amount, (secret, blinding_factor))| { + let (b_, alice_secret_key) = self + .dhke + .step1_alice(secret, Some(&blinding_factor.secret_bytes())) + .unwrap(); // FIXME ( BlindedMessage { amount,