mint.rs 15 KB

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