mod.rs 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  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. println!("{:?}", keysets);
  204. self.localstore
  205. .add_mint_keysets(
  206. mint_url.clone(),
  207. keysets.keysets.clone().into_iter().collect(),
  208. )
  209. .await?;
  210. for keyset in &keysets.keysets {
  211. if keyset.unit.eq(unit) && keyset.active {
  212. return Ok(Some(keyset.id));
  213. }
  214. }
  215. Ok(None)
  216. }
  217. async fn active_keys(
  218. &mut self,
  219. mint_url: &UncheckedUrl,
  220. unit: &CurrencyUnit,
  221. ) -> Result<Option<Keys>, Error> {
  222. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  223. let keys;
  224. if let Some(k) = self.localstore.get_keys(&active_keyset_id).await? {
  225. keys = Some(k.clone())
  226. } else {
  227. let keyset = self
  228. .client
  229. .get_mint_keyset(mint_url.try_into()?, active_keyset_id)
  230. .await?;
  231. self.localstore.add_keys(keyset.keys.clone()).await?;
  232. keys = Some(keyset.keys);
  233. }
  234. Ok(keys)
  235. }
  236. /// Mint
  237. pub async fn mint(&mut self, mint_url: UncheckedUrl, quote_id: &str) -> Result<Amount, Error> {
  238. // Check that mint is in store of mints
  239. if self.localstore.get_mint(mint_url.clone()).await?.is_none() {
  240. self.add_mint(mint_url.clone()).await?;
  241. }
  242. let quote_info = self.localstore.get_mint_quote(quote_id).await?;
  243. let quote_info = if let Some(quote) = quote_info {
  244. if quote.expiry.le(&unix_time()) && quote.expiry.ne(&0) {
  245. return Err(Error::QuoteExpired);
  246. }
  247. quote.clone()
  248. } else {
  249. return Err(Error::QuoteUnknown);
  250. };
  251. let active_keyset_id = self
  252. .active_mint_keyset(&mint_url, &quote_info.unit)
  253. .await?
  254. .unwrap();
  255. let premint_secrets = match &self.backup_info {
  256. Some(backup_info) => PreMintSecrets::from_seed(
  257. active_keyset_id,
  258. *backup_info.counter.get(&active_keyset_id).unwrap_or(&0),
  259. &backup_info.mnemonic,
  260. quote_info.amount,
  261. )?,
  262. None => PreMintSecrets::random(active_keyset_id, quote_info.amount)?,
  263. };
  264. let mint_res = self
  265. .client
  266. .post_mint(
  267. mint_url.clone().try_into()?,
  268. quote_id,
  269. premint_secrets.clone(),
  270. )
  271. .await?;
  272. let keys = self.get_mint_keys(&mint_url, active_keyset_id).await?;
  273. let proofs = construct_proofs(
  274. mint_res.signatures,
  275. premint_secrets.rs(),
  276. premint_secrets.secrets(),
  277. &keys,
  278. )?;
  279. let minted_amount = proofs.iter().map(|p| p.amount).sum();
  280. // Remove filled quote from store
  281. self.localstore.remove_mint_quote(&quote_info.id).await?;
  282. // Add new proofs to store
  283. self.localstore.add_proofs(mint_url, proofs).await?;
  284. Ok(minted_amount)
  285. }
  286. /// Receive
  287. pub async fn receive(&mut self, encoded_token: &str) -> Result<(), Error> {
  288. let token_data = Token::from_str(encoded_token)?;
  289. let unit = token_data.unit.unwrap_or_default();
  290. let mut proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
  291. for token in token_data.token {
  292. if token.proofs.is_empty() {
  293. continue;
  294. }
  295. let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
  296. // TODO: if none fetch keyset for mint
  297. let keys =
  298. if let Some(keys) = self.localstore.get_keys(&active_keyset_id.unwrap()).await? {
  299. keys
  300. } else {
  301. self.get_mint_keys(&token.mint, active_keyset_id.unwrap())
  302. .await?;
  303. self.localstore
  304. .get_keys(&active_keyset_id.unwrap())
  305. .await?
  306. .unwrap()
  307. };
  308. // Sum amount of all proofs
  309. let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  310. let pre_swap = self
  311. .create_swap(&token.mint, &unit, Some(amount), token.proofs)
  312. .await?;
  313. let swap_response = self
  314. .client
  315. .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
  316. .await?;
  317. // Proof to keep
  318. let p = construct_proofs(
  319. swap_response.signatures,
  320. pre_swap.pre_mint_secrets.rs(),
  321. pre_swap.pre_mint_secrets.secrets(),
  322. &keys,
  323. )?;
  324. // println!("{:?}", p);
  325. let mint_proofs = proofs.entry(token.mint).or_default();
  326. mint_proofs.extend(p);
  327. }
  328. //println!("{:?}", proofs);
  329. for (mint, p) in proofs {
  330. println!("{:?}", serde_json::to_string(&p));
  331. println!("{:?}", mint);
  332. self.add_mint(mint.clone()).await?;
  333. self.localstore.add_proofs(mint, p).await?;
  334. }
  335. Ok(())
  336. }
  337. /// Create Swap Payload
  338. async fn create_swap(
  339. &mut self,
  340. mint_url: &UncheckedUrl,
  341. unit: &CurrencyUnit,
  342. amount: Option<Amount>,
  343. proofs: Proofs,
  344. ) -> Result<PreSwap, Error> {
  345. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  346. let pre_mint_secrets = if let Some(amount) = amount {
  347. let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
  348. let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
  349. let change_messages = PreMintSecrets::random(active_keyset_id, change_amount)?;
  350. // Combine the BlindedMessages totoalling the desired amount with change
  351. desired_messages.combine(change_messages);
  352. // Sort the premint secrets to avoid finger printing
  353. desired_messages.sort_secrets();
  354. desired_messages
  355. } else {
  356. let amount = proofs.iter().map(|p| p.amount).sum();
  357. PreMintSecrets::random(active_keyset_id, amount)?
  358. };
  359. let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
  360. Ok(PreSwap {
  361. pre_mint_secrets,
  362. swap_request,
  363. })
  364. }
  365. /*
  366. /// Create Swap Payload
  367. async fn create_swap_signed(
  368. &mut self,
  369. mint_url: &UncheckedUrl,
  370. unit: &CurrencyUnit,
  371. amount: Option<Amount>,
  372. proofs: Proofs,
  373. signing_key: Option<SigningKey>,
  374. ) -> Result<PreSwap, Error> {
  375. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  376. let pre_mint_secrets = if let Some(amount) = amount {
  377. let mut desired_messages = PreMintSecrets::random(active_keyset_id, amount)?;
  378. let change_amount = proofs.iter().map(|p| p.amount).sum::<Amount>() - amount;
  379. let change_messages = if let Some(signing_key) = signing_key {
  380. PreMintSecrets::random_signed(active_keyset_id, change_amount, signing_key)?
  381. } else {
  382. PreMintSecrets::random(active_keyset_id, change_amount)?
  383. };
  384. // Combine the BlindedMessages totoalling the desired amount with change
  385. desired_messages.combine(change_messages);
  386. // Sort the premint secrets to avoid finger printing
  387. desired_messages.sort_secrets();
  388. desired_messages
  389. } else {
  390. let amount = proofs.iter().map(|p| p.amount).sum();
  391. if let Some(signing_key) = signing_key {
  392. PreMintSecrets::random_signed(active_keyset_id, amount, signing_key)?
  393. } else {
  394. PreMintSecrets::random(active_keyset_id, amount)?
  395. }
  396. };
  397. let swap_request = SwapRequest::new(proofs, pre_mint_secrets.blinded_messages());
  398. Ok(PreSwap {
  399. pre_mint_secrets,
  400. swap_request,
  401. })
  402. }
  403. */
  404. pub async fn process_swap_response(
  405. &self,
  406. blinded_messages: PreMintSecrets,
  407. promises: Vec<BlindedSignature>,
  408. ) -> Result<Proofs, Error> {
  409. let mut proofs = vec![];
  410. for (promise, premint) in promises.iter().zip(blinded_messages) {
  411. let a = self
  412. .localstore
  413. .get_keys(&promise.keyset_id)
  414. .await?
  415. .unwrap()
  416. .amount_key(promise.amount)
  417. .unwrap()
  418. .to_owned();
  419. let blinded_c = promise.c.clone();
  420. let unblinded_sig = unblind_message(blinded_c, premint.r.into(), a).unwrap();
  421. let proof = Proof::new(
  422. promise.amount,
  423. promise.keyset_id,
  424. premint.secret,
  425. unblinded_sig,
  426. );
  427. proofs.push(proof);
  428. }
  429. Ok(proofs)
  430. }
  431. /// Send
  432. pub async fn send(
  433. &mut self,
  434. mint_url: &UncheckedUrl,
  435. unit: &CurrencyUnit,
  436. amount: Amount,
  437. ) -> Result<Proofs, Error> {
  438. let proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
  439. let pre_swap = self
  440. .create_swap(mint_url, unit, Some(amount), proofs.clone())
  441. .await?;
  442. let swap_response = self
  443. .client
  444. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  445. .await?;
  446. let mut keep_proofs = Proofs::new();
  447. let mut send_proofs = Proofs::new();
  448. let mut post_swap_proofs = construct_proofs(
  449. swap_response.signatures,
  450. pre_swap.pre_mint_secrets.rs(),
  451. pre_swap.pre_mint_secrets.secrets(),
  452. &self.active_keys(mint_url, unit).await?.unwrap(),
  453. )?;
  454. post_swap_proofs.reverse();
  455. for proof in post_swap_proofs {
  456. if (proof.amount + send_proofs.iter().map(|p| p.amount).sum()).gt(&amount) {
  457. keep_proofs.push(proof);
  458. } else {
  459. send_proofs.push(proof);
  460. }
  461. }
  462. let send_amount: Amount = send_proofs.iter().map(|p| p.amount).sum();
  463. if send_amount.ne(&amount) {
  464. warn!(
  465. "Send amount proofs is {:?} expected {:?}",
  466. send_amount, amount
  467. );
  468. }
  469. self.localstore
  470. .remove_proofs(mint_url.clone(), &proofs)
  471. .await?;
  472. self.localstore
  473. .add_pending_proofs(mint_url.clone(), proofs)
  474. .await?;
  475. self.localstore
  476. .add_pending_proofs(mint_url.clone(), send_proofs.clone())
  477. .await?;
  478. self.localstore
  479. .add_proofs(mint_url.clone(), keep_proofs)
  480. .await?;
  481. Ok(send_proofs)
  482. }
  483. /// Melt Quote
  484. pub async fn melt_quote(
  485. &mut self,
  486. mint_url: UncheckedUrl,
  487. unit: CurrencyUnit,
  488. request: String,
  489. ) -> Result<MeltQuote, Error> {
  490. let quote_res = self
  491. .client
  492. .post_melt_quote(
  493. mint_url.clone().try_into()?,
  494. unit.clone(),
  495. Bolt11Invoice::from_str(&request.clone()).unwrap(),
  496. )
  497. .await?;
  498. let quote = MeltQuote {
  499. id: quote_res.quote,
  500. amount: quote_res.amount.into(),
  501. request,
  502. unit,
  503. fee_reserve: quote_res.fee_reserve.into(),
  504. paid: quote_res.paid,
  505. expiry: quote_res.expiry,
  506. };
  507. self.localstore.add_melt_quote(quote.clone()).await?;
  508. Ok(quote)
  509. }
  510. // Select proofs
  511. pub async fn select_proofs(
  512. &self,
  513. mint_url: UncheckedUrl,
  514. unit: &CurrencyUnit,
  515. amount: Amount,
  516. ) -> Result<Proofs, Error> {
  517. let mint_proofs = self
  518. .localstore
  519. .get_proofs(mint_url.clone())
  520. .await?
  521. .ok_or(Error::InsufficientFunds)?;
  522. let mint_keysets = self.localstore.get_mint_keysets(mint_url).await?.unwrap();
  523. let (active, inactive): (HashSet<KeySetInfo>, HashSet<KeySetInfo>) = mint_keysets
  524. .into_iter()
  525. .filter(|p| p.unit.eq(unit))
  526. .partition(|x| x.active);
  527. let active: HashSet<Id> = active.iter().map(|k| k.id).collect();
  528. let inactive: HashSet<Id> = inactive.iter().map(|k| k.id).collect();
  529. let mut active_proofs: Proofs = Vec::new();
  530. let mut inactive_proofs: Proofs = Vec::new();
  531. for proof in mint_proofs {
  532. if active.contains(&proof.keyset_id) {
  533. active_proofs.push(proof);
  534. } else if inactive.contains(&proof.keyset_id) {
  535. inactive_proofs.push(proof);
  536. }
  537. }
  538. active_proofs.reverse();
  539. inactive_proofs.reverse();
  540. inactive_proofs.append(&mut active_proofs);
  541. let proofs = inactive_proofs;
  542. let mut selected_proofs: Proofs = Vec::new();
  543. for proof in proofs {
  544. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  545. selected_proofs.push(proof);
  546. }
  547. }
  548. if selected_proofs.iter().map(|p| p.amount).sum::<Amount>() < amount {
  549. return Err(Error::InsufficientFunds);
  550. }
  551. Ok(selected_proofs)
  552. }
  553. /// Melt
  554. pub async fn melt(&mut self, mint_url: &UncheckedUrl, quote_id: &str) -> Result<Melted, Error> {
  555. let quote_info = self.localstore.get_melt_quote(quote_id).await?;
  556. let quote_info = if let Some(quote) = quote_info {
  557. if quote.expiry.le(&unix_time()) {
  558. return Err(Error::QuoteExpired);
  559. }
  560. quote.clone()
  561. } else {
  562. return Err(Error::QuoteUnknown);
  563. };
  564. let proofs = self
  565. .select_proofs(mint_url.clone(), &quote_info.unit, quote_info.amount)
  566. .await?;
  567. let proofs_amount = proofs.iter().map(|p| p.amount).sum();
  568. let blinded = PreMintSecrets::blank(
  569. self.active_mint_keyset(mint_url, &quote_info.unit)
  570. .await?
  571. .unwrap(),
  572. proofs_amount,
  573. )?;
  574. let melt_response = self
  575. .client
  576. .post_melt(
  577. mint_url.clone().try_into()?,
  578. quote_id.to_string(),
  579. proofs.clone(),
  580. Some(blinded.blinded_messages()),
  581. )
  582. .await?;
  583. let change_proofs = match melt_response.change {
  584. Some(change) => Some(construct_proofs(
  585. change,
  586. blinded.rs(),
  587. blinded.secrets(),
  588. &self.active_keys(mint_url, &quote_info.unit).await?.unwrap(),
  589. )?),
  590. None => None,
  591. };
  592. let melted = Melted {
  593. paid: true,
  594. preimage: melt_response.payment_preimage,
  595. change: change_proofs.clone(),
  596. };
  597. if let Some(change_proofs) = change_proofs {
  598. self.localstore
  599. .add_proofs(mint_url.clone(), change_proofs)
  600. .await?;
  601. }
  602. self.localstore.remove_melt_quote(&quote_info.id).await?;
  603. self.localstore
  604. .remove_proofs(mint_url.clone(), &proofs)
  605. .await?;
  606. Ok(melted)
  607. }
  608. /// Create P2PK locked proofs
  609. /// Uses a swap to swap proofs for locked p2pk conditions
  610. pub async fn send_p2pk(
  611. &mut self,
  612. mint_url: &UncheckedUrl,
  613. unit: &CurrencyUnit,
  614. amount: Amount,
  615. conditions: P2PKConditions,
  616. ) -> Result<Proofs, Error> {
  617. let input_proofs = self.select_proofs(mint_url.clone(), unit, amount).await?;
  618. let active_keyset_id = self.active_mint_keyset(mint_url, unit).await?.unwrap();
  619. let input_amount: Amount = input_proofs.iter().map(|p| p.amount).sum();
  620. let change_amount = input_amount - amount;
  621. let send_premint_secrets =
  622. PreMintSecrets::with_p2pk_conditions(active_keyset_id, amount, conditions)?;
  623. let change_premint_secrets = PreMintSecrets::random(active_keyset_id, change_amount)?;
  624. let mut pre_mint_secrets = send_premint_secrets;
  625. pre_mint_secrets.combine(change_premint_secrets);
  626. let swap_request =
  627. SwapRequest::new(input_proofs.clone(), pre_mint_secrets.blinded_messages());
  628. let pre_swap = PreSwap {
  629. pre_mint_secrets,
  630. swap_request,
  631. };
  632. let swap_response = self
  633. .client
  634. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  635. .await?;
  636. let post_swap_proofs = construct_proofs(
  637. swap_response.signatures,
  638. pre_swap.pre_mint_secrets.rs(),
  639. pre_swap.pre_mint_secrets.secrets(),
  640. &self.active_keys(mint_url, unit).await?.unwrap(),
  641. )?;
  642. let mut send_proofs = vec![];
  643. let mut change_proofs = vec![];
  644. for proof in post_swap_proofs {
  645. println!("post swap proof: {:?}", proof);
  646. let conditions: Result<cashu::nuts::nut10::Secret, _> = (&proof.secret).try_into();
  647. if conditions.is_ok() {
  648. send_proofs.push(proof);
  649. } else {
  650. change_proofs.push(proof);
  651. }
  652. }
  653. self.localstore
  654. .remove_proofs(mint_url.clone(), &input_proofs)
  655. .await?;
  656. self.localstore
  657. .add_pending_proofs(mint_url.clone(), input_proofs)
  658. .await?;
  659. self.localstore
  660. .add_pending_proofs(mint_url.clone(), send_proofs.clone())
  661. .await?;
  662. self.localstore
  663. .add_proofs(mint_url.clone(), change_proofs.clone())
  664. .await?;
  665. Ok(send_proofs)
  666. }
  667. /// Receive p2pk
  668. pub async fn receive_p2pk(
  669. &mut self,
  670. encoded_token: &str,
  671. signing_keys: Vec<SigningKey>,
  672. ) -> Result<(), Error> {
  673. let signing_key = signing_keys[0].clone();
  674. let pubkey_secret_key: HashMap<String, SigningKey> = signing_keys
  675. .into_iter()
  676. .map(|s| (s.public_key().to_string(), s))
  677. .collect();
  678. let token_data = Token::from_str(encoded_token)?;
  679. let unit = token_data.unit.unwrap_or_default();
  680. let mut received_proofs: HashMap<UncheckedUrl, Proofs> = HashMap::new();
  681. for token in token_data.token {
  682. if token.proofs.is_empty() {
  683. continue;
  684. }
  685. let active_keyset_id = self.active_mint_keyset(&token.mint, &unit).await?;
  686. // TODO: if none fetch keyset for mint
  687. let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
  688. // Sum amount of all proofs
  689. let amount: Amount = token.proofs.iter().map(|p| p.amount).sum();
  690. let mut proofs = token.proofs;
  691. let mut sig_flag = None;
  692. for proof in &mut proofs {
  693. if let Ok(secret) =
  694. <cashu::secret::Secret as TryInto<cashu::nuts::nut10::Secret>>::try_into(
  695. proof.secret.clone(),
  696. )
  697. {
  698. let conditions: Result<P2PKConditions, _> = secret.try_into();
  699. if let Ok(conditions) = conditions {
  700. println!("{:?}", conditions);
  701. let pubkeys = conditions.pubkeys;
  702. for pubkey in pubkeys {
  703. if let Some(signing) = pubkey_secret_key.get(&pubkey.to_string()) {
  704. proof.sign_p2pk_proof(signing.clone()).unwrap();
  705. proof.verify_p2pk().unwrap();
  706. println!("v");
  707. }
  708. }
  709. sig_flag = Some(conditions.sig_flag);
  710. }
  711. }
  712. }
  713. let mut pre_swap = self
  714. .create_swap(&token.mint, &unit, Some(amount), proofs)
  715. .await?;
  716. if let Some(sigflag) = sig_flag {
  717. if sigflag.eq(&SigFlag::SigAll) {
  718. for blinded_message in &mut pre_swap.swap_request.outputs {
  719. blinded_message
  720. .sign_p2pk_blinded_message(signing_key.clone())
  721. .unwrap();
  722. }
  723. }
  724. }
  725. let swap_response = self
  726. .client
  727. .post_swap(token.mint.clone().try_into()?, pre_swap.swap_request)
  728. .await?;
  729. // Proof to keep
  730. let p = construct_proofs(
  731. swap_response.signatures,
  732. pre_swap.pre_mint_secrets.rs(),
  733. pre_swap.pre_mint_secrets.secrets(),
  734. &keys.unwrap(),
  735. )?;
  736. let mint_proofs = received_proofs.entry(token.mint).or_default();
  737. mint_proofs.extend(p);
  738. }
  739. for (mint, proofs) in received_proofs {
  740. self.localstore.add_proofs(mint, proofs).await?;
  741. }
  742. Ok(())
  743. }
  744. /*
  745. pub async fn claim_p2pk_locked_proofs(
  746. &mut self,
  747. sigflag: SigFlag,
  748. mint_url: &UncheckedUrl,
  749. unit: &CurrencyUnit,
  750. signing_key: SigningKey,
  751. proofs: Proofs,
  752. ) -> Result<(), Error> {
  753. let active_keyset_id = self.active_mint_keyset(&mint_url, &unit).await?;
  754. let keys = self.localstore.get_keys(&active_keyset_id.unwrap()).await?;
  755. let mut signed_proofs: Proofs = Vec::with_capacity(proofs.len());
  756. // Sum amount of all proofs
  757. let amount: Amount = proofs.iter().map(|p| p.amount).sum();
  758. for p in proofs.clone() {
  759. let mut p = p;
  760. p.sign_p2pk_proof(signing_key.clone()).unwrap();
  761. signed_proofs.push(p);
  762. }
  763. let pre_swap = match sigflag {
  764. SigFlag::SigInputs => {
  765. self.create_swap(mint_url, &unit, Some(amount), signed_proofs)
  766. .await?
  767. }
  768. SigFlag::SigAll => {
  769. self.create_swap_signed(
  770. mint_url,
  771. unit,
  772. Some(amount),
  773. signed_proofs,
  774. Some(signing_key),
  775. )
  776. .await?
  777. }
  778. _ => todo!(),
  779. };
  780. let swap_response = self
  781. .client
  782. .post_swap(mint_url.clone().try_into()?, pre_swap.swap_request)
  783. .await?;
  784. // Proof to keep
  785. let p = construct_proofs(
  786. swap_response.signatures,
  787. pre_swap.pre_mint_secrets.rs(),
  788. pre_swap.pre_mint_secrets.secrets(),
  789. &keys.unwrap(),
  790. )?;
  791. self.localstore
  792. .remove_proofs(mint_url.clone(), &proofs)
  793. .await?;
  794. self.localstore.add_proofs(mint_url.clone(), p).await?;
  795. Ok(())
  796. }
  797. */
  798. pub fn proofs_to_token(
  799. &self,
  800. mint_url: UncheckedUrl,
  801. proofs: Proofs,
  802. memo: Option<String>,
  803. unit: Option<CurrencyUnit>,
  804. ) -> Result<String, Error> {
  805. Ok(Token::new(mint_url, proofs, memo, unit)?.to_string())
  806. }
  807. }
  808. /*
  809. #[cfg(test)]
  810. mod tests {
  811. use std::collections::{HashMap, HashSet};
  812. use super::*;
  813. use crate::client::Client;
  814. use crate::mint::Mint;
  815. use cashu::nuts::nut04;
  816. #[test]
  817. fn test_wallet() {
  818. let mut mint = Mint::new(
  819. "supersecretsecret",
  820. "0/0/0/0",
  821. HashMap::new(),
  822. HashSet::new(),
  823. 32,
  824. );
  825. let keys = mint.active_keyset_pubkeys();
  826. let client = Client::new("https://cashu-rs.thesimplekid.space/").unwrap();
  827. let wallet = Wallet::new(client, keys.keys);
  828. let blinded_messages = BlindedMessages::random(Amount::from_sat(64)).unwrap();
  829. let mint_request = nut04::MintRequest {
  830. outputs: blinded_messages.blinded_messages.clone(),
  831. };
  832. let res = mint.process_mint_request(mint_request).unwrap();
  833. let proofs = wallet
  834. .process_split_response(blinded_messages, res.promises)
  835. .unwrap();
  836. for proof in &proofs {
  837. mint.verify_proof(proof).unwrap();
  838. }
  839. let split = wallet.create_split(proofs.clone()).unwrap();
  840. let split_request = split.split_payload;
  841. let split_response = mint.process_split_request(split_request).unwrap();
  842. let p = split_response.promises;
  843. let snd_proofs = wallet
  844. .process_split_response(split.blinded_messages, p.unwrap())
  845. .unwrap();
  846. let mut error = false;
  847. for proof in &snd_proofs {
  848. if let Err(err) = mint.verify_proof(proof) {
  849. println!("{err}{:?}", serde_json::to_string(proof));
  850. error = true;
  851. }
  852. }
  853. if error {
  854. panic!()
  855. }
  856. }
  857. }
  858. */