mint.rs 14 KB

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