diff --git a/crates/cdk-cli/src/sub_commands/receive.rs b/crates/cdk-cli/src/sub_commands/receive.rs index 24eea39b5..f63bed667 100644 --- a/crates/cdk-cli/src/sub_commands/receive.rs +++ b/crates/cdk-cli/src/sub_commands/receive.rs @@ -137,7 +137,7 @@ async fn receive_token( ) -> Result { let token: Token = Token::from_str(token_str)?; - let mint_url = token.proofs().into_keys().next().expect("Mint in token"); + let mint_url = token.mint_url()?; let wallet_key = WalletKey::new(mint_url.clone(), token.unit().unwrap_or_default()); diff --git a/crates/cdk/src/nuts/nut00/token.rs b/crates/cdk/src/nuts/nut00/token.rs index 12ffbd5db..96e15302a 100644 --- a/crates/cdk/src/nuts/nut00/token.rs +++ b/crates/cdk/src/nuts/nut00/token.rs @@ -66,7 +66,7 @@ impl Token { } /// Proofs in [`Token`] - pub fn proofs(&self) -> HashMap { + pub fn proofs(&self) -> Proofs { match self { Self::TokenV3(token) => token.proofs(), Self::TokenV4(token) => token.proofs(), @@ -92,11 +92,27 @@ impl Token { /// Unit pub fn unit(&self) -> Option { match self { - Self::TokenV3(token) => token.unit(), + Self::TokenV3(token) => *token.unit(), Self::TokenV4(token) => Some(token.unit()), } } + /// Mint url + pub fn mint_url(&self) -> Result { + match self { + Self::TokenV3(token) => { + let mint_urls = token.mint_urls(); + + if mint_urls.len() != 1 { + return Err(Error::UnsupportedToken); + } + + Ok(mint_urls.first().expect("Length is checked above").clone()) + } + Self::TokenV4(token) => Ok(token.mint_url.clone()), + } + } + /// To v3 string pub fn to_v3_string(&self) -> String { let v3_token = match self { @@ -187,24 +203,17 @@ impl TokenV3 { }) } - fn proofs(&self) -> HashMap { - let mut proofs: HashMap = HashMap::new(); - - for token in self.token.clone() { - let mint_url = token.mint; - let mut mint_proofs = token.proofs; - - proofs - .entry(mint_url) - .and_modify(|p| p.append(&mut mint_proofs)) - .or_insert(mint_proofs); - } - - proofs + /// Proofs + pub fn proofs(&self) -> Proofs { + self.token + .iter() + .flat_map(|token| token.proofs.clone()) + .collect() } + /// Value #[inline] - fn value(&self) -> Result { + pub fn value(&self) -> Result { Ok(Amount::try_sum( self.token .iter() @@ -213,14 +222,27 @@ impl TokenV3 { )?) } + /// Memo #[inline] - fn memo(&self) -> &Option { + pub fn memo(&self) -> &Option { &self.memo } + /// Unit #[inline] - fn unit(&self) -> Option { - self.unit + pub fn unit(&self) -> &Option { + &self.unit + } + + /// Mint Url + pub fn mint_urls(&self) -> Vec { + let mut mint_urls = Vec::new(); + + for token in self.token.iter() { + mint_urls.push(token.mint.clone()); + } + + mint_urls } } @@ -249,13 +271,10 @@ impl fmt::Display for TokenV3 { impl From for TokenV3 { fn from(token: TokenV4) -> Self { - let (mint_url, proofs) = token - .proofs() - .into_iter() - .next() - .expect("Token has no proofs"); + let proofs = token.proofs(); + TokenV3 { - token: vec![TokenV3Token::new(mint_url, proofs)], + token: vec![TokenV3Token::new(token.mint_url, proofs)], memo: token.memo, unit: Some(token.unit), } @@ -281,28 +300,16 @@ pub struct TokenV4 { impl TokenV4 { /// Proofs from token - pub fn proofs(&self) -> HashMap { - let mint_url = &self.mint_url; - let mut proofs: HashMap = HashMap::new(); - - for token in self.token.clone() { - let mut mint_proofs = token - .proofs - .iter() - .map(|p| p.into_proof(&token.keyset_id)) - .collect(); - - proofs - .entry(mint_url.clone()) - .and_modify(|p| p.append(&mut mint_proofs)) - .or_insert(mint_proofs); - } - - proofs + pub fn proofs(&self) -> Proofs { + self.token + .iter() + .flat_map(|token| token.proofs.iter().map(|p| p.into_proof(&token.keyset_id))) + .collect() } + /// Value #[inline] - fn value(&self) -> Result { + pub fn value(&self) -> Result { Ok(Amount::try_sum( self.token .iter() @@ -311,13 +318,15 @@ impl TokenV4 { )?) } + /// Memo #[inline] - fn memo(&self) -> &Option { + pub fn memo(&self) -> &Option { &self.memo } + /// Unit #[inline] - fn unit(&self) -> CurrencyUnit { + pub fn unit(&self) -> CurrencyUnit { self.unit } } @@ -350,13 +359,15 @@ impl TryFrom for TokenV4 { type Error = Error; fn try_from(token: TokenV3) -> Result { let proofs = token.proofs(); - if proofs.len() != 1 { + let mint_urls = token.mint_urls(); + + if mint_urls.len() != 1 { return Err(Error::UnsupportedToken); } - let (mint_url, mint_proofs) = proofs.iter().next().expect("No proofs"); + let mint_url = mint_urls.first().expect("Len is checked"); - let proofs = mint_proofs + let proofs = proofs .iter() .fold(HashMap::new(), |mut acc, val| { acc.entry(val.keyset_id) @@ -369,7 +380,7 @@ impl TryFrom for TokenV4 { .collect(); Ok(TokenV4 { - mint_url: mint_url.to_owned(), + mint_url: mint_url.clone(), token: proofs, memo: token.memo, unit: token.unit.ok_or(Error::UnsupportedUnit)?, diff --git a/crates/cdk/src/wallet/mod.rs b/crates/cdk/src/wallet/mod.rs index 0d9e9bec3..c0488c2f6 100644 --- a/crates/cdk/src/wallet/mod.rs +++ b/crates/cdk/src/wallet/mod.rs @@ -393,87 +393,87 @@ impl Wallet { "Must set locktime".to_string(), )); } + if token.mint_url()? != self.mint_url { + return Err(Error::IncorrectWallet(format!( + "Should be {} not {}", + self.mint_url, + token.mint_url()? + ))); + } - for (mint_url, proofs) in &token.proofs() { - if mint_url != &self.mint_url { - return Err(Error::IncorrectWallet(format!( - "Should be {} not {}", - self.mint_url, mint_url - ))); - } - for proof in proofs { - let secret: nut10::Secret = (&proof.secret).try_into()?; + let proofs = token.proofs(); + for proof in proofs { + let secret: nut10::Secret = (&proof.secret).try_into()?; - let proof_conditions: SpendingConditions = secret.try_into()?; + let proof_conditions: SpendingConditions = secret.try_into()?; - if num_sigs.ne(&proof_conditions.num_sigs()) { - tracing::debug!( - "Spending condition requires: {:?} sigs proof secret specifies: {:?}", - num_sigs, - proof_conditions.num_sigs() - ); + if num_sigs.ne(&proof_conditions.num_sigs()) { + tracing::debug!( + "Spending condition requires: {:?} sigs proof secret specifies: {:?}", + num_sigs, + proof_conditions.num_sigs() + ); - return Err(Error::P2PKConditionsNotMet( - "Num sigs did not match spending condition".to_string(), - )); - } + return Err(Error::P2PKConditionsNotMet( + "Num sigs did not match spending condition".to_string(), + )); + } - let spending_condition_pubkeys = 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()) - || !proof_pubkeys - .iter() - .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(), - )); - } + // Check the Proof has the required pubkeys + if proof_pubkeys.len().ne(&spending_condition_pubkeys.len()) + || !proof_pubkeys + .iter() + .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(), + )); + } - // If spending condition refund keys is allowed (Some(Empty Vec)) - // If spending conition refund keys is allowed to restricted set of keys check - // it is one of them Check that proof locktime is > condition - // locktime + // If spending condition refund keys is allowed (Some(Empty Vec)) + // If spending conition refund keys is allowed to restricted set of keys check + // it is one of them Check that proof locktime is > condition + // locktime - if let Some(proof_refund_keys) = proof_conditions.refund_keys() { - let proof_locktime = proof_conditions - .locktime() - .ok_or(Error::LocktimeNotProvided)?; + if let Some(proof_refund_keys) = proof_conditions.refund_keys() { + let proof_locktime = proof_conditions + .locktime() + .ok_or(Error::LocktimeNotProvided)?; - if let (Some(condition_refund_keys), Some(condition_locktime)) = - (&refund_keys, 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) { + return Err(Error::P2PKConditionsNotMet( + "Proof locktime less then required".to_string(), + )); + } + + // A non empty condition refund key list is used as a restricted set of keys + // returns are allowed to An empty list means the + // proof can be refunded to anykey set in the secret + if !condition_refund_keys.is_empty() + && !proof_refund_keys + .iter() + .all(|refund_key| condition_refund_keys.contains(refund_key)) { - // Proof locktime must be greater then condition locktime to ensure it - // cannot be claimed back - if proof_locktime.lt(&condition_locktime) { - return Err(Error::P2PKConditionsNotMet( - "Proof locktime less then required".to_string(), - )); - } - - // A non empty condition refund key list is used as a restricted set of keys - // returns are allowed to An empty list means the - // proof can be refunded to anykey set in the secret - if !condition_refund_keys.is_empty() - && !proof_refund_keys - .iter() - .all(|refund_key| condition_refund_keys.contains(refund_key)) - { - return Err(Error::P2PKConditionsNotMet( - "Refund Key not allowed".to_string(), - )); - } - } else { - // Spending conditions does not allow refund keys return Err(Error::P2PKConditionsNotMet( - "Spending condition does not allow refund keys".to_string(), + "Refund Key not allowed".to_string(), )); } + } else { + // Spending conditions does not allow refund keys + return Err(Error::P2PKConditionsNotMet( + "Spending condition does not allow refund keys".to_string(), + )); } } } @@ -486,31 +486,32 @@ impl Wallet { pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> { let mut keys_cache: HashMap = HashMap::new(); - for (mint_url, proofs) in &token.proofs() { - if mint_url != &self.mint_url { - return Err(Error::IncorrectWallet(format!( - "Should be {} not {}", - self.mint_url, mint_url - ))); - } - for proof in proofs { - let mint_pubkey = match keys_cache.get(&proof.keyset_id) { - Some(keys) => keys.amount_key(proof.amount), - None => { - let keys = self.get_keyset_keys(proof.keyset_id).await?; + // TODO: Get mint url + // if mint_url != &self.mint_url { + // return Err(Error::IncorrectWallet(format!( + // "Should be {} not {}", + // self.mint_url, mint_url + // ))); + // } - let key = keys.amount_key(proof.amount); - keys_cache.insert(proof.keyset_id, keys); + let proofs = token.proofs(); + for proof in proofs { + let mint_pubkey = match keys_cache.get(&proof.keyset_id) { + Some(keys) => keys.amount_key(proof.amount), + None => { + let keys = self.get_keyset_keys(proof.keyset_id).await?; - key - } - } - .ok_or(Error::AmountKey)?; + let key = keys.amount_key(proof.amount); + keys_cache.insert(proof.keyset_id, keys); - proof - .verify_dleq(mint_pubkey) - .map_err(|_| Error::CouldNotVerifyDleq)?; + key + } } + .ok_or(Error::AmountKey)?; + + proof + .verify_dleq(mint_pubkey) + .map_err(|_| Error::CouldNotVerifyDleq)?; } Ok(()) diff --git a/crates/cdk/src/wallet/multi_mint_wallet.rs b/crates/cdk/src/wallet/multi_mint_wallet.rs index 3852fe496..6f3c33341 100644 --- a/crates/cdk/src/wallet/multi_mint_wallet.rs +++ b/crates/cdk/src/wallet/multi_mint_wallet.rs @@ -237,36 +237,36 @@ impl MultiMintWallet { let token_data = Token::from_str(encoded_token)?; let unit = token_data.unit().unwrap_or_default(); - let mint_proofs = token_data.proofs(); + let proofs = token_data.proofs(); let mut amount_received = Amount::ZERO; let mut mint_errors = None; + let mint_url = token_data.mint_url()?; + // Check that all mints in tokes have wallets - for (mint_url, proofs) in mint_proofs { - let wallet_key = WalletKey::new(mint_url.clone(), unit); - if !self.has(&wallet_key).await { - return Err(Error::UnknownWallet(wallet_key.clone())); - } + let wallet_key = WalletKey::new(mint_url.clone(), unit); + if !self.has(&wallet_key).await { + return Err(Error::UnknownWallet(wallet_key.clone())); + } - let wallet_key = WalletKey::new(mint_url.clone(), unit); - let wallet = self - .get_wallet(&wallet_key) - .await - .ok_or(Error::UnknownWallet(wallet_key.clone()))?; - - match wallet - .receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages) - .await - { - Ok(amount) => { - amount_received += amount; - } - Err(err) => { - tracing::error!("Could no receive proofs for mint: {}", err); - mint_errors = Some(err); - } + let wallet_key = WalletKey::new(mint_url.clone(), unit); + let wallet = self + .get_wallet(&wallet_key) + .await + .ok_or(Error::UnknownWallet(wallet_key.clone()))?; + + match wallet + .receive_proofs(proofs, SplitTarget::default(), p2pk_signing_keys, preimages) + .await + { + Ok(amount) => { + amount_received += amount; + } + Err(err) => { + tracing::error!("Could no receive proofs for mint: {}", err); + mint_errors = Some(err); } } diff --git a/crates/cdk/src/wallet/receive.rs b/crates/cdk/src/wallet/receive.rs index fe565b30f..d5999232a 100644 --- a/crates/cdk/src/wallet/receive.rs +++ b/crates/cdk/src/wallet/receive.rs @@ -207,9 +207,7 @@ impl Wallet { return Err(Error::MultiMintTokenNotSupported); } - let (mint_url, proofs) = proofs.into_iter().next().expect("Token has proofs"); - - if self.mint_url != mint_url { + if self.mint_url != token_data.mint_url()? { return Err(Error::IncorrectMint); }