mod.rs 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040
  1. //! Cashu Wallet
  2. use std::collections::{HashMap, HashSet};
  3. use std::str::FromStr;
  4. use bip39::Mnemonic;
  5. use cashu::dhke::{construct_proofs, unblind_message};
  6. #[cfg(feature = "nut07")]
  7. use cashu::nuts::nut07::ProofState;
  8. use cashu::nuts::nut11::SigningKey;
  9. use cashu::nuts::{
  10. BlindedSignature, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, P2PKConditions, PreMintSecrets,
  11. PreSwap, Proof, Proofs, SigFlag, SwapRequest, Token,
  12. };
  13. #[cfg(feature = "nut07")]
  14. use cashu::secret::Secret;
  15. use cashu::types::{MeltQuote, Melted, MintQuote};
  16. use cashu::url::UncheckedUrl;
  17. use cashu::{Amount, Bolt11Invoice};
  18. use localstore::LocalStore;
  19. use thiserror::Error;
  20. use tracing::warn;
  21. use crate::client::Client;
  22. use crate::utils::unix_time;
  23. pub mod localstore;
  24. #[derive(Debug, Error)]
  25. pub enum Error {
  26. /// Insufficient Funds
  27. #[error("Insufficient Funds")]
  28. InsufficientFunds,
  29. #[error("`{0}`")]
  30. Cashu(#[from] cashu::error::wallet::Error),
  31. #[error("`{0}`")]
  32. Client(#[from] crate::client::Error),
  33. /// Cashu Url Error
  34. #[error("`{0}`")]
  35. CashuUrl(#[from] cashu::url::Error),
  36. #[error("Quote Expired")]
  37. QuoteExpired,
  38. #[error("Quote Unknown")]
  39. QuoteUnknown,
  40. #[error("`{0}`")]
  41. LocalStore(#[from] localstore::Error),
  42. #[error("`{0}`")]
  43. Custom(String),
  44. }
  45. #[derive(Clone, Debug)]
  46. pub struct BackupInfo {
  47. mnemonic: Mnemonic,
  48. counter: HashMap<Id, u64>,
  49. }
  50. #[derive(Clone, Debug)]
  51. pub struct Wallet<C: Client, L: LocalStore> {
  52. pub client: C,
  53. localstore: L,
  54. backup_info: Option<BackupInfo>,
  55. }
  56. impl<C: Client, L: LocalStore> Wallet<C, L> {
  57. pub async fn new(client: C, localstore: L, backup_info: Option<BackupInfo>) -> Self {
  58. Self {
  59. backup_info,
  60. client,
  61. localstore,
  62. }
  63. }
  64. /// Back up seed
  65. pub fn mnemonic(&self) -> Option<Mnemonic> {
  66. self.backup_info.clone().map(|b| b.mnemonic)
  67. }
  68. /// Back up keyset counters
  69. pub fn keyset_counters(&self) -> Option<HashMap<Id, u64>> {
  70. self.backup_info.clone().map(|b| b.counter)
  71. }
  72. pub async fn mint_balances(&self) -> Result<HashMap<UncheckedUrl, Amount>, Error> {
  73. let mints = self.localstore.get_mints().await?;
  74. let mut balances = HashMap::new();
  75. for (mint, _) in mints {
  76. if let Some(proofs) = self.localstore.get_proofs(mint.clone()).await? {
  77. let amount = proofs.iter().map(|p| p.amount).sum();
  78. balances.insert(mint, amount);
  79. } else {
  80. balances.insert(mint, Amount::ZERO);
  81. }
  82. }
  83. Ok(balances)
  84. }
  85. pub async fn get_proofs(&self, mint_url: UncheckedUrl) -> Result<Option<Proofs>, Error> {
  86. Ok(self.localstore.get_proofs(mint_url).await?)
  87. }
  88. pub async fn add_mint(&self, mint_url: UncheckedUrl) -> Result<Option<MintInfo>, Error> {
  89. let mint_info = match self
  90. .client
  91. .get_mint_info(mint_url.clone().try_into()?)
  92. .await
  93. {
  94. Ok(mint_info) => Some(mint_info),
  95. Err(err) => {
  96. warn!("Could not get mint info {}", err);
  97. None
  98. }
  99. };
  100. self.localstore
  101. .add_mint(mint_url, mint_info.clone())
  102. .await?;
  103. Ok(mint_info)
  104. }
  105. pub async fn get_mint_keys(
  106. &self,
  107. mint_url: &UncheckedUrl,
  108. keyset_id: Id,
  109. ) -> Result<Keys, Error> {
  110. let keys = if let Some(keys) = self.localstore.get_keys(&keyset_id).await? {
  111. keys
  112. } else {
  113. let keys = self
  114. .client
  115. .get_mint_keyset(mint_url.try_into()?, keyset_id)
  116. .await?;
  117. self.localstore.add_keys(keys.keys.clone()).await?;
  118. keys.keys
  119. };
  120. Ok(keys)
  121. }
  122. /// Check if a proof is spent
  123. #[cfg(feature = "nut07")]
  124. pub async fn check_proofs_spent(
  125. &self,
  126. mint_url: UncheckedUrl,
  127. proofs: Proofs,
  128. ) -> Result<Vec<ProofState>, Error> {
  129. let spendable = self
  130. .client
  131. .post_check_state(
  132. mint_url.try_into()?,
  133. proofs
  134. .clone()
  135. .into_iter()
  136. .map(|p| p.secret)
  137. .collect::<Vec<Secret>>()
  138. .clone(),
  139. )
  140. .await?;
  141. // Separate proofs in spent and unspent based on mint response
  142. Ok(spendable.states)
  143. }
  144. /*
  145. // TODO: This should be create token
  146. // the requited proofs for the token amount may already be in the wallet and mint is not needed
  147. // Mint a token
  148. pub async fn mint_token(
  149. &mut self,
  150. mint_url: UncheckedUrl,
  151. amount: Amount,
  152. memo: Option<String>,
  153. unit: Option<CurrencyUnit>,
  154. ) -> Result<Token, Error> {
  155. let quote = self
  156. .mint_quote(
  157. mint_url.clone(),
  158. amount,
  159. unit.clone()
  160. .ok_or(Error::Custom("Unit required".to_string()))?,
  161. )
  162. .await?;
  163. let proofs = self.mint(mint_url.clone(), &quote.id).await?;
  164. let token = Token::new(mint_url.clone(), proofs, memo, unit);
  165. Ok(token?)
  166. }
  167. */
  168. /// Mint Quote
  169. pub async fn mint_quote(
  170. &mut self,
  171. mint_url: UncheckedUrl,
  172. amount: Amount,
  173. unit: CurrencyUnit,
  174. ) -> Result<MintQuote, Error> {
  175. let quote_res = self
  176. .client
  177. .post_mint_quote(mint_url.try_into()?, amount, unit.clone())
  178. .await?;
  179. let quote = MintQuote {
  180. id: quote_res.quote.clone(),
  181. amount,
  182. unit: unit.clone(),
  183. request: quote_res.request,
  184. paid: quote_res.paid,
  185. expiry: quote_res.expiry,
  186. };
  187. self.localstore.add_mint_quote(quote.clone()).await?;
  188. Ok(quote)
  189. }
  190. async fn active_mint_keyset(
  191. &mut self,
  192. mint_url: &UncheckedUrl,
  193. unit: &CurrencyUnit,
  194. ) -> Result<Option<Id>, Error> {
  195. if let Some(keysets) = self.localstore.get_mint_keysets(mint_url.clone()).await? {
  196. for keyset in keysets {
  197. if keyset.unit.eq(unit) && keyset.active {
  198. return Ok(Some(keyset.id));
  199. }
  200. }
  201. }
  202. let keysets = self.client.get_mint_keysets(mint_url.try_into()?).await?;
  203. self.localstore
  204. .add_mint_keysets(
  205. mint_url.clone(),
  206. keysets.keysets.clone().into_iter().collect(),
  207. )
  208. .await?;
  209. for keyset in &keysets.keysets {
  210. if keyset.unit.eq(unit) && keyset.active {
  211. return Ok(Some(keyset.id));
  212. }
  213. }
  214. Ok(None)
  215. }
  216. async fn active_keys(
  217. &mut self,
  218. mint_url: &UncheckedUrl,
  219. unit: &CurrencyUnit,
  220. ) -> Result<Option<Keys>, Error> {
  221. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  222. let keys;
  223. if let Some(k) = self.localstore.get_keys(&active_keyset_id).await? {
  224. keys = Some(k.clone())
  225. } else {
  226. let keyset = self
  227. .client
  228. .get_mint_keyset(mint_url.try_into()?, active_keyset_id)
  229. .await?;
  230. self.localstore.add_keys(keyset.keys.clone()).await?;
  231. keys = Some(keyset.keys);
  232. }
  233. Ok(keys)
  234. }
  235. /// Mint
  236. pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Amount, Error> {
  237. // Check that mint is in store of mints
  238. if self.localstore.get_mint(mint_url.clone()).await?.is_none() {
  239. self.add_mint(mint_url.clone()).await?;
  240. }
  241. let quote_info = self.localstore.get_mint_quote(quote_id).await?;
  242. let quote_info = if let Some(quote) = quote_info {
  243. if quote.expiry.le(&unix_time()) && quote.expiry.ne(&0) {
  244. return Err(Error::QuoteExpired);
  245. }
  246. quote.clone()
  247. } else {
  248. return Err(Error::QuoteUnknown);
  249. };
  250. let active_keyset_id = self
  251. .active_mint_keyset(&mint_url, &quote_info.unit)
  252. .await?
  253. .unwrap();
  254. let premint_secrets = match &self.backup_info {
  255. Some(backup_info) => PreMintSecrets::from_seed(
  256. active_keyset_id,
  257. *backup_info.counter.get(&active_keyset_id).unwrap_or(&0),
  258. &backup_info.mnemonic,
  259. quote_info.amount,
  260. )?,
  261. None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?,
  262. };
  263. let mint_res = self
  264. .client
  265. .post_mint(
  266. mint_url.clone().try_into()?,
  267. quote_id,
  268. premint_secrets.clone(),
  269. )
  270. .await?;
  271. let keys = self.get_mint_keys(&mint_url, active_keyset_id).await?;
  272. let proofs = construct_proofs(
  273. mint_res.signatures,
  274. premint_secrets.rs(),
  275. premint_secrets.secrets(),
  276. &keys,
  277. )?;
  278. let minted_amount = proofs.iter().map(|p| p.amount).sum();
  279. // Remove filled quote from store
  280. self.localstore.remove_mint_quote(&quote_info.id).await?;
  281. // Add new proofs to store
  282. self.localstore.add_proofs(mint_url, proofs).await?;
  283. Ok(minted_amount)
  284. }
  285. /// Receive
  286. pub async fn receive(&mut self, encoded_token: &str) -> Result<(), Error> {
  287. let token_data = Token::from_str(encoded_token)?;
  288. let unit = token_data.unit.unwrap_or_default();
  289. let mut proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
  290. for token in token_data.token {
  291. if token.proofs.is_empty() {
  292. continue;
  293. }
  294. let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
  295. // TODO: if none fetch keyset for mint
  296. let keys =
  297. if let Some(keys) = self.localstore.get_keys(&active_keyset_id.unwrap()).await? {
  298. keys
  299. } else {
  300. self.get_mint_keys(&token.mint, active_keyset_id.unwrap())
  301. .await?;
  302. self.localstore
  303. .get_keys(&active_keyset_id.unwrap())
  304. .await?
  305. .unwrap()
  306. };
  307. // Sum amount of all proofs
  308. let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  309. let pre_swap = self
  310. .create_swap(&token.mint, &unit, Some(amount), token.proofs)
  311. .await?;
  312. let swap_response = self
  313. .client
  314. .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
  315. .await?;
  316. // Proof to keep
  317. let p = construct_proofs(
  318. swap_response.signatures,
  319. pre_swap.pre_mint_secrets.rs(),
  320. pre_swap.pre_mint_secrets.secrets(),
  321. &keys,
  322. )?;
  323. let mint_proofs = proofs.entry(token.mint).or_default();
  324. mint_proofs.extend(p);
  325. }
  326. for (mint, p) in proofs {
  327. self.add_mint(mint.clone()).await?;
  328. self.localstore.add_proofs(mint, p).await?;
  329. }
  330. Ok(())
  331. }
  332. /// Create Swap Payload
  333. async fn create_swap(
  334. &mut self,
  335. mint_url: &UncheckedUrl,
  336. unit: &CurrencyUnit,
  337. amount: Option<Amount>,
  338. proofs: Proofs,
  339. ) -> Result<PreSwap, Error> {
  340. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  341. let pre_mint_secrets = if let Some(amount) = amount {
  342. let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
  343. let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
  344. let change_messages = PreMintSecrets::random(active_keyset_id, change_amount)?;
  345. // Combine the BlindedMessages totoalling the desired amount with change
  346. desired_messages.combine(change_messages);
  347. // Sort the premint secrets to avoid finger printing
  348. desired_messages.sort_secrets();
  349. desired_messages
  350. } else {
  351. let amount = proofs.iter().map(|p| p.amount).sum();
  352. PreMintSecrets::random(active_keyset_id, amount)?
  353. };
  354. let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
  355. Ok(PreSwap {
  356. pre_mint_secrets,
  357. swap_request,
  358. })
  359. }
  360. /*
  361. /// Create Swap Payload
  362. async fn create_swap_signed(
  363. &mut self,
  364. mint_url: &UncheckedUrl,
  365. unit: &CurrencyUnit,
  366. amount: Option<Amount>,
  367. proofs: Proofs,
  368. signing_key: Option<SigningKey>,
  369. ) -> Result<PreSwap, Error> {
  370. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  371. let pre_mint_secrets = if let Some(amount) = amount {
  372. let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
  373. let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
  374. let change_messages = if let Some(signing_key) = signing_key {
  375. PreMintSecrets::random_signed(active_keyset_id, change_amount, signing_key)?
  376. } else {
  377. PreMintSecrets::random(active_keyset_id, change_amount)?
  378. };
  379. // Combine the BlindedMessages totoalling the desired amount with change
  380. desired_messages.combine(change_messages);
  381. // Sort the premint secrets to avoid finger printing
  382. desired_messages.sort_secrets();
  383. desired_messages
  384. } else {
  385. let amount = proofs.iter().map(|p| p.amount).sum();
  386. if let Some(signing_key) = signing_key {
  387. PreMintSecrets::random_signed(active_keyset_id, amount, signing_key)?
  388. } else {
  389. PreMintSecrets::random(active_keyset_id, amount)?
  390. }
  391. };
  392. let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
  393. Ok(PreSwap {
  394. pre_mint_secrets,
  395. swap_request,
  396. })
  397. }
  398. */
  399. pub async fn process_swap_response(
  400. &self,
  401. blinded_messages: PreMintSecrets,
  402. promises: Vec<BlindedSignature>,
  403. ) -> Result<Proofs, Error> {
  404. let mut proofs = vec![];
  405. for (promise, premint) in promises.iter().zip(blinded_messages) {
  406. let a = self
  407. .localstore
  408. .get_keys(&promise.keyset_id)
  409. .await?
  410. .unwrap()
  411. .amount_key(promise.amount)
  412. .unwrap()
  413. .to_owned();
  414. let blinded_c = promise.c.clone();
  415. let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
  416. let proof = Proof::new(
  417. promise.amount,
  418. promise.keyset_id,
  419. premint.secret,
  420. unblinded_sig,
  421. );
  422. proofs.push(proof);
  423. }
  424. Ok(proofs)
  425. }
  426. /// Send
  427. pub async fn send(
  428. &mut self,
  429. mint_url: &UncheckedUrl,
  430. unit: &CurrencyUnit,
  431. amount: Amount,
  432. ) -> Result<Proofs, Error> {
  433. let proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
  434. let pre_swap = self
  435. .create_swap(mint_url, unit, Some(amount), proofs.clone())
  436. .await?;
  437. let swap_response = self
  438. .client
  439. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  440. .await?;
  441. let mut keep_proofs = Proofs::new();
  442. let mut send_proofs = Proofs::new();
  443. let mut post_swap_proofs = construct_proofs(
  444. swap_response.signatures,
  445. pre_swap.pre_mint_secrets.rs(),
  446. pre_swap.pre_mint_secrets.secrets(),
  447. &self.active_keys(mint_url, unit).await?.unwrap(),
  448. )?;
  449. post_swap_proofs.reverse();
  450. for proof in post_swap_proofs {
  451. if (proof.amount + send_proofs.iter().map(|p| p.amount).sum()).gt(&amount) {
  452. keep_proofs.push(proof);
  453. } else {
  454. send_proofs.push(proof);
  455. }
  456. }
  457. let send_amount: Amount = send_proofs.iter().map(|p| p.amount).sum();
  458. if send_amount.ne(&amount) {
  459. warn!(
  460. "Send amount proofs is {:?} expected {:?}",
  461. send_amount, amount
  462. );
  463. }
  464. self.localstore
  465. .remove_proofs(mint_url.clone(), &proofs)
  466. .await?;
  467. self.localstore
  468. .add_pending_proofs(mint_url.clone(), proofs)
  469. .await?;
  470. self.localstore
  471. .add_pending_proofs(mint_url.clone(), send_proofs.clone())
  472. .await?;
  473. self.localstore
  474. .add_proofs(mint_url.clone(), keep_proofs)
  475. .await?;
  476. Ok(send_proofs)
  477. }
  478. /// Melt Quote
  479. pub async fn melt_quote(
  480. &mut self,
  481. mint_url: UncheckedUrl,
  482. unit: CurrencyUnit,
  483. request: String,
  484. ) -> Result<MeltQuote, Error> {
  485. let quote_res = self
  486. .client
  487. .post_melt_quote(
  488. mint_url.clone().try_into()?,
  489. unit.clone(),
  490. Bolt11Invoice::from_str(&request.clone()).unwrap(),
  491. )
  492. .await?;
  493. let quote = MeltQuote {
  494. id: quote_res.quote,
  495. amount: quote_res.amount.into(),
  496. request,
  497. unit,
  498. fee_reserve: quote_res.fee_reserve.into(),
  499. paid: quote_res.paid,
  500. expiry: quote_res.expiry,
  501. };
  502. self.localstore.add_melt_quote(quote.clone()).await?;
  503. Ok(quote)
  504. }
  505. // Select proofs
  506. pub async fn select_proofs(
  507. &self,
  508. mint_url: UncheckedUrl,
  509. unit: &CurrencyUnit,
  510. amount: Amount,
  511. ) -> Result<Proofs, Error> {
  512. let mint_proofs = self
  513. .localstore
  514. .get_proofs(mint_url.clone())
  515. .await?
  516. .ok_or(Error::InsufficientFunds)?;
  517. let mint_keysets = self.localstore.get_mint_keysets(mint_url).await?.unwrap();
  518. let (active, inactive): (HashSet<KeySetInfo>, HashSet<KeySetInfo>) = mint_keysets
  519. .into_iter()
  520. .filter(|p| p.unit.eq(unit))
  521. .partition(|x| x.active);
  522. let active: HashSet<Id> = active.iter().map(|k| k.id).collect();
  523. let inactive: HashSet<Id> = inactive.iter().map(|k| k.id).collect();
  524. let mut active_proofs: Proofs = Vec::new();
  525. let mut inactive_proofs: Proofs = Vec::new();
  526. for proof in mint_proofs {
  527. if active.contains(&proof.keyset_id) {
  528. active_proofs.push(proof);
  529. } else if inactive.contains(&proof.keyset_id) {
  530. inactive_proofs.push(proof);
  531. }
  532. }
  533. active_proofs.reverse();
  534. inactive_proofs.reverse();
  535. inactive_proofs.append(&mut active_proofs);
  536. let proofs = inactive_proofs;
  537. let mut selected_proofs: Proofs = Vec::new();
  538. for proof in proofs {
  539. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  540. selected_proofs.push(proof);
  541. }
  542. }
  543. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  544. return Err(Error::InsufficientFunds);
  545. }
  546. Ok(selected_proofs)
  547. }
  548. /// Melt
  549. pub async fn melt(&mut self, mint_url: &UncheckedUrl, quote_id: &str) -> Result<Melted, Error> {
  550. let quote_info = self.localstore.get_melt_quote(quote_id).await?;
  551. let quote_info = if let Some(quote) = quote_info {
  552. if quote.expiry.le(&unix_time()) {
  553. return Err(Error::QuoteExpired);
  554. }
  555. quote.clone()
  556. } else {
  557. return Err(Error::QuoteUnknown);
  558. };
  559. let proofs = self
  560. .select_proofs(mint_url.clone(), &quote_info.unit, quote_info.amount)
  561. .await?;
  562. let proofs_amount = proofs.iter().map(|p| p.amount).sum();
  563. let blinded = PreMintSecrets::blank(
  564. self.active_mint_keyset(mint_url, &quote_info.unit)
  565. .await?
  566. .unwrap(),
  567. proofs_amount,
  568. )?;
  569. let melt_response = self
  570. .client
  571. .post_melt(
  572. mint_url.clone().try_into()?,
  573. quote_id.to_string(),
  574. proofs.clone(),
  575. Some(blinded.blinded_messages()),
  576. )
  577. .await?;
  578. let change_proofs = match melt_response.change {
  579. Some(change) => Some(construct_proofs(
  580. change,
  581. blinded.rs(),
  582. blinded.secrets(),
  583. &self.active_keys(mint_url, &quote_info.unit).await?.unwrap(),
  584. )?),
  585. None => None,
  586. };
  587. let melted = Melted {
  588. paid: true,
  589. preimage: melt_response.payment_preimage,
  590. change: change_proofs.clone(),
  591. };
  592. if let Some(change_proofs) = change_proofs {
  593. self.localstore
  594. .add_proofs(mint_url.clone(), change_proofs)
  595. .await?;
  596. }
  597. self.localstore.remove_melt_quote(&quote_info.id).await?;
  598. self.localstore
  599. .remove_proofs(mint_url.clone(), &proofs)
  600. .await?;
  601. Ok(melted)
  602. }
  603. /// Create P2PK locked proofs
  604. /// Uses a swap to swap proofs for locked p2pk conditions
  605. pub async fn send_p2pk(
  606. &mut self,
  607. mint_url: &UncheckedUrl,
  608. unit: &CurrencyUnit,
  609. amount: Amount,
  610. conditions: P2PKConditions,
  611. ) -> Result<Proofs, Error> {
  612. let input_proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
  613. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  614. let input_amount: Amount = input_proofs.iter().map(|p| p.amount).sum();
  615. let change_amount = input_amount - amount;
  616. let send_premint_secrets =
  617. PreMintSecrets::with_p2pk_conditions(active_keyset_id, amount, conditions)?;
  618. let change_premint_secrets = PreMintSecrets::random(active_keyset_id, change_amount)?;
  619. let mut pre_mint_secrets = send_premint_secrets;
  620. pre_mint_secrets.combine(change_premint_secrets);
  621. let swap_request =
  622. SwapRequest::new(input_proofs.clone(), pre_mint_secrets.blinded_messages());
  623. let pre_swap = PreSwap {
  624. pre_mint_secrets,
  625. swap_request,
  626. };
  627. let swap_response = self
  628. .client
  629. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  630. .await?;
  631. let post_swap_proofs = construct_proofs(
  632. swap_response.signatures,
  633. pre_swap.pre_mint_secrets.rs(),
  634. pre_swap.pre_mint_secrets.secrets(),
  635. &self.active_keys(mint_url, unit).await?.unwrap(),
  636. )?;
  637. let mut send_proofs = vec![];
  638. let mut change_proofs = vec![];
  639. for proof in post_swap_proofs {
  640. let conditions: Result<cashu::nuts::nut10::Secret, _> = (&proof.secret).try_into();
  641. if conditions.is_ok() {
  642. send_proofs.push(proof);
  643. } else {
  644. change_proofs.push(proof);
  645. }
  646. }
  647. self.localstore
  648. .remove_proofs(mint_url.clone(), &input_proofs)
  649. .await?;
  650. self.localstore
  651. .add_pending_proofs(mint_url.clone(), input_proofs)
  652. .await?;
  653. self.localstore
  654. .add_pending_proofs(mint_url.clone(), send_proofs.clone())
  655. .await?;
  656. self.localstore
  657. .add_proofs(mint_url.clone(), change_proofs.clone())
  658. .await?;
  659. Ok(send_proofs)
  660. }
  661. /// Receive p2pk
  662. pub async fn receive_p2pk(
  663. &mut self,
  664. encoded_token: &str,
  665. signing_keys: Vec<SigningKey>,
  666. ) -> Result<(), Error> {
  667. let signing_key = signing_keys[0].clone();
  668. let pubkey_secret_key: HashMap<String, SigningKey> = signing_keys
  669. .into_iter()
  670. .map(|s| (s.public_key().to_string(), s))
  671. .collect();
  672. let token_data = Token::from_str(encoded_token)?;
  673. let unit = token_data.unit.unwrap_or_default();
  674. let mut received_proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
  675. for token in token_data.token {
  676. if token.proofs.is_empty() {
  677. continue;
  678. }
  679. let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
  680. // TODO: if none fetch keyset for mint
  681. let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
  682. // Sum amount of all proofs
  683. let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  684. let mut proofs = token.proofs;
  685. let mut sig_flag = None;
  686. for proof in &mut proofs {
  687. if let Ok(secret) =
  688. <cashu::secret::Secret as TryInto<cashu::nuts::nut10::Secret>>::try_into(
  689. proof.secret.clone(),
  690. )
  691. {
  692. let conditions: Result<P2PKConditions, _> = secret.try_into();
  693. if let Ok(conditions) = conditions {
  694. let pubkeys = conditions.pubkeys;
  695. for pubkey in pubkeys {
  696. if let Some(signing) = pubkey_secret_key.get(&pubkey.to_string()) {
  697. proof.sign_p2pk_proof(signing.clone()).unwrap();
  698. proof.verify_p2pk().unwrap();
  699. }
  700. }
  701. sig_flag = Some(conditions.sig_flag);
  702. }
  703. }
  704. }
  705. let mut pre_swap = self
  706. .create_swap(&token.mint, &unit, Some(amount), proofs)
  707. .await?;
  708. if let Some(sigflag) = sig_flag {
  709. if sigflag.eq(&SigFlag::SigAll) {
  710. for blinded_message in &mut pre_swap.swap_request.outputs {
  711. blinded_message
  712. .sign_p2pk_blinded_message(signing_key.clone())
  713. .unwrap();
  714. }
  715. }
  716. }
  717. let swap_response = self
  718. .client
  719. .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
  720. .await?;
  721. // Proof to keep
  722. let p = construct_proofs(
  723. swap_response.signatures,
  724. pre_swap.pre_mint_secrets.rs(),
  725. pre_swap.pre_mint_secrets.secrets(),
  726. &keys.unwrap(),
  727. )?;
  728. let mint_proofs = received_proofs.entry(token.mint).or_default();
  729. mint_proofs.extend(p);
  730. }
  731. for (mint, proofs) in received_proofs {
  732. self.localstore.add_proofs(mint, proofs).await?;
  733. }
  734. Ok(())
  735. }
  736. /*
  737. pub async fn claim_p2pk_locked_proofs(
  738. &mut self,
  739. sigflag: SigFlag,
  740. mint_url: &UncheckedUrl,
  741. unit: &CurrencyUnit,
  742. signing_key: SigningKey,
  743. proofs: Proofs,
  744. ) -> Result<(), Error> {
  745. let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?;
  746. let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
  747. let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len());
  748. // Sum amount of all proofs
  749. let amount: Amount = proofs.iter().map(|p| p.amount).sum();
  750. for p in proofs.clone() {
  751. let mut p = p;
  752. p.sign_p2pk_proof(signing_key.clone()).unwrap();
  753. signed_proofs.push(p);
  754. }
  755. let pre_swap = match sigflag {
  756. SigFlag::SigInputs => {
  757. self.create_swap(mint_url, &unit, Some(amount), signed_proofs)
  758. .await?
  759. }
  760. SigFlag::SigAll => {
  761. self.create_swap_signed(
  762. mint_url,
  763. unit,
  764. Some(amount),
  765. signed_proofs,
  766. Some(signing_key),
  767. )
  768. .await?
  769. }
  770. _ => todo!(),
  771. };
  772. let swap_response = self
  773. .client
  774. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  775. .await?;
  776. // Proof to keep
  777. let p = construct_proofs(
  778. swap_response.signatures,
  779. pre_swap.pre_mint_secrets.rs(),
  780. pre_swap.pre_mint_secrets.secrets(),
  781. &keys.unwrap(),
  782. )?;
  783. self.localstore
  784. .remove_proofs(mint_url.clone(), &proofs)
  785. .await?;
  786. self.localstore.add_proofs(mint_url.clone(), p).await?;
  787. Ok(())
  788. }
  789. */
  790. pub fn proofs_to_token(
  791. &self,
  792. mint_url: UncheckedUrl,
  793. proofs: Proofs,
  794. memo: Option<String>,
  795. unit: Option<CurrencyUnit>,
  796. ) -> Result<String, Error> {
  797. Ok(Token::new(mint_url, proofs, memo, unit)?.to_string())
  798. }
  799. }
  800. /*
  801. #[cfg(test)]
  802. mod tests {
  803. use std::collections::{HashMap, HashSet};
  804. use super::*;
  805. use crate::client::Client;
  806. use crate::mint::Mint;
  807. use cashu::nuts::nut04;
  808. #[test]
  809. fn test_wallet() {
  810. let mut mint = Mint::new(
  811. "supersecretsecret",
  812. "0/0/0/0",
  813. HashMap::new(),
  814. HashSet::new(),
  815. 32,
  816. );
  817. let keys = mint.active_keyset_pubkeys();
  818. let client = Client::new("https://cashu-rs.thesimplekid.space/").unwrap();
  819. let wallet = Wallet::new(client, keys.keys);
  820. let blinded_messages = BlindedMessages::random(Amount::from_sat(64)).unwrap();
  821. let mint_request = nut04::MintRequest {
  822. outputs: blinded_messages.blinded_messages.clone(),
  823. };
  824. let res = mint.process_mint_request(mint_request).unwrap();
  825. let proofs = wallet
  826. .process_split_response(blinded_messages, res.promises)
  827. .unwrap();
  828. for proof in &proofs {
  829. mint.verify_proof(proof).unwrap();
  830. }
  831. let split = wallet.create_split(proofs.clone()).unwrap();
  832. let split_request = split.split_payload;
  833. let split_response = mint.process_split_request(split_request).unwrap();
  834. let p = split_response.promises;
  835. let snd_proofs = wallet
  836. .process_split_response(split.blinded_messages, p.unwrap())
  837. .unwrap();
  838. let mut error = false;
  839. for proof in &snd_proofs {
  840. if let Err(err) = mint.verify_proof(proof) {
  841. println!("{err}{:?}", serde_json::to_string(proof));
  842. error = true;
  843. }
  844. }
  845. if error {
  846. panic!()
  847. }
  848. }
  849. }
  850. */