Browse Source

feat(nut12): verify receive proofs

thesimplekid 1 year ago
parent
commit
96a151bfaa
3 changed files with 125 additions and 33 deletions
  1. 56 3
      crates/cashu-sdk/src/wallet/mod.rs
  2. 38 12
      crates/cashu/src/dhke.rs
  3. 31 18
      crates/cashu/src/nuts/nut12.rs

+ 56 - 3
crates/cashu-sdk/src/wallet/mod.rs

@@ -50,6 +50,10 @@ pub enum Error {
     LocalStore(#[from] localstore::Error),
     #[error("`{0}`")]
     Cashu(#[from] cashu::error::Error),
+    #[error("Could not verify Dleq")]
+    CouldNotVerifyDleq,
+    #[error("Unknown Key")]
+    UnknownKey,
     #[error("`{0}`")]
     Custom(String),
 }
@@ -338,12 +342,17 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
 
         let keys = self.get_keyset_keys(&mint_url, active_keyset_id).await?;
 
+        // Verify the signature DLEQ is valid
         #[cfg(feature = "nut12")]
         {
             for (sig, premint) in mint_res.signatures.iter().zip(&premint_secrets.secrets) {
-                let keys = self.localstore.get_keys(&sig.keyset_id).await?.unwrap();
-                let key = keys.amount_key(sig.amount).unwrap();
-                sig.verify_dleq(&key, &premint.blinded_message.b).unwrap();
+                let keys = self.get_keyset_keys(&mint_url, sig.keyset_id).await?;
+                let key = keys.amount_key(sig.amount).ok_or(Error::UnknownKey)?;
+                match sig.verify_dleq(&key, &premint.blinded_message.b) {
+                    Ok(_) => (),
+                    Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
+                    Err(_) => return Err(Error::CouldNotVerifyDleq),
+                }
             }
         }
 
@@ -378,6 +387,26 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
 
         let unit = token_data.unit.unwrap_or_default();
 
+        // Verify the signature DLEQ is valid
+        // Verify that all proofs in the token have a vlid DLEQ proof if one is supplied
+        #[cfg(feature = "nut12")]
+        {
+            for mint_proof in &token_data.token {
+                let mint_url = &mint_proof.mint;
+                let proofs = &mint_proof.proofs;
+
+                for proof in proofs {
+                    let keys = self.get_keyset_keys(mint_url, proof.keyset_id).await?;
+                    let key = keys.amount_key(proof.amount).ok_or(Error::UnknownKey)?;
+                    match proof.verify_dleq(&key) {
+                        Ok(_) => continue,
+                        Err(cashu::nuts::nut12::Error::MissingDleqProof) => continue,
+                        Err(_) => return Err(Error::CouldNotVerifyDleq),
+                    }
+                }
+            }
+        }
+
         let mut proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
         for token in token_data.token {
             if token.proofs.is_empty() {
@@ -510,6 +539,22 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
         let mut proof_count: HashMap<Id, u64> = HashMap::new();
 
         for (promise, premint) in promises.iter().zip(blinded_messages) {
+            // Verify the signature DLEQ is valid
+            #[cfg(feature = "nut12")]
+            {
+                let keys = self
+                    .localstore
+                    .get_keys(&promise.keyset_id)
+                    .await?
+                    .ok_or(Error::UnknownKey)?;
+                let key = keys.amount_key(promise.amount).ok_or(Error::UnknownKey)?;
+                match promise.verify_dleq(&key, &premint.blinded_message.b) {
+                    Ok(_) => (),
+                    Err(cashu::nuts::nut12::Error::MissingDleqProof) => (),
+                    Err(_) => return Err(Error::CouldNotVerifyDleq),
+                }
+            }
+
             let a = self
                 .localstore
                 .get_keys(&promise.keyset_id)
@@ -909,6 +954,14 @@ impl<C: Client, L: LocalStore> Wallet<C, L> {
             let mut sig_flag = None;
 
             for proof in &mut proofs {
+                // Verify that proof DLEQ is valid
+                #[cfg(feature = "nut12")]
+                {
+                    let keys = self.localstore.get_keys(&proof.keyset_id).await?.unwrap();
+                    let key = keys.amount_key(proof.amount).unwrap();
+                    proof.verify_dleq(&key).unwrap();
+                }
+
                 if let Ok(secret) =
                     <cashu::secret::Secret as TryInto<cashu::nuts::nut10::Secret>>::try_into(
                         proof.secret.clone(),

+ 38 - 12
crates/cashu/src/dhke.rs

@@ -115,18 +115,44 @@ mod wallet {
                 ))?
                 .to_owned();
 
-            let unblinded_signature = unblind_message(blinded_c, r.into(), a)?;
-
-            let proof = Proof {
-                amount: blinded_signature.amount,
-                keyset_id: blinded_signature.keyset_id,
-                secret,
-                c: unblinded_signature,
-                #[cfg(feature = "nut11")]
-                witness: None,
-                #[cfg(feature = "nut12")]
-                dleq: blinded_signature.dleq,
-            };
+            let unblinded_signature = unblind_message(blinded_c, r.clone().into(), a)?;
+
+            let proof;
+
+            #[cfg(not(feature = "nut12"))]
+            {
+                proof = Proof {
+                    amount: blinded_signature.amount,
+                    keyset_id: blinded_signature.keyset_id,
+                    secret,
+                    c: unblinded_signature,
+                    #[cfg(feature = "nut11")]
+                    witness: None,
+                };
+            }
+
+            #[cfg(feature = "nut12")]
+            {
+                let dleq = if let Some(dleq) = blinded_signature.dleq {
+                    Some(DleqProof {
+                        e: dleq.e,
+                        s: dleq.s,
+                        r: Some(r),
+                    })
+                } else {
+                    None
+                };
+
+                proof = Proof {
+                    amount: blinded_signature.amount,
+                    keyset_id: blinded_signature.keyset_id,
+                    secret,
+                    c: unblinded_signature,
+                    #[cfg(feature = "nut11")]
+                    witness: None,
+                    dleq,
+                };
+            }
 
             proofs.push(proof);
         }

+ 31 - 18
crates/cashu/src/nuts/nut12.rs

@@ -5,17 +5,31 @@ use std::ops::Mul;
 use k256::Scalar;
 use log::{debug, warn};
 use serde::{Deserialize, Serialize};
+use thiserror::Error;
 
 use super::{BlindedSignature, Proof, PublicKey, SecretKey};
 use crate::dhke::{hash_e, hash_to_curve};
-use crate::error::Error;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("No Dleq Proof provided")]
+    MissingDleqProof,
+    #[error("Incomplete DLEQ Proof")]
+    IncompleteDleqProof,
+    #[error("Invalid Dleq Prood")]
+    InvalidDleqProof,
+    #[error("`{0}`")]
+    EllipticCurve(#[from] k256::elliptic_curve::Error),
+    #[error("`{0}`")]
+    Cashu(#[from] crate::error::Error),
+}
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub struct DleqProof {
-    e: SecretKey,
-    s: SecretKey,
+    pub e: SecretKey,
+    pub s: SecretKey,
     #[serde(skip_serializing_if = "Option::is_none")]
-    r: Option<SecretKey>,
+    pub r: Option<SecretKey>,
 }
 
 fn verify_dleq(
@@ -50,28 +64,27 @@ fn verify_dleq(
         warn!("DLEQ on signature failed");
         debug!("e_bytes: {:?}, Hash e: {:?}", e_bytes, hash_e);
         // TODO: fix error
-        return Err(Error::TokenSpent);
+        return Err(Error::InvalidDleqProof);
     }
 
     Ok(())
 }
 
 impl Proof {
-    pub fn verify_dleq(
-        &self,
-        mint_pubkey: PublicKey,
-        blinding_factor: SecretKey,
-    ) -> Result<(), Error> {
-        let (e, s): (k256::SecretKey, k256::SecretKey) = if let Some(dleq) = &self.dleq {
-            (dleq.e.clone().into(), dleq.s.clone().into())
-        } else {
-            // TODO: fix error
-            return Err(Error::AmountKey);
-        };
+    pub fn verify_dleq(&self, mint_pubkey: &PublicKey) -> Result<(), Error> {
+        let (e, s, blinding_factor): (k256::SecretKey, k256::SecretKey, k256::SecretKey) =
+            if let Some(dleq) = self.dleq.clone() {
+                if let Some(r) = dleq.r {
+                    (dleq.e.into(), dleq.s.into(), r.into())
+                } else {
+                    return Err(Error::IncompleteDleqProof);
+                }
+            } else {
+                return Err(Error::MissingDleqProof);
+            };
 
         let c: k256::PublicKey = (&self.c).into();
         let mint_pubkey: k256::PublicKey = mint_pubkey.into();
-        let blinding_factor: k256::SecretKey = blinding_factor.into();
 
         let y = hash_to_curve(self.secret.0.as_bytes())?;
         let blinded_signature = c.to_projective()
@@ -97,7 +110,7 @@ impl BlindedSignature {
             (dleq.e.clone().into(), dleq.s.clone().into())
         } else {
             // TODO: fix error
-            return Err(Error::AmountKey);
+            return Err(Error::MissingDleqProof);
         };
 
         let mint_pubkey: k256::PublicKey = mint_pubkey.into();