diff --git a/bindings/cashu-ffi/src/lib.rs b/bindings/cashu-ffi/src/lib.rs index a84bec1c9..0eaa09faa 100644 --- a/bindings/cashu-ffi/src/lib.rs +++ b/bindings/cashu-ffi/src/lib.rs @@ -18,13 +18,12 @@ mod ffi { pub use crate::nuts::nut01::public_key::PublicKey; pub use crate::nuts::nut01::secret_key::SecretKey; pub use crate::nuts::nut02::{Id, KeySet, KeySetResponse, MintKeySet}; - pub use crate::nuts::nut03::RequestMintResponse; + pub use crate::nuts::nut03::{RequestMintResponse, SplitRequest, SplitResponse}; pub use crate::nuts::nut04::{MintRequest, PostMintResponse}; pub use crate::nuts::nut05::{ CheckFeesRequest, CheckFeesResponse, MeltRequest as Nut05MeltRequest, MeltResponse as Nut05MeltResponse, }; - pub use crate::nuts::nut06::{SplitRequest, SplitResponse}; pub use crate::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; pub use crate::nuts::nut08::{MeltRequest, MeltResponse}; pub use crate::nuts::nut09::{MintInfo, MintVersion}; diff --git a/bindings/cashu-ffi/src/nuts/mod.rs b/bindings/cashu-ffi/src/nuts/mod.rs index 72608a547..aa0a442e8 100644 --- a/bindings/cashu-ffi/src/nuts/mod.rs +++ b/bindings/cashu-ffi/src/nuts/mod.rs @@ -4,7 +4,6 @@ pub mod nut02; pub mod nut03; pub mod nut04; pub mod nut05; -pub mod nut06; pub mod nut07; pub mod nut08; pub mod nut09; diff --git a/bindings/cashu-ffi/src/nuts/nut03/mod.rs b/bindings/cashu-ffi/src/nuts/nut03/mod.rs index 6197f0fa5..cfbebdbc0 100644 --- a/bindings/cashu-ffi/src/nuts/nut03/mod.rs +++ b/bindings/cashu-ffi/src/nuts/nut03/mod.rs @@ -1,9 +1,13 @@ +use std::ops::Deref; use std::str::FromStr; +use std::sync::Arc; use cashu::nuts::nut03::RequestMintResponse as RequestMintResponseSdk; +use cashu::nuts::{SplitRequest as SplitRequestSdk, SplitResponse as SplitResponseSdk}; use cashu::Bolt11Invoice; use crate::error::Result; +use crate::{Amount, BlindedMessage, BlindedSignature, Proof}; pub struct RequestMintResponse { inner: RequestMintResponseSdk, @@ -34,3 +38,84 @@ impl From for RequestMintResponse { } } } + +pub struct SplitRequest { + inner: SplitRequestSdk, +} + +impl Deref for SplitRequest { + type Target = SplitRequestSdk; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl SplitRequest { + pub fn new(proofs: Vec>, outputs: Vec>) -> Self { + let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect(); + let outputs = outputs.into_iter().map(|o| o.as_ref().into()).collect(); + + Self { + inner: SplitRequestSdk::new(proofs, outputs), + } + } + + pub fn proofs(&self) -> Vec> { + self.inner + .inputs + .clone() + .into_iter() + .map(|p| Arc::new(p.into())) + .collect() + } + + pub fn outputs(&self) -> Vec> { + self.inner + .outputs + .clone() + .into_iter() + .map(|o| Arc::new(o.into())) + .collect() + } + + pub fn proofs_amount(&self) -> Arc { + Arc::new(self.inner.input_amount().into()) + } + + pub fn output_amount(&self) -> Arc { + Arc::new(self.inner.output_amount().into()) + } +} + +pub struct SplitResponse { + inner: SplitResponseSdk, +} + +impl SplitResponse { + pub fn new(promises: Vec>) -> Self { + let promises = promises.into_iter().map(|p| p.as_ref().into()).collect(); + Self { + inner: SplitResponseSdk::new(promises), + } + } + + pub fn promises(&self) -> Vec> { + self.inner + .promises + .clone() + .unwrap_or_default() + .into_iter() + .map(|p| Arc::new(p.into())) + .collect() + } + + pub fn promises_amount(&self) -> Option> { + self.inner.promises_amount().map(|a| Arc::new(a.into())) + } +} + +impl From for SplitResponse { + fn from(inner: cashu::nuts::SplitResponse) -> SplitResponse { + SplitResponse { inner } + } +} diff --git a/bindings/cashu-ffi/src/nuts/nut06/mod.rs b/bindings/cashu-ffi/src/nuts/nut06/mod.rs deleted file mode 100644 index de5f824ef..000000000 --- a/bindings/cashu-ffi/src/nuts/nut06/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::ops::Deref; -use std::sync::Arc; - -use cashu::nuts::nut06::{SplitRequest as SplitRequestSdk, SplitResponse as SplitResponseSdk}; - -use crate::{Amount, BlindedMessage, BlindedSignature, Proof}; - -pub struct SplitRequest { - inner: SplitRequestSdk, -} - -impl Deref for SplitRequest { - type Target = SplitRequestSdk; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl SplitRequest { - pub fn new(proofs: Vec>, outputs: Vec>) -> Self { - let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect(); - let outputs = outputs.into_iter().map(|o| o.as_ref().into()).collect(); - - Self { - inner: SplitRequestSdk::new(proofs, outputs), - } - } - - pub fn proofs(&self) -> Vec> { - self.inner - .proofs - .clone() - .into_iter() - .map(|p| Arc::new(p.into())) - .collect() - } - - pub fn outputs(&self) -> Vec> { - self.inner - .outputs - .clone() - .into_iter() - .map(|o| Arc::new(o.into())) - .collect() - } - - pub fn proofs_amount(&self) -> Arc { - Arc::new(self.inner.proofs_amount().into()) - } - - pub fn output_amount(&self) -> Arc { - Arc::new(self.inner.output_amount().into()) - } -} - -pub struct SplitResponse { - inner: SplitResponseSdk, -} - -impl SplitResponse { - pub fn new(promises: Vec>) -> Self { - let promises = promises.into_iter().map(|p| p.as_ref().into()).collect(); - Self { - inner: SplitResponseSdk::new(promises), - } - } - - pub fn promises(&self) -> Vec> { - self.inner - .promises - .clone() - .unwrap_or_default() - .into_iter() - .map(|p| Arc::new(p.into())) - .collect() - } - - pub fn promises_amount(&self) -> Option> { - self.inner.promises_amount().map(|a| Arc::new(a.into())) - } -} - -impl From for SplitResponse { - fn from(inner: cashu::nuts::nut06::SplitResponse) -> SplitResponse { - SplitResponse { inner } - } -} diff --git a/bindings/cashu-js/src/nuts/mod.rs b/bindings/cashu-js/src/nuts/mod.rs index ea16e8e3a..790c5a6ca 100644 --- a/bindings/cashu-js/src/nuts/mod.rs +++ b/bindings/cashu-js/src/nuts/mod.rs @@ -4,9 +4,17 @@ pub mod nut02; pub mod nut03; pub mod nut04; pub mod nut05; -pub mod nut06; #[cfg(feature = "nut07")] pub mod nut07; pub mod nut08; #[cfg(feature = "nut09")] pub mod nut09; + +pub use nut00::{JsBlindedMessage, JsBlindedMessages, JsBlindedSignature, JsProof, JsToken}; +pub use nut01::{JsKeyPair, JsKeys, JsPublicKey, JsSecretKey}; +pub use nut02::{JsId, JsKeySet, JsKeySetsResponse, JsKeysResponse, JsMintKeySet}; +pub use nut03::{JsRequestMintResponse, JsSplitRequest, JsSplitResponse}; +pub use nut04::{JsMintRequest, JsPostMintResponse}; +#[cfg(feature = "nut07")] +pub use nut07::{JsCheckSpendableRequest, JsCheckSpendableResponse}; +pub use nut08::{JsMeltRequest, JsMeltResponse}; diff --git a/bindings/cashu-js/src/nuts/nut03.rs b/bindings/cashu-js/src/nuts/nut03.rs index 6474e89c2..f536a6513 100644 --- a/bindings/cashu-js/src/nuts/nut03.rs +++ b/bindings/cashu-js/src/nuts/nut03.rs @@ -1,9 +1,10 @@ use std::ops::Deref; -use cashu::nuts::nut03::RequestMintResponse; +use cashu::nuts::{RequestMintResponse, SplitRequest, SplitResponse}; use wasm_bindgen::prelude::*; -use crate::types::JsBolt11Invoice; +use crate::error::{into_err, Result}; +use crate::types::{JsAmount, JsBolt11Invoice}; #[wasm_bindgen(js_name = RequestMintResponse)] pub struct JsRequestMintResponse { @@ -47,3 +48,102 @@ impl JsRequestMintResponse { self.inner.hash.to_string() } } + +#[wasm_bindgen(js_name = SplitRequest)] +pub struct JsSplitRequest { + inner: SplitRequest, +} + +impl Deref for JsSplitRequest { + type Target = SplitRequest; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl From for JsSplitRequest { + fn from(inner: SplitRequest) -> JsSplitRequest { + JsSplitRequest { inner } + } +} + +#[wasm_bindgen(js_class = SplitRequest)] +impl JsSplitRequest { + #[wasm_bindgen(constructor)] + pub fn new(inputs: JsValue, outputs: JsValue) -> Result { + let inputs = serde_wasm_bindgen::from_value(inputs).map_err(into_err)?; + let outputs = serde_wasm_bindgen::from_value(outputs).map_err(into_err)?; + + Ok(JsSplitRequest { + inner: SplitRequest { inputs, outputs }, + }) + } + + /// Get Proofs + #[wasm_bindgen(getter)] + pub fn proofs(&self) -> Result { + serde_wasm_bindgen::to_value(&self.inner.inputs).map_err(into_err) + } + + /// Get Outputs + #[wasm_bindgen(getter)] + pub fn outputs(&self) -> Result { + serde_wasm_bindgen::to_value(&self.inner.outputs).map_err(into_err) + } + + /// Proofs Amount + #[wasm_bindgen(js_name = proofsAmount)] + pub fn proofs_amount(&self) -> JsAmount { + self.inner.input_amount().into() + } + + /// Output Amount + #[wasm_bindgen(js_name = outputAmount)] + pub fn output_amount(&self) -> JsAmount { + self.inner.output_amount().into() + } +} + +#[wasm_bindgen(js_name = SplitResponse)] +pub struct JsSplitResponse { + inner: SplitResponse, +} + +impl Deref for JsSplitResponse { + type Target = SplitResponse; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl From for JsSplitResponse { + fn from(inner: SplitResponse) -> JsSplitResponse { + JsSplitResponse { inner } + } +} + +#[wasm_bindgen(js_class = SplitResponse)] +impl JsSplitResponse { + #[wasm_bindgen(constructor)] + pub fn new(promises: JsValue) -> Result { + let promises = serde_wasm_bindgen::from_value(promises).map_err(into_err)?; + + Ok(JsSplitResponse { + inner: SplitResponse { + promises: Some(promises), + }, + }) + } + + /// Get Promises + #[wasm_bindgen(getter)] + pub fn promises(&self) -> Result { + serde_wasm_bindgen::to_value(&self.inner.promises).map_err(into_err) + } + + /// Promises Amount + #[wasm_bindgen(js_name = promisesAmount)] + pub fn promises_amount(&self) -> Option { + self.inner.promises_amount().map(|a| a.into()) + } +} diff --git a/bindings/cashu-js/src/nuts/nut06.rs b/bindings/cashu-js/src/nuts/nut06.rs index 55dd9901f..728d9a0bd 100644 --- a/bindings/cashu-js/src/nuts/nut06.rs +++ b/bindings/cashu-js/src/nuts/nut06.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use cashu::nuts::nut06::{SplitRequest, SplitResponse}; +use cashu::nuts::{SplitRequest, SplitResponse}; use wasm_bindgen::prelude::*; use crate::error::{into_err, Result}; @@ -27,23 +27,19 @@ impl From for JsSplitRequest { #[wasm_bindgen(js_class = SplitRequest)] impl JsSplitRequest { #[wasm_bindgen(constructor)] - pub fn new(proofs: JsValue, outputs: JsValue) -> Result { - let proofs = serde_wasm_bindgen::from_value(proofs).map_err(into_err)?; + pub fn new(inputs: JsValue, outputs: JsValue) -> Result { + let inputs = serde_wasm_bindgen::from_value(inputs).map_err(into_err)?; let outputs = serde_wasm_bindgen::from_value(outputs).map_err(into_err)?; Ok(JsSplitRequest { - inner: SplitRequest { - amount: None, - proofs, - outputs, - }, + inner: SplitRequest { inputs, outputs }, }) } /// Get Proofs #[wasm_bindgen(getter)] pub fn proofs(&self) -> Result { - serde_wasm_bindgen::to_value(&self.inner.proofs).map_err(into_err) + serde_wasm_bindgen::to_value(&self.inner.inputs).map_err(into_err) } /// Get Outputs @@ -55,7 +51,7 @@ impl JsSplitRequest { /// Proofs Amount #[wasm_bindgen(js_name = proofsAmount)] pub fn proofs_amount(&self) -> JsAmount { - self.inner.proofs_amount().into() + self.inner.input_amount().into() } /// Output Amount @@ -91,8 +87,6 @@ impl JsSplitResponse { Ok(JsSplitResponse { inner: SplitResponse { - fst: None, - snd: None, promises: Some(promises), }, }) diff --git a/bindings/cashu-sdk-js/src/mint.rs b/bindings/cashu-sdk-js/src/mint.rs index 1f36cdb9e..0cbce5af1 100644 --- a/bindings/cashu-sdk-js/src/mint.rs +++ b/bindings/cashu-sdk-js/src/mint.rs @@ -1,11 +1,11 @@ use std::ops::Deref; -use cashu_js::nuts::nut02::{JsId, JsKeySet, JsKeySetsResponse, JsKeysResponse, JsMintKeySet}; -use cashu_js::nuts::nut04::{JsMintRequest, JsPostMintResponse}; -use cashu_js::nuts::nut06::{JsSplitRequest, JsSplitResponse}; #[cfg(feature = "nut07")] -use cashu_js::nuts::nut07::{JsCheckSpendableRequest, JsCheckSpendableResponse}; -use cashu_js::nuts::nut08::{JsMeltRequest, JsMeltResponse}; +use cashu_js::nuts::{JsCheckSpendableRequest, JsCheckSpendableResponse}; +use cashu_js::nuts::{ + JsId, JsKeySet, JsKeySetsResponse, JsKeysResponse, JsMeltRequest, JsMeltResponse, JsMintKeySet, + JsMintRequest, JsPostMintResponse, JsSplitRequest, JsSplitResponse, +}; use cashu_js::JsAmount; use cashu_sdk::mint::Mint; use cashu_sdk::nuts::{KeySet, KeysResponse}; diff --git a/crates/cashu-sdk/src/client/gloo_client.rs b/crates/cashu-sdk/src/client/gloo_client.rs index 0e313a6b1..b0a11341c 100644 --- a/crates/cashu-sdk/src/client/gloo_client.rs +++ b/crates/cashu-sdk/src/client/gloo_client.rs @@ -1,19 +1,15 @@ //! gloo wasm http Client use async_trait::async_trait; -use cashu::nuts::nut00::wallet::BlindedMessages; -use cashu::nuts::nut00::{BlindedMessage, Proof}; -use cashu::nuts::nut01::Keys; -use cashu::nuts::nut03::RequestMintResponse; -use cashu::nuts::nut04::{MintRequest, PostMintResponse}; -use cashu::nuts::nut05::{CheckFeesRequest, CheckFeesResponse}; -use cashu::nuts::nut06::{SplitRequest, SplitResponse}; -#[cfg(feature = "nut07")] -use cashu::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; -use cashu::nuts::nut08::{MeltRequest, MeltResponse}; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; -use cashu::nuts::*; +use cashu::nuts::{ + BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, + MeltResponse, MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, + SplitResponse, *, +}; +#[cfg(feature = "nut07")] +use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse}; use cashu::{Amount, Bolt11Invoice}; use gloo::net::http::Request; use serde_json::Value; diff --git a/crates/cashu-sdk/src/client/minreq_client.rs b/crates/cashu-sdk/src/client/minreq_client.rs index d01c589d5..fbfa939ff 100644 --- a/crates/cashu-sdk/src/client/minreq_client.rs +++ b/crates/cashu-sdk/src/client/minreq_client.rs @@ -1,19 +1,15 @@ //! Minreq http Client use async_trait::async_trait; -use cashu::nuts::nut00::wallet::BlindedMessages; -use cashu::nuts::nut00::{BlindedMessage, Proof}; -use cashu::nuts::nut01::Keys; -use cashu::nuts::nut03::RequestMintResponse; -use cashu::nuts::nut04::{MintRequest, PostMintResponse}; -use cashu::nuts::nut05::{CheckFeesRequest, CheckFeesResponse}; -use cashu::nuts::nut06::{SplitRequest, SplitResponse}; -#[cfg(feature = "nut07")] -use cashu::nuts::nut07::{CheckSpendableRequest, CheckSpendableResponse}; -use cashu::nuts::nut08::{MeltRequest, MeltResponse}; #[cfg(feature = "nut09")] use cashu::nuts::MintInfo; -use cashu::nuts::*; +use cashu::nuts::{ + BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse, Keys, MeltRequest, + MeltResponse, MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, + SplitResponse, *, +}; +#[cfg(feature = "nut07")] +use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse}; use cashu::{Amount, Bolt11Invoice}; use serde_json::Value; use url::Url; diff --git a/crates/cashu-sdk/src/mint.rs b/crates/cashu-sdk/src/mint.rs index 1b731a919..55bcd7a87 100644 --- a/crates/cashu-sdk/src/mint.rs +++ b/crates/cashu-sdk/src/mint.rs @@ -147,7 +147,7 @@ impl Mint { &mut self, split_request: SplitRequest, ) -> Result { - let proofs_total = split_request.proofs_amount(); + let proofs_total = split_request.input_amount(); let output_total = split_request.output_amount(); @@ -155,10 +155,10 @@ impl Mint { return Err(Error::Amount); } - let proof_count = split_request.proofs.len(); + let proof_count = split_request.inputs.len(); let secrets: HashSet = split_request - .proofs + .inputs .iter() .map(|p| p.secret.clone()) .collect(); @@ -168,7 +168,7 @@ impl Mint { return Err(Error::DuplicateProofs); } - for proof in &split_request.proofs { + for proof in &split_request.inputs { self.verify_proof(proof)? } @@ -176,36 +176,13 @@ impl Mint { self.spent_secrets.insert(secret); } - match &split_request.amount { - None => { - let promises: Vec = split_request - .outputs - .iter() - .map(|b| self.blind_sign(b).unwrap()) - .collect(); - - Ok(SplitResponse::new(promises)) - } - Some(amount) => { - let outs_fst = (proofs_total.to_owned() - amount.to_owned()).split(); - - // Blinded change messages - let b_fst = split_request.outputs[0..outs_fst.len()].to_vec(); - let b_snd = split_request.outputs[outs_fst.len()..].to_vec(); - let fst: Vec = - b_fst.iter().map(|b| self.blind_sign(b).unwrap()).collect(); - let snd: Vec = - b_snd.iter().map(|b| self.blind_sign(b).unwrap()).collect(); - - let split_response = SplitResponse::new_from_amount(fst, snd); - - if split_response.target_amount() != split_request.amount { - return Err(Error::CustomError("Output order".to_string())); - } + let promises: Vec = split_request + .outputs + .iter() + .map(|b| self.blind_sign(b).unwrap()) + .collect(); - Ok(split_response) - } - } + Ok(SplitResponse::new(promises)) } fn verify_proof(&self, proof: &Proof) -> Result<(), Error> { diff --git a/crates/cashu/src/nuts/mod.rs b/crates/cashu/src/nuts/mod.rs index 7273f380b..84490e8ed 100644 --- a/crates/cashu/src/nuts/mod.rs +++ b/crates/cashu/src/nuts/mod.rs @@ -18,14 +18,12 @@ pub use nut00::{BlindedMessage, BlindedSignature, Proof}; pub use nut01::{Keys, KeysResponse, PublicKey, SecretKey}; pub use nut02::mint::KeySet as MintKeySet; pub use nut02::{Id, KeySet, KeySetInfo, KeysetResponse}; -pub use nut03::RequestMintResponse; +pub use nut03::{RequestMintResponse, SplitPayload, SplitRequest, SplitResponse}; pub use nut04::{MintRequest, PostMintResponse}; pub use nut05::{CheckFeesRequest, CheckFeesResponse}; #[cfg(not(feature = "nut08"))] pub use nut05::{MeltRequest, MeltResponse}; #[cfg(feature = "wallet")] -pub use nut06::SplitPayload; -pub use nut06::{SplitRequest, SplitResponse}; #[cfg(feature = "nut07")] pub use nut07::{CheckSpendableRequest, CheckSpendableResponse}; #[cfg(feature = "nut08")] diff --git a/crates/cashu/src/nuts/nut03.rs b/crates/cashu/src/nuts/nut03.rs index cc6b9722c..ae0b41860 100644 --- a/crates/cashu/src/nuts/nut03.rs +++ b/crates/cashu/src/nuts/nut03.rs @@ -3,6 +3,11 @@ use serde::{Deserialize, Serialize}; +use super::nut00::BlindedSignature; +#[cfg(feature = "wallet")] +use crate::nuts::BlindedMessages; +use crate::nuts::{BlindedMessage, Proofs}; +use crate::Amount; pub use crate::Bolt11Invoice; /// Mint request response [NUT-03] @@ -13,3 +18,59 @@ pub struct RequestMintResponse { /// Random hash MUST not be the hash of invoice pub hash: String, } + +#[cfg(feature = "wallet")] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct SplitPayload { + pub blinded_messages: BlindedMessages, + pub split_payload: SplitRequest, +} + +/// Split Request [NUT-06] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SplitRequest { + /// Proofs that are to be spent in `Split` + pub inputs: Proofs, + /// Blinded Messages for Mint to sign + pub outputs: Vec, +} + +impl SplitRequest { + pub fn new(inputs: Proofs, outputs: Vec) -> Self { + Self { inputs, outputs } + } + + /// Total value of proofs in `SplitRequest` + pub fn input_amount(&self) -> Amount { + self.inputs.iter().map(|proof| proof.amount).sum() + } + + /// Total value of outputs in `SplitRequest` + pub fn output_amount(&self) -> Amount { + self.outputs.iter().map(|proof| proof.amount).sum() + } +} + +/// Split Response [NUT-06] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SplitResponse { + /// Promises + pub promises: Option>, +} + +impl SplitResponse { + pub fn new(promises: Vec) -> SplitResponse { + SplitResponse { + promises: Some(promises), + } + } + + pub fn promises_amount(&self) -> Option { + self.promises.as_ref().map(|promises| { + promises + .iter() + .map(|BlindedSignature { amount, .. }| *amount) + .sum() + }) + } +}