1use 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#[derive(Debug, Error)]
16pub enum Error {
17 #[error(transparent)]
19 DHKE(#[from] crate::dhke::Error),
20 #[error(transparent)]
22 Amount(#[from] crate::amount::Error),
23}
24
25#[cfg(feature = "wallet")]
27#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
28pub struct PreSwap {
29 pub pre_mint_secrets: PreMintSecrets,
31 pub swap_request: SwapRequest,
33 pub derived_secret_count: u32,
35 pub fee: Amount,
37 pub p2bk_secret_keys: Option<Vec<crate::nuts::nut01::SecretKey>>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct SwapRequest {
44 inputs: Proofs,
46 outputs: Vec<BlindedMessage>,
48}
49
50impl SwapRequest {
51 pub fn new(inputs: Proofs, outputs: Vec<BlindedMessage>) -> Self {
53 Self {
54 inputs: inputs.without_dleqs(),
55 outputs,
56 }
57 }
58
59 pub fn inputs(&self) -> &Proofs {
61 &self.inputs
62 }
63
64 pub fn inputs_mut(&mut self) -> &mut Proofs {
66 &mut self.inputs
67 }
68
69 pub fn outputs(&self) -> &Vec<BlindedMessage> {
71 &self.outputs
72 }
73
74 pub fn outputs_mut(&mut self) -> &mut Vec<BlindedMessage> {
76 &mut self.outputs
77 }
78
79 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 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 for proof in &self.inputs {
105 msg.push_str(&proof.secret.to_string());
106 msg.push_str(&proof.c.to_hex());
107 }
108
109 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct SwapResponse {
123 pub signatures: Vec<BlindSignature>,
125}
126
127impl SwapResponse {
128 pub fn new(promises: Vec<BlindSignature>) -> Self {
130 Self {
131 signatures: promises,
132 }
133 }
134
135 pub fn promises_amount(&self) -> Result<Amount, Error> {
137 Ok(Amount::try_sum(
138 self.signatures
139 .iter()
140 .map(|BlindSignature { amount, .. }| *amount),
141 )?)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 const SWAP_REQUEST_JSON: &str = r#"{
150 "inputs": [
151 {
152 "amount": 2,
153 "id": "00bfa73302d12ffd",
154 "secret": "[\"P2PK\",{\"nonce\":\"c7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950303\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
155 "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd",
156 "witness": "{\"signatures\":[\"ce017ca25b1b97df2f72e4b49f69ac26a240ce14b3690a8fe619d41ccc42d3c1282e073f85acd36dc50011638906f35b56615f24e4d03e8effe8257f6a808538\"]}"
157 },
158 {
159 "amount": 4,
160 "id": "00bfa73302d12ffd",
161 "secret": "[\"P2PK\",{\"nonce\":\"d7f280eb55c1e8564e03db06973e94bc9b666d9e1ca42ad278408fe625950304\",\"data\":\"030d8acedfe072c9fa449a1efe0817157403fbec460d8e79f957966056e5dd76c1\",\"tags\":[[\"sigflag\",\"SIG_ALL\"]]}]",
162 "C": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd"
163 }
164 ],
165 "outputs": [
166 {
167 "amount": 2,
168 "id": "00bfa73302d12ffd",
169 "B_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
170 }
171 ]
172 }"#;
173
174 #[test]
175 fn test_swap_request_inputs_outputs_getters() {
176 let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
179
180 let inputs = req.inputs();
181 assert_eq!(inputs.len(), 2, "expected 2 inputs");
182 assert_eq!(u64::from(inputs[0].amount), 2);
183 assert_eq!(u64::from(inputs[1].amount), 4);
184
185 let outputs = req.outputs();
186 assert_eq!(outputs.len(), 1, "expected 1 output");
187 assert_eq!(u64::from(outputs[0].amount), 2);
188 }
189
190 #[test]
191 fn test_swap_request_inputs_outputs_getters_via_new() {
192 let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
195 let inputs_clone = req.inputs().clone();
196 let outputs_clone = req.outputs().clone();
197
198 let rebuilt = SwapRequest::new(inputs_clone, outputs_clone);
199 assert_eq!(rebuilt.inputs().len(), 2);
200 assert_eq!(rebuilt.outputs().len(), 1);
201 assert!(!rebuilt.inputs().is_empty());
202 assert!(!rebuilt.outputs().is_empty());
203 }
204
205 #[test]
206 fn test_swap_request_outputs_mut_updates_outputs() {
207 let mut req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
208 let output = req.outputs()[0].clone();
209
210 req.outputs_mut().push(output);
211
212 assert_eq!(req.outputs().len(), 2);
213 }
214
215 #[test]
216 fn test_swap_request_amounts() {
217 let req: SwapRequest = serde_json::from_str(SWAP_REQUEST_JSON).unwrap();
218
219 assert_eq!(req.input_amount().unwrap(), Amount::from(6));
220 assert_eq!(req.output_amount().unwrap(), Amount::from(2));
221 }
222
223 #[test]
224 fn test_swap_response_promises_amount() {
225 let response: SwapResponse = serde_json::from_str(
226 r#"{
227 "signatures": [
228 {
229 "amount": 8,
230 "id": "00bfa73302d12ffd",
231 "C_": "02c97ee3d1db41cf0a3ddb601724be8711a032950811bf326f8219c50c4808d3cd"
232 },
233 {
234 "amount": 4,
235 "id": "00bfa73302d12ffd",
236 "C_": "038ec853d65ae1b79b5cdbc2774150b2cb288d6d26e12958a16fb33c32d9a86c39"
237 }
238 ]
239 }"#,
240 )
241 .unwrap();
242
243 assert_eq!(response.promises_amount().unwrap(), Amount::from(12));
244 }
245}