Parcourir la source

feat(NUT09-13): Add support for NUT09 and NUT13

thesimplekid il y a 1 an
Parent
commit
a3faf2fb2e

+ 1 - 1
crates/cashu-sdk/Cargo.toml

@@ -17,7 +17,7 @@ gloo = ["dep:gloo"]
 all-nuts = ["nut07", "nut08", "nut09", "nut10", "nut11", "nut13"]
 nut07 = ["cashu/nut07"]
 nut08 = ["cashu/nut08"]
-nut09 = ["cashu/nut09"]
+nut09 = ["cashu/nut07", "cashu/nut09"]
 nut10 = ["cashu/nut10"]
 nut11 = ["cashu/nut11"]
 nut13 = ["cashu/nut13"]

+ 6 - 5
crates/cashu-sdk/src/mint/localstore/redb_store.rs

@@ -288,10 +288,9 @@ impl LocalStore for RedbLocalStore {
 
         {
             let mut table = write_txn.open_table(SPENT_PROOFS_TABLE)?;
+            let y: PublicKey = hash_to_curve(&proof.secret.to_bytes())?.into();
             table.insert(
-                hash_to_curve(&proof.secret.to_bytes())?
-                    .to_sec1_bytes()
-                    .as_ref(),
+                y.to_bytes().as_ref(),
                 serde_json::to_string(&proof)?.as_str(),
             )?;
         }
@@ -320,9 +319,9 @@ impl LocalStore for RedbLocalStore {
         let read_txn = db.begin_read()?;
         let table = read_txn.open_table(SPENT_PROOFS_TABLE)?;
 
-        let secret_hash = hash_to_curve(&secret.to_bytes())?;
+        let y: PublicKey = hash_to_curve(&secret.to_bytes())?.into();
 
-        let proof = table.get(secret_hash.to_sec1_bytes().as_ref())?;
+        let proof = table.get(y.to_bytes().as_ref())?;
 
         debug!("Checking secret: {}", secret.to_string());
 
@@ -446,6 +445,8 @@ impl LocalStore for RedbLocalStore {
         for blinded_message in blinded_messages {
             if let Some(blinded_signature) = table.get(blinded_message.to_bytes().as_ref())? {
                 signatures.push(Some(serde_json::from_str(blinded_signature.value())?))
+            } else {
+                signatures.push(None);
             }
         }
 

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

@@ -11,6 +11,8 @@ use cashu::nuts::{
 };
 #[cfg(feature = "nut07")]
 use cashu::nuts::{CheckStateRequest, CheckStateResponse};
+#[cfg(feature = "nut09")]
+use cashu::nuts::{RestoreRequest, RestoreResponse};
 use cashu::types::{MeltQuote, MintQuote};
 use cashu::Amount;
 use http::StatusCode;
@@ -291,6 +293,7 @@ impl Mint {
                 .await?
                 .is_some()
             {
+                error!("Output has already been signed: {}", blinded_message.b);
                 return Err(Error::BlindedMessageAlreadySigned);
             }
         }
@@ -373,6 +376,7 @@ impl Mint {
                 .await?
                 .is_some()
             {
+                error!("Output has already been signed: {}", blinded_message.b);
                 return Err(Error::BlindedMessageAlreadySigned);
             }
         }
@@ -658,6 +662,7 @@ impl Mint {
                     .await?
                     .is_some()
                 {
+                    error!("Output has already been signed: {}", blinded_message.b);
                     return Err(Error::BlindedMessageAlreadySigned);
                 }
             }
@@ -740,13 +745,15 @@ impl Mint {
         let mut outputs = Vec::with_capacity(output_len);
         let mut signatures = Vec::with_capacity(output_len);
 
-        let blinded_message = request.outputs.iter().map(|b| b.b.clone()).collect();
+        let blinded_message: Vec<PublicKey> = request.outputs.iter().map(|b| b.b.clone()).collect();
 
         let blinded_signatures = self
             .localstore
             .get_blinded_signatures(blinded_message)
             .await?;
 
+        assert_eq!(blinded_signatures.len(), output_len);
+
         for (blinded_message, blinded_signature) in
             request.outputs.into_iter().zip(blinded_signatures)
         {

+ 7 - 2
crates/cashu-sdk/src/wallet/localstore/memory.rs

@@ -211,8 +211,13 @@ impl LocalStore for MemoryLocalStore {
         Ok(())
     }
 
-    async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
-        self.keyset_counter.lock().await.insert(*keyset_id, count);
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
+        let keyset_counter = self.keyset_counter.lock().await;
+        let current_counter = keyset_counter.get(keyset_id).unwrap_or(&0);
+        self.keyset_counter
+            .lock()
+            .await
+            .insert(*keyset_id, current_counter + count);
         Ok(())
     }
 

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

@@ -84,7 +84,7 @@ pub trait LocalStore {
     ) -> Result<(), Error>;
 
     #[cfg(feature = "nut13")]
-    async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error>;
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error>;
     #[cfg(feature = "nut13")]
     async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u64>, Error>;
 }

+ 15 - 3
crates/cashu-sdk/src/wallet/localstore/redb_store.rs

@@ -389,15 +389,27 @@ impl LocalStore for RedbLocalStore {
     }
 
     #[cfg(feature = "nut13")]
-    async fn add_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
+    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u64) -> Result<(), Error> {
         let db = self.db.lock().await;
 
-        let write_txn = db.begin_write()?;
+        let current_counter;
+        {
+            let read_txn = db.begin_read()?;
+            let table = read_txn.open_table(KEYSET_COUNTER)?;
+            let counter = table.get(keyset_id.to_string().as_str())?;
+            current_counter = if let Some(counter) = counter {
+                counter.value()
+            } else {
+                0
+            };
+        }
 
+        let write_txn = db.begin_write()?;
         {
             let mut table = write_txn.open_table(KEYSET_COUNTER)?;
+            let new_counter = current_counter + count;
 
-            table.insert(keyset_id.to_string().as_str(), count)?;
+            table.insert(keyset_id.to_string().as_str(), new_counter)?;
         }
         write_txn.commit()?;
 

+ 27 - 23
crates/cashu-sdk/src/wallet/mod.rs

@@ -351,10 +351,9 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         self.localstore.remove_mint_quote(&quote_info.id).await?;
 
         // Update counter for keyset
-        if let Some(counter) = counter {
-            let count = counter + proofs.len() as u64;
+        if counter.is_some() {
             self.localstore
-                .add_keyset_counter(&active_keyset_id, count)
+                .increment_keyset_counter(&active_keyset_id, proofs.len() as u64)
                 .await?;
         }
 
@@ -406,6 +405,13 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
                 pre_swap.pre_mint_secrets.secrets(),
                 &keys,
             )?;
+
+            if self.mnemonic.is_some() {
+                self.localstore
+                    .increment_keyset_counter(&active_keyset_id, p.len() as u64)
+                    .await?;
+            }
+
             let mint_proofs = proofs.entry(token.mint).or_default();
 
             mint_proofs.extend(p);
@@ -523,14 +529,8 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
 
         if self.mnemonic.is_some() {
             for (keyset_id, count) in proof_count {
-                let counter = self
-                    .localstore
-                    .get_keyset_counter(&keyset_id)
-                    .await?
-                    .unwrap_or(0);
-
                 self.localstore
-                    .add_keyset_counter(&keyset_id, counter + count)
+                    .increment_keyset_counter(&keyset_id, count)
                     .await?;
             }
         }
@@ -569,16 +569,8 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         let active_keyset = self.active_mint_keyset(mint_url, unit).await?;
 
         if self.mnemonic.is_some() {
-            let count = self
-                .localstore
-                .get_keyset_counter(&active_keyset)
-                .await?
-                .unwrap_or(0);
-
-            let new_count = count + post_swap_proofs.len() as u64;
-
             self.localstore
-                .add_keyset_counter(&active_keyset, new_count)
+                .increment_keyset_counter(&active_keyset, post_swap_proofs.len() as u64)
                 .await?;
         }
 
@@ -776,11 +768,14 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         };
 
         if let Some(change_proofs) = change_proofs {
+            debug!(
+                "Change amount returned from melt: {}",
+                change_proofs.iter().map(|p| p.amount).sum::<Amount>()
+            );
             // Update counter for keyset
-            if let Some(counter) = counter {
-                let count = counter + change_proofs.len() as u64;
+            if counter.is_some() {
                 self.localstore
-                    .add_keyset_counter(&active_keyset_id, count)
+                    .increment_keyset_counter(&active_keyset_id, change_proofs.len() as u64)
                     .await?;
             }
 
@@ -951,6 +946,12 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
             )?;
             let mint_proofs = received_proofs.entry(token.mint).or_default();
 
+            if self.mnemonic.is_some() {
+                self.localstore
+                    .increment_keyset_counter(&active_keyset_id, p.len() as u64)
+                    .await?;
+            }
+
             mint_proofs.extend(p);
         }
 
@@ -1014,6 +1015,7 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
 
                 if response.signatures.is_empty() {
                     empty_batch += 1;
+                    start_counter += 100;
                     continue;
                 }
 
@@ -1039,8 +1041,10 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
                     &keys,
                 )?;
 
+                debug!("Restored {} proofs", proofs.len());
+
                 self.localstore
-                    .add_keyset_counter(&keyset.id, start_counter + proofs.len() as u64)
+                    .increment_keyset_counter(&keyset.id, proofs.len() as u64)
                     .await?;
 
                 let states = self

+ 3 - 0
crates/cashu/src/dhke.rs

@@ -123,6 +123,7 @@ mod mint {
     use std::ops::Mul;
 
     use k256::{Scalar, SecretKey};
+    use log::warn;
 
     use super::hash_to_curve;
     use crate::error;
@@ -154,6 +155,8 @@ mod mint {
             return Ok(());
         }
 
+        warn!("Message not verifed");
+
         Err(error::mint::Error::TokenNotVerifed)
     }
 }

