Browse Source

feat: remove multimint support

thesimplekid 4 months ago
parent
commit
4e6bf594be

+ 1 - 1
crates/cdk-cli/src/sub_commands/receive.rs

@@ -137,7 +137,7 @@ async fn receive_token(
 ) -> Result<Amount> {
     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());
 

+ 62 - 51
crates/cdk/src/nuts/nut00/token.rs

@@ -66,7 +66,7 @@ impl Token {
     }
 
     /// Proofs in [`Token`]
-    pub fn proofs(&self) -> HashMap<MintUrl, Proofs> {
+    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<CurrencyUnit> {
         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<MintUrl, Error> {
+        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<MintUrl, Proofs> {
-        let mut proofs: HashMap<MintUrl, Proofs> = 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<Amount, Error> {
+    pub fn value(&self) -> Result<Amount, Error> {
         Ok(Amount::try_sum(
             self.token
                 .iter()
@@ -213,14 +222,27 @@ impl TokenV3 {
         )?)
     }
 
+    /// Memo
     #[inline]
-    fn memo(&self) -> &Option<String> {
+    pub fn memo(&self) -> &Option<String> {
         &self.memo
     }
 
+    /// Unit
     #[inline]
-    fn unit(&self) -> Option<CurrencyUnit> {
-        self.unit
+    pub fn unit(&self) -> &Option<CurrencyUnit> {
+        &self.unit
+    }
+
+    /// Mint Url
+    pub fn mint_urls(&self) -> Vec<MintUrl> {
+        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<TokenV4> 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<MintUrl, Proofs> {
-        let mint_url = &self.mint_url;
-        let mut proofs: HashMap<MintUrl, Proofs> = 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<Amount, Error> {
+    pub fn value(&self) -> Result<Amount, Error> {
         Ok(Amount::try_sum(
             self.token
                 .iter()
@@ -311,13 +318,15 @@ impl TokenV4 {
         )?)
     }
 
+    /// Memo
     #[inline]
-    fn memo(&self) -> &Option<String> {
+    pub fn memo(&self) -> &Option<String> {
         &self.memo
     }
 
+    /// Unit
     #[inline]
-    fn unit(&self) -> CurrencyUnit {
+    pub fn unit(&self) -> CurrencyUnit {
         self.unit
     }
 }
@@ -350,13 +359,15 @@ impl TryFrom<TokenV3> for TokenV4 {
     type Error = Error;
     fn try_from(token: TokenV3) -> Result<Self, Self::Error> {
         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<TokenV3> 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)?,

+ 90 - 89
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<Id, Keys> = 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(())

+ 23 - 23
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);
             }
         }
 

+ 1 - 3
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);
         }