diff --git a/crates/cashu-sdk/src/mint/localstore/memory.rs b/crates/cashu-sdk/src/mint/localstore/memory.rs index cf70c574..ab313a84 100644 --- a/crates/cashu-sdk/src/mint/localstore/memory.rs +++ b/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>>, pending_proofs: Arc, Proof>>>, spent_proofs: Arc, Proof>>>, + blinded_signatures: Arc>>, } impl MemoryLocalStore { + #[allow(clippy::too_many_arguments)] pub fn new( mint_info: MintInfo, active_keysets: HashMap, @@ -31,6 +33,7 @@ impl MemoryLocalStore { melt_quotes: Vec, pending_proofs: Proofs, spent_proofs: Proofs, + blinded_signatures: HashMap, ) -> Result { 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, Error> { + Ok(self + .blinded_signatures + .lock() + .await + .get(&blinded_message.to_string()) + .cloned()) + } } diff --git a/crates/cashu-sdk/src/mint/localstore/mod.rs b/crates/cashu-sdk/src/mint/localstore/mod.rs index 5f9463fa..396c5c5b 100644 --- a/crates/cashu-sdk/src/mint/localstore/mod.rs +++ b/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, Error>; async fn get_pending_proof_by_y(&self, y: &PublicKey) -> Result, 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, Error>; } diff --git a/crates/cashu-sdk/src/mint/localstore/redb_store.rs b/crates/cashu-sdk/src/mint/localstore/redb_store.rs index ba8df57d..f2c38d1f 100644 --- a/crates/cashu-sdk/src/mint/localstore/redb_store.rs +++ b/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, 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) + } } diff --git a/crates/cashu-sdk/src/mint/mod.rs b/crates/cashu-sdk/src/mint/mod.rs index e97a29b1..bdd530f6 100644 --- a/crates/cashu-sdk/src/mint/mod.rs +++ b/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 for ErrorResponse { @@ -282,6 +284,17 @@ impl Mint { &mut self, mint_request: nut04::MintBolt11Request, ) -> Result { + 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 { + 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 { 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);