+ 12 - 1
crates/cashu/src/nuts/nut13.rs

@@ -2,6 +2,7 @@ use std::str::FromStr;
 
 use bip32::{DerivationPath, XPrv};
 use bip39::Mnemonic;
+use log::debug;
 
 use super::{Id, SecretKey};
 use crate::error::Error;
@@ -9,6 +10,11 @@ use crate::secret::Secret;
 
 impl Secret {
     pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
+        debug!(
+            "Deriving secret for {} with count {}",
+            keyset_id.to_string(),
+            counter.to_string()
+        );
         let path = DerivationPath::from_str(&format!(
             "m/129372'/0'/{}'/{}'/0",
             u64::try_from(keyset_id)?,
@@ -23,6 +29,11 @@ impl Secret {
 
 impl SecretKey {
     pub fn from_seed(mnemonic: &Mnemonic, keyset_id: Id, counter: u64) -> Result<Self, Error> {
+        debug!(
+            "Deriving key for {} with count {}",
+            keyset_id.to_string(),
+            counter.to_string()
+        );
         let path = DerivationPath::from_str(&format!(
             "m/129372'/0'/{}'/{}'/1",
             u64::try_from(keyset_id)?,
@@ -95,7 +106,7 @@ mod wallet {
         ) -> Result<Self, wallet::Error> {
             let mut pre_mint_secrets = PreMintSecrets::default();
 
-            for i in start_count..end_count {
+            for i in start_count..=end_count {
                 let secret = Secret::from_seed(mnemonic, keyset_id, i)?;
                 let blinding_factor = SecretKey::from_seed(mnemonic, keyset_id, i)?;