mint.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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::{
  9. CurrencyUnit, Id, MintBolt11Request, MintInfo, Nuts, PreMintSecrets, Proofs, SecretKey,
  10. SpendingConditions, SwapRequest,
  11. };
  12. use cdk::types::QuoteTTL;
  13. use cdk::util::unix_time;
  14. use cdk::Mint;
  15. use std::collections::HashMap;
  16. use std::sync::Arc;
  17. use tokio::sync::OnceCell;
  18. pub const MINT_URL: &str = "http://127.0.0.1:8088";
  19. static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
  20. async fn new_mint(fee: u64) -> Mint {
  21. let mut supported_units = HashMap::new();
  22. supported_units.insert(CurrencyUnit::Sat, (fee, 32));
  23. let nuts = Nuts::new()
  24. .nut07(true)
  25. .nut08(true)
  26. .nut09(true)
  27. .nut10(true)
  28. .nut11(true)
  29. .nut12(true)
  30. .nut14(true);
  31. let mint_info = MintInfo::new().nuts(nuts);
  32. let mnemonic = Mnemonic::generate(12).unwrap();
  33. let quote_ttl = QuoteTTL::new(10000, 10000);
  34. Mint::new(
  35. MINT_URL,
  36. &mnemonic.to_seed_normalized(""),
  37. mint_info,
  38. quote_ttl,
  39. Arc::new(MintMemoryDatabase::default()),
  40. HashMap::new(),
  41. supported_units,
  42. )
  43. .await
  44. .unwrap()
  45. }
  46. async fn initialize() -> &'static Mint {
  47. INSTANCE.get_or_init(|| new_mint(0)).await
  48. }
  49. async fn mint_proofs(
  50. mint: &Mint,
  51. amount: Amount,
  52. split_target: &SplitTarget,
  53. keys: cdk::nuts::Keys,
  54. ) -> Result<Proofs> {
  55. let request_lookup = uuid::Uuid::new_v4().to_string();
  56. let quote = MintQuote::new(
  57. mint.mint_url.clone(),
  58. "".to_string(),
  59. CurrencyUnit::Sat,
  60. amount,
  61. unix_time() + 36000,
  62. request_lookup.to_string(),
  63. );
  64. mint.localstore.add_mint_quote(quote.clone()).await?;
  65. mint.pay_mint_quote_for_request_id(&request_lookup).await?;
  66. let keyset_id = Id::from(&keys);
  67. let premint = PreMintSecrets::random(keyset_id, amount, split_target)?;
  68. let mint_request = MintBolt11Request {
  69. quote: quote.id,
  70. outputs: premint.blinded_messages(),
  71. };
  72. let after_mint = mint.process_mint_request(mint_request).await?;
  73. let proofs = construct_proofs(
  74. after_mint.signatures,
  75. premint.rs(),
  76. premint.secrets(),
  77. &keys,
  78. )?;
  79. Ok(proofs)
  80. }
  81. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  82. async fn test_mint_double_spend() -> Result<()> {
  83. let mint = initialize().await;
  84. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  85. let keyset_id = Id::from(&keys);
  86. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  87. let preswap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  88. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  89. let swap = mint.process_swap_request(swap_request).await;
  90. assert!(swap.is_ok());
  91. let preswap_two = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  92. let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
  93. match mint.process_swap_request(swap_two_request).await {
  94. Ok(_) => bail!("Proofs double spent"),
  95. Err(err) => match err {
  96. cdk::Error::TokenAlreadySpent => (),
  97. _ => bail!("Wrong error returned"),
  98. },
  99. }
  100. Ok(())
  101. }
  102. /// This attempts to swap for more outputs then inputs.
  103. /// This will work if the mint does not check for outputs amounts overflowing
  104. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  105. async fn test_attempt_to_swap_by_overflowing() -> Result<()> {
  106. let mint = initialize().await;
  107. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  108. let keyset_id = Id::from(&keys);
  109. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  110. let amount = 2_u64.pow(63);
  111. let pre_mint_amount =
  112. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
  113. let pre_mint_amount_two =
  114. PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
  115. let mut pre_mint = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
  116. pre_mint.combine(pre_mint_amount);
  117. pre_mint.combine(pre_mint_amount_two);
  118. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  119. match mint.process_swap_request(swap_request).await {
  120. Ok(_) => bail!("Swap occurred with overflow"),
  121. Err(err) => match err {
  122. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  123. _ => {
  124. println!("{:?}", err);
  125. bail!("Wrong error returned in swap overflow")
  126. }
  127. },
  128. }
  129. Ok(())
  130. }
  131. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  132. pub async fn test_p2pk_swap() -> Result<()> {
  133. let mint = initialize().await;
  134. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  135. let keyset_id = Id::from(&keys);
  136. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  137. let secret = SecretKey::generate();
  138. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  139. let pre_swap = PreMintSecrets::with_conditions(
  140. keyset_id,
  141. 100.into(),
  142. &SplitTarget::default(),
  143. &spending_conditions,
  144. )?;
  145. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  146. let keys = mint.pubkeys().await?.keysets.first().cloned().unwrap().keys;
  147. let post_swap = mint.process_swap_request(swap_request).await?;
  148. let mut proofs = construct_proofs(
  149. post_swap.signatures,
  150. pre_swap.rs(),
  151. pre_swap.secrets(),
  152. &keys,
  153. )?;
  154. let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
  155. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  156. match mint.process_swap_request(swap_request).await {
  157. Ok(_) => bail!("Proofs spent without sig"),
  158. Err(err) => match err {
  159. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  160. _ => {
  161. println!("{:?}", err);
  162. bail!("Wrong error returned")
  163. }
  164. },
  165. }
  166. for proof in &mut proofs {
  167. proof.sign_p2pk(secret.clone())?;
  168. }
  169. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  170. let attempt_swap = mint.process_swap_request(swap_request).await;
  171. assert!(attempt_swap.is_ok());
  172. Ok(())
  173. }
  174. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  175. async fn test_swap_unbalanced() -> Result<()> {
  176. let mint = initialize().await;
  177. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  178. let keyset_id = Id::from(&keys);
  179. let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
  180. let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
  181. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  182. match mint.process_swap_request(swap_request).await {
  183. Ok(_) => bail!("Swap was allowed unbalanced"),
  184. Err(err) => match err {
  185. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  186. _ => bail!("Wrong error returned"),
  187. },
  188. }
  189. let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())?;
  190. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  191. match mint.process_swap_request(swap_request).await {
  192. Ok(_) => bail!("Swap was allowed unbalanced"),
  193. Err(err) => match err {
  194. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  195. _ => bail!("Wrong error returned"),
  196. },
  197. }
  198. Ok(())
  199. }
  200. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  201. async fn test_swap_overpay_underpay_fee() -> Result<()> {
  202. let mint = new_mint(1).await;
  203. mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1).await?;
  204. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  205. let keyset_id = Id::from(&keys);
  206. let proofs = mint_proofs(&mint, 1000.into(), &SplitTarget::default(), keys).await?;
  207. let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default())?;
  208. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  209. // Attempt to swap overpaying fee
  210. match mint.process_swap_request(swap_request).await {
  211. Ok(_) => bail!("Swap was allowed unbalanced"),
  212. Err(err) => match err {
  213. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  214. _ => {
  215. println!("{:?}", err);
  216. bail!("Wrong error returned")
  217. }
  218. },
  219. }
  220. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  221. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  222. // Attempt to swap underpaying fee
  223. match mint.process_swap_request(swap_request).await {
  224. Ok(_) => bail!("Swap was allowed unbalanced"),
  225. Err(err) => match err {
  226. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  227. _ => {
  228. println!("{:?}", err);
  229. bail!("Wrong error returned")
  230. }
  231. },
  232. }
  233. Ok(())
  234. }
  235. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  236. async fn test_mint_enforce_fee() -> Result<()> {
  237. let mint = new_mint(1).await;
  238. let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
  239. let keyset_id = Id::from(&keys);
  240. let mut proofs = mint_proofs(&mint, 1010.into(), &SplitTarget::Value(1.into()), keys).await?;
  241. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  242. let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default())?;
  243. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  244. // Attempt to swap underpaying fee
  245. match mint.process_swap_request(swap_request).await {
  246. Ok(_) => bail!("Swap was allowed unbalanced"),
  247. Err(err) => match err {
  248. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  249. _ => {
  250. println!("{:?}", err);
  251. bail!("Wrong error returned")
  252. }
  253. },
  254. }
  255. let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default())?;
  256. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  257. let _ = mint.process_swap_request(swap_request).await?;
  258. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  259. let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
  260. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  261. // Attempt to swap underpaying fee
  262. match mint.process_swap_request(swap_request).await {
  263. Ok(_) => bail!("Swap was allowed unbalanced"),
  264. Err(err) => match err {
  265. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  266. _ => {
  267. println!("{:?}", err);
  268. bail!("Wrong error returned")
  269. }
  270. },
  271. }
  272. let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default())?;
  273. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  274. let _ = mint.process_swap_request(swap_request).await?;
  275. Ok(())
  276. }