mint.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. //! Mint tests
  2. use std::collections::{HashMap, HashSet};
  3. use std::sync::Arc;
  4. use std::time::Duration;
  5. use anyhow::{bail, Result};
  6. use bip39::Mnemonic;
  7. use cdk::amount::{Amount, SplitTarget};
  8. use cdk::cdk_database::mint_memory::MintMemoryDatabase;
  9. use cdk::cdk_database::MintDatabase;
  10. use cdk::dhke::construct_proofs;
  11. use cdk::mint::{FeeReserve, MintBuilder, MintMeltLimits, MintQuote};
  12. use cdk::nuts::nut00::ProofsMethods;
  13. use cdk::nuts::{
  14. CurrencyUnit, Id, MintBolt11Request, MintInfo, NotificationPayload, Nuts, PaymentMethod,
  15. PreMintSecrets, ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest,
  16. };
  17. use cdk::subscription::{IndexableParams, Params};
  18. use cdk::util::unix_time;
  19. use cdk::Mint;
  20. use cdk_fake_wallet::FakeWallet;
  21. use tokio::sync::OnceCell;
  22. use tokio::time::sleep;
  23. pub const MINT_URL: &str = "http://127.0.0.1:8088";
  24. static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
  25. async fn new_mint(fee: u64) -> Mint {
  26. let mut supported_units = HashMap::new();
  27. supported_units.insert(CurrencyUnit::Sat, (fee, 32));
  28. let nuts = Nuts::new()
  29. .nut07(true)
  30. .nut08(true)
  31. .nut09(true)
  32. .nut10(true)
  33. .nut11(true)
  34. .nut12(true)
  35. .nut14(true);
  36. let mint_info = MintInfo::new().nuts(nuts);
  37. let localstore = MintMemoryDatabase::default();
  38. localstore
  39. .set_mint_info(mint_info)
  40. .await
  41. .expect("Could not set mint info");
  42. let mnemonic = Mnemonic::generate(12).unwrap();
  43. Mint::new(
  44. &mnemonic.to_seed_normalized(""),
  45. Arc::new(localstore),
  46. HashMap::new(),
  47. supported_units,
  48. HashMap::new(),
  49. )
  50. .await
  51. .unwrap()
  52. }
  53. async fn initialize() -> &'static Mint {
  54. INSTANCE.get_or_init(|| new_mint(0)).await
  55. }
  56. async fn mint_proofs(
  57. mint: &Mint,
  58. amount: Amount,
  59. split_target: &SplitTarget,
  60. keys: cdk::nuts::Keys,
  61. ) -> Result<Proofs> {
  62. let request_lookup = uuid::Uuid::new_v4().to_string();
  63. let quote = MintQuote::new(
  64. "".to_string(),
  65. CurrencyUnit::Sat,
  66. amount,
  67. unix_time() + 36000,
  68. request_lookup.to_string(),
  69. None,
  70. );
  71. mint.localstore.add_mint_quote(quote.clone()).await?;
  72. mint.pay_mint_quote_for_request_id(&request_lookup).await?;
  73. let keyset_id = Id::from(&keys);
  74. let premint = PreMintSecrets::random(keyset_id, amount, split_target)?;
  75. let mint_request = MintBolt11Request {
  76. quote: quote.id,
  77. outputs: premint.blinded_messages(),
  78. signature: None,
  79. };
  80. let after_mint = mint.process_mint_request(mint_request).await?;
  81. let proofs = construct_proofs(
  82. after_mint.signatures,
  83. premint.rs(),
  84. premint.secrets(),
  85. &keys,
  86. )?;
  87. Ok(proofs)
  88. }
  89. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  90. async fn test_mint_double_spend() -> Result<()> {
  91. let mint = initialize().await;
  92. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  93. let keyset_id = Id::from(&keys);
  94. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  95. let preswap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  96. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  97. let swap = mint.process_swap_request(swap_request).await;
  98. assert!(swap.is_ok());
  99. let preswap_two = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  100. let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
  101. match mint.process_swap_request(swap_two_request).await {
  102. Ok(_) => bail!("Proofs double spent"),
  103. Err(err) => match err {
  104. cdk::Error::TokenAlreadySpent => (),
  105. _ => bail!("Wrong error returned"),
  106. },
  107. }
  108. Ok(())
  109. }
  110. /// This attempts to swap for more outputs then inputs.
  111. /// This will work if the mint does not check for outputs amounts overflowing
  112. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  113. async fn test_attempt_to_swap_by_overflowing() -> Result<()> {
  114. let mint = initialize().await;
  115. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  116. let keyset_id = Id::from(&keys);
  117. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  118. let amount = 2_u64.pow(63);
  119. let pre_mint_amount =
  120. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
  121. let pre_mint_amount_two =
  122. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
  123. let mut pre_mint = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
  124. pre_mint.combine(pre_mint_amount);
  125. pre_mint.combine(pre_mint_amount_two);
  126. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  127. match mint.process_swap_request(swap_request).await {
  128. Ok(_) => bail!("Swap occurred with overflow"),
  129. Err(err) => match err {
  130. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  131. _ => {
  132. println!("{:?}", err);
  133. bail!("Wrong error returned in swap overflow")
  134. }
  135. },
  136. }
  137. Ok(())
  138. }
  139. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  140. pub async fn test_p2pk_swap() -> Result<()> {
  141. let mint = initialize().await;
  142. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  143. let keyset_id = Id::from(&keys);
  144. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  145. let secret = SecretKey::generate();
  146. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  147. let pre_swap = PreMintSecrets::with_conditions(
  148. keyset_id,
  149. 100.into(),
  150. &SplitTarget::default(),
  151. &spending_conditions,
  152. )?;
  153. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  154. let keys = mint.pubkeys().await?.keysets.first().cloned().unwrap().keys;
  155. let post_swap = mint.process_swap_request(swap_request).await?;
  156. let mut proofs = construct_proofs(
  157. post_swap.signatures,
  158. pre_swap.rs(),
  159. pre_swap.secrets(),
  160. &keys,
  161. )?;
  162. let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  163. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  164. let public_keys_to_listen: Vec<_> = swap_request
  165. .inputs
  166. .ys()
  167. .expect("key")
  168. .into_iter()
  169. .enumerate()
  170. .filter_map(|(key, pk)| {
  171. if key % 2 == 0 {
  172. // Only expect messages from every other key
  173. Some(pk.to_string())
  174. } else {
  175. None
  176. }
  177. })
  178. .collect();
  179. let mut listener = mint
  180. .pubsub_manager
  181. .try_subscribe::<IndexableParams>(
  182. Params {
  183. kind: cdk::nuts::nut17::Kind::ProofState,
  184. filters: public_keys_to_listen.clone(),
  185. id: "test".into(),
  186. }
  187. .into(),
  188. )
  189. .await
  190. .expect("valid subscription");
  191. match mint.process_swap_request(swap_request).await {
  192. Ok(_) => bail!("Proofs spent without sig"),
  193. Err(err) => match err {
  194. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  195. _ => {
  196. println!("{:?}", err);
  197. bail!("Wrong error returned")
  198. }
  199. },
  200. }
  201. for proof in &mut proofs {
  202. proof.sign_p2pk(secret.clone())?;
  203. }
  204. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  205. let attempt_swap = mint.process_swap_request(swap_request).await;
  206. assert!(attempt_swap.is_ok());
  207. sleep(Duration::from_millis(10)).await;
  208. let mut msgs = HashMap::new();
  209. while let Ok((sub_id, msg)) = listener.try_recv() {
  210. assert_eq!(sub_id, "test".into());
  211. match msg {
  212. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  213. let pk = y.to_string();
  214. msgs.get_mut(&pk)
  215. .map(|x: &mut Vec<State>| {
  216. x.push(state);
  217. })
  218. .unwrap_or_else(|| {
  219. msgs.insert(pk, vec![state]);
  220. });
  221. }
  222. _ => bail!("Wrong message received"),
  223. }
  224. }
  225. for keys in public_keys_to_listen {
  226. let statuses = msgs.remove(&keys).expect("some events");
  227. assert_eq!(statuses, vec![State::Pending, State::Pending, State::Spent]);
  228. }
  229. assert!(listener.try_recv().is_err(), "no other event is happening");
  230. assert!(msgs.is_empty(), "Only expected key events are received");
  231. Ok(())
  232. }
  233. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  234. async fn test_swap_unbalanced() -> Result<()> {
  235. let mint = initialize().await;
  236. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  237. let keyset_id = Id::from(&keys);
  238. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  239. let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
  240. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  241. match mint.process_swap_request(swap_request).await {
  242. Ok(_) => bail!("Swap was allowed unbalanced"),
  243. Err(err) => match err {
  244. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  245. _ => bail!("Wrong error returned"),
  246. },
  247. }
  248. let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())?;
  249. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  250. match mint.process_swap_request(swap_request).await {
  251. Ok(_) => bail!("Swap was allowed unbalanced"),
  252. Err(err) => match err {
  253. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  254. _ => bail!("Wrong error returned"),
  255. },
  256. }
  257. Ok(())
  258. }
  259. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  260. async fn test_swap_overpay_underpay_fee() -> Result<()> {
  261. let mint = new_mint(1).await;
  262. mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new())
  263. .await?;
  264. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  265. let keyset_id = Id::from(&keys);
  266. let proofs = mint_proofs(&mint, 1000.into(), &SplitTarget::default(), keys).await?;
  267. let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default())?;
  268. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  269. // Attempt to swap overpaying fee
  270. match mint.process_swap_request(swap_request).await {
  271. Ok(_) => bail!("Swap was allowed unbalanced"),
  272. Err(err) => match err {
  273. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  274. _ => {
  275. println!("{:?}", err);
  276. bail!("Wrong error returned")
  277. }
  278. },
  279. }
  280. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  281. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  282. // Attempt to swap underpaying fee
  283. match mint.process_swap_request(swap_request).await {
  284. Ok(_) => bail!("Swap was allowed unbalanced"),
  285. Err(err) => match err {
  286. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  287. _ => {
  288. println!("{:?}", err);
  289. bail!("Wrong error returned")
  290. }
  291. },
  292. }
  293. Ok(())
  294. }
  295. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  296. async fn test_mint_enforce_fee() -> Result<()> {
  297. let mint = new_mint(1).await;
  298. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  299. let keyset_id = Id::from(&keys);
  300. let mut proofs = mint_proofs(&mint, 1010.into(), &SplitTarget::Value(1.into()), keys).await?;
  301. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  302. let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default())?;
  303. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  304. // Attempt to swap underpaying fee
  305. match mint.process_swap_request(swap_request).await {
  306. Ok(_) => bail!("Swap was allowed unbalanced"),
  307. Err(err) => match err {
  308. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  309. _ => {
  310. println!("{:?}", err);
  311. bail!("Wrong error returned")
  312. }
  313. },
  314. }
  315. let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default())?;
  316. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  317. let _ = mint.process_swap_request(swap_request).await?;
  318. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  319. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  320. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  321. // Attempt to swap underpaying fee
  322. match mint.process_swap_request(swap_request).await {
  323. Ok(_) => bail!("Swap was allowed unbalanced"),
  324. Err(err) => match err {
  325. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  326. _ => {
  327. println!("{:?}", err);
  328. bail!("Wrong error returned")
  329. }
  330. },
  331. }
  332. let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default())?;
  333. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  334. let _ = mint.process_swap_request(swap_request).await?;
  335. Ok(())
  336. }
  337. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  338. async fn test_correct_keyset() -> Result<()> {
  339. let mnemonic = Mnemonic::generate(12)?;
  340. let fee_reserve = FeeReserve {
  341. min_fee_reserve: 1.into(),
  342. percent_fee_reserve: 1.0,
  343. };
  344. let database = MintMemoryDatabase::default();
  345. let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
  346. let mut mint_builder = MintBuilder::new();
  347. let localstore = Arc::new(database);
  348. mint_builder = mint_builder.with_localstore(localstore.clone());
  349. mint_builder = mint_builder.add_ln_backend(
  350. CurrencyUnit::Sat,
  351. PaymentMethod::Bolt11,
  352. MintMeltLimits::new(1, 5_000),
  353. Arc::new(fake_wallet),
  354. );
  355. mint_builder = mint_builder
  356. .with_name("regtest mint".to_string())
  357. .with_description("regtest mint".to_string())
  358. .with_quote_ttl(10000, 1000)
  359. .with_seed(mnemonic.to_seed_normalized("").to_vec());
  360. let mint = mint_builder.build().await?;
  361. mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
  362. mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
  363. let active = mint.localstore.get_active_keysets().await?;
  364. let active = active
  365. .get(&CurrencyUnit::Sat)
  366. .expect("There is a keyset for unit");
  367. let keyset_info = mint
  368. .localstore
  369. .get_keyset_info(active)
  370. .await?
  371. .expect("There is keyset");
  372. assert!(keyset_info.derivation_path_index == Some(2));
  373. let mint = mint_builder.build().await?;
  374. let active = mint.localstore.get_active_keysets().await?;
  375. let active = active
  376. .get(&CurrencyUnit::Sat)
  377. .expect("There is a keyset for unit");
  378. let keyset_info = mint
  379. .localstore
  380. .get_keyset_info(active)
  381. .await?
  382. .expect("There is keyset");
  383. assert!(keyset_info.derivation_path_index == Some(2));
  384. Ok(())
  385. }