diff --git a/crates/cdk/src/nuts/nut11/mod.rs b/crates/cdk/src/nuts/nut11/mod.rs index c547a1b3..a3ea812e 100644 --- a/crates/cdk/src/nuts/nut11/mod.rs +++ b/crates/cdk/src/nuts/nut11/mod.rs @@ -3,9 +3,9 @@ //! use std::collections::{HashMap, HashSet}; -use std::fmt; use std::ops::Deref; use std::str::FromStr; +use std::{fmt, vec}; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; @@ -297,6 +297,39 @@ impl SpendingConditions { Self::HTLCConditions { .. } => Kind::HTLC, } } + + pub fn num_sigs(&self) -> Option { + match self { + Self::P2PKConditions { conditions, .. } => conditions.num_sigs, + Self::HTLCConditions { conditions, .. } => conditions.num_sigs, + } + } + + pub fn pubkeys(&self) -> Option> { + match self { + Self::P2PKConditions { data, conditions } => { + let mut pubkeys = vec![data.clone()]; + pubkeys.extend(conditions.pubkeys.clone().unwrap_or_default().into_iter()); + + Some(pubkeys) + } + Self::HTLCConditions { conditions, .. } => conditions.pubkeys.clone(), + } + } + + pub fn locktime(&self) -> Option { + match self { + Self::P2PKConditions { conditions, .. } => conditions.locktime, + Self::HTLCConditions { conditions, .. } => conditions.locktime, + } + } + + pub fn refund_keys(&self) -> &Option> { + match self { + Self::P2PKConditions { conditions, .. } => &conditions.refund_keys, + Self::HTLCConditions { conditions, .. } => &conditions.refund_keys, + } + } } impl TryFrom for SpendingConditions { @@ -332,7 +365,7 @@ impl From for super::nut10::Secret { } /// P2PK and HTLC spending conditions -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)] pub struct Conditions { #[serde(skip_serializing_if = "Option::is_none")] pub locktime: Option, diff --git a/crates/cdk/src/wallet.rs b/crates/cdk/src/wallet.rs index dbeab366..f100e14f 100644 --- a/crates/cdk/src/wallet.rs +++ b/crates/cdk/src/wallet.rs @@ -15,9 +15,9 @@ use crate::cdk_database::{self, WalletDatabase}; use crate::client::HttpClient; use crate::dhke::{construct_proofs, hash_to_curve, unblind_message}; use crate::nuts::{ - nut12, BlindSignature, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind, MintInfo, - PreMintSecrets, PreSwap, Proof, ProofState, Proofs, PublicKey, RestoreRequest, SigFlag, - SigningKey, SpendingConditions, State, SwapRequest, Token, VerifyingKey, + nut10, nut12, BlindSignature, Conditions, CurrencyUnit, Id, KeySet, KeySetInfo, Keys, Kind, + MintInfo, PreMintSecrets, PreSwap, Proof, ProofState, Proofs, PublicKey, RestoreRequest, + SigFlag, SigningKey, SpendingConditions, State, SwapRequest, Token, VerifyingKey, }; use crate::types::{MeltQuote, Melted, MintQuote}; use crate::url::UncheckedUrl; @@ -416,6 +416,7 @@ impl Wallet { None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?, }; } + let mint_res = self .client .post_mint( @@ -425,7 +426,7 @@ impl Wallet { ) .await?; - let keys = self.localstore.get_keys(&active_keyset_id).await?; //.get_keyset_keys(&mint_url, active_keyset_id).await?; + let keys = self.get_keyset_keys(&mint_url, active_keyset_id).await?; // Verify the signature DLEQ is valid { @@ -443,7 +444,7 @@ impl Wallet { mint_res.signatures, premint_secrets.rs(), premint_secrets.secrets(), - &keys.unwrap(), + &keys, )?; let minted_amount = proofs.iter().map(|p| p.amount).sum(); @@ -705,7 +706,18 @@ impl Wallet { post_swap_proofs.reverse(); + let mut left_proofs = vec![]; + for proof in post_swap_proofs { + let nut10: Result = proof.secret.clone().try_into(); + + match nut10 { + Ok(_) => send_proofs.push(proof), + Err(_) => left_proofs.push(proof), + } + } + + for proof in left_proofs { if (proof.amount + send_proofs.iter().map(|p| p.amount).sum()).gt(&amount) { keep_proofs.push(proof); } else { @@ -1202,11 +1214,30 @@ impl Wallet { pub fn verify_token_p2pk( &self, token: &Token, - spending_conditions: Conditions, + spending_conditions: SpendingConditions, ) -> Result<(), Error> { - use crate::nuts::nut10; + let (refund_keys, pubkeys, locktime, num_sigs) = match spending_conditions { + SpendingConditions::P2PKConditions { data, conditions } => { + let mut pubkeys = vec![data]; - if spending_conditions.refund_keys.is_some() && spending_conditions.locktime.is_none() { + pubkeys.extend(conditions.pubkeys.unwrap_or_default()); + + ( + conditions.refund_keys, + Some(pubkeys), + conditions.locktime, + conditions.num_sigs, + ) + } + SpendingConditions::HTLCConditions { conditions, .. } => ( + conditions.refund_keys, + conditions.pubkeys, + conditions.locktime, + conditions.num_sigs, + ), + }; + + if refund_keys.is_some() && locktime.is_none() { tracing::warn!( "Invalid spending conditions set: Locktime must be set if refund keys are allowed" ); @@ -1219,13 +1250,13 @@ impl Wallet { for proof in &mint_proof.proofs { let secret: nut10::Secret = (&proof.secret).try_into()?; - let proof_conditions: Conditions = secret.secret_data.tags.try_into()?; + let proof_conditions: SpendingConditions = secret.try_into()?; - if spending_conditions.num_sigs.ne(&proof_conditions.num_sigs) { + if num_sigs.ne(&proof_conditions.num_sigs()) { tracing::debug!( "Spending condition requires: {:?} sigs proof secret specifies: {:?}", - spending_conditions.num_sigs, - proof_conditions.num_sigs + num_sigs, + proof_conditions.num_sigs() ); return Err(Error::P2PKConditionsNotMet( @@ -1233,9 +1264,8 @@ impl Wallet { )); } - let spending_condition_pubkeys = - spending_conditions.pubkeys.clone().unwrap_or_default(); - let proof_pubkeys = proof_conditions.pubkeys.unwrap_or_default(); + let spending_condition_pubkeys = pubkeys.clone().unwrap_or_default(); + let proof_pubkeys = proof_conditions.pubkeys().unwrap_or_default(); // Check the Proof has the required pubkeys if proof_pubkeys.len().ne(&spending_condition_pubkeys.len()) @@ -1244,6 +1274,8 @@ impl Wallet { .all(|pubkey| spending_condition_pubkeys.contains(pubkey)) { tracing::debug!("Proof did not included Publickeys meeting condition"); + tracing::debug!("{:?}", proof_pubkeys); + tracing::debug!("{:?}", spending_condition_pubkeys); return Err(Error::P2PKConditionsNotMet( "Pubkeys in proof not allowed by spending condition".to_string(), )); @@ -1254,15 +1286,14 @@ impl Wallet { // it is one of them Check that proof locktime is > condition // locktime - if let Some(proof_refund_keys) = proof_conditions.refund_keys { + if let Some(proof_refund_keys) = proof_conditions.refund_keys() { let proof_locktime = proof_conditions - .locktime + .locktime() .ok_or(Error::LocktimeNotProvided)?; - if let (Some(condition_refund_keys), Some(condition_locktime)) = ( - &spending_conditions.refund_keys, - spending_conditions.locktime, - ) { + if let (Some(condition_refund_keys), Some(condition_locktime)) = + (&refund_keys, locktime) + { // Proof locktime must be greater then condition locktime to ensure it // cannot be claimed back if proof_locktime.lt(&condition_locktime) { @@ -1306,10 +1337,8 @@ impl Wallet { Some(keys) => keys.amount_key(proof.amount), None => { let keys = self - .localstore - .get_keys(&proof.keyset_id) - .await? - .ok_or(Error::UnknownKey)?; + .get_keyset_keys(&mint_proof.mint, proof.keyset_id) + .await?; let key = keys.amount_key(proof.amount); keys_cache.insert(proof.keyset_id, keys);