mod.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. #![doc = include_str!("./README.md")]
  2. use std::collections::HashMap;
  3. use std::fmt::Debug;
  4. use std::str::FromStr;
  5. use std::sync::atomic::AtomicBool;
  6. use std::sync::Arc;
  7. use std::time::Duration;
  8. use cdk_common::amount::FeeAndAmounts;
  9. use cdk_common::database::{self, DynWalletDatabaseTransaction, WalletDatabase};
  10. use cdk_common::parking_lot::RwLock;
  11. use cdk_common::subscription::WalletParams;
  12. use getrandom::getrandom;
  13. use subscription::{ActiveSubscription, SubscriptionManager};
  14. #[cfg(feature = "auth")]
  15. use tokio::sync::RwLock as TokioRwLock;
  16. use tracing::instrument;
  17. use zeroize::Zeroize;
  18. use crate::amount::SplitTarget;
  19. use crate::dhke::construct_proofs;
  20. use crate::error::Error;
  21. use crate::fees::calculate_fee;
  22. use crate::mint_url::MintUrl;
  23. use crate::nuts::nut00::token::Token;
  24. use crate::nuts::nut17::Kind;
  25. use crate::nuts::{
  26. nut10, CurrencyUnit, Id, Keys, MintInfo, MintQuoteState, PreMintSecrets, Proof, Proofs,
  27. RestoreRequest, SpendingConditions, State,
  28. };
  29. use crate::types::ProofInfo;
  30. use crate::util::unix_time;
  31. use crate::wallet::mint_metadata_cache::MintMetadataCache;
  32. use crate::Amount;
  33. #[cfg(feature = "auth")]
  34. use crate::OidcClient;
  35. #[cfg(feature = "auth")]
  36. mod auth;
  37. #[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
  38. pub use mint_connector::TorHttpClient;
  39. mod balance;
  40. mod builder;
  41. mod issue;
  42. mod keysets;
  43. mod melt;
  44. mod mint_connector;
  45. mod mint_metadata_cache;
  46. pub mod multi_mint_wallet;
  47. pub mod payment_request;
  48. mod proofs;
  49. mod receive;
  50. mod reclaim;
  51. mod send;
  52. #[cfg(not(target_arch = "wasm32"))]
  53. mod streams;
  54. pub mod subscription;
  55. mod swap;
  56. mod transactions;
  57. pub mod util;
  58. #[cfg(feature = "auth")]
  59. pub use auth::{AuthMintConnector, AuthWallet};
  60. pub use builder::WalletBuilder;
  61. pub use cdk_common::wallet as types;
  62. #[cfg(feature = "auth")]
  63. pub use mint_connector::http_client::AuthHttpClient as BaseAuthHttpClient;
  64. pub use mint_connector::http_client::HttpClient as BaseHttpClient;
  65. pub use mint_connector::transport::Transport as HttpTransport;
  66. #[cfg(feature = "auth")]
  67. pub use mint_connector::AuthHttpClient;
  68. pub use mint_connector::{HttpClient, LnurlPayInvoiceResponse, LnurlPayResponse, MintConnector};
  69. pub use multi_mint_wallet::{MultiMintReceiveOptions, MultiMintSendOptions, MultiMintWallet};
  70. pub use receive::ReceiveOptions;
  71. pub use send::{PreparedSend, SendMemo, SendOptions};
  72. pub use types::{MeltQuote, MintQuote, SendKind};
  73. use crate::nuts::nut00::ProofsMethods;
  74. /// CDK Wallet
  75. ///
  76. /// The CDK [`Wallet`] is a high level cashu wallet.
  77. ///
  78. /// A [`Wallet`] is for a single mint and single unit.
  79. #[derive(Debug, Clone)]
  80. pub struct Wallet {
  81. /// Mint Url
  82. pub mint_url: MintUrl,
  83. /// Unit
  84. pub unit: CurrencyUnit,
  85. /// Storage backend
  86. pub localstore: Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
  87. /// Mint metadata cache for this mint (lock-free cached access to keys, keysets, and mint info)
  88. pub metadata_cache: Arc<MintMetadataCache>,
  89. /// The targeted amount of proofs to have at each size
  90. pub target_proof_count: usize,
  91. metadata_cache_ttl: Arc<RwLock<Option<Duration>>>,
  92. #[cfg(feature = "auth")]
  93. auth_wallet: Arc<TokioRwLock<Option<AuthWallet>>>,
  94. seed: [u8; 64],
  95. client: Arc<dyn MintConnector + Send + Sync>,
  96. subscription: SubscriptionManager,
  97. in_error_swap_reverted_proofs: Arc<AtomicBool>,
  98. }
  99. const ALPHANUMERIC: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  100. /// Wallet Subscription filter
  101. #[derive(Debug, Clone)]
  102. pub enum WalletSubscription {
  103. /// Proof subscription
  104. ProofState(Vec<String>),
  105. /// Mint quote subscription
  106. Bolt11MintQuoteState(Vec<String>),
  107. /// Melt quote subscription
  108. Bolt11MeltQuoteState(Vec<String>),
  109. /// Mint bolt12 quote subscription
  110. Bolt12MintQuoteState(Vec<String>),
  111. }
  112. impl From<WalletSubscription> for WalletParams {
  113. fn from(val: WalletSubscription) -> Self {
  114. let mut buffer = vec![0u8; 10];
  115. getrandom(&mut buffer).expect("Failed to generate random bytes");
  116. let id = Arc::new(
  117. buffer
  118. .iter()
  119. .map(|&byte| {
  120. let index = byte as usize % ALPHANUMERIC.len(); // 62 alphanumeric characters (A-Z, a-z, 0-9)
  121. ALPHANUMERIC[index] as char
  122. })
  123. .collect::<String>(),
  124. );
  125. match val {
  126. WalletSubscription::ProofState(filters) => WalletParams {
  127. filters,
  128. kind: Kind::ProofState,
  129. id,
  130. },
  131. WalletSubscription::Bolt11MintQuoteState(filters) => WalletParams {
  132. filters,
  133. kind: Kind::Bolt11MintQuote,
  134. id,
  135. },
  136. WalletSubscription::Bolt11MeltQuoteState(filters) => WalletParams {
  137. filters,
  138. kind: Kind::Bolt11MeltQuote,
  139. id,
  140. },
  141. WalletSubscription::Bolt12MintQuoteState(filters) => WalletParams {
  142. filters,
  143. kind: Kind::Bolt12MintQuote,
  144. id,
  145. },
  146. }
  147. }
  148. }
  149. impl Wallet {
  150. /// Create new [`Wallet`] using the builder pattern
  151. /// # Synopsis
  152. /// ```rust
  153. /// use bitcoin::bip32::Xpriv;
  154. /// use std::sync::Arc;
  155. ///
  156. /// use cdk::nuts::CurrencyUnit;
  157. /// use cdk::wallet::{Wallet, WalletBuilder};
  158. /// use cdk_sqlite::wallet::memory;
  159. /// use rand::random;
  160. ///
  161. /// async fn test() -> anyhow::Result<()> {
  162. /// let seed = random::<[u8; 64]>();
  163. /// let mint_url = "https://fake.thesimplekid.dev";
  164. /// let unit = CurrencyUnit::Sat;
  165. ///
  166. /// let localstore = memory::empty().await?;
  167. /// let wallet = WalletBuilder::new()
  168. /// .mint_url(mint_url.parse().unwrap())
  169. /// .unit(unit)
  170. /// .localstore(Arc::new(localstore))
  171. /// .seed(seed)
  172. /// .build();
  173. /// Ok(())
  174. /// }
  175. /// ```
  176. pub fn new(
  177. mint_url: &str,
  178. unit: CurrencyUnit,
  179. localstore: Arc<dyn WalletDatabase<Err = database::Error> + Send + Sync>,
  180. seed: [u8; 64],
  181. target_proof_count: Option<usize>,
  182. ) -> Result<Self, Error> {
  183. let mint_url = MintUrl::from_str(mint_url)?;
  184. WalletBuilder::new()
  185. .mint_url(mint_url)
  186. .unit(unit)
  187. .localstore(localstore)
  188. .seed(seed)
  189. .target_proof_count(target_proof_count.unwrap_or(3))
  190. .build()
  191. }
  192. /// Subscribe to events
  193. pub async fn subscribe<T: Into<WalletParams>>(&self, query: T) -> ActiveSubscription {
  194. self.subscription
  195. .subscribe(self.mint_url.clone(), query.into())
  196. .expect("FIXME")
  197. }
  198. /// Fee required for proof set
  199. #[instrument(skip_all)]
  200. pub async fn get_proofs_fee(
  201. &self,
  202. proofs: &Proofs,
  203. ) -> Result<crate::fees::ProofsFeeBreakdown, Error> {
  204. let proofs_per_keyset = proofs.count_by_keyset();
  205. self.get_proofs_fee_by_count(proofs_per_keyset).await
  206. }
  207. /// Fee required for proof set by count
  208. pub async fn get_proofs_fee_by_count(
  209. &self,
  210. proofs_per_keyset: HashMap<Id, u64>,
  211. ) -> Result<crate::fees::ProofsFeeBreakdown, Error> {
  212. let mut fee_per_keyset = HashMap::new();
  213. let metadata = self
  214. .metadata_cache
  215. .load(&self.localstore, &self.client, {
  216. let ttl = self.metadata_cache_ttl.read();
  217. *ttl
  218. })
  219. .await?;
  220. for keyset_id in proofs_per_keyset.keys() {
  221. let mint_keyset_info = metadata
  222. .keysets
  223. .get(keyset_id)
  224. .ok_or(Error::UnknownKeySet)?;
  225. fee_per_keyset.insert(*keyset_id, mint_keyset_info.input_fee_ppk);
  226. }
  227. let fee_breakdown = calculate_fee(&proofs_per_keyset, &fee_per_keyset)?;
  228. Ok(fee_breakdown)
  229. }
  230. /// Get fee for count of proofs in a keyset
  231. #[instrument(skip_all)]
  232. pub async fn get_keyset_count_fee(&self, keyset_id: &Id, count: u64) -> Result<Amount, Error> {
  233. let input_fee_ppk = self
  234. .metadata_cache
  235. .load(&self.localstore, &self.client, {
  236. let ttl = self.metadata_cache_ttl.read();
  237. *ttl
  238. })
  239. .await?
  240. .keysets
  241. .get(keyset_id)
  242. .ok_or(Error::UnknownKeySet)?
  243. .input_fee_ppk;
  244. let fee = (input_fee_ppk * count).div_ceil(1000);
  245. Ok(Amount::from(fee))
  246. }
  247. /// Update Mint information and related entries in the event a mint changes
  248. /// its URL
  249. #[instrument(skip(self))]
  250. pub async fn update_mint_url(&mut self, new_mint_url: MintUrl) -> Result<(), Error> {
  251. // Update the mint URL in the wallet DB
  252. let mut tx = self.localstore.begin_db_transaction().await?;
  253. tx.update_mint_url(self.mint_url.clone(), new_mint_url.clone())
  254. .await?;
  255. tx.commit().await?;
  256. // Update the mint URL in the wallet struct field
  257. self.mint_url = new_mint_url;
  258. Ok(())
  259. }
  260. /// Query mint for current mint information
  261. #[instrument(skip(self))]
  262. pub async fn fetch_mint_info(&self) -> Result<Option<MintInfo>, Error> {
  263. let mint_info = self
  264. .metadata_cache
  265. .load_from_mint(&self.localstore, &self.client)
  266. .await?
  267. .mint_info
  268. .clone();
  269. // If mint provides time make sure it is accurate
  270. if let Some(mint_unix_time) = mint_info.time {
  271. let current_unix_time = unix_time();
  272. if current_unix_time.abs_diff(mint_unix_time) > 30 {
  273. tracing::warn!(
  274. "Mint time does match wallet time. Mint: {}, Wallet: {}",
  275. mint_unix_time,
  276. current_unix_time
  277. );
  278. return Err(Error::MintTimeExceedsTolerance);
  279. }
  280. }
  281. // Create or update auth wallet
  282. #[cfg(feature = "auth")]
  283. {
  284. let mut auth_wallet = self.auth_wallet.write().await;
  285. match &*auth_wallet {
  286. Some(auth_wallet) => {
  287. let mut protected_endpoints = auth_wallet.protected_endpoints.write().await;
  288. *protected_endpoints = mint_info.protected_endpoints();
  289. if let Some(oidc_client) = mint_info
  290. .openid_discovery()
  291. .map(|url| OidcClient::new(url, None))
  292. {
  293. auth_wallet.set_oidc_client(Some(oidc_client)).await;
  294. }
  295. }
  296. None => {
  297. tracing::info!("Mint has auth enabled creating auth wallet");
  298. let oidc_client = mint_info
  299. .openid_discovery()
  300. .map(|url| OidcClient::new(url, None));
  301. let new_auth_wallet = AuthWallet::new(
  302. self.mint_url.clone(),
  303. None,
  304. self.localstore.clone(),
  305. self.metadata_cache.clone(),
  306. mint_info.protected_endpoints(),
  307. oidc_client,
  308. );
  309. *auth_wallet = Some(new_auth_wallet.clone());
  310. self.client.set_auth_wallet(Some(new_auth_wallet)).await;
  311. }
  312. }
  313. }
  314. tracing::trace!("Mint info updated for {}", self.mint_url);
  315. Ok(Some(mint_info))
  316. }
  317. /// Load mint info from cache
  318. ///
  319. /// This is a helper function that loads the mint info from the metadata cache
  320. /// using the configured TTL. Unlike `fetch_mint_info()`, this does not make
  321. /// a network call if the cache is fresh.
  322. #[instrument(skip(self))]
  323. pub async fn load_mint_info(&self) -> Result<MintInfo, Error> {
  324. let mint_info = self
  325. .metadata_cache
  326. .load(&self.localstore, &self.client, {
  327. let ttl = self.metadata_cache_ttl.read();
  328. *ttl
  329. })
  330. .await?
  331. .mint_info
  332. .clone();
  333. Ok(mint_info)
  334. }
  335. /// Get amounts needed to refill proof state
  336. #[instrument(skip(self, tx))]
  337. pub(crate) async fn amounts_needed_for_state_target(
  338. &self,
  339. tx: &mut DynWalletDatabaseTransaction,
  340. fee_and_amounts: &FeeAndAmounts,
  341. ) -> Result<Vec<Amount>, Error> {
  342. let unspent_proofs = self
  343. .get_proofs_with(Some(tx), Some(vec![State::Unspent]), None)
  344. .await?;
  345. let amounts_count: HashMap<u64, u64> =
  346. unspent_proofs
  347. .iter()
  348. .fold(HashMap::new(), |mut acc, proof| {
  349. let amount = proof.amount;
  350. let counter = acc.entry(u64::from(amount)).or_insert(0);
  351. *counter += 1;
  352. acc
  353. });
  354. let needed_amounts =
  355. fee_and_amounts
  356. .amounts()
  357. .iter()
  358. .fold(Vec::new(), |mut acc, amount| {
  359. let count_needed = (self.target_proof_count as u64)
  360. .saturating_sub(*amounts_count.get(amount).unwrap_or(&0));
  361. for _i in 0..count_needed {
  362. acc.push(Amount::from(*amount));
  363. }
  364. acc
  365. });
  366. Ok(needed_amounts)
  367. }
  368. /// Determine [`SplitTarget`] for amount based on state
  369. #[instrument(skip(self, tx))]
  370. async fn determine_split_target_values(
  371. &self,
  372. tx: &mut DynWalletDatabaseTransaction,
  373. change_amount: Amount,
  374. fee_and_amounts: &FeeAndAmounts,
  375. ) -> Result<SplitTarget, Error> {
  376. let mut amounts_needed_refill = self
  377. .amounts_needed_for_state_target(tx, fee_and_amounts)
  378. .await?;
  379. amounts_needed_refill.sort();
  380. let mut values = Vec::new();
  381. for amount in amounts_needed_refill {
  382. let values_sum = Amount::try_sum(values.clone().into_iter())?;
  383. if values_sum + amount <= change_amount {
  384. values.push(amount);
  385. }
  386. }
  387. Ok(SplitTarget::Values(values))
  388. }
  389. /// Restore
  390. #[instrument(skip(self))]
  391. pub async fn restore(&self) -> Result<Amount, Error> {
  392. // Check that mint is in store of mints
  393. if self
  394. .localstore
  395. .get_mint(self.mint_url.clone())
  396. .await?
  397. .is_none()
  398. {
  399. self.fetch_mint_info().await?;
  400. }
  401. let keysets = self.load_mint_keysets().await?;
  402. let mut restored_value = Amount::ZERO;
  403. for keyset in keysets {
  404. let keys = self.load_keyset_keys(keyset.id).await?;
  405. let mut empty_batch = 0;
  406. let mut start_counter = 0;
  407. while empty_batch.lt(&3) {
  408. let premint_secrets = PreMintSecrets::restore_batch(
  409. keyset.id,
  410. &self.seed,
  411. start_counter,
  412. start_counter + 100,
  413. )?;
  414. tracing::debug!(
  415. "Attempting to restore counter {}-{} for mint {} keyset {}",
  416. start_counter,
  417. start_counter + 100,
  418. self.mint_url,
  419. keyset.id
  420. );
  421. let restore_request = RestoreRequest {
  422. outputs: premint_secrets.blinded_messages(),
  423. };
  424. let response = self.client.post_restore(restore_request).await?;
  425. if response.signatures.is_empty() {
  426. empty_batch += 1;
  427. start_counter += 100;
  428. continue;
  429. }
  430. let premint_secrets: Vec<_> = premint_secrets
  431. .secrets
  432. .iter()
  433. .filter(|p| response.outputs.contains(&p.blinded_message))
  434. .collect();
  435. // the response outputs and premint secrets should be the same after filtering
  436. // blinded messages the mint did not have signatures for
  437. assert_eq!(response.outputs.len(), premint_secrets.len());
  438. let proofs = construct_proofs(
  439. response.signatures,
  440. premint_secrets.iter().map(|p| p.r.clone()).collect(),
  441. premint_secrets.iter().map(|p| p.secret.clone()).collect(),
  442. &keys,
  443. )?;
  444. tracing::debug!("Restored {} proofs", proofs.len());
  445. let mut tx = self.localstore.begin_db_transaction().await?;
  446. tx.increment_keyset_counter(&keyset.id, proofs.len() as u32)
  447. .await?;
  448. tx.commit().await?;
  449. let states = self.check_proofs_spent(proofs.clone()).await?;
  450. let unspent_proofs: Vec<Proof> = proofs
  451. .iter()
  452. .zip(states)
  453. .filter(|(_, state)| !state.state.eq(&State::Spent))
  454. .map(|(p, _)| p)
  455. .cloned()
  456. .collect();
  457. restored_value += unspent_proofs.total_amount()?;
  458. let unspent_proofs = unspent_proofs
  459. .into_iter()
  460. .map(|proof| {
  461. ProofInfo::new(
  462. proof,
  463. self.mint_url.clone(),
  464. State::Unspent,
  465. keyset.unit.clone(),
  466. )
  467. })
  468. .collect::<Result<Vec<ProofInfo>, _>>()?;
  469. let mut tx = self.localstore.begin_db_transaction().await?;
  470. tx.update_proofs(unspent_proofs, vec![]).await?;
  471. tx.commit().await?;
  472. empty_batch = 0;
  473. start_counter += 100;
  474. }
  475. }
  476. Ok(restored_value)
  477. }
  478. /// Verify all proofs in token have meet the required spend
  479. /// Can be used to allow a wallet to accept payments offline while reducing
  480. /// the risk of claiming back to the limits let by the spending_conditions
  481. #[instrument(skip(self, token))]
  482. pub async fn verify_token_p2pk(
  483. &self,
  484. token: &Token,
  485. spending_conditions: SpendingConditions,
  486. ) -> Result<(), Error> {
  487. let (refund_keys, pubkeys, locktime, num_sigs) = match spending_conditions {
  488. SpendingConditions::P2PKConditions { data, conditions } => {
  489. let mut pubkeys = vec![data];
  490. match conditions {
  491. Some(conditions) => {
  492. pubkeys.extend(conditions.pubkeys.unwrap_or_default());
  493. (
  494. conditions.refund_keys,
  495. Some(pubkeys),
  496. conditions.locktime,
  497. conditions.num_sigs,
  498. )
  499. }
  500. None => (None, Some(pubkeys), None, None),
  501. }
  502. }
  503. SpendingConditions::HTLCConditions {
  504. conditions,
  505. data: _,
  506. } => match conditions {
  507. Some(conditions) => (
  508. conditions.refund_keys,
  509. conditions.pubkeys,
  510. conditions.locktime,
  511. conditions.num_sigs,
  512. ),
  513. None => (None, None, None, None),
  514. },
  515. };
  516. if refund_keys.is_some() && locktime.is_none() {
  517. tracing::warn!(
  518. "Invalid spending conditions set: Locktime must be set if refund keys are allowed"
  519. );
  520. return Err(Error::InvalidSpendConditions(
  521. "Must set locktime".to_string(),
  522. ));
  523. }
  524. if token.mint_url()? != self.mint_url {
  525. return Err(Error::IncorrectWallet(format!(
  526. "Should be {} not {}",
  527. self.mint_url,
  528. token.mint_url()?
  529. )));
  530. }
  531. // We need the keysets information to properly convert from token proof to proof
  532. let keysets_info = self.load_mint_keysets().await?;
  533. let proofs = token.proofs(&keysets_info)?;
  534. for proof in proofs {
  535. let secret: nut10::Secret = (&proof.secret).try_into()?;
  536. let proof_conditions: SpendingConditions = secret.try_into()?;
  537. if num_sigs.ne(&proof_conditions.num_sigs()) {
  538. tracing::debug!(
  539. "Spending condition requires: {:?} sigs proof secret specifies: {:?}",
  540. num_sigs,
  541. proof_conditions.num_sigs()
  542. );
  543. return Err(Error::P2PKConditionsNotMet(
  544. "Num sigs did not match spending condition".to_string(),
  545. ));
  546. }
  547. let spending_condition_pubkeys = pubkeys.clone().unwrap_or_default();
  548. let proof_pubkeys = proof_conditions.pubkeys().unwrap_or_default();
  549. // Check the Proof has the required pubkeys
  550. if proof_pubkeys.len().ne(&spending_condition_pubkeys.len())
  551. || !proof_pubkeys
  552. .iter()
  553. .all(|pubkey| spending_condition_pubkeys.contains(pubkey))
  554. {
  555. tracing::debug!("Proof did not included Publickeys meeting condition");
  556. tracing::debug!("{:?}", proof_pubkeys);
  557. tracing::debug!("{:?}", spending_condition_pubkeys);
  558. return Err(Error::P2PKConditionsNotMet(
  559. "Pubkeys in proof not allowed by spending condition".to_string(),
  560. ));
  561. }
  562. // If spending condition refund keys is allowed (Some(Empty Vec))
  563. // If spending conition refund keys is allowed to restricted set of keys check
  564. // it is one of them Check that proof locktime is > condition
  565. // locktime
  566. if let Some(proof_refund_keys) = proof_conditions.refund_keys() {
  567. let proof_locktime = proof_conditions
  568. .locktime()
  569. .ok_or(Error::LocktimeNotProvided)?;
  570. if let (Some(condition_refund_keys), Some(condition_locktime)) =
  571. (&refund_keys, locktime)
  572. {
  573. // Proof locktime must be greater then condition locktime to ensure it
  574. // cannot be claimed back
  575. if proof_locktime.lt(&condition_locktime) {
  576. return Err(Error::P2PKConditionsNotMet(
  577. "Proof locktime less then required".to_string(),
  578. ));
  579. }
  580. // A non empty condition refund key list is used as a restricted set of keys
  581. // returns are allowed to An empty list means the
  582. // proof can be refunded to anykey set in the secret
  583. if !condition_refund_keys.is_empty()
  584. && !proof_refund_keys
  585. .iter()
  586. .all(|refund_key| condition_refund_keys.contains(refund_key))
  587. {
  588. return Err(Error::P2PKConditionsNotMet(
  589. "Refund Key not allowed".to_string(),
  590. ));
  591. }
  592. } else {
  593. // Spending conditions does not allow refund keys
  594. return Err(Error::P2PKConditionsNotMet(
  595. "Spending condition does not allow refund keys".to_string(),
  596. ));
  597. }
  598. }
  599. }
  600. Ok(())
  601. }
  602. /// Verify all proofs in token have a valid DLEQ proof
  603. #[instrument(skip(self, token))]
  604. pub async fn verify_token_dleq(&self, token: &Token) -> Result<(), Error> {
  605. let mut keys_cache: HashMap<Id, Keys> = HashMap::new();
  606. // TODO: Get mint url
  607. // if mint_url != &self.mint_url {
  608. // return Err(Error::IncorrectWallet(format!(
  609. // "Should be {} not {}",
  610. // self.mint_url, mint_url
  611. // )));
  612. // }
  613. // We need the keysets information to properly convert from token proof to proof
  614. let keysets_info = self.load_mint_keysets().await?;
  615. let proofs = token.proofs(&keysets_info)?;
  616. for proof in proofs {
  617. let mint_pubkey = match keys_cache.get(&proof.keyset_id) {
  618. Some(keys) => keys.amount_key(proof.amount),
  619. None => {
  620. let keys = self.load_keyset_keys(proof.keyset_id).await?;
  621. let key = keys.amount_key(proof.amount);
  622. keys_cache.insert(proof.keyset_id, keys);
  623. key
  624. }
  625. }
  626. .ok_or(Error::AmountKey)?;
  627. proof
  628. .verify_dleq(mint_pubkey)
  629. .map_err(|_| Error::CouldNotVerifyDleq)?;
  630. }
  631. Ok(())
  632. }
  633. /// Set the client (MintConnector) for this wallet
  634. ///
  635. /// This allows updating the connector without recreating the wallet.
  636. pub fn set_client(&mut self, client: Arc<dyn MintConnector + Send + Sync>) {
  637. self.client = client;
  638. }
  639. /// Set the target proof count for this wallet
  640. ///
  641. /// This controls how many proofs of each denomination the wallet tries to maintain.
  642. pub fn set_target_proof_count(&mut self, count: usize) {
  643. self.target_proof_count = count;
  644. }
  645. }
  646. impl Drop for Wallet {
  647. fn drop(&mut self) {
  648. self.seed.zeroize();
  649. }
  650. }