wallet.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. //! Cashu Wallet
  2. use std::str::FromStr;
  3. use crate::dhke::unblind_message;
  4. use crate::nuts::nut00::{mint, BlindedMessages, BlindedSignature, Proof, Proofs, Token};
  5. use crate::nuts::nut01::Keys;
  6. use crate::nuts::nut03::RequestMintResponse;
  7. use crate::nuts::nut06::{SplitPayload, SplitRequest};
  8. use crate::types::{Melted, ProofsStatus, SendProofs};
  9. pub use crate::Invoice;
  10. use crate::{client::Client, dhke::construct_proofs, error::Error};
  11. use crate::amount::Amount;
  12. #[derive(Clone, Debug)]
  13. pub struct Wallet {
  14. pub client: Client,
  15. pub mint_keys: Keys,
  16. pub balance: Amount,
  17. }
  18. impl Wallet {
  19. pub fn new(client: Client, mint_keys: Keys) -> Self {
  20. Self {
  21. client,
  22. mint_keys,
  23. balance: Amount::ZERO,
  24. }
  25. }
  26. // TODO: getter method for keys that if it cant get them try again
  27. /// Check if a proof is spent
  28. pub async fn check_proofs_spent(&self, proofs: &mint::Proofs) -> Result<ProofsStatus, Error> {
  29. let spendable = self.client.check_spendable(proofs).await?;
  30. // Separate proofs in spent and unspent based on mint response
  31. let (spendable, spent): (Vec<_>, Vec<_>) = proofs
  32. .iter()
  33. .zip(spendable.spendable.iter())
  34. .partition(|(_, &b)| b);
  35. Ok(ProofsStatus {
  36. spendable: spendable.into_iter().map(|(s, _)| s).cloned().collect(),
  37. spent: spent.into_iter().map(|(s, _)| s).cloned().collect(),
  38. })
  39. }
  40. /// Request Token Mint
  41. pub async fn request_mint(&self, amount: Amount) -> Result<RequestMintResponse, Error> {
  42. Ok(self.client.request_mint(amount).await?)
  43. }
  44. /// Mint Token
  45. pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result<Token, Error> {
  46. let proofs = self.mint(amount, hash).await?;
  47. let token = Token::new(self.client.mint_url.clone(), proofs, None);
  48. Ok(token)
  49. }
  50. /// Mint Proofs
  51. pub async fn mint(&self, amount: Amount, hash: &str) -> Result<Proofs, Error> {
  52. let blinded_messages = BlindedMessages::random(amount)?;
  53. let mint_res = self.client.mint(blinded_messages.clone(), hash).await?;
  54. let proofs = construct_proofs(
  55. mint_res.promises,
  56. blinded_messages.rs,
  57. blinded_messages.secrets,
  58. &self.mint_keys,
  59. )?;
  60. Ok(proofs)
  61. }
  62. /// Check fee
  63. pub async fn check_fee(&self, invoice: Invoice) -> Result<Amount, Error> {
  64. Ok(self.client.check_fees(invoice).await?.fee)
  65. }
  66. /// Receive
  67. pub async fn receive(&self, encoded_token: &str) -> Result<Proofs, Error> {
  68. let token_data = Token::from_str(encoded_token)?;
  69. let mut proofs: Vec<Proofs> = vec![vec![]];
  70. for token in token_data.token {
  71. if token.proofs.is_empty() {
  72. continue;
  73. }
  74. let keys = if token.mint.to_string().eq(&self.client.mint_url.to_string()) {
  75. self.mint_keys.clone()
  76. } else {
  77. Client::new(token.mint.as_str())?.get_keys().await?
  78. };
  79. // Sum amount of all proofs
  80. let amount = token
  81. .proofs
  82. .iter()
  83. .fold(Amount::ZERO, |acc, p| acc + p.amount);
  84. let split_payload = self.create_split(Amount::ZERO, amount, token.proofs)?;
  85. let split_response = self.client.split(split_payload.split_payload).await?;
  86. if let Some(promises) = &split_response.promises {
  87. // Proof to keep
  88. let p = construct_proofs(
  89. split_response.promises.unwrap(),
  90. split_payload.keep_blinded_messages.rs,
  91. split_payload.keep_blinded_messages.secrets,
  92. &keys,
  93. )?;
  94. proofs.push(p);
  95. } else {
  96. // Proof to keep
  97. let keep_proofs = construct_proofs(
  98. split_response.fst.unwrap(),
  99. split_payload.keep_blinded_messages.rs,
  100. split_payload.keep_blinded_messages.secrets,
  101. &keys,
  102. )?;
  103. // Proofs to send
  104. let send_proofs = construct_proofs(
  105. split_response.snd.unwrap(),
  106. split_payload.send_blinded_messages.rs,
  107. split_payload.send_blinded_messages.secrets,
  108. &keys,
  109. )?;
  110. proofs.push(send_proofs);
  111. proofs.push(keep_proofs);
  112. }
  113. }
  114. Ok(proofs.iter().flatten().cloned().collect())
  115. }
  116. /// Create Split Payload
  117. fn create_split(
  118. &self,
  119. keep_amount: Amount,
  120. send_amount: Amount,
  121. proofs: Proofs,
  122. ) -> Result<SplitPayload, Error> {
  123. let keep_blinded_messages = BlindedMessages::random(keep_amount)?;
  124. let send_blinded_messages = BlindedMessages::random(send_amount)?;
  125. let outputs = {
  126. let mut outputs = keep_blinded_messages.blinded_messages.clone();
  127. outputs.extend(send_blinded_messages.blinded_messages.clone());
  128. outputs
  129. };
  130. let split_payload = SplitRequest {
  131. amount: Some(send_amount),
  132. proofs,
  133. outputs,
  134. };
  135. Ok(SplitPayload {
  136. keep_blinded_messages,
  137. send_blinded_messages,
  138. split_payload,
  139. })
  140. }
  141. pub fn process_split_response(
  142. &self,
  143. blinded_messages: BlindedMessages,
  144. promises: Vec<BlindedSignature>,
  145. ) -> Result<Proofs, Error> {
  146. let BlindedMessages {
  147. blinded_messages: _,
  148. secrets,
  149. rs,
  150. amounts: _,
  151. } = blinded_messages;
  152. let secrets: Vec<_> = secrets.iter().collect();
  153. let mut proofs = vec![];
  154. for (i, promise) in promises.iter().enumerate() {
  155. let a = self
  156. .mint_keys
  157. .amount_key(promise.amount)
  158. .unwrap()
  159. .to_owned();
  160. let blinded_c = promise.c.clone();
  161. let unblinded_sig = unblind_message(blinded_c, rs[i].clone().into(), a).unwrap();
  162. let proof = Proof {
  163. id: Some(promise.id.clone()),
  164. amount: promise.amount,
  165. secret: secrets[i].clone(),
  166. c: unblinded_sig,
  167. script: None,
  168. };
  169. proofs.push(proof);
  170. }
  171. Ok(proofs)
  172. }
  173. /// Send
  174. pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result<SendProofs, Error> {
  175. let mut amount_available = Amount::ZERO;
  176. let mut send_proofs = SendProofs::default();
  177. for proof in proofs {
  178. let proof_value = proof.amount;
  179. if amount_available > amount {
  180. send_proofs.change_proofs.push(proof);
  181. } else {
  182. send_proofs.send_proofs.push(proof);
  183. }
  184. amount_available += proof_value;
  185. }
  186. if amount_available.lt(&amount) {
  187. println!("Not enough funds");
  188. return Err(Error::InsufficantFunds);
  189. }
  190. // If amount available is EQUAL to send amount no need to split
  191. if amount_available.eq(&amount) {
  192. return Ok(send_proofs);
  193. }
  194. let amount_to_keep = amount_available - amount;
  195. let amount_to_send = amount;
  196. // TODO: Will need to change https://github.com/cashubtc/cashu/pull/263/files
  197. let split_payload =
  198. self.create_split(amount_to_keep, amount_to_send, send_proofs.send_proofs)?;
  199. let split_response = self.client.split(split_payload.split_payload).await?;
  200. // If only prmises assemble proofs needed for amount
  201. let keep_proofs;
  202. let send_proofs;
  203. if let Some(promises) = split_response.promises {
  204. let proofs = construct_proofs(
  205. promises,
  206. split_payload.keep_blinded_messages.rs,
  207. split_payload.keep_blinded_messages.secrets,
  208. &self.mint_keys,
  209. )?;
  210. let split = amount_to_send.split();
  211. keep_proofs = proofs[0..split.len()].to_vec();
  212. send_proofs = proofs[split.len()..].to_vec();
  213. } else if let (Some(fst), Some(snd)) = (split_response.fst, split_response.snd) {
  214. // Proof to keep
  215. keep_proofs = construct_proofs(
  216. fst,
  217. split_payload.keep_blinded_messages.rs,
  218. split_payload.keep_blinded_messages.secrets,
  219. &self.mint_keys,
  220. )?;
  221. // Proofs to send
  222. send_proofs = construct_proofs(
  223. snd,
  224. split_payload.send_blinded_messages.rs,
  225. split_payload.send_blinded_messages.secrets,
  226. &self.mint_keys,
  227. )?;
  228. } else {
  229. return Err(Error::CustomError("Invalid split response".to_string()));
  230. }
  231. // println!("Send Proofs: {:#?}", send_proofs);
  232. // println!("Keep Proofs: {:#?}", keep_proofs);
  233. Ok(SendProofs {
  234. change_proofs: keep_proofs,
  235. send_proofs,
  236. })
  237. }
  238. pub async fn melt(
  239. &self,
  240. invoice: Invoice,
  241. proofs: Proofs,
  242. fee_reserve: Amount,
  243. ) -> Result<Melted, Error> {
  244. let blinded = BlindedMessages::blank(fee_reserve)?;
  245. let melt_response = self
  246. .client
  247. .melt(proofs, invoice, Some(blinded.blinded_messages))
  248. .await?;
  249. let change_proofs = match melt_response.change {
  250. Some(change) => Some(construct_proofs(
  251. change,
  252. blinded.rs,
  253. blinded.secrets,
  254. &self.mint_keys,
  255. )?),
  256. None => None,
  257. };
  258. let melted = Melted {
  259. paid: true,
  260. preimage: melt_response.preimage,
  261. change: change_proofs,
  262. };
  263. Ok(melted)
  264. }
  265. pub fn proofs_to_token(&self, proofs: Proofs, memo: Option<String>) -> Result<String, Error> {
  266. Token::new(self.client.mint_url.clone(), proofs, memo).convert_to_string()
  267. }
  268. }
  269. #[cfg(test)]
  270. mod tests {
  271. use std::collections::{HashMap, HashSet};
  272. use super::*;
  273. use crate::client::Client;
  274. use crate::mint::Mint;
  275. use crate::nuts::nut04;
  276. #[test]
  277. fn test_wallet() {
  278. let mut mint = Mint::new(
  279. "supersecretsecret",
  280. "0/0/0/0",
  281. HashMap::new(),
  282. HashSet::new(),
  283. 32,
  284. );
  285. let keys = mint.active_keyset_pubkeys();
  286. let client = Client::new("https://cashu-rs.thesimplekid.space/").unwrap();
  287. let wallet = Wallet::new(client, keys.keys);
  288. let blinded_messages = BlindedMessages::random(Amount::from_sat(64)).unwrap();
  289. let mint_request = nut04::MintRequest {
  290. outputs: blinded_messages.blinded_messages.clone(),
  291. };
  292. let res = mint.process_mint_request(mint_request).unwrap();
  293. let proofs = wallet
  294. .process_split_response(blinded_messages, res.promises)
  295. .unwrap();
  296. for proof in &proofs {
  297. mint.verify_proof(proof).unwrap();
  298. }
  299. let split = wallet
  300. .create_split(Amount::from_sat(24), Amount::from_sat(40), proofs.clone())
  301. .unwrap();
  302. let split_request = split.split_payload;
  303. let split_response = mint.process_split_request(split_request).unwrap();
  304. let p = split_response.snd;
  305. let snd_proofs = wallet
  306. .process_split_response(split.send_blinded_messages, p.unwrap())
  307. .unwrap();
  308. let mut error = false;
  309. for proof in &snd_proofs {
  310. if let Err(err) = mint.verify_proof(proof) {
  311. println!("{err}{:?}", serde_json::to_string(proof));
  312. error = true;
  313. }
  314. }
  315. if error {
  316. panic!()
  317. }
  318. }
  319. }