Browse Source

feat(mint): store blinded messages and sigs

thesimplekid 1 year ago
parent
commit
27677f881a

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

@@ -4,7 +4,7 @@ use std::sync::Arc;
 use async_trait::async_trait;
 use cashu::dhke::hash_to_curve;
 use cashu::nuts::nut02::mint::KeySet;
-use cashu::nuts::{CurrencyUnit, Id, MintInfo, Proof, Proofs, PublicKey};
+use cashu::nuts::{BlindedSignature, CurrencyUnit, Id, MintInfo, Proof, Proofs, PublicKey};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
 use tokio::sync::Mutex;
@@ -20,9 +20,11 @@ pub struct MemoryLocalStore {
     melt_quotes: Arc<Mutex<HashMap<String, MeltQuote>>>,
     pending_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
     spent_proofs: Arc<Mutex<HashMap<Vec<u8>, Proof>>>,
+    blinded_signatures: Arc<Mutex<HashMap<String, BlindedSignature>>>,
 }
 
 impl MemoryLocalStore {
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         mint_info: MintInfo,
         active_keysets: HashMap<CurrencyUnit, Id>,
@@ -31,6 +33,7 @@ impl MemoryLocalStore {
         melt_quotes: Vec<MeltQuote>,
         pending_proofs: Proofs,
         spent_proofs: Proofs,
+        blinded_signatures: HashMap<String, BlindedSignature>,
     ) -> Result<Self, Error> {
         Ok(Self {
             mint_info: Arc::new(Mutex::new(mint_info)),
@@ -70,6 +73,7 @@ impl MemoryLocalStore {
                     })
                     .collect(),
             )),
+            blinded_signatures: Arc::new(Mutex::new(blinded_signatures)),
         })
     }
 }
@@ -218,4 +222,27 @@ impl LocalStore for MemoryLocalStore {
             .remove(&secret_point.to_sec1_bytes().to_vec());
         Ok(())
     }
+
+    async fn add_blinded_signature(
+        &self,
+        blinded_message: PublicKey,
+        blinded_signature: BlindedSignature,
+    ) -> Result<(), Error> {
+        self.blinded_signatures
+            .lock()
+            .await
+            .insert(blinded_message.to_string(), blinded_signature);
+        Ok(())
+    }
+    async fn get_blinded_signature(
+        &self,
+        blinded_message: &PublicKey,
+    ) -> Result<Option<BlindedSignature>, Error> {
+        Ok(self
+            .blinded_signatures
+            .lock()
+            .await
+            .get(&blinded_message.to_string())
+            .cloned())
+    }
 }

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

@@ -6,7 +6,7 @@ use std::collections::HashMap;
 
 use async_trait::async_trait;
 use cashu::nuts::nut02::mint::KeySet;
-use cashu::nuts::{CurrencyUnit, Id, MintInfo, Proof, PublicKey};
+use cashu::nuts::{BlindedSignature, CurrencyUnit, Id, MintInfo, Proof, PublicKey};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
 pub use memory::MemoryLocalStore;
@@ -78,4 +78,14 @@ pub trait LocalStore {
     async fn get_pending_proof_by_secret(&self, secret: &Secret) -> Result<Option<Proof>, Error>;
     async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result<Option<Proof>, Error>;
     async fn remove_pending_proof(&self, secret: &Secret) -> Result<(), Error>;
+
+    async fn add_blinded_signature(
+        &self,
+        blinded_message: PublicKey,
+        blinded_signature: BlindedSignature,
+    ) -> Result<(), Error>;
+    async fn get_blinded_signature(
+        &self,
+        blinded_message: &PublicKey,
+    ) -> Result<Option<BlindedSignature>, Error>;
 }

+ 42 - 1
crates/cashu-sdk/src/mint/localstore/redb_store.rs

@@ -4,7 +4,9 @@ use std::sync::Arc;
 
 use async_trait::async_trait;
 use cashu::dhke::hash_to_curve;
-use cashu::nuts::{CurrencyUnit, Id, MintInfo, MintKeySet as KeySet, Proof, PublicKey};
+use cashu::nuts::{
+    BlindedSignature, CurrencyUnit, Id, MintInfo, MintKeySet as KeySet, Proof, PublicKey,
+};
 use cashu::secret::Secret;
 use cashu::types::{MeltQuote, MintQuote};
 use redb::{Database, ReadableTable, TableDefinition};
@@ -20,6 +22,8 @@ const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mel
 const PENDING_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("pending_proofs");
 const SPENT_PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("spent_proofs");
 const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
