Skip to main content

cashu/nuts/
nut03.rs

1//! NUT-03: Swap
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/03.md>
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8#[cfg(feature = "wallet")]
9use super::nut00::PreMintSecrets;
10use super::nut00::{BlindSignature, BlindedMessage, Proofs};
11use super::ProofsMethods;
12use crate::Amount;
13
14/// NUT03 Error
15#[derive(Debug, Error)]
16pub enum Error {
17    /// DHKE error
18    #[error(transparent)]
19    DHKE(#[from] crate::dhke::Error),
20    /// Amount Error
21    #[error(transparent)]
22    Amount(#[from] crate::amount::Error),
23}
24
25/// Preswap information
26#[cfg(feature = "wallet")]
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28pub struct PreSwap {
29    /// Preswap mint secrets
30    pub pre_mint_secrets: PreMintSecrets,
31    /// Swap request
32    pub swap_request: SwapRequest,
33    /// Amount to increment keyset counter by
34    pub derived_secret_count: u32,
35    /// Fee amount
36    pub fee: Amount,
37}
38
39/// Swap Request [NUT-03]
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
42pub struct SwapRequest {
43    /// Proofs that are to be spent in a `Swap`
44    #[cfg_attr(feature = "swagger", schema(value_type = Vec<crate::Proof>))]
45    inputs: Proofs,
46    /// Blinded Messages for Mint to sign
47    outputs: Vec<BlindedMessage>,
48}
49
50impl SwapRequest {
51    /// Create new [`SwapRequest`]
52    pub fn new(inputs: Proofs, outputs: Vec<BlindedMessage>) -> Self {
53        Self {
54            inputs: inputs.without_dleqs(),
55            outputs,
56        }
57    }
58
59    /// Get inputs (proofs)
60    pub fn inputs(&self) -> &Proofs {
61        &self.inputs
62    }
63
64    /// Get mutable inputs (proofs)
65    pub fn inputs_mut(&mut self) -> &mut Proofs {
66        &mut self.inputs
67    }
68
69    /// Get outputs (blinded messages)
70    pub fn outputs(&self) -> &Vec<BlindedMessage> {
71        &self.outputs
72    }
73
74    /// Get mutable reference to outputs (blinded messages)
75    pub fn outputs_mut(&mut self) -> &mut Vec<BlindedMessage> {
76        &mut self.outputs
77    }
78
79    /// Total value of proofs in [`SwapRequest`]
80    pub fn input_amount(&self) -> Result<Amount, Error> {
81        Ok(Amount::try_sum(
82            self.inputs.iter().map(|proof| proof.amount),
83        )?)
84    }
85
86    /// Total value of outputs in [`SwapRequest`]
87    pub fn output_amount(&self) -> Result<Amount, Error> {
88        Ok(Amount::try_sum(
89            self.outputs.iter().map(|proof| proof.amount),
90        )?)
91    }
92}
93
94impl super::nut10::SpendingConditionVerification for SwapRequest {
95    fn inputs(&self) -> &Proofs {
96        &self.inputs
97    }
98
99    fn sig_all_msg_to_sign(&self) -> String {
100        let mut msg = String::new();
101
102        // Add all input secrets and C values in order
103        // msg = secret_0 || C_0 || ... || secret_n || C_n
104        for proof in &self.inputs {
105            msg.push_str(&proof.secret.to_string());
106            msg.push_str(&proof.c.to_hex());
107        }
108
109        // Add all output amounts and B_ values in order
110        // msg = ... || amount_0 || B_0 || ... || amount_m || B_m
111        for output in &self.outputs {
112            msg.push_str(&output.amount.to_string());
113            msg.push_str(&output.blinded_secret.to_hex());
114        }
115
116        msg
117    }
118}
119
120/// Split Response [NUT-06]
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
123pub struct SwapResponse {
124    /// Promises
125    pub signatures: Vec<BlindSignature>,
126}
127
128impl SwapResponse {
129    /// Create new [`SwapResponse`]
130    pub fn new(promises: Vec<BlindSignature>) -> Self {
131        Self {
132            signatures: promises,
133        }
134    }
135
136    /// Total [`Amount`] of promises
137    pub fn promises_amount(&self) -> Result<Amount, Error> {
138        Ok(Amount::try_sum(
139            self.signatures
140                .iter()
141                .map(|BlindSignature { amount, .. }| *amount),
142        )?)
143    }
144}