mint.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. //! Mint tests
  2. use anyhow::{bail, Result};
  3. use bip39::Mnemonic;
  4. use cdk::amount::{Amount, SplitTarget};
  5. use cdk::cdk_database::mint_memory::MintMemoryDatabase;
  6. use cdk::dhke::construct_proofs;
  7. use cdk::mint::MintQuote;
  8. use cdk::nuts::nut00::ProofsMethods;
  9. use cdk::nuts::nut17::Params;
  10. use cdk::nuts::{
  11. CurrencyUnit, Id, MintBolt11Request, MintInfo, NotificationPayload, Nuts, PreMintSecrets,
  12. ProofState, Proofs, SecretKey, SpendingConditions, State, SwapRequest,
  13. };
  14. use cdk::types::QuoteTTL;
  15. use cdk::util::unix_time;
  16. use cdk::Mint;
  17. use std::collections::HashMap;
  18. use std::sync::Arc;
  19. use std::time::Duration;
  20. use tokio::sync::OnceCell;
  21. use tokio::time::sleep;
  22. pub const MINT_URL: &str = "http://127.0.0.1:8088";
  23. static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
  24. async fn new_mint(fee: u64) -> Mint {
  25. let mut supported_units = HashMap::new();
  26. supported_units.insert(CurrencyUnit::Sat, (fee, 32));
  27. let nuts = Nuts::new()
  28. .nut07(true)
  29. .nut08(true)
  30. .nut09(true)
  31. .nut10(true)
  32. .nut11(true)
  33. .nut12(true)
  34. .nut14(true);
  35. let mint_info = MintInfo::new().nuts(nuts);
  36. let mnemonic = Mnemonic::generate(12).unwrap();
  37. let quote_ttl = QuoteTTL::new(10000, 10000);
  38. Mint::new(
  39. MINT_URL,
  40. &mnemonic.to_seed_normalized(""),
  41. mint_info,
  42. quote_ttl,
  43. Arc::new(MintMemoryDatabase::default()),
  44. HashMap::new(),
  45. supported_units,
  46. HashMap::new(),
  47. )
  48. .await
  49. .unwrap()
  50. }
  51. async fn initialize() -> &'static Mint {
  52. INSTANCE.get_or_init(|| new_mint(0)).await
  53. }
  54. async fn mint_proofs(
  55. mint: &Mint,
  56. amount: Amount,
  57. split_target: &SplitTarget,
  58. keys: cdk::nuts::Keys,
  59. ) -> Result<Proofs> {
  60. let request_lookup = uuid::Uuid::new_v4().to_string();
  61. let quote = MintQuote::new(
  62. mint.mint_url.clone(),
  63. "".to_string(),
  64. CurrencyUnit::Sat,
  65. amount,
  66. unix_time() + 36000,
  67. request_lookup.to_string(),
  68. );
  69. mint.localstore.add_mint_quote(quote.clone()).await?;
  70. mint.pay_mint_quote_for_request_id(&request_lookup).await?;
  71. let keyset_id = Id::from(&keys);
  72. let premint = PreMintSecrets::random(keyset_id, amount, split_target)?;
  73. let mint_request = MintBolt11Request {
  74. quote: quote.id,
  75. outputs: premint.blinded_messages(),
  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. _ => {
  129. println!("{:?}", err);
  130. bail!("Wrong error returned in swap overflow")
  131. }
  132. },
  133. }
  134. Ok(())
  135. }
  136. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  137. pub async fn test_p2pk_swap() -> Result<()> {
  138. let mint = initialize().await;
  139. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  140. let keyset_id = Id::from(&keys);
  141. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  142. let secret = SecretKey::generate();
  143. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  144. let pre_swap = PreMintSecrets::with_conditions(
  145. keyset_id,
  146. 100.into(),
  147. &SplitTarget::default(),
  148. &spending_conditions,
  149. )?;
  150. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  151. let keys = mint.pubkeys().await?.keysets.first().cloned().unwrap().keys;
  152. let post_swap = mint.process_swap_request(swap_request).await?;
  153. let mut proofs = construct_proofs(
  154. post_swap.signatures,
  155. pre_swap.rs(),
  156. pre_swap.secrets(),
  157. &keys,
  158. )?;
  159. let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  160. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  161. let public_keys_to_listen: Vec<_> = swap_request
  162. .inputs
  163. .ys()
  164. .expect("key")
  165. .into_iter()
  166. .enumerate()
  167. .filter_map(|(key, pk)| {
  168. if key % 2 == 0 {
  169. // Only expect messages from every other key
  170. Some(pk.to_string())
  171. } else {
  172. None
  173. }
  174. })
  175. .collect();
  176. let mut listener = mint
  177. .pubsub_manager
  178. .subscribe(Params {
  179. kind: cdk::nuts::nut17::Kind::ProofState,
  180. filters: public_keys_to_listen.clone(),
  181. id: "test".into(),
  182. })
  183. .await;
  184. match mint.process_swap_request(swap_request).await {
  185. Ok(_) => bail!("Proofs spent without sig"),
  186. Err(err) => match err {
  187. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  188. _ => {
  189. println!("{:?}", err);
  190. bail!("Wrong error returned")
  191. }
  192. },
  193. }
  194. for proof in &mut proofs {
  195. proof.sign_p2pk(secret.clone())?;
  196. }
  197. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  198. let attempt_swap = mint.process_swap_request(swap_request).await;
  199. assert!(attempt_swap.is_ok());
  200. sleep(Duration::from_millis(10)).await;
  201. let mut msgs = HashMap::new();
  202. while let Ok((sub_id, msg)) = listener.try_recv() {
  203. assert_eq!(sub_id, "test".into());
  204. match msg {
  205. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  206. let pk = y.to_string();
  207. msgs.get_mut(&pk)
  208. .map(|x: &mut Vec<State>| {
  209. x.push(state);
  210. })
  211. .unwrap_or_else(|| {
  212. msgs.insert(pk, vec![state]);
  213. });
  214. }
  215. _ => bail!("Wrong message received"),
  216. }
  217. }
  218. for keys in public_keys_to_listen {
  219. let statuses = msgs.remove(&keys).expect("some events");
  220. assert_eq!(statuses, vec![State::Pending, State::Pending, State::Spent]);
  221. }
  222. assert!(listener.try_recv().is_err(), "no other event is happening");
  223. assert!(msgs.is_empty(), "Only expected key events are received");
  224. Ok(())
  225. }
  226. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  227. async fn test_swap_unbalanced() -> Result<()> {
  228. let mint = initialize().await;
  229. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  230. let keyset_id = Id::from(&keys);
  231. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  232. let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
  233. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  234. match mint.process_swap_request(swap_request).await {
  235. Ok(_) => bail!("Swap was allowed unbalanced"),
  236. Err(err) => match err {
  237. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  238. _ => bail!("Wrong error returned"),
  239. },
  240. }
  241. let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())?;
  242. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  243. match mint.process_swap_request(swap_request).await {
  244. Ok(_) => bail!("Swap was allowed unbalanced"),
  245. Err(err) => match err {
  246. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  247. _ => bail!("Wrong error returned"),
  248. },
  249. }
  250. Ok(())
  251. }
  252. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  253. async fn test_swap_overpay_underpay_fee() -> Result<()> {
  254. let mint = new_mint(1).await;
  255. mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, HashMap::new())
  256. .await?;
  257. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  258. let keyset_id = Id::from(&keys);
  259. let proofs = mint_proofs(&mint, 1000.into(), &SplitTarget::default(), keys).await?;
  260. let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default())?;
  261. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  262. // Attempt to swap overpaying fee
  263. match mint.process_swap_request(swap_request).await {
  264. Ok(_) => bail!("Swap was allowed unbalanced"),
  265. Err(err) => match err {
  266. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  267. _ => {
  268. println!("{:?}", err);
  269. bail!("Wrong error returned")
  270. }
  271. },
  272. }
  273. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  274. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  275. // Attempt to swap underpaying fee
  276. match mint.process_swap_request(swap_request).await {
  277. Ok(_) => bail!("Swap was allowed unbalanced"),
  278. Err(err) => match err {
  279. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  280. _ => {
  281. println!("{:?}", err);
  282. bail!("Wrong error returned")
  283. }
  284. },
  285. }
  286. Ok(())
  287. }
  288. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  289. async fn test_mint_enforce_fee() -> Result<()> {
  290. let mint = new_mint(1).await;
  291. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  292. let keyset_id = Id::from(&keys);
  293. let mut proofs = mint_proofs(&mint, 1010.into(), &SplitTarget::Value(1.into()), keys).await?;
  294. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  295. let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default())?;
  296. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  297. // Attempt to swap underpaying fee
  298. match mint.process_swap_request(swap_request).await {
  299. Ok(_) => bail!("Swap was allowed unbalanced"),
  300. Err(err) => match err {
  301. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  302. _ => {
  303. println!("{:?}", err);
  304. bail!("Wrong error returned")
  305. }
  306. },
  307. }
  308. let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default())?;
  309. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  310. let _ = mint.process_swap_request(swap_request).await?;
  311. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  312. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  313. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  314. // Attempt to swap underpaying fee
  315. match mint.process_swap_request(swap_request).await {
  316. Ok(_) => bail!("Swap was allowed unbalanced"),
  317. Err(err) => match err {
  318. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  319. _ => {
  320. println!("{:?}", err);
  321. bail!("Wrong error returned")
  322. }
  323. },
  324. }
  325. let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default())?;
  326. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  327. let _ = mint.process_swap_request(swap_request).await?;
  328. Ok(())
  329. }