+// Key is hex blinded_message B_ value is blinded_signature
+const BLINDED_SIGNATURES: TableDefinition<&str, &str> = TableDefinition::new("blinded_signatures");
 
 #[derive(Debug, Clone)]
 pub struct RedbLocalStore {
@@ -39,6 +43,7 @@ impl RedbLocalStore {
             let _ = write_txn.open_table(PENDING_PROOFS_TABLE)?;
             let _ = write_txn.open_table(SPENT_PROOFS_TABLE)?;
             let _ = write_txn.open_table(CONFIG_TABLE)?;
+            let _ = write_txn.open_table(BLINDED_SIGNATURES)?;
         }
         write_txn.commit()?;
 
@@ -391,4 +396,40 @@ impl LocalStore for RedbLocalStore {
 
         Ok(())
     }
+
+    async fn add_blinded_signature(
+        &self,
+        blinded_message: PublicKey,
+        blinded_signature: BlindedSignature,
+    ) -> Result<(), Error> {
+        let db = self.db.lock().await;
+        let write_txn = db.begin_write()?;
+
+        {
+            let mut table = write_txn.open_table(BLINDED_SIGNATURES)?;
+            table.insert(
+                blinded_message.to_string().as_str(),
+                serde_json::to_string(&blinded_signature)?.as_str(),
+            )?;
+        }
+
+        write_txn.commit()?;
+
+        Ok(())
+    }
+
+    async fn get_blinded_signature(
+        &self,
+        blinded_message: &PublicKey,
+    ) -> Result<Option<BlindedSignature>, Error> {
+        let db = self.db.lock().await;
+        let read_txn = db.begin_read()?;
+        let table = read_txn.open_table(BLINDED_SIGNATURES)?;
+
+        if let Some(blinded_signature) = table.get(blinded_message.to_string().as_str())? {
+            return Ok(serde_json::from_str(blinded_signature.value())?);
+        }
+
+        Ok(None)
+    }
 }

+ 53 - 6
crates/cashu-sdk/src/mint/mod.rs

@@ -61,6 +61,8 @@ pub enum Error {
     UnknownSecretKind,
     #[error("Cannot have multiple units")]
     MultipleUnits,
+    #[error("Blinded Message is already signed")]
+    BlindedMessageAlreadySigned,
 }
 
 impl From<Error> for ErrorResponse {
@@ -282,6 +284,17 @@ impl Mint {
         &mut self,
         mint_request: nut04::MintBolt11Request,
     ) -> Result<nut04::MintBolt11Response, Error> {
+        for blinded_message in &mint_request.outputs {
+            if self
+                .localstore
+                .get_blinded_signature(&blinded_message.b)
+                .await?
+                .is_some()
+            {
+                return Err(Error::BlindedMessageAlreadySigned);
+            }
+        }
+
         let quote = self
             .localstore
             .get_mint_quote(&mint_request.quote)
@@ -295,7 +308,11 @@ impl Mint {
         let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
 
         for blinded_message in mint_request.outputs {
-            blind_signatures.push(self.blind_sign(&blinded_message).await?);
+            let blinded_signature = self.blind_sign(&blinded_message).await?;
+            self.localstore
+                .add_blinded_signature(blinded_message.b, blinded_signature.clone())
+                .await?;
+            blind_signatures.push(blinded_signature);
         }
 
         Ok(nut04::MintBolt11Response {
@@ -349,6 +366,17 @@ impl Mint {
         &mut self,
         swap_request: SwapRequest,
     ) -> Result<SwapResponse, Error> {
+        for blinded_message in &swap_request.outputs {
+            if self
+                .localstore
+                .get_blinded_signature(&blinded_message.b)
+                .await?
+                .is_some()
+            {
+                return Err(Error::BlindedMessageAlreadySigned);
+            }
+        }
+
         let proofs_total = swap_request.input_amount();
 
         let output_total = swap_request.output_amount();
@@ -416,9 +444,12 @@ impl Mint {
 
         let mut promises = Vec::with_capacity(swap_request.outputs.len());
 
-        for output in swap_request.outputs {
-            let promise = self.blind_sign(&output).await?;
-            promises.push(promise);
+        for blinded_message in swap_request.outputs {
+            let blinded_signature = self.blind_sign(&blinded_message).await?;
+            self.localstore
+                .add_blinded_signature(blinded_message.b, blinded_signature.clone())
+                .await?;
+            promises.push(blinded_signature);
         }
 
         Ok(SwapResponse::new(promises))
@@ -619,6 +650,19 @@ impl Mint {
     ) -> Result<MeltBolt11Response, Error> {
         self.verify_melt_request(melt_request).await?;
 
+        if let Some(outputs) = &melt_request.outputs {
+            for blinded_message in outputs {
+                if self
+                    .localstore
+                    .get_blinded_signature(&blinded_message.b)
+                    .await?
+                    .is_some()
+                {
+                    return Err(Error::BlindedMessageAlreadySigned);
+                }
+            }
+        }
+
         for input in &melt_request.inputs {
             self.localstore.add_spent_proof(input.clone()).await?;
         }
@@ -647,8 +691,11 @@ impl Mint {
                 let mut blinded_message = blinded_message;
                 blinded_message.amount = *amount;
 
-                let signature = self.blind_sign(&blinded_message).await?;
-                change_sigs.push(signature)
+                let blinded_signature = self.blind_sign(&blinded_message).await?;
+                self.localstore
+                    .add_blinded_signature(blinded_message.b, blinded_signature.clone())
+                    .await?;
+                change_sigs.push(blinded_signature)
             }
 
             change = Some(change_sigs);