Преглед изворни кода

refactor(mint): verify melt request

thesimplekid пре 1 година
родитељ
комит
8753e71854

+ 11 - 1
crates/cashu-sdk/src/mint/localstore/memory.rs

@@ -3,7 +3,7 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use cashu::nuts::nut02::mint::KeySet;
-use cashu::nuts::{Id, Proof};
+use cashu::nuts::{CurrencyUnit, Id, Proof};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
 use tokio::sync::Mutex;
@@ -12,6 +12,7 @@ use super::{Error, LocalStore};
 
 #[derive(Default, Debug, Clone)]
 pub struct MemoryLocalStore {
+    active_keysets: Arc<Mutex<HashMap<CurrencyUnit, Id>>>,
     keysets: Arc<Mutex<HashMap<Id, KeySet>>>,
     mint_quotes: Arc<Mutex<HashMap<String, MintQuote>>>,
     melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
@@ -21,6 +22,15 @@ pub struct MemoryLocalStore {
 
 #[async_trait(?Send)]
 impl LocalStore for MemoryLocalStore {
+    async fn add_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Error> {
+        self.active_keysets.lock().await.insert(unit, id);
+        Ok(())
+    }
+
+    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Error> {
+        Ok(self.active_keysets.lock().await.get(unit).cloned())
+    }
+
     async fn add_keyset(&self, keyset: KeySet) -> Result<(), Error> {
         self.keysets.lock().await.insert(keyset.id, keyset);
         Ok(())

+ 4 - 1
crates/cashu-sdk/src/mint/localstore/mod.rs

@@ -2,7 +2,7 @@ mod memory;
 
 use async_trait::async_trait;
 use cashu::nuts::nut02::mint::KeySet;
-use cashu::nuts::{Id, Proof};
+use cashu::nuts::{CurrencyUnit, Id, Proof};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
 use thiserror::Error;
@@ -34,6 +34,9 @@ pub enum Error {
 
 #[async_trait(?Send)]
 pub trait LocalStore {
+    async fn add_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
+    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Error>;
+
     async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Error>;
     async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Error>;
     async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Error>;

+ 50 - 7
crates/cashu-sdk/src/mint/mod.rs

@@ -44,6 +44,10 @@ pub enum Error {
     Cashu(#[from] cashu::error::mint::Error),
     #[error("`{0}`")]
     Localstore(#[from] localstore::Error),
+    #[error("Unknown quote")]
+    UnknownQuote,
+    #[error("Cannot have multiple units")]
+    MultipleUnits,
 }
 
 pub struct Mint<L: LocalStore> {
@@ -342,13 +346,8 @@ impl<L: LocalStore> Mint<L> {
         let quote = self
             .localstore
             .get_melt_quote(&melt_request.quote)
-            .await
-            .unwrap();
-        let quote = if let Some(quote) = quote {
-            quote
-        } else {
-            return Err(Error::Custom("Unknown Quote".to_string()));
-        };
+            .await?
+            .ok_or(Error::UnknownQuote)?;
 
         let proofs_total = melt_request.proofs_amount();
 
@@ -362,6 +361,50 @@ impl<L: LocalStore> Mint<L> {
             return Err(Error::Amount);
         }
 
+        let input_keyset_ids: HashSet<Id> =
+            melt_request.inputs.iter().map(|p| p.keyset_id).collect();
+
+        let mut keyset_units = Vec::with_capacity(input_keyset_ids.capacity());
+
+        for id in input_keyset_ids {
+            let keyset = self
+                .localstore
+                .get_keyset(&id)
+                .await?
+                .ok_or(Error::UnknownKeySet)?;
+            keyset_units.push(keyset.unit);
+        }
+
+        if let Some(outputs) = &melt_request.outputs {
+            let output_keysets_ids: HashSet<Id> = outputs.iter().map(|b| b.keyset_id).collect();
+            for id in output_keysets_ids {
+                let keyset = self
+                    .localstore
+                    .get_keyset(&id)
+                    .await?
+                    .ok_or(Error::UnknownKeySet)?;
+
+                // Get the active keyset for the unit
+                let active_keyset_id = self
+                    .localstore
+                    .get_active_keyset_id(&keyset.unit)
+                    .await?
+                    .ok_or(Error::InactiveKeyset)?;
+
+                // Check output is for current active keyset
+                if id.ne(&active_keyset_id) {
+                    return Err(Error::InactiveKeyset);
+                }
+                keyset_units.push(keyset.unit);
+            }
+        }
+
+        // Check that all input and output proofs are the same unit
+        let seen_units: HashSet<CurrencyUnit> = HashSet::new();
+        if keyset_units.iter().any(|unit| !seen_units.contains(unit)) && seen_units.len() != 1 {
+            return Err(Error::MultipleUnits);
+        }
+
         let secrets: HashSet<&Secret> = melt_request.inputs.iter().map(|p| &p.secret).collect();
 
         // Ensure proofs are unique and not being double spent