diff --git a/crates/cdk-redb/src/mint/mod.rs b/crates/cdk-redb/src/mint/mod.rs index d53055b2..0ca822e9 100644 --- a/crates/cdk-redb/src/mint/mod.rs +++ b/crates/cdk-redb/src/mint/mod.rs @@ -10,6 +10,7 @@ use async_trait::async_trait; use cdk::cdk_database::MintDatabase; use cdk::dhke::hash_to_curve; use cdk::mint::{MintKeySetInfo, MintQuote}; +use cdk::nuts::nut00::ProofsMethods; use cdk::nuts::{ BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, State, @@ -603,10 +604,7 @@ impl MintDatabase for MintRedbDatabase { .filter(|p| &p.keyset_id == keyset_id) .collect::(); - let proof_ys = proofs_for_id - .iter() - .map(|p| p.y()) - .collect::, _>>()?; + let proof_ys = proofs_for_id.ys()?; assert_eq!(proofs_for_id.len(), proof_ys.len()); diff --git a/crates/cdk-sqlite/src/mint/mod.rs b/crates/cdk-sqlite/src/mint/mod.rs index 20fc70bb..c72e5220 100644 --- a/crates/cdk-sqlite/src/mint/mod.rs +++ b/crates/cdk-sqlite/src/mint/mod.rs @@ -10,6 +10,7 @@ use bitcoin::bip32::DerivationPath; use cdk::cdk_database::{self, MintDatabase}; use cdk::mint::{MintKeySetInfo, MintQuote}; use cdk::mint_url::MintUrl; +use cdk::nuts::nut00::ProofsMethods; use cdk::nuts::nut05::QuoteState; use cdk::nuts::{ BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, @@ -838,10 +839,7 @@ WHERE quote_id=?; .map(sqlite_row_to_proof) .collect::, _>>()?; - proofs - .iter() - .map(|p| p.y()) - .collect::, _>>()? + proofs.ys()? } Err(err) => match err { sqlx::Error::RowNotFound => { diff --git a/crates/cdk/src/cdk_database/mint_memory.rs b/crates/cdk/src/cdk_database/mint_memory.rs index 5ae612aa..3b4e1e79 100644 --- a/crates/cdk/src/cdk_database/mint_memory.rs +++ b/crates/cdk/src/cdk_database/mint_memory.rs @@ -9,6 +9,7 @@ use tokio::sync::{Mutex, RwLock}; use super::{Error, MintDatabase}; use crate::dhke::hash_to_curve; use crate::mint::{self, MintKeySetInfo, MintQuote}; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::nut07::State; use crate::nuts::{ nut07, BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, @@ -346,10 +347,7 @@ impl MintDatabase for MintMemoryDatabase { .cloned() .collect(); - let proof_ys = proofs_for_id - .iter() - .map(|p| p.y()) - .collect::, _>>()?; + let proof_ys = proofs_for_id.ys()?; assert_eq!(proofs_for_id.len(), proof_ys.len()); diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index a46452de..f187d282 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -8,7 +8,7 @@ use tracing::instrument; use crate::cdk_lightning; use crate::cdk_lightning::MintLightning; use crate::cdk_lightning::PayInvoiceResponse; -use crate::dhke::hash_to_curve; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::nut11::enforce_sig_flag; use crate::nuts::nut11::EnforceSigFlag; use crate::{ @@ -264,11 +264,7 @@ impl Mint { } } - let ys = melt_request - .inputs - .iter() - .map(|p| hash_to_curve(&p.secret.to_bytes())) - .collect::, _>>()?; + let ys = melt_request.inputs.ys()?; // Ensure proofs are unique and not being double spent if melt_request.inputs.len() != ys.iter().collect::>().len() { @@ -374,11 +370,7 @@ impl Mint { /// quote should be unpaid #[instrument(skip_all)] pub async fn process_unpaid_melt(&self, melt_request: &MeltBolt11Request) -> Result<(), Error> { - let input_ys = melt_request - .inputs - .iter() - .map(|p| hash_to_curve(&p.secret.to_bytes())) - .collect::, _>>()?; + let input_ys = melt_request.inputs.ys()?; self.localstore .update_proofs_states(&input_ys, State::Unspent) @@ -615,11 +607,7 @@ impl Mint { .await? .ok_or(Error::UnknownQuote)?; - let input_ys = melt_request - .inputs - .iter() - .map(|p| hash_to_curve(&p.secret.to_bytes())) - .collect::, _>>()?; + let input_ys = melt_request.inputs.ys()?; self.localstore .update_proofs_states(&input_ys, State::Spent) diff --git a/crates/cdk/src/mint/swap.rs b/crates/cdk/src/mint/swap.rs index f763fff1..16a72afe 100644 --- a/crates/cdk/src/mint/swap.rs +++ b/crates/cdk/src/mint/swap.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use tracing::instrument; -use crate::dhke::hash_to_curve; +use crate::nuts::nut00::ProofsMethods; use crate::Error; use super::nut11::{enforce_sig_flag, EnforceSigFlag}; @@ -59,11 +59,7 @@ impl Mint { let proof_count = swap_request.inputs.len(); - let input_ys = swap_request - .inputs - .iter() - .map(|p| hash_to_curve(&p.secret.to_bytes())) - .collect::, _>>()?; + let input_ys = swap_request.inputs.ys()?; self.localstore .add_proofs(swap_request.inputs.clone(), None) diff --git a/crates/cdk/src/nuts/nut00/mod.rs b/crates/cdk/src/nuts/nut00/mod.rs index 98223792..5b03a185 100644 --- a/crates/cdk/src/nuts/nut00/mod.rs +++ b/crates/cdk/src/nuts/nut00/mod.rs @@ -29,6 +29,28 @@ pub use token::{Token, TokenV3, TokenV4}; /// List of [Proof] pub type Proofs = Vec; +/// Utility methods for [Proofs] +pub trait ProofsMethods { + /// Try to sum up the amounts of all [Proof]s + fn total_amount(&self) -> Result; + + /// Try to fetch the pubkeys of all [Proof]s + fn ys(&self) -> Result, Error>; +} + +impl ProofsMethods for Proofs { + fn total_amount(&self) -> Result { + Amount::try_sum(self.iter().map(|p| p.amount)).map_err(Into::into) + } + + fn ys(&self) -> Result, Error> { + self.iter() + .map(|p| p.y()) + .collect::, _>>() + .map_err(Into::into) + } +} + /// NUT00 Error #[derive(Debug, Error)] pub enum Error { diff --git a/crates/cdk/src/nuts/nut00/token.rs b/crates/cdk/src/nuts/nut00/token.rs index f4616620..8fe9e99f 100644 --- a/crates/cdk/src/nuts/nut00/token.rs +++ b/crates/cdk/src/nuts/nut00/token.rs @@ -13,6 +13,7 @@ use url::Url; use super::{Error, Proof, ProofV4, Proofs}; use crate::mint_url::MintUrl; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::{CurrencyUnit, Id}; use crate::Amount; @@ -211,7 +212,7 @@ impl TokenV3 { Ok(Amount::try_sum( self.token .iter() - .map(|t| Amount::try_sum(t.proofs.iter().map(|p| p.amount))) + .map(|t| t.proofs.total_amount()) .collect::, _>>()?, )?) } diff --git a/crates/cdk/src/types.rs b/crates/cdk/src/types.rs index 60c360fe..213a8e7e 100644 --- a/crates/cdk/src/types.rs +++ b/crates/cdk/src/types.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; use crate::mint_url::MintUrl; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::{ CurrencyUnit, MeltQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SpendingConditions, State, @@ -34,9 +35,9 @@ impl Melted { proofs: Proofs, change_proofs: Option, ) -> Result { - let proofs_amount = Amount::try_sum(proofs.iter().map(|p| p.amount))?; + let proofs_amount = proofs.total_amount()?; let change_amount = match &change_proofs { - Some(change_proofs) => Amount::try_sum(change_proofs.iter().map(|p| p.amount))?, + Some(change_proofs) => change_proofs.total_amount()?, None => Amount::ZERO, }; let fee_paid = proofs_amount diff --git a/crates/cdk/src/wallet/melt.rs b/crates/cdk/src/wallet/melt.rs index c7217c28..37b3f71e 100644 --- a/crates/cdk/src/wallet/melt.rs +++ b/crates/cdk/src/wallet/melt.rs @@ -3,9 +3,10 @@ use std::str::FromStr; use lightning_invoice::Bolt11Invoice; use tracing::instrument; +use crate::nuts::nut00::ProofsMethods; use crate::{ dhke::construct_proofs, - nuts::{CurrencyUnit, MeltQuoteBolt11Response, PreMintSecrets, Proofs, PublicKey, State}, + nuts::{CurrencyUnit, MeltQuoteBolt11Response, PreMintSecrets, Proofs, State}, types::{Melted, ProofInfo}, util::unix_time, Amount, Error, Wallet, @@ -121,15 +122,12 @@ impl Wallet { return Err(Error::UnknownQuote); }; - let proofs_total = Amount::try_sum(proofs.iter().map(|p| p.amount))?; + let proofs_total = proofs.total_amount()?; if proofs_total < quote_info.amount + quote_info.fee_reserve { return Err(Error::InsufficientFunds); } - let ys = proofs - .iter() - .map(|p| p.y()) - .collect::, _>>()?; + let ys = proofs.ys()?; self.localstore.set_pending_proofs(ys).await?; let active_keyset_id = self.get_active_mint_keyset().await?.id; @@ -213,7 +211,7 @@ impl Wallet { Some(change_proofs) => { tracing::debug!( "Change amount returned from melt: {}", - Amount::try_sum(change_proofs.iter().map(|p| p.amount))? + change_proofs.total_amount()? ); // Update counter for keyset @@ -238,10 +236,7 @@ impl Wallet { self.localstore.remove_melt_quote("e_info.id).await?; - let deleted_ys = proofs - .iter() - .map(|p| p.y()) - .collect::, _>>()?; + let deleted_ys = proofs.ys()?; self.localstore .update_proofs(change_proof_infos, deleted_ys) .await?; diff --git a/crates/cdk/src/wallet/mint.rs b/crates/cdk/src/wallet/mint.rs index 6b241452..766f40c8 100644 --- a/crates/cdk/src/wallet/mint.rs +++ b/crates/cdk/src/wallet/mint.rs @@ -1,6 +1,7 @@ use tracing::instrument; use super::MintQuote; +use crate::nuts::nut00::ProofsMethods; use crate::{ amount::SplitTarget, dhke::construct_proofs, @@ -242,7 +243,7 @@ impl Wallet { &keys, )?; - let minted_amount = Amount::try_sum(proofs.iter().map(|p| p.amount))?; + let minted_amount = proofs.total_amount()?; // Remove filled quote from store self.localstore.remove_mint_quote("e_info.id).await?; diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index 93e1b76b..174a83a8 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -35,6 +35,7 @@ mod swap; pub mod types; pub mod util; +use crate::nuts::nut00::ProofsMethods; pub use multi_mint_wallet::MultiMintWallet; pub use types::{MeltQuote, MintQuote, SendKind}; @@ -327,7 +328,7 @@ impl Wallet { .cloned() .collect(); - restored_value += Amount::try_sum(unspent_proofs.iter().map(|p| p.amount))?; + restored_value += unspent_proofs.total_amount()?; let unspent_proofs = unspent_proofs .into_iter() diff --git a/crates/cdk/src/wallet/proofs.rs b/crates/cdk/src/wallet/proofs.rs index 9d652f17..79764a73 100644 --- a/crates/cdk/src/wallet/proofs.rs +++ b/crates/cdk/src/wallet/proofs.rs @@ -2,9 +2,9 @@ use std::collections::HashSet; use tracing::instrument; +use crate::nuts::nut00::ProofsMethods; use crate::{ amount::SplitTarget, - dhke::hash_to_curve, nuts::{Proof, ProofState, Proofs, PublicKey, State}, types::ProofInfo, Amount, Error, Wallet, @@ -73,11 +73,7 @@ impl Wallet { /// Checks the stats of [`Proofs`] swapping for a new [`Proof`] if unspent #[instrument(skip(self, proofs))] pub async fn reclaim_unspent(&self, proofs: Proofs) -> Result<(), Error> { - let proof_ys = proofs - .iter() - // Find Y for the secret - .map(|p| hash_to_curve(p.secret.as_bytes())) - .collect::, _>>()?; + let proof_ys = proofs.ys()?; let spendable = self .client @@ -102,14 +98,7 @@ impl Wallet { pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result, Error> { let spendable = self .client - .post_check_state( - self.mint_url.clone().try_into()?, - proofs - .iter() - // Find Y for the secret - .map(|p| hash_to_curve(p.secret.as_bytes())) - .collect::, _>>()?, - ) + .post_check_state(self.mint_url.clone().try_into()?, proofs.ys()?) .await?; let spent_ys: Vec<_> = spendable .states @@ -186,7 +175,7 @@ impl Wallet { ) -> Result { // TODO: Check all proofs are same unit - if Amount::try_sum(proofs.iter().map(|p| p.amount))? < amount { + if proofs.total_amount()? < amount { return Err(Error::InsufficientFunds); } @@ -226,7 +215,7 @@ impl Wallet { } remaining_amount = amount.checked_add(fees).ok_or(Error::AmountOverflow)? - - Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + - selected_proofs.total_amount()?; (proofs_larger, proofs_smaller) = proofs_smaller .into_iter() .skip(1) @@ -262,7 +251,7 @@ impl Wallet { for inactive_proof in inactive_proofs { selected_proofs.push(inactive_proof); - let selected_total = Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_total = selected_proofs.total_amount()?; let fees = self.get_proofs_fee(&selected_proofs).await?; if selected_total >= amount + fees { @@ -274,7 +263,7 @@ impl Wallet { for active_proof in active_proofs { selected_proofs.push(active_proof); - let selected_total = Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_total = selected_proofs.total_amount()?; let fees = self.get_proofs_fee(&selected_proofs).await?; if selected_total >= amount + fees { diff --git a/crates/cdk/src/wallet/receive.rs b/crates/cdk/src/wallet/receive.rs index 88dc4a08..898eb40c 100644 --- a/crates/cdk/src/wallet/receive.rs +++ b/crates/cdk/src/wallet/receive.rs @@ -4,6 +4,7 @@ use bitcoin::hashes::Hash; use bitcoin::{hashes::sha256::Hash as Sha256Hash, XOnlyPublicKey}; use tracing::instrument; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::nut10::Kind; use crate::nuts::{Conditions, Token}; use crate::{ @@ -148,7 +149,7 @@ impl Wallet { .increment_keyset_counter(&active_keyset_id, recv_proofs.len() as u32) .await?; - let total_amount = Amount::try_sum(recv_proofs.iter().map(|p| p.amount))?; + let total_amount = recv_proofs.total_amount()?; let recv_proof_infos = recv_proofs .into_iter() diff --git a/crates/cdk/src/wallet/send.rs b/crates/cdk/src/wallet/send.rs index a3024bde..1e8b99fc 100644 --- a/crates/cdk/src/wallet/send.rs +++ b/crates/cdk/src/wallet/send.rs @@ -1,8 +1,9 @@ use tracing::instrument; +use crate::nuts::nut00::ProofsMethods; use crate::{ amount::SplitTarget, - nuts::{Proofs, PublicKey, SpendingConditions, State, Token}, + nuts::{Proofs, SpendingConditions, State, Token}, Amount, Error, Wallet, }; @@ -12,10 +13,7 @@ impl Wallet { /// Send specific proofs #[instrument(skip(self))] pub async fn send_proofs(&self, memo: Option, proofs: Proofs) -> Result { - let ys = proofs - .iter() - .map(|p| p.y()) - .collect::, _>>()?; + let ys = proofs.ys()?; self.localstore.reserve_proofs(ys).await?; Ok(Token::new( @@ -114,8 +112,7 @@ impl Wallet { let send_proofs: Proofs = match (send_kind, selected, conditions.clone()) { // Handle exact matches offline (SendKind::OfflineExact, Ok(selected_proofs), _) => { - let selected_proofs_amount = - Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_proofs_amount = selected_proofs.total_amount()?; let amount_to_send = match include_fees { true => amount + self.get_proofs_fee(&selected_proofs).await?, @@ -131,8 +128,7 @@ impl Wallet { // Handle exact matches (SendKind::OnlineExact, Ok(selected_proofs), _) => { - let selected_proofs_amount = - Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_proofs_amount = selected_proofs.total_amount()?; let amount_to_send = match include_fees { true => amount + self.get_proofs_fee(&selected_proofs).await?, @@ -152,8 +148,7 @@ impl Wallet { // Handle offline tolerance (SendKind::OfflineTolerance(tolerance), Ok(selected_proofs), _) => { - let selected_proofs_amount = - Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_proofs_amount = selected_proofs.total_amount()?; let amount_to_send = match include_fees { true => amount + self.get_proofs_fee(&selected_proofs).await?, @@ -178,8 +173,7 @@ impl Wallet { // Handle online tolerance with successful selection (SendKind::OnlineTolerance(tolerance), Ok(selected_proofs), _) => { - let selected_proofs_amount = - Amount::try_sum(selected_proofs.iter().map(|p| p.amount))?; + let selected_proofs_amount = selected_proofs.total_amount()?; let amount_to_send = match include_fees { true => amount + self.get_proofs_fee(&selected_proofs).await?, false => amount, diff --git a/crates/cdk/src/wallet/swap.rs b/crates/cdk/src/wallet/swap.rs index 7f59866a..82274fb9 100644 --- a/crates/cdk/src/wallet/swap.rs +++ b/crates/cdk/src/wallet/swap.rs @@ -2,6 +2,7 @@ use tracing::instrument; use crate::amount::SplitTarget; use crate::dhke::construct_proofs; +use crate::nuts::nut00::ProofsMethods; use crate::nuts::nut10; use crate::nuts::PreMintSecrets; use crate::nuts::PreSwap; @@ -85,8 +86,7 @@ impl Wallet { let mut proofs_to_keep = Vec::new(); for proof in all_proofs { - let proofs_to_send_amount = - Amount::try_sum(proofs_to_send.iter().map(|p| p.amount))?; + let proofs_to_send_amount = proofs_to_send.total_amount()?; if proof.amount + proofs_to_send_amount <= amount + pre_swap.fee { proofs_to_send.push(proof); } else { @@ -98,7 +98,7 @@ impl Wallet { } }; - let send_amount = Amount::try_sum(proofs_to_send.iter().map(|p| p.amount))?; + let send_amount = proofs_to_send.total_amount()?; if send_amount.ne(&(amount + pre_swap.fee)) { tracing::warn!( @@ -199,9 +199,9 @@ impl Wallet { let active_keyset_id = self.get_active_mint_keyset().await?.id; // Desired amount is either amount passed or value of all proof - let proofs_total = Amount::try_sum(proofs.iter().map(|p| p.amount))?; + let proofs_total = proofs.total_amount()?; - let ys: Vec = proofs.iter().map(|p| p.y()).collect::>()?; + let ys: Vec = proofs.ys()?; self.localstore.set_pending_proofs(ys).await?; let fee = self.get_proofs_fee(&proofs).await?;