From e93104a3f1f15a735631c1c8e09ab86fb7ca5e64 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Sun, 23 Apr 2023 15:21:09 -0400 Subject: [PATCH] mint tests --- Cargo.toml | 1 + src/cashu_mint.rs | 11 ++++++----- src/dhke.rs | 1 - src/lib.rs | 1 + src/types.rs | 40 ++++++++++++++++++++++++++++++++++++++- src/utils.rs | 36 +++++++++++++++++++++++++++++++++++ tests/integration_test.rs | 22 ++++++++++++++++++++- 7 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 7ff2b324..023cf29c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ bitcoin_hashes = "0.12.0" hex = "0.4.3" lightning-invoice = { version = "0.22.0", features=["serde"] } minreq = { version = "2.7.0", features = ["json-using-serde", "https"] } +rand = "0.8.5" secp256k1 = { version = "0.27.0", features = ["rand-std", "bitcoin-hashes-std"] } serde = { version = "1.0.160", features = ["derive"]} thiserror = "1.0.40" diff --git a/src/cashu_mint.rs b/src/cashu_mint.rs index 5881a2e6..2b8a3a3b 100644 --- a/src/cashu_mint.rs +++ b/src/cashu_mint.rs @@ -5,9 +5,10 @@ use url::Url; use crate::{ error::Error, types::{ - BlindedMessage, CheckFeesRequest, CheckFeesResponse, CheckSpendableRequest, - CheckSpendableResponse, MeltRequest, MeltResposne, MintInfo, MintKeySets, MintKeys, - MintRequest, PostMintResponse, Proof, RequestMintResponse, SplitRequest, SplitResponse, + BlindedMessage, BlindedMessages, CheckFeesRequest, CheckFeesResponse, + CheckSpendableRequest, CheckSpendableResponse, MeltRequest, MeltResposne, MintInfo, + MintKeySets, MintKeys, MintRequest, PostMintResponse, Proof, RequestMintResponse, + SplitRequest, SplitResponse, }, }; @@ -45,7 +46,7 @@ impl CashuMint { /// Mint Tokens [NUT-04] pub async fn mint( &self, - blinded_messages: Vec, + blinded_messages: BlindedMessages, payment_hash: &str, ) -> Result { let mut url = self.url.join("mint")?; @@ -53,7 +54,7 @@ impl CashuMint { .append_pair("payment_hash", payment_hash); let request = MintRequest { - outputs: blinded_messages, + outputs: blinded_messages.blinded_messages, }; Ok(minreq::post(url) diff --git a/src/dhke.rs b/src/dhke.rs index 42829205..035363f9 100644 --- a/src/dhke.rs +++ b/src/dhke.rs @@ -1,7 +1,6 @@ //! Diffie-Hellmann key exchange use bitcoin_hashes::sha256; -// use bitcoin_hashes::Hash; use bitcoin_hashes::Hash; use secp256k1::rand::rngs::OsRng; use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey}; diff --git a/src/lib.rs b/src/lib.rs index 973197f6..b555bae8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,3 +2,4 @@ pub mod cashu_mint; pub mod dhke; pub mod error; pub mod types; +pub mod utils; diff --git a/src/types.rs b/src/types.rs index e21d66d9..2b2815c5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,8 +4,12 @@ use std::collections::HashMap; use bitcoin::Amount; use lightning_invoice::Invoice; +use rand::Rng; +use secp256k1::{PublicKey, SecretKey}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::{dhke::blind_message, error::Error, utils::split_amount}; + /// Blinded Message [NUT-00] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BlindedMessage { @@ -14,7 +18,41 @@ pub struct BlindedMessage { pub amount: Amount, /// encrypted secret message (B_) #[serde(rename = "B_")] - pub b: String, + pub b: PublicKey, +} + +/// Blinded Messages [NUT-00] +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct BlindedMessages { + /// Blinded messages + pub blinded_messages: Vec, + /// Secrets + pub secrets: Vec>, + /// Rs + pub rs: Vec, + /// Amounts + pub amounts: Vec, +} + +impl BlindedMessages { + pub fn random(amount: Amount) -> Result { + let mut blinded_messages = BlindedMessages::default(); + + let mut rng = rand::thread_rng(); + for amount in split_amount(amount) { + let bytes: [u8; 32] = rng.gen(); + let (blinded, r) = blind_message(&bytes, None)?; + + let blinded_message = BlindedMessage { amount, b: blinded }; + + blinded_messages.secrets.push(bytes.to_vec()); + blinded_messages.blinded_messages.push(blinded_message); + blinded_messages.rs.push(r); + blinded_messages.amounts.push(amount); + } + + Ok(blinded_messages) + } } /// Promise (BlindedSignature) [NIP-00] diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..4527bd38 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,36 @@ +use bitcoin::Amount; + +/// Split amount into cashu denominations (powers of 2) +pub fn split_amount(amount: Amount) -> Vec { + let mut chunks = Vec::new(); + let value = amount.to_sat(); + for i in 0..64 { + let mask = 1 << i; + if (value & mask) != 0 { + chunks.push(Amount::from_sat(2u64.pow(i as u32))); + } + } + chunks +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_amount() { + assert_eq!(split_amount(Amount::from_sat(1)), vec![Amount::from_sat(1)]); + assert_eq!(split_amount(Amount::from_sat(2)), vec![Amount::from_sat(2)]); + assert_eq!( + split_amount(Amount::from_sat(3)), + vec![Amount::from_sat(1), Amount::from_sat(2)] + ); + let amounts: Vec = vec![1, 2, 8].iter().map(|a| Amount::from_sat(*a)).collect(); + assert_eq!(split_amount(Amount::from_sat(11)), amounts); + let amounts: Vec = vec![1, 2, 4, 8, 16, 32, 64, 128] + .iter() + .map(|a| Amount::from_sat(*a)) + .collect(); + assert_eq!(split_amount(Amount::from_sat(255)), amounts); + } +} diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 888d7174..553ed7c5 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,10 +1,12 @@ use std::str::FromStr; +use std::thread; +use std::time::Duration; use bitcoin::Amount; use lightning_invoice::Invoice; use url::Url; -use cashu_rs::cashu_mint::CashuMint; +use cashu_rs::{cashu_mint::CashuMint, types::BlindedMessages}; const MINTURL: &str = "https://legend.lnbits.com/cashu/api/v1/SKvHRus9dmjWHhstHrsazW/"; @@ -36,6 +38,24 @@ async fn test_request_mint() { assert!(mint.pr.check_signature().is_ok()) } +#[tokio::test] +async fn test_mint() { + let url = Url::from_str(MINTURL).unwrap(); + let mint = CashuMint::new(url); + let mint_req = mint.request_mint(Amount::from_sat(21)).await.unwrap(); + println!("Mint Req: {:?}", mint_req.pr.to_string()); + + // Since before the mind happens the invoice in the int req has to be payed this wait is here + // probally some way to simulate this in a better way + // but for now pay it quick + thread::sleep(Duration::from_secs(10)); + + let blinded_messages = BlindedMessages::random(Amount::from_sat(21)).unwrap(); + let mint_res = mint.mint(blinded_messages, &mint_req.hash).await.unwrap(); + + println!("Mint: {:?}", mint_res); +} + #[tokio::test] async fn test_check_fees() { let invoice = Invoice::from_str("lnbc10n1p3a6s0dsp5n55r506t2fv4r0mjcg30v569nk2u9s40ur4v3r3mgtscjvkvnrqqpp5lzfv8fmjzduelk74y9rsrxrayvhyzcdsh3zkdgv0g50napzalvqsdqhf9h8vmmfvdjn5gp58qengdqxq8p3aaymdcqpjrzjqwryaup9lh50kkranzgcdnn2fgvx390wgj5jd07rwr3vxeje0glc7z70cgqqg4sqqqqqqqlgqqqqrucqjq9qyysgqrjky5axsldzhqsjwsc38xa37k6t04le3ws4t26nqej62vst5xkz56qw85r6c4a3tr79588e0ceuuahwgfnkqc6n6269unlwqtvwr5vqqy0ncdq").unwrap();