From 96a151bfaa163308bd7103c3efb9a22ff03cbb48 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Mon, 25 Mar 2024 14:42:02 +0000 Subject: [PATCH] feat(nut12): verify receive proofs --- crates/cashu-sdk/src/wallet/mod.rs | 59 ++++++++++++++++++++++++++++-- crates/cashu/src/dhke.rs | 48 ++++++++++++++++++------ crates/cashu/src/nuts/nut12.rs | 49 ++++++++++++++++--------- 3 files changed, 124 insertions(+), 32 deletions(-) diff --git a/crates/cashu-sdk/src/wallet/mod.rs b/crates/cashu-sdk/src/wallet/mod.rs index 51be6b69..7837f728 100644 --- a/crates/cashu-sdk/src/wallet/mod.rs +++ b/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 Wallet { 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 Wallet { 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 = HashMap::new(); for token in token_data.token { if token.proofs.is_empty() { @@ -510,6 +539,22 @@ impl Wallet { let mut proof_count: HashMap = 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 Wallet { 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) = >::try_into( proof.secret.clone(), diff --git a/crates/cashu/src/dhke.rs b/crates/cashu/src/dhke.rs index 6f960430..5e7159ba 100644 --- a/crates/cashu/src/dhke.rs +++ b/crates/cashu/src/dhke.rs @@ -115,18 +115,44 @@ mod wallet { ))? .to_owned(); - let unblinded_signature = unblind_message(blinded_c, r.into(), a)?; + let unblinded_signature = unblind_message(blinded_c, r.clone().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 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); } diff --git a/crates/cashu/src/nuts/nut12.rs b/crates/cashu/src/nuts/nut12.rs index 8414b550..7647ca4f 100644 --- a/crates/cashu/src/nuts/nut12.rs +++ b/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, + pub r: Option, } 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();