diff --git a/bindings/cashu-ffi/src/cashu.udl b/bindings/cashu-ffi/src/cashu.udl index f6334334..b063d018 100644 --- a/bindings/cashu-ffi/src/cashu.udl +++ b/bindings/cashu-ffi/src/cashu.udl @@ -98,7 +98,7 @@ interface Token { }; -interface BlindedMessages { +interface PreMintSecrets { [Throws=CashuError, Name=random] constructor(Id keyset_id, Amount amount); [Throws=CashuError, Name=blank] diff --git a/bindings/cashu-ffi/src/lib.rs b/bindings/cashu-ffi/src/lib.rs index 0eaa09fa..0ff687e0 100644 --- a/bindings/cashu-ffi/src/lib.rs +++ b/bindings/cashu-ffi/src/lib.rs @@ -7,9 +7,9 @@ mod ffi { pub use crate::error::CashuError; pub use crate::nuts::nut00::blinded_message::BlindedMessage; - pub use crate::nuts::nut00::blinded_messages::BlindedMessages; pub use crate::nuts::nut00::blinded_signature::BlindedSignature; pub use crate::nuts::nut00::mint_proofs::MintProofs; + pub use crate::nuts::nut00::premint_secrets::PreMintSecrets; pub use crate::nuts::nut00::proof::mint::Proof as MintProof; pub use crate::nuts::nut00::proof::Proof; pub use crate::nuts::nut00::token::Token; diff --git a/bindings/cashu-ffi/src/nuts/nut00/blinded_messages.rs b/bindings/cashu-ffi/src/nuts/nut00/blinded_messages.rs deleted file mode 100644 index 0a11bb81..00000000 --- a/bindings/cashu-ffi/src/nuts/nut00/blinded_messages.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::ops::Deref; -use std::sync::Arc; - -use cashu::nuts::nut00::wallet::BlindedMessages as BlindedMessagesSdk; - -use crate::error::Result; -use crate::{Amount, BlindedMessage, Id, Secret, SecretKey}; - -pub struct BlindedMessages { - inner: BlindedMessagesSdk, -} - -impl Deref for BlindedMessages { - type Target = BlindedMessagesSdk; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl BlindedMessages { - pub fn random(keyset_id: Arc, amount: Arc) -> Result { - Ok(Self { - inner: BlindedMessagesSdk::random( - *keyset_id.as_ref().deref(), - *amount.as_ref().deref(), - )?, - }) - } - - pub fn blank(keyset_id: Arc, fee_reserve: Arc) -> Result { - Ok(Self { - inner: BlindedMessagesSdk::blank( - *keyset_id.as_ref().deref(), - *fee_reserve.as_ref().deref(), - )?, - }) - } - - pub fn blinded_messages(&self) -> Vec> { - self.inner - .blinded_messages - .clone() - .into_iter() - .map(|b| Arc::new(b.into())) - .collect() - } - - pub fn secrets(&self) -> Vec> { - self.inner - .secrets - .clone() - .into_iter() - .map(|s| Arc::new(s.into())) - .collect() - } - - pub fn rs(&self) -> Vec> { - self.inner - .rs - .clone() - .into_iter() - .map(|s| Arc::new(s.into())) - .collect() - } - - pub fn amounts(&self) -> Vec> { - self.inner - .amounts - .clone() - .into_iter() - .map(|a| Arc::new(a.into())) - .collect() - } -} - -impl From for BlindedMessages { - fn from(inner: cashu::nuts::nut00::wallet::BlindedMessages) -> BlindedMessages { - BlindedMessages { inner } - } -} - -impl From for cashu::nuts::nut00::wallet::BlindedMessages { - fn from(blinded_messages: BlindedMessages) -> cashu::nuts::nut00::wallet::BlindedMessages { - blinded_messages.inner - } -} diff --git a/bindings/cashu-ffi/src/nuts/nut00/mod.rs b/bindings/cashu-ffi/src/nuts/nut00/mod.rs index b708b13d..fb559e3c 100644 --- a/bindings/cashu-ffi/src/nuts/nut00/mod.rs +++ b/bindings/cashu-ffi/src/nuts/nut00/mod.rs @@ -1,6 +1,6 @@ pub mod blinded_message; -pub mod blinded_messages; pub mod blinded_signature; pub mod mint_proofs; +pub mod premint_secrets; pub mod proof; pub mod token; diff --git a/bindings/cashu-ffi/src/nuts/nut00/premint_secrets.rs b/bindings/cashu-ffi/src/nuts/nut00/premint_secrets.rs new file mode 100644 index 00000000..5bdd4903 --- /dev/null +++ b/bindings/cashu-ffi/src/nuts/nut00/premint_secrets.rs @@ -0,0 +1,78 @@ +use std::ops::Deref; +use std::sync::Arc; + +use cashu::nuts::nut00::wallet::PreMintSecrets as PreMintSecretsSdk; + +use crate::error::Result; +use crate::{Amount, BlindedMessage, Id, Secret, SecretKey}; + +pub struct PreMintSecrets { + inner: PreMintSecretsSdk, +} + +impl Deref for PreMintSecrets { + type Target = PreMintSecretsSdk; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl PreMintSecrets { + pub fn random(keyset_id: Arc, amount: Arc) -> Result { + Ok(Self { + inner: PreMintSecretsSdk::random( + *keyset_id.as_ref().deref(), + *amount.as_ref().deref(), + )?, + }) + } + + pub fn blank(keyset_id: Arc, fee_reserve: Arc) -> Result { + Ok(Self { + inner: PreMintSecretsSdk::blank( + *keyset_id.as_ref().deref(), + *fee_reserve.as_ref().deref(), + )?, + }) + } + + pub fn blinded_messages(&self) -> Vec> { + self.inner + .iter() + .map(|premint| Arc::new(premint.blinded_message.clone().into())) + .collect() + } + + pub fn secrets(&self) -> Vec> { + self.inner + .iter() + .map(|premint| Arc::new(premint.secret.clone().into())) + .collect() + } + + pub fn rs(&self) -> Vec> { + self.inner + .iter() + .map(|premint| Arc::new(premint.r.clone().into())) + .collect() + } + + pub fn amounts(&self) -> Vec> { + self.inner + .iter() + .map(|premint| Arc::new(premint.amount.into())) + .collect() + } +} + +impl From for PreMintSecrets { + fn from(inner: cashu::nuts::nut00::wallet::PreMintSecrets) -> PreMintSecrets { + PreMintSecrets { inner } + } +} + +impl From for cashu::nuts::nut00::wallet::PreMintSecrets { + fn from(blinded_messages: PreMintSecrets) -> cashu::nuts::nut00::wallet::PreMintSecrets { + blinded_messages.inner + } +} diff --git a/bindings/cashu-js/src/nuts/nut00/blinded_messages.rs b/bindings/cashu-js/src/nuts/nut00/blinded_messages.rs index b19599c6..b0b6874f 100644 --- a/bindings/cashu-js/src/nuts/nut00/blinded_messages.rs +++ b/bindings/cashu-js/src/nuts/nut00/blinded_messages.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use cashu::nuts::nut00::wallet::BlindedMessages; +use cashu::nuts::nut00::wallet::PreMintSecrets; use wasm_bindgen::prelude::*; use crate::error::{into_err, Result}; @@ -9,11 +9,11 @@ use crate::types::JsAmount; #[wasm_bindgen(js_name = BlindedMessages)] pub struct JsBlindedMessages { - inner: BlindedMessages, + inner: PreMintSecrets, } impl Deref for JsBlindedMessages { - type Target = BlindedMessages; + type Target = PreMintSecrets; fn deref(&self) -> &Self::Target { &self.inner } @@ -24,15 +24,14 @@ impl JsBlindedMessages { #[wasm_bindgen(js_name = random)] pub fn random(keyset_id: JsId, amount: JsAmount) -> Result { Ok(JsBlindedMessages { - inner: BlindedMessages::random(*keyset_id.deref(), *amount.deref()) - .map_err(into_err)?, + inner: PreMintSecrets::random(*keyset_id.deref(), *amount.deref()).map_err(into_err)?, }) } #[wasm_bindgen(js_name = blank)] pub fn blank(keyset_id: JsId, fee_reserve: JsAmount) -> Result { Ok(JsBlindedMessages { - inner: BlindedMessages::blank(*keyset_id.deref(), *fee_reserve.deref()) + inner: PreMintSecrets::blank(*keyset_id.deref(), *fee_reserve.deref()) .map_err(into_err)?, }) } @@ -40,24 +39,24 @@ impl JsBlindedMessages { /// Blinded Messages #[wasm_bindgen(getter)] pub fn blinded_messages(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.blinded_messages).map_err(into_err) + serde_wasm_bindgen::to_value(&self.inner.blinded_messages()).map_err(into_err) } /// Secrets #[wasm_bindgen(getter)] pub fn secrets(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.secrets).map_err(into_err) + serde_wasm_bindgen::to_value(&self.inner.secrets()).map_err(into_err) } /// rs #[wasm_bindgen(getter)] pub fn rs(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.rs).map_err(into_err) + serde_wasm_bindgen::to_value(&self.inner.rs()).map_err(into_err) } /// Amounts #[wasm_bindgen(getter)] pub fn amounts(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.amounts).map_err(into_err) + serde_wasm_bindgen::to_value(&self.inner.amounts()).map_err(into_err) } } diff --git a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl index 716a8848..8dc65f7e 100644 --- a/bindings/cashu-sdk-ffi/src/cashu_sdk.udl +++ b/bindings/cashu-sdk-ffi/src/cashu_sdk.udl @@ -101,7 +101,7 @@ interface Token { }; -interface BlindedMessages { +interface PreMintSecrets { [Throws=CashuError, Name=random] constructor(Id keyset_id, Amount amount); [Throws=CashuError, Name=blank] @@ -307,7 +307,7 @@ interface Wallet { [Throws=CashuSdkError] sequence receive(string encoded_token); [Throws=CashuSdkError] - sequence process_split_response(BlindedMessages blinded_messages, sequence promises); + sequence process_split_response(PreMintSecrets blinded_messages, sequence promises); [Throws=CashuSdkError] SendProofs send(Amount amount, sequence proofs); [Throws=CashuSdkError] diff --git a/bindings/cashu-sdk-ffi/src/lib.rs b/bindings/cashu-sdk-ffi/src/lib.rs index 9329550a..2b3a7056 100644 --- a/bindings/cashu-sdk-ffi/src/lib.rs +++ b/bindings/cashu-sdk-ffi/src/lib.rs @@ -5,12 +5,12 @@ mod wallet; mod ffi { pub use cashu_ffi::{ - Amount, BlindedMessage, BlindedMessages, BlindedSignature, Bolt11Invoice, CashuError, - CheckFeesRequest, CheckFeesResponse, CheckSpendableRequest, CheckSpendableResponse, Id, - InvoiceStatus, KeyPair, KeySet, KeySetInfo, KeySetResponse, Keys, KeysResponse, - MeltRequest, MeltResponse, MintInfo, MintKeySet, MintProof, MintProofs, MintRequest, - MintVersion, Nut05MeltRequest, Nut05MeltResponse, PostMintResponse, Proof, PublicKey, - RequestMintResponse, Secret, SecretKey, SplitRequest, SplitResponse, Token, + Amount, BlindedMessage, BlindedSignature, Bolt11Invoice, CashuError, CheckFeesRequest, + CheckFeesResponse, CheckSpendableRequest, CheckSpendableResponse, Id, InvoiceStatus, + KeyPair, KeySet, KeySetInfo, KeySetResponse, Keys, KeysResponse, MeltRequest, MeltResponse, + MintInfo, MintKeySet, MintProof, MintProofs, MintRequest, MintVersion, Nut05MeltRequest, + Nut05MeltResponse, PostMintResponse, PreMintSecrets, Proof, PublicKey, RequestMintResponse, + Secret, SecretKey, SplitRequest, SplitResponse, Token, }; pub use crate::error::CashuSdkError; diff --git a/bindings/cashu-sdk-ffi/src/wallet.rs b/bindings/cashu-sdk-ffi/src/wallet.rs index 12cc8b48..a25538f0 100644 --- a/bindings/cashu-sdk-ffi/src/wallet.rs +++ b/bindings/cashu-sdk-ffi/src/wallet.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use std::sync::Arc; use cashu_ffi::{ - BlindedMessages, BlindedSignature, Bolt11Invoice, Proof, RequestMintResponse, Token, + BlindedSignature, Bolt11Invoice, PreMintSecrets, Proof, RequestMintResponse, Token, }; use cashu_sdk::client::minreq_client::HttpClient; use cashu_sdk::types::ProofsStatus; @@ -88,7 +88,7 @@ impl Wallet { pub fn process_split_response( &self, - blinded_messages: Arc, + blinded_messages: Arc, promises: Vec>, ) -> Result>> { Ok(self diff --git a/crates/cashu-sdk/src/client/gloo_client.rs b/crates/cashu-sdk/src/client/gloo_client.rs index b0a11341..ed3450a9 100644 --- a/crates/cashu-sdk/src/client/gloo_client.rs +++ b/crates/cashu-sdk/src/client/gloo_client.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, - MeltResponse, MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, + BlindedMessage, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, MeltResponse, + MintRequest, PostMintResponse, PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, *, }; #[cfg(feature = "nut07")] @@ -89,14 +89,14 @@ impl Client for HttpClient { async fn post_mint( &self, mint_url: Url, - blinded_messages: BlindedMessages, + premint_secrets: PreMintSecrets, hash: &str, ) -> Result { let mut url = join_url(mint_url, "mint")?; url.query_pairs_mut().append_pair("hash", hash); let request = MintRequest { - outputs: blinded_messages.blinded_messages, + outputs: premint_secrets.blinded_messages(), }; let res = Request::post(url.as_str()) diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs index 63369d02..75315ee5 100644 --- a/crates/cashu-sdk/src/client/minreq_client.rs +++ b/crates/cashu-sdk/src/client/minreq_client.rs @@ -6,8 +6,8 @@ use async_trait::async_trait; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, - MeltResponse, MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, + BlindedMessage, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, MeltResponse, + MintRequest, PostMintResponse, PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, *, }; #[cfg(feature = "nut07")] @@ -72,14 +72,14 @@ impl Client for HttpClient { async fn post_mint( &self, mint_url: Url, - blinded_messages: BlindedMessages, + premint_secrets: PreMintSecrets, hash: &str, ) -> Result { let mut url = join_url(mint_url, "mint")?; url.query_pairs_mut().append_pair("hash", hash); let request = MintRequest { - outputs: blinded_messages.blinded_messages, + outputs: premint_secrets.blinded_messages(), }; let res = minreq::post(url) diff --git a/crates/cashu-sdk/src/client/mod.rs b/crates/cashu-sdk/src/client/mod.rs index a2aaeef0..0699da65 100644 --- a/crates/cashu-sdk/src/client/mod.rs +++ b/crates/cashu-sdk/src/client/mod.rs @@ -8,8 +8,8 @@ use cashu::nuts::CheckSpendableResponse; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; use cashu::nuts::{ - BlindedMessage, BlindedMessages, CheckFeesResponse, Keys, KeysetResponse, MeltResponse, - PostMintResponse, Proof, RequestMintResponse, SplitRequest, SplitResponse, + BlindedMessage, CheckFeesResponse, Keys, KeysetResponse, MeltResponse, PostMintResponse, + PreMintSecrets, Proof, RequestMintResponse, SplitRequest, SplitResponse, }; use cashu::{utils, Amount}; use serde::{Deserialize, Serialize}; @@ -99,7 +99,7 @@ pub trait Client { async fn post_mint( &self, mint_url: Url, - blinded_messages: BlindedMessages, + premint_secrets: PreMintSecrets, hash: &str, ) -> Result; diff --git a/crates/cashu-sdk/src/mint.rs b/crates/cashu-sdk/src/mint.rs index d2d1430d..d10fea6f 100644 --- a/crates/cashu-sdk/src/mint.rs +++ b/crates/cashu-sdk/src/mint.rs @@ -38,11 +38,9 @@ impl Mint { // Check that there is only one active keyset per unit for keyset_info in keysets_info { - if keyset_info.active { - if !active_units.insert(keyset_info.unit.clone()) { - // TODO: Handle Error - todo!() - } + if keyset_info.active && !active_units.insert(keyset_info.unit.clone()) { + // TODO: Handle Error + todo!() } let keyset = nut02::mint::KeySet::generate( @@ -88,7 +86,6 @@ impl Mint { let keysets = self .keysets_info .values() - .into_iter() .map(|k| k.clone().into()) .collect(); diff --git a/crates/cashu-sdk/src/wallet.rs b/crates/cashu-sdk/src/wallet.rs index e050db23..983e316a 100644 --- a/crates/cashu-sdk/src/wallet.rs +++ b/crates/cashu-sdk/src/wallet.rs @@ -5,7 +5,7 @@ use cashu::dhke::{construct_proofs, unblind_message}; #[cfg(feature = "nut07")] use cashu::nuts::nut00::mint; use cashu::nuts::{ - BlindedMessages, BlindedSignature, Keys, Proof, Proofs, RequestMintResponse, SplitPayload, + BlindedSignature, Keys, PreMintSecrets, Proof, Proofs, RequestMintResponse, SplitPayload, SplitRequest, Token, }; #[cfg(feature = "nut07")] @@ -106,21 +106,21 @@ impl Wallet { /// Mint Proofs pub async fn mint(&self, amount: Amount, hash: &str) -> Result { - let blinded_messages = BlindedMessages::random((&self.mint_keys).into(), amount)?; + let premint_secrets = PreMintSecrets::random((&self.mint_keys).into(), amount)?; let mint_res = self .client .post_mint( self.mint_url.clone().try_into()?, - blinded_messages.clone(), + premint_secrets.clone(), hash, ) .await?; let proofs = construct_proofs( mint_res.promises, - blinded_messages.rs, - blinded_messages.secrets, + premint_secrets.rs(), + premint_secrets.secrets(), &self.mint_keys, )?; @@ -169,8 +169,8 @@ impl Wallet { // Proof to keep let p = construct_proofs( promises.to_owned(), - split_payload.blinded_messages.rs, - split_payload.blinded_messages.secrets, + split_payload.pre_mint_secrets.rs(), + split_payload.pre_mint_secrets.secrets(), &keys, )?; proofs.push(p); @@ -183,49 +183,43 @@ impl Wallet { } /// Create Split Payload - /// TODO: This needs to sort to avoid finer printing fn create_split(&self, amount: Option, proofs: Proofs) -> Result { // Since split is used to get the needed combination of tokens for a specific // amount first blinded messages are created for the amount - let blinded_messages = if let Some(amount) = amount { - let mut desired_messages = BlindedMessages::random((&self.mint_keys).into(), amount)?; + let pre_mint_secrets = if let Some(amount) = amount { + let mut desired_messages = PreMintSecrets::random((&self.mint_keys).into(), amount)?; let change_amount = proofs.iter().map(|p| p.amount).sum::() - amount; - let change_messages = BlindedMessages::random((&self.mint_keys).into(), change_amount)?; + let change_messages = PreMintSecrets::random((&self.mint_keys).into(), 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 value = proofs.iter().map(|p| p.amount).sum(); - BlindedMessages::random((&self.mint_keys).into(), value)? + PreMintSecrets::random((&self.mint_keys).into(), value)? }; - let split_payload = SplitRequest::new(proofs, blinded_messages.blinded_messages.clone()); + let split_payload = SplitRequest::new(proofs, pre_mint_secrets.blinded_messages()); Ok(SplitPayload { - blinded_messages, + pre_mint_secrets, split_payload, }) } pub fn process_split_response( &self, - blinded_messages: BlindedMessages, + blinded_messages: PreMintSecrets, promises: Vec, ) -> Result { - let BlindedMessages { - blinded_messages: _, - secrets, - rs, - amounts: _, - } = blinded_messages; - - let secrets: Vec<_> = secrets.iter().collect(); let mut proofs = vec![]; - for (i, promise) in promises.iter().enumerate() { + for (promise, premint) in promises.iter().zip(blinded_messages) { let a = self .mint_keys .amount_key(promise.amount) @@ -234,11 +228,11 @@ impl Wallet { let blinded_c = promise.c.clone(); - let unblinded_sig = unblind_message(blinded_c, rs[i].clone().into(), a).unwrap(); + let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap(); let proof = Proof { id: promise.id, amount: promise.amount, - secret: secrets[i].clone(), + secret: premint.secret, c: unblinded_sig, }; @@ -273,8 +267,8 @@ impl Wallet { if let Some(promises) = split_response.promises { let mut proofs = construct_proofs( promises, - split_payload.blinded_messages.rs, - split_payload.blinded_messages.secrets, + split_payload.pre_mint_secrets.rs(), + split_payload.pre_mint_secrets.secrets(), &self.mint_keys, )?; @@ -316,22 +310,22 @@ impl Wallet { proofs: Proofs, fee_reserve: Amount, ) -> Result { - let blinded = BlindedMessages::blank((&self.mint_keys).into(), fee_reserve)?; + let blinded = PreMintSecrets::blank((&self.mint_keys).into(), fee_reserve)?; let melt_response = self .client .post_melt( self.mint_url.clone().try_into()?, proofs, invoice, - Some(blinded.blinded_messages), + Some(blinded.blinded_messages()), ) .await?; let change_proofs = match melt_response.change { Some(change) => Some(construct_proofs( change, - blinded.rs, - blinded.secrets, + blinded.rs(), + blinded.secrets(), &self.mint_keys, )?), None => None, diff --git a/crates/cashu/src/nuts/mod.rs b/crates/cashu/src/nuts/mod.rs index 8b15bebe..3b507f78 100644 --- a/crates/cashu/src/nuts/mod.rs +++ b/crates/cashu/src/nuts/mod.rs @@ -13,7 +13,7 @@ pub mod nut08; pub mod nut09; #[cfg(feature = "wallet")] -pub use nut00::wallet::{BlindedMessages, Token}; +pub use nut00::wallet::{PreMint, PreMintSecrets, Token}; pub use nut00::{BlindedMessage, BlindedSignature, Proof}; pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey}; pub use nut02::mint::KeySet as MintKeySet; diff --git a/crates/cashu/src/nuts/nut00.rs b/crates/cashu/src/nuts/nut00.rs index 39426b86..4327758d 100644 --- a/crates/cashu/src/nuts/nut00.rs +++ b/crates/cashu/src/nuts/nut00.rs @@ -22,6 +22,7 @@ pub struct BlindedMessage { #[cfg(feature = "wallet")] pub mod wallet { + use std::cmp::Ordering; use std::str::FromStr; use base64::engine::{general_purpose, GeneralPurpose}; @@ -37,25 +38,65 @@ pub mod wallet { use crate::url::UncheckedUrl; use crate::{error, Amount}; - /// Blinded Messages [NUT-00] - #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] - pub struct BlindedMessages { - /// Blinded messages - pub blinded_messages: Vec, - /// Secrets - pub secrets: Vec, - /// Rs - pub rs: Vec, - /// Amounts - pub amounts: Vec, + #[derive(Debug, Clone, PartialEq, Eq, Serialize)] + pub struct PreMint { + /// Blinded message + pub blinded_message: BlindedMessage, + /// Secret + pub secret: Secret, + /// R + pub r: SecretKey, + /// Amount + pub amount: Amount, } - impl BlindedMessages { + impl Ord for PreMint { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.amount.cmp(&other.amount) + } + } + + impl PartialOrd for PreMint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] + pub struct PreMintSecrets { + secrets: Vec, + } + + // Implement Iterator for PreMintSecrets + impl Iterator for PreMintSecrets { + type Item = PreMint; + + fn next(&mut self) -> Option { + // Use the iterator of the vector + self.secrets.pop() + } + } + + impl Ord for PreMintSecrets { + fn cmp(&self, other: &Self) -> Ordering { + self.secrets.cmp(&other.secrets) + } + } + + impl PartialOrd for PreMintSecrets { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl PreMintSecrets { /// Outputs for speceifed amount with random secret pub fn random(keyset_id: Id, amount: Amount) -> Result { - let mut blinded_messages = BlindedMessages::default(); + let amount_split = amount.split(); - for amount in amount.split() { + let mut output = Vec::with_capacity(amount_split.len()); + + for amount in amount_split { let secret = Secret::new(); let (blinded, r) = blind_message(secret.as_bytes(), None)?; @@ -65,19 +106,19 @@ pub mod wallet { keyset_id, }; - blinded_messages.secrets.push(secret); - blinded_messages.blinded_messages.push(blinded_message); - blinded_messages.rs.push(r.into()); - blinded_messages.amounts.push(amount); + output.push(PreMint { + secret, + blinded_message, + r: r.into(), + amount, + }); } - Ok(blinded_messages) + Ok(PreMintSecrets { secrets: output }) } /// Blank Outputs used for NUT-08 change pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result { - let mut blinded_messages = BlindedMessages::default(); - let fee_reserve = bitcoin::Amount::from_sat(fee_reserve.to_sat()); let count = (fee_reserve @@ -86,6 +127,8 @@ pub mod wallet { .ceil() as u64) .max(1); + let mut output = Vec::with_capacity(count as usize); + for _i in 0..count { let secret = Secret::new(); let (blinded, r) = blind_message(secret.as_bytes(), None)?; @@ -96,20 +139,58 @@ pub mod wallet { keyset_id, }; - blinded_messages.secrets.push(secret); - blinded_messages.blinded_messages.push(blinded_message); - blinded_messages.rs.push(r.into()); - blinded_messages.amounts.push(Amount::ZERO); + output.push(PreMint { + secret, + blinded_message, + r: r.into(), + amount: Amount::ZERO, + }) } - Ok(blinded_messages) + Ok(PreMintSecrets { secrets: output }) + } + + pub fn iter(&self) -> impl Iterator { + self.secrets.iter() + } + + pub fn len(&self) -> usize { + self.secrets.len() + } + + pub fn is_empty(&self) -> bool { + self.secrets.is_empty() + } + + pub fn total_amount(&self) -> Amount { + self.secrets + .iter() + .map(|PreMint { amount, .. }| *amount) + .sum() + } + + pub fn blinded_messages(&self) -> Vec { + self.iter().map(|pm| pm.blinded_message.clone()).collect() + } + + pub fn secrets(&self) -> Vec { + self.iter().map(|pm| pm.secret.clone()).collect() + } + + pub fn rs(&self) -> Vec { + self.iter().map(|pm| pm.r.clone()).collect() + } + + pub fn amounts(&self) -> Vec { + self.iter().map(|pm| pm.amount).collect() } pub fn combine(&mut self, mut other: Self) { - self.blinded_messages.append(&mut other.blinded_messages); - self.secrets.append(&mut other.secrets); - self.rs.append(&mut other.rs); - self.amounts.append(&mut other.amounts); + self.secrets.append(&mut other.secrets) + } + + pub fn sort_secrets(&mut self) { + self.secrets.sort(); } } @@ -335,11 +416,11 @@ mod tests { #[test] fn test_blank_blinded_messages() { // TODO: Need to update id to new type in proof - let b = BlindedMessages::blank(Id::from_str("").unwrap(), Amount::from_sat(1000)).unwrap(); - assert_eq!(b.blinded_messages.len(), 10); + let b = PreMintSecrets::blank(Id::from_str("").unwrap(), Amount::from_sat(1000)).unwrap(); + assert_eq!(b.len(), 10); // TODO: Need to update id to new type in proof - let b = BlindedMessages::blank(Id::from_str("").unwrap(), Amount::from_sat(1)).unwrap(); - assert_eq!(b.blinded_messages.len(), 1); + let b = PreMintSecrets::blank(Id::from_str("").unwrap(), Amount::from_sat(1)).unwrap(); + assert_eq!(b.len(), 1); } } diff --git a/crates/cashu/src/nuts/nut03.rs b/crates/cashu/src/nuts/nut03.rs index ae0b4186..0dd16c50 100644 --- a/crates/cashu/src/nuts/nut03.rs +++ b/crates/cashu/src/nuts/nut03.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use super::nut00::BlindedSignature; #[cfg(feature = "wallet")] -use crate::nuts::BlindedMessages; +use crate::nuts::PreMintSecrets; use crate::nuts::{BlindedMessage, Proofs}; use crate::Amount; pub use crate::Bolt11Invoice; @@ -22,7 +22,7 @@ pub struct RequestMintResponse { #[cfg(feature = "wallet")] #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct SplitPayload { - pub blinded_messages: BlindedMessages, + pub pre_mint_secrets: PreMintSecrets, pub split_payload: SplitRequest, } diff --git a/crates/cashu/src/nuts/nut06.rs b/crates/cashu/src/nuts/nut06.rs index df90d147..15b5b46e 100644 --- a/crates/cashu/src/nuts/nut06.rs +++ b/crates/cashu/src/nuts/nut06.rs @@ -4,14 +4,14 @@ use serde::{Deserialize, Serialize}; use super::nut00::BlindedSignature; #[cfg(feature = "wallet")] -use crate::nuts::BlindedMessages; +use crate::nuts::PreMintSecrets; use crate::nuts::{BlindedMessage, Proofs}; use crate::Amount; #[cfg(feature = "wallet")] #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct SplitPayload { - pub blinded_messages: BlindedMessages, + pub pre_mint_secrets: PreMintSecrets, pub split_payload: SplitRequest, }