diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs index 747d7839..1dd08881 100644 --- a/crates/cashu-sdk/src/client/minreq_client.rs +++ b/crates/cashu-sdk/src/client/minreq_client.rs @@ -168,15 +168,19 @@ impl Client for HttpClient { async fn post_swap( &self, mint_url: Url, - split_request: SwapRequest, + swap_request: SwapRequest, ) -> Result { let url = join_url(mint_url, &["v1", "swap"])?; - let res = minreq::post(url).with_json(&split_request)?.send()?; + println!("{}", serde_json::to_string(&swap_request).unwrap()); + + let res = minreq::post(url).with_json(&swap_request)?.send()?; let value = res.json::()?; + println!("{}", value); let response: Result = serde_json::from_value(value.clone()); + println!("{:?}", response); match response { Ok(res) => Ok(res), diff --git a/crates/cashu-sdk/src/mint/mod.rs b/crates/cashu-sdk/src/mint/mod.rs index 96a5df07..966eda5c 100644 --- a/crates/cashu-sdk/src/mint/mod.rs +++ b/crates/cashu-sdk/src/mint/mod.rs @@ -311,6 +311,7 @@ impl Mint { amount, b, keyset_id, + .. } = blinded_message; let keyset = self diff --git a/crates/cashu-sdk/src/wallet/mod.rs b/crates/cashu-sdk/src/wallet/mod.rs index dea3408f..d9d527d4 100644 --- a/crates/cashu-sdk/src/wallet/mod.rs +++ b/crates/cashu-sdk/src/wallet/mod.rs @@ -9,7 +9,7 @@ use cashu::nuts::nut07::ProofState; use cashu::nuts::nut11::SigningKey; use cashu::nuts::{ BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, P2PKConditions, PreMintSecrets, - PreSwap, Proof, Proofs, SwapRequest, Token, + PreSwap, Proof, Proofs, SigFlag, SwapRequest, Token, }; #[cfg(feature = "nut07")] use cashu::secret::Secret; @@ -234,6 +234,7 @@ impl Wallet { } let keysets = self.client.get_mint_keysets(mint_url.try_into()?).await?; + println!("{:?}", keysets); self.localstore .add_mint_keysets( @@ -353,7 +354,17 @@ impl Wallet { // TODO: if none fetch keyset for mint - let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?; + let keys = + if let Some(keys) = self.localstore.get_keys(&active_keyset_id.unwrap()).await? { + keys + } else { + self.get_mint_keys(&token.mint, active_keyset_id.unwrap()) + .await?; + self.localstore + .get_keys(&active_keyset_id.unwrap()) + .await? + .unwrap() + }; // Sum amount of all proofs let amount: Amount = token.proofs.iter().map(|p| p.amount).sum(); @@ -372,15 +383,20 @@ impl Wallet { swap_response.signatures, pre_swap.pre_mint_secrets.rs(), pre_swap.pre_mint_secrets.secrets(), - &keys.unwrap(), + &keys, )?; + // println!("{:?}", p); let mint_proofs = proofs.entry(token.mint).or_default(); mint_proofs.extend(p); } + //println!("{:?}", proofs); - for (mint, proofs) in proofs { - self.localstore.add_proofs(mint, proofs).await?; + for (mint, p) in proofs { + println!("{:?}", serde_json::to_string(&p)); + println!("{:?}", mint); + self.add_mint(mint.clone()).await?; + self.localstore.add_proofs(mint, p).await?; } Ok(()) @@ -408,9 +424,9 @@ impl Wallet { desired_messages.sort_secrets(); desired_messages } else { - let value = proofs.iter().map(|p| p.amount).sum(); + let amount = proofs.iter().map(|p| p.amount).sum(); - PreMintSecrets::random(active_keyset_id, value)? + PreMintSecrets::random(active_keyset_id, amount)? }; let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages()); @@ -420,7 +436,51 @@ impl Wallet { swap_request, }) } + /* + /// Create Swap Payload + async fn create_swap_signed( + &mut self, + mint_url: &UncheckedUrl, + unit: &CurrencyUnit, + amount: Option, + proofs: Proofs, + signing_key: Option, + ) -> Result { + let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap(); + let pre_mint_secrets = if let Some(amount) = amount { + let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?; + + let change_amount = proofs.iter().map(|p| p.amount).sum::() - amount; + + let change_messages = if let Some(signing_key) = signing_key { + PreMintSecrets::random_signed(active_keyset_id, change_amount, signing_key)? + } else { + PreMintSecrets::random(active_keyset_id, change_amount)? + }; + // Combine the BlindedMessages totoalling the desired amount with change + desired_messages.combine(change_messages); + // Sort the premint secrets to avoid finger printing + desired_messages.sort_secrets(); + desired_messages + } else { + let amount = proofs.iter().map(|p| p.amount).sum(); + + if let Some(signing_key) = signing_key { + PreMintSecrets::random_signed(active_keyset_id, amount, signing_key)? + } else { + PreMintSecrets::random(active_keyset_id, amount)? + } + }; + + let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages()); + + Ok(PreSwap { + pre_mint_secrets, + swap_request, + }) + } + */ pub async fn process_swap_response( &self, blinded_messages: PreMintSecrets, @@ -752,6 +812,7 @@ impl Wallet { encoded_token: &str, signing_keys: Vec, ) -> Result<(), Error> { + let signing_key = signing_keys[0].clone(); let pubkey_secret_key: HashMap = signing_keys .into_iter() .map(|s| (s.public_key().to_string(), s)) @@ -778,6 +839,8 @@ impl Wallet { let mut proofs = token.proofs; + let mut sig_flag = None; + for proof in &mut proofs { if let Ok(secret) = >::try_into( @@ -786,21 +849,36 @@ impl Wallet { { let conditions: Result = secret.try_into(); if let Ok(conditions) = conditions { + println!("{:?}", conditions); let pubkeys = conditions.pubkeys; for pubkey in pubkeys { if let Some(signing) = pubkey_secret_key.get(&pubkey.to_string()) { - proof.sign_p2pk_proof(signing.clone()).ok(); + proof.sign_p2pk_proof(signing.clone()).unwrap(); + proof.verify_p2pk().unwrap(); + println!("v"); } } + + sig_flag = Some(conditions.sig_flag); } } } - let pre_swap = self + let mut pre_swap = self .create_swap(&token.mint, &unit, Some(amount), proofs) .await?; + if let Some(sigflag) = sig_flag { + if sigflag.eq(&SigFlag::SigAll) { + for blinded_message in &mut pre_swap.swap_request.outputs { + blinded_message + .sign_p2pk_blinded_message(signing_key.clone()) + .unwrap(); + } + } + } + let swap_response = self .client .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request) @@ -824,54 +902,70 @@ impl Wallet { Ok(()) } + /* + pub async fn claim_p2pk_locked_proofs( + &mut self, + sigflag: SigFlag, + mint_url: &UncheckedUrl, + unit: &CurrencyUnit, + signing_key: SigningKey, + proofs: Proofs, + ) -> Result<(), Error> { + let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?; - pub async fn claim_p2pk_locked_proofs( - &mut self, - mint_url: &UncheckedUrl, - unit: &CurrencyUnit, - signing_key: SigningKey, - proofs: Proofs, - ) -> Result<(), Error> { - let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?; + let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?; - let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?; + let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len()); - let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len()); + // Sum amount of all proofs + let amount: Amount = proofs.iter().map(|p| p.amount).sum(); - // Sum amount of all proofs - let amount: Amount = proofs.iter().map(|p| p.amount).sum(); + for p in proofs.clone() { + let mut p = p; + p.sign_p2pk_proof(signing_key.clone()).unwrap(); + signed_proofs.push(p); + } - for p in proofs.clone() { - let mut p = p; - p.sign_p2pk_proof(signing_key.clone()).unwrap(); - signed_proofs.push(p); + let pre_swap = match sigflag { + SigFlag::SigInputs => { + self.create_swap(mint_url, &unit, Some(amount), signed_proofs) + .await? + } + SigFlag::SigAll => { + self.create_swap_signed( + mint_url, + unit, + Some(amount), + signed_proofs, + Some(signing_key), + ) + .await? + } + _ => todo!(), + }; + + let swap_response = self + .client + .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request) + .await?; + + // Proof to keep + let p = construct_proofs( + swap_response.signatures, + pre_swap.pre_mint_secrets.rs(), + pre_swap.pre_mint_secrets.secrets(), + &keys.unwrap(), + )?; + + self.localstore + .remove_proofs(mint_url.clone(), &proofs) + .await?; + + self.localstore.add_proofs(mint_url.clone(), p).await?; + + Ok(()) } - - let pre_swap = self - .create_swap(mint_url, &unit, Some(amount), signed_proofs) - .await?; - - let swap_response = self - .client - .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request) - .await?; - - // Proof to keep - let p = construct_proofs( - swap_response.signatures, - pre_swap.pre_mint_secrets.rs(), - pre_swap.pre_mint_secrets.secrets(), - &keys.unwrap(), - )?; - - self.localstore - .remove_proofs(mint_url.clone(), &proofs) - .await?; - - self.localstore.add_proofs(mint_url.clone(), p).await?; - - Ok(()) - } + */ pub fn proofs_to_token( &self, diff --git a/crates/cashu/src/error.rs b/crates/cashu/src/error.rs index 6bb9aaae..4acd6100 100644 --- a/crates/cashu/src/error.rs +++ b/crates/cashu/src/error.rs @@ -64,7 +64,15 @@ pub struct ErrorResponse { impl ErrorResponse { pub fn from_json(json: &str) -> Result { - serde_json::from_str(json) + if let Ok(res) = serde_json::from_str::(json) { + Ok(res) + } else { + Ok(Self { + code: 999, + error: Some(json.to_string()), + detail: None, + }) + } } } diff --git a/crates/cashu/src/nuts/nut00.rs b/crates/cashu/src/nuts/nut00.rs index f6452720..28b565bf 100644 --- a/crates/cashu/src/nuts/nut00.rs +++ b/crates/cashu/src/nuts/nut00.rs @@ -2,13 +2,14 @@ // https://github.com/cashubtc/nuts/blob/main/00.md use std::fmt; -use std::hash::{Hash, Hasher}; +use std::hash::{self, Hasher}; use std::str::FromStr; use serde::{Deserialize, Serialize}; -use super::{Id, Proofs, PublicKey}; +use super::{Id, Proofs, PublicKey, Signatures, SigningKey}; use crate::error::Error; +use crate::nuts::nut11::{witness_deserialize, witness_serialize}; use crate::secret::Secret; use crate::url::UncheckedUrl; use crate::Amount; @@ -24,9 +25,39 @@ pub struct BlindedMessage { /// encrypted secret message (B_) #[serde(rename = "B_")] pub b: PublicKey, + /// Witness + #[serde(default)] + #[serde(skip_serializing_if = "Signatures::is_empty")] + #[serde(serialize_with = "witness_serialize")] + #[serde(deserialize_with = "witness_deserialize")] + pub witness: Signatures, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash)] +impl BlindedMessage { + pub fn new(amount: Amount, keyset_id: Id, b: PublicKey) -> Self { + Self { + amount, + keyset_id, + b, + witness: Signatures::default(), + } + } + + pub fn sign_p2pk_blinded_message(&mut self, secret_key: SigningKey) -> Result<(), Error> { + let msg_to_sign = hex::decode(self.b.to_string())?; + + println!("{:?}", msg_to_sign); + + let signature = secret_key.sign(&msg_to_sign); + + self.witness + .signatures + .push(hex::encode(signature.to_bytes())); + Ok(()) + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, hash::Hash)] #[serde(rename_all = "lowercase")] pub enum CurrencyUnit { #[default] @@ -145,11 +176,7 @@ pub mod wallet { let secret = Secret::new(); let (blinded, r) = blind_message(&secret.to_bytes()?, None)?; - let blinded_message = BlindedMessage { - amount, - b: blinded, - keyset_id, - }; + let blinded_message = BlindedMessage::new(amount, keyset_id, blinded); output.push(PreMint { secret, @@ -172,11 +199,7 @@ pub mod wallet { for (secret, amount) in secrets.into_iter().zip(amounts) { let (blinded, r) = blind_message(&secret.to_bytes()?, None)?; - let blinded_message = BlindedMessage { - amount, - b: blinded, - keyset_id, - }; + let blinded_message = BlindedMessage::new(amount, keyset_id, blinded); output.push(PreMint { secret, @@ -199,11 +222,7 @@ pub mod wallet { let secret = Secret::new(); let (blinded, r) = blind_message(&secret.to_bytes()?, None)?; - let blinded_message = BlindedMessage { - amount: Amount::ZERO, - b: blinded, - keyset_id, - }; + let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded); output.push(PreMint { secret, @@ -236,11 +255,7 @@ pub mod wallet { let (blinded, r) = blind_message(&secret.to_bytes()?, Some(blinding_factor.into()))?; - let blinded_message = BlindedMessage { - keyset_id, - amount, - b: blinded, - }; + let blinded_message = BlindedMessage::new(amount, keyset_id, blinded); let pre_mint = PreMint { blinded_message, @@ -270,11 +285,7 @@ pub mod wallet { let secret: Secret = conditions.clone().try_into().unwrap(); let (blinded, r) = blind_message(&secret.to_bytes()?, None)?; - let blinded_message = BlindedMessage { - amount, - b: blinded, - keyset_id, - }; + let blinded_message = BlindedMessage::new(amount, keyset_id, blinded); output.push(PreMint { secret, @@ -480,7 +491,7 @@ impl Proof { } } -impl Hash for Proof { +impl hash::Hash for Proof { fn hash(&self, state: &mut H) { self.secret.hash(state); } diff --git a/crates/cashu/src/nuts/nut01.rs b/crates/cashu/src/nuts/nut01.rs index 050e682b..aedb48d8 100644 --- a/crates/cashu/src/nuts/nut01.rs +++ b/crates/cashu/src/nuts/nut01.rs @@ -64,6 +64,12 @@ impl From for PublicKey { } } +impl From for PublicKey { + fn from(value: super::VerifyingKey) -> PublicKey { + let v: VerifyingKey = value.into(); + PublicKey(v.into()) + } +} impl PublicKey { pub fn to_bytes(&self) -> Box<[u8]> { self.0.to_sec1_bytes() diff --git a/crates/cashu/src/nuts/nut11.rs b/crates/cashu/src/nuts/nut11.rs index 435d3e41..22a40c21 100644 --- a/crates/cashu/src/nuts/nut11.rs +++ b/crates/cashu/src/nuts/nut11.rs @@ -6,12 +6,11 @@ use std::fmt; use std::hash::{self, Hasher}; use std::str::FromStr; -use bitcoin::hashes::{sha256, Hash}; use k256::schnorr::signature::{Signer, Verifier}; use k256::schnorr::Signature; use serde::de::Error as DeserializerError; use serde::ser::SerializeSeq; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer}; use super::nut01::PublicKey; use super::nut02::Id; @@ -25,7 +24,7 @@ use crate::Amount; pub struct Signatures { #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] - signatures: Vec, + pub signatures: Vec, } impl Signatures { @@ -50,9 +49,26 @@ pub struct Proof { /// Witness #[serde(default)] #[serde(skip_serializing_if = "Signatures::is_empty")] + #[serde(serialize_with = "witness_serialize")] + #[serde(deserialize_with = "witness_deserialize")] pub witness: Signatures, } +pub fn witness_serialize(x: &Signatures, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(&serde_json::to_string(x).map_err(ser::Error::custom)?) +} + +pub fn witness_deserialize<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s: String = String::deserialize(deserializer)?; + serde_json::from_str(&s).map_err(de::Error::custom) +} + impl Proof { pub fn new(amount: Amount, keyset_id: Id, secret: crate::secret::Secret, c: PublicKey) -> Self { Proof { @@ -136,7 +152,9 @@ impl TryFrom for Secret { return Err(Error::Amount); } - let data = pubkeys[0].to_string(); + let data: PublicKey = pubkeys[0].clone().into(); + + let data = data.to_string(); let mut tags = vec![]; @@ -259,7 +277,13 @@ impl Proof { let mut valid_sigs = 0; - let msg = sha256::Hash::hash(&self.secret.to_bytes().unwrap()); + println!("{:?}", self.secret.to_string()); + println!( + "sec bytes: {:?}", + self.secret.to_string().into_bytes().len() + ); + + let msg = &self.secret.to_bytes().unwrap(); for signature in &self.witness.signatures { let mut pubkeys = spending_conditions.pubkeys.clone(); @@ -268,31 +292,30 @@ impl Proof { for v in &spending_conditions.pubkeys { let sig = Signature::try_from(hex::decode(signature).unwrap().as_slice()).unwrap(); - if v.verify(&msg.to_byte_array(), &sig).is_ok() { + if v.verify(msg, &sig).is_ok() { valid_sigs += 1; } else { - println!("{:?}", v.verify(&msg.to_byte_array(), &sig).unwrap()); + println!("{:?}", v.verify(msg, &sig).unwrap()); } } } if valid_sigs.ge(&spending_conditions.num_sigs.unwrap_or(1)) { + println!("valid sigs: {}", valid_sigs); return Ok(()); } if let Some(locktime) = spending_conditions.locktime { // If lock time has passed check if refund witness signature is valid - if locktime.lt(&unix_time()) { - if !spending_conditions.refund_keys.is_empty() { - for s in &self.witness.signatures { - for v in &spending_conditions.refund_keys { - let sig = Signature::try_from(s.as_bytes()) - .map_err(|_| Error::InvalidSignature)?; + if locktime.lt(&unix_time()) && !spending_conditions.refund_keys.is_empty() { + for s in &self.witness.signatures { + for v in &spending_conditions.refund_keys { + let sig = Signature::try_from(s.as_bytes()) + .map_err(|_| Error::InvalidSignature)?; - // As long as there is one valid refund signature it can be spent - if v.verify(&msg.to_byte_array(), &sig).is_ok() { - return Ok(()); - } + // As long as there is one valid refund signature it can be spent + if v.verify(msg, &sig).is_ok() { + return Ok(()); } } } @@ -303,13 +326,14 @@ impl Proof { } pub fn sign_p2pk_proof(&mut self, secret_key: SigningKey) -> Result<(), Error> { - let msg_to_sign = sha256::Hash::hash(&self.secret.to_bytes().unwrap()); + let msg_to_sign = &self.secret.to_bytes().unwrap(); - let signature = secret_key.sign(msg_to_sign.as_byte_array()); + let signature = secret_key.sign(msg_to_sign); self.witness .signatures .push(hex::encode(signature.to_bytes())); + Ok(()) } } @@ -476,6 +500,7 @@ impl From for Vec { let mut tag = vec![TagKind::Pubkeys.to_string()]; for pubkey in pubkeys { + let pubkey: PublicKey = pubkey.into(); tag.push(pubkey.to_string()) } tag @@ -529,7 +554,10 @@ impl VerifyingKey { } pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { - Ok(self.0.verify(msg, signature).unwrap()) + self.0 + .verify(msg, signature) + .map_err(|_| Error::InvalidSignature)?; + Ok(()) } } @@ -622,7 +650,7 @@ impl From for SigningKey { impl SigningKey { pub fn public_key(&self) -> VerifyingKey { - self.0.verifying_key().clone().into() + (*self.0.verifying_key()).into() } pub fn sign(&self, msg: &[u8]) -> Signature { @@ -630,7 +658,7 @@ impl SigningKey { } pub fn verifying_key(&self) -> VerifyingKey { - VerifyingKey(self.0.verifying_key().clone()) + VerifyingKey(*self.0.verifying_key()) } } @@ -701,15 +729,6 @@ mod tests { assert_eq!(secret_der, secret); } - #[test] - fn test_verify() { - let proof_str = r#"{"amount":0,"secret":"[\"P2PK\",{\"nonce\":\"190badde56afcbf67937e228744ea896bb3e48bcb60efa412799e1518618c287\",\"data\":\"0249098aa8b9d2fbec49ff8598feb17b592b986e62319a4fa488a3dc36387157a7\",\"tags\":[[\"sigflag\",\"SIG_INPUTS\"]]}]","C":"02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904","id": "009a1f293253e41e","witness":{"signatures":["2b117c29a0e405fcbcac4c632b5862eb3ace0d67c681e8209d3aa2f52d5198471629b1ec6bce75d3879c47725be89d28938e31236307b40bc6c89491fa540e35"]}}"#; - - let proof: Proof = serde_json::from_str(proof_str).unwrap(); - - assert!(proof.verify_p2pk().is_ok()); - } - #[test] fn sign_proof() { let secret_key = SigningKey::from_str( @@ -721,7 +740,7 @@ mod tests { let conditions = P2PKConditions { locktime: None, - pubkeys: vec![v_key.into()], + pubkeys: vec![v_key], refund_keys: vec![], num_sigs: None, sig_flag: SigFlag::SigInputs, @@ -732,7 +751,7 @@ mod tests { let mut proof = Proof { keyset_id: Id::from_str("009a1f293253e41e").unwrap(), amount: Amount::ZERO, - secret: secret.try_into().unwrap(), + secret: secret.clone().try_into().unwrap(), c: PublicKey::from_str( "02698c4e2b5f9534cd0687d87513c759790cf829aa5739184a3e3735471fbda904", ) @@ -740,9 +759,7 @@ mod tests { witness: Signatures { signatures: vec![] }, }; - let signing_key: SigningKey = secret_key.try_into().unwrap(); - - proof.sign_p2pk_proof(signing_key).unwrap(); + proof.sign_p2pk_proof(secret_key).unwrap(); assert!(proof.verify_p2pk().is_ok()); } diff --git a/crates/cashu/src/secret.rs b/crates/cashu/src/secret.rs index 208c5d0e..a8917c53 100644 --- a/crates/cashu/src/secret.rs +++ b/crates/cashu/src/secret.rs @@ -69,7 +69,7 @@ impl Secret { serde_json::from_str(&self.0); match secret { - Ok(_) => Ok(self.0.clone().into_bytes()), + Ok(_) => Ok(self.0.clone().replace('\\', "").into_bytes()), Err(_) => Ok(hex::decode(&self.0)?), } }