diff --git a/crates/cashu-sdk/src/wallet.rs b/crates/cashu-sdk/src/wallet.rs index c34e4c70..7981901b 100644 --- a/crates/cashu-sdk/src/wallet.rs +++ b/crates/cashu-sdk/src/wallet.rs @@ -171,6 +171,11 @@ impl Wallet { /// Create Split Payload fn create_split(&self, proofs: Proofs) -> Result { + let mut proofs = proofs; + + // Sort proofs in ascending order to avoid fingerprinting + proofs.sort(); + let value = proofs.iter().map(|p| p.amount).sum(); let blinded_messages = BlindedMessages::random(value)?; @@ -223,33 +228,14 @@ impl Wallet { /// Send pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result { - let mut amount_available = Amount::ZERO; - let mut send_proofs = SendProofs::default(); - - for proof in proofs { - let proof_value = proof.amount; - if amount_available > amount { - send_proofs.change_proofs.push(proof); - } else { - send_proofs.send_proofs.push(proof); - } - amount_available += proof_value; - } + let amount_available: Amount = proofs.iter().map(|p| p.amount).sum(); if amount_available.lt(&amount) { println!("Not enough funds"); return Err(Error::InsufficientFunds); } - // If amount available is EQUAL to send amount no need to split - if amount_available.eq(&amount) { - return Ok(send_proofs); - } - - let _amount_to_keep = amount_available - amount; - let amount_to_send = amount; - - let split_payload = self.create_split(send_proofs.send_proofs)?; + let split_payload = self.create_split(proofs)?; let split_response = self .client @@ -259,22 +245,26 @@ impl Wallet { ) .await?; - // If only promises assemble proofs needed for amount - let keep_proofs; - let send_proofs; + let mut keep_proofs = Proofs::new(); + let mut send_proofs = Proofs::new(); if let Some(promises) = split_response.promises { - let proofs = construct_proofs( + let mut proofs = construct_proofs( promises, split_payload.blinded_messages.rs, split_payload.blinded_messages.secrets, &self.mint_keys, )?; - let split = amount_to_send.split(); + proofs.reverse(); - keep_proofs = proofs[0..split.len()].to_vec(); - send_proofs = proofs[split.len()..].to_vec(); + for proof in proofs { + if (proof.amount + send_proofs.iter().map(|p| p.amount).sum()).gt(&amount) { + keep_proofs.push(proof); + } else { + send_proofs.push(proof); + } + } } else { return Err(Error::Custom("Invalid split response".to_string())); } @@ -282,6 +272,16 @@ impl Wallet { // println!("Send Proofs: {:#?}", send_proofs); // println!("Keep Proofs: {:#?}", keep_proofs); + let send_amount: Amount = send_proofs.iter().map(|p| p.amount).sum(); + + if send_amount.ne(&amount) { + warn!( + "Send amount proofs is {} expected {}", + send_amount.to_sat(), + amount.to_sat() + ); + } + Ok(SendProofs { change_proofs: keep_proofs, send_proofs, diff --git a/crates/cashu/src/nuts/nut00.rs b/crates/cashu/src/nuts/nut00.rs index 8e54ea1f..cd939d6b 100644 --- a/crates/cashu/src/nuts/nut00.rs +++ b/crates/cashu/src/nuts/nut00.rs @@ -204,6 +204,18 @@ pub struct Proof { pub id: Option, } +impl Ord for Proof { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.amount.cmp(&other.amount) + } +} + +impl PartialOrd for Proof { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl From for mint::Proof { fn from(proof: Proof) -> Self { Self {