From 92e91b7d9b2712e505d2e0decb9f6ed1e7e1c11d Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Fri, 5 May 2023 20:36:21 -0400 Subject: [PATCH] rename token --- integration_test/src/main.rs | 34 ++++++++-------- src/cashu_wallet.rs | 58 ++++++++++++++++------------ src/client.rs | 45 +++++++++++++++------- src/dhke.rs | 4 +- src/error.rs | 8 ++++ src/types.rs | 75 ++++++++++++++++++++++++------------ 6 files changed, 139 insertions(+), 85 deletions(-) diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 297a9d64..9edc010c 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -7,15 +7,13 @@ use std::time::Duration; use bitcoin::Amount; use cashu_crab::cashu_wallet::CashuWallet; use cashu_crab::client::Client; -use cashu_crab::types::{MintKeys, Proof, Token, TokenData}; +use cashu_crab::types::{MintKeys, MintProofs, Proofs, Token}; use lightning_invoice::Invoice; -use url::Url; #[tokio::main] async fn main() { - let url = - Url::from_str("https://legend.lnbits.com/cashu/api/v1/SKvHRus9dmjWHhstHrsazW/").unwrap(); - let client = Client::new(url); + let url = "https://legend.lnbits.com/cashu/api/v1/MWMnRmbewX92AHjcL55mRo/"; + let client = Client::new(url).unwrap(); // NUT-09 // test_get_mint_info(&mint).await; @@ -25,16 +23,14 @@ async fn main() { test_get_mint_keysets(&client).await; test_request_mint(&wallet).await; let proofs = test_mint(&wallet).await; - let token = TokenData::new( + let token = Token::new( client.mint_url.clone(), proofs, Some("Hello World".to_string()), ); let new_token = test_receive(&wallet, &token.to_string()).await; - let proofs = TokenData::from_str(&new_token).unwrap().token[0] - .clone() - .proofs; + let proofs = Token::from_str(&new_token).unwrap().token[0].clone().proofs; test_send(&wallet, proofs.clone()).await; let spendable = test_check_spendable(&client, &new_token).await; @@ -60,13 +56,13 @@ async fn test_get_mint_keysets(client: &Client) { } async fn test_request_mint(wallet: &CashuWallet) { - let mint = wallet.request_mint(Amount::from_sat(21)).await.unwrap(); + let mint = wallet.request_mint(Amount::from_sat(5)).await.unwrap(); assert!(mint.pr.check_signature().is_ok()) } -async fn test_mint(wallet: &CashuWallet) -> Vec { - let mint_req = wallet.request_mint(Amount::from_sat(21)).await.unwrap(); +async fn test_mint(wallet: &CashuWallet) -> Proofs { + let mint_req = wallet.request_mint(Amount::from_sat(5)).await.unwrap(); println!("Mint Req: {:?}", mint_req.pr.to_string()); // Since before the mint happens the invoice in the mint req has to be paid this wait is here @@ -75,7 +71,7 @@ async fn test_mint(wallet: &CashuWallet) -> Vec { thread::sleep(Duration::from_secs(30)); wallet - .mint_token(Amount::from_sat(21), &mint_req.hash) + .mint_token(Amount::from_sat(5), &mint_req.hash) .await .unwrap() @@ -92,12 +88,12 @@ async fn test_check_fees(mint: &Client) { async fn test_receive(wallet: &CashuWallet, token: &str) -> String { let prom = wallet.receive(token).await.unwrap(); println!("{:?}", prom); - let token = Token { + let token = MintProofs { mint: wallet.client.mint_url.clone(), proofs: prom, }; - let token = TokenData { + let token = Token { token: vec![token], memo: Some("Hello world".to_string()), }; @@ -107,12 +103,12 @@ async fn test_receive(wallet: &CashuWallet, token: &str) -> String { s } -async fn test_check_spendable(client: &Client, token: &str) -> Vec { +async fn test_check_spendable(client: &Client, token: &str) -> Proofs { let mint_keys = client.get_keys().await.unwrap(); let wallet = CashuWallet::new(client.to_owned(), mint_keys); - let token_data = TokenData::from_str(token).unwrap(); + let token_data = Token::from_str(token).unwrap(); let spendable = wallet .check_proofs_spent(token_data.token[0].clone().proofs) .await @@ -124,7 +120,7 @@ async fn test_check_spendable(client: &Client, token: &str) -> Vec { spendable.spendable } -async fn test_send(wallet: &CashuWallet, proofs: Vec) { +async fn test_send(wallet: &CashuWallet, proofs: Proofs) { let send = wallet.send(Amount::from_sat(2), proofs).await.unwrap(); println!("{:?}", send); @@ -137,7 +133,7 @@ async fn test_send(wallet: &CashuWallet, proofs: Vec) { println!("Send Token: {send_token}"); } -async fn test_melt(wallet: &CashuWallet, invoice: Invoice, proofs: Vec) { +async fn test_melt(wallet: &CashuWallet, invoice: Invoice, proofs: Proofs) { let res = wallet.melt(invoice, proofs).await.unwrap(); println!("{:?}", res); diff --git a/src/cashu_wallet.rs b/src/cashu_wallet.rs index 673c0544..06c7e3ae 100644 --- a/src/cashu_wallet.rs +++ b/src/cashu_wallet.rs @@ -8,23 +8,31 @@ use crate::{ dhke::construct_proofs, error::Error, types::{ - BlindedMessages, Melted, MintKeys, Proof, ProofsStatus, RequestMintResponse, SendProofs, - SplitPayload, SplitRequest, TokenData, + BlindedMessages, Melted, MintKeys, Proofs, ProofsStatus, RequestMintResponse, SendProofs, + SplitPayload, SplitRequest, Token, }, }; +#[derive(Clone, Debug)] pub struct CashuWallet { pub client: Client, pub mint_keys: MintKeys, + pub balance: Amount, } impl CashuWallet { pub fn new(client: Client, mint_keys: MintKeys) -> Self { - Self { client, mint_keys } + Self { + client, + mint_keys, + balance: Amount::ZERO, + } } + // TODO: getter method for keys that if it cant get them try agian + /// Check if a proof is spent - pub async fn check_proofs_spent(&self, proofs: Vec) -> Result { + pub async fn check_proofs_spent(&self, proofs: Proofs) -> Result { let spendable = self.client.check_spendable(&proofs).await?; // Separate proofs in spent and unspent based on mint response @@ -45,7 +53,7 @@ impl CashuWallet { } /// Mint Token - pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result, Error> { + pub async fn mint_token(&self, amount: Amount, hash: &str) -> Result { let blinded_messages = BlindedMessages::random(amount)?; let mint_res = self.client.mint(blinded_messages.clone(), hash).await?; @@ -66,8 +74,8 @@ impl CashuWallet { } /// Receive - pub async fn receive(&self, encoded_token: &str) -> Result, Error> { - let token_data = TokenData::from_str(encoded_token)?; + pub async fn receive(&self, encoded_token: &str) -> Result { + let token_data = Token::from_str(encoded_token)?; let mut proofs = vec![]; for token in token_data.token { @@ -75,13 +83,12 @@ impl CashuWallet { continue; } - let keys = if token.mint.eq(&self.client.mint_url) { + let keys = if token.mint.to_string().eq(&self.client.mint_url.to_string()) { self.mint_keys.clone() } else { - // FIXME: - println!("No match"); - self.mint_keys.clone() - // CashuMint::new(token.mint).get_keys().await.unwrap() + // println!("dd"); + // self.mint_keys.clone() + Client::new(token.mint.as_str())?.get_keys().await.unwrap() }; // Sum amount of all proofs @@ -90,11 +97,13 @@ impl CashuWallet { .iter() .fold(Amount::ZERO, |acc, p| acc + p.amount); - let split_payload = self - .create_split(Amount::ZERO, amount, token.proofs) - .await?; + let split_payload = self.create_split(Amount::ZERO, amount, token.proofs)?; - let split_response = self.client.split(split_payload.split_payload).await?; + let split_response = self + .client + .split(split_payload.split_payload) + .await + .unwrap(); // Proof to keep let keep_proofs = construct_proofs( @@ -120,11 +129,11 @@ impl CashuWallet { } /// Create Split Payload - async fn create_split( + fn create_split( &self, keep_amount: Amount, send_amount: Amount, - proofs: Vec, + proofs: Proofs, ) -> Result { let keep_blinded_messages = BlindedMessages::random(keep_amount)?; let send_blinded_messages = BlindedMessages::random(send_amount)?; @@ -148,7 +157,7 @@ impl CashuWallet { } /// Send - pub async fn send(&self, amount: Amount, proofs: Vec) -> Result { + pub async fn send(&self, amount: Amount, proofs: Proofs) -> Result { let mut amount_available = Amount::ZERO; let mut send_proofs = SendProofs::default(); @@ -175,9 +184,8 @@ impl CashuWallet { let amount_to_keep = amount_available - amount; let amount_to_send = amount; - let split_payload = self - .create_split(amount_to_keep, amount_to_send, send_proofs.send_proofs) - .await?; + let split_payload = + self.create_split(amount_to_keep, amount_to_send, send_proofs.send_proofs)?; let split_response = self.client.split(split_payload.split_payload).await?; @@ -209,7 +217,7 @@ impl CashuWallet { pub async fn melt( &self, invoice: lightning_invoice::Invoice, - proofs: Vec, + proofs: Proofs, ) -> Result { let change = BlindedMessages::blank()?; let melt_response = self @@ -234,7 +242,7 @@ impl CashuWallet { }) } - pub fn proofs_to_token(&self, proofs: Vec, memo: Option) -> String { - TokenData::new(self.client.mint_url.clone(), proofs, memo).to_string() + pub fn proofs_to_token(&self, proofs: Proofs, memo: Option) -> String { + Token::new(self.client.mint_url.clone(), proofs, memo).to_string() } } diff --git a/src/client.rs b/src/client.rs index 9545d951..f965bbaa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -24,25 +24,42 @@ pub struct Client { } impl Client { - pub fn new(mint_url: Url) -> Self { - Self { mint_url } + pub fn new(mint_url: &str) -> Result { + // HACK + let mut mint_url = String::from(mint_url); + if !mint_url.ends_with('/') { + mint_url.push('/'); + } + let mint_url = Url::parse(&mint_url).unwrap(); + Ok(Self { mint_url }) } /// Get Mint Keys [NUT-01] pub async fn get_keys(&self) -> Result { let url = self.mint_url.join("keys")?; - let keys = minreq::get(url).send()?.json::>()?; + let keys = minreq::get(url.clone()).send()?.json::()?; - Ok(MintKeys( - keys.into_iter() - .map(|(k, v)| { - ( - k, - PublicKey::from_sec1_bytes(&hex::decode(v).unwrap()).unwrap(), - ) - }) - .collect(), - )) + let keys: HashMap = match serde_json::from_value(keys.clone()) { + Ok(keys) => keys, + Err(_err) => { + return Err(Error::CustomError(format!( + "url: {}, {}", + url, + serde_json::to_string(&keys)? + ))) + } + }; + + let mint_keys: HashMap = keys + .into_iter() + .filter_map(|(k, v)| { + let key = hex::decode(v).ok()?; + let public_key = PublicKey::from_sec1_bytes(&key).ok()?; + Some((k, public_key)) + }) + .collect(); + + Ok(MintKeys(mint_keys)) } /// Get Keysets [NUT-02] @@ -126,7 +143,7 @@ impl Client { // TODO: need to handle response error // specifically token already spent - // println!("{:?}", res); + println!("Split Res: {:?}", res); Ok(serde_json::from_value(res).unwrap()) } diff --git a/src/dhke.rs b/src/dhke.rs index 61fb457e..9fa53d8e 100644 --- a/src/dhke.rs +++ b/src/dhke.rs @@ -7,7 +7,7 @@ use bitcoin_hashes::Hash; use k256::{ProjectivePoint, PublicKey, Scalar, SecretKey}; use crate::error::Error; -use crate::types::{MintKeys, Promise, Proof}; +use crate::types::{MintKeys, Promise, Proof, Proofs}; fn hash_to_curve(message: &[u8]) -> PublicKey { let mut msg_to_hash = message.to_vec(); @@ -68,7 +68,7 @@ pub fn construct_proofs( rs: Vec, secrets: Vec, keys: &MintKeys, -) -> Result, Error> { +) -> Result { let mut proofs = vec![]; for (i, promise) in promises.into_iter().enumerate() { let blinded_c = promise.c; diff --git a/src/error.rs b/src/error.rs index 4b15b790..cec86412 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,4 +23,12 @@ pub enum Error { /// Insufficaint Funds #[error("Not enough funds")] InsufficantFunds, + #[error("Custom error: {0}")] + CustomError(String), + /// From hex error + #[error("From Hex Error: {0}")] + HexError(#[from] hex::FromHexError), + /// From elliptic curve + #[error("From Elliptic: {0}")] + EllipticError(#[from] k256::elliptic_curve::Error), } diff --git a/src/types.rs b/src/types.rs index e6ab9181..3d6a90c1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -40,7 +40,7 @@ pub struct BlindedMessages { } impl BlindedMessages { - /// + /// Outputs for specfied amount with random secret pub fn random(amount: Amount) -> Result { let mut blinded_messages = BlindedMessages::default(); @@ -59,6 +59,7 @@ impl BlindedMessages { Ok(blinded_messages) } + /// Blank Outputs used for NUT-08 change pub fn blank() -> Result { let mut blinded_messages = BlindedMessages::default(); @@ -120,10 +121,22 @@ pub struct Proof { pub script: Option, } +/// List of proofs +pub type Proofs = Vec; + /// Mint Keys [NUT-01] #[derive(Debug, Clone, PartialEq, Eq)] pub struct MintKeys(pub HashMap); +impl MintKeys { + pub fn as_hashmap(&self) -> HashMap { + self.0 + .iter() + .map(|(k, v)| (k.to_owned(), hex::encode(v.to_sec1_bytes()))) + .collect() + } +} + /// Mint Keysets [UT-02] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeySets { @@ -136,7 +149,7 @@ pub struct MintKeySets { pub struct RequestMintResponse { /// Bolt11 payment request pub pr: Invoice, - /// Random Hash + /// Random hash MUST not be the hash of invoice pub hash: String, } @@ -171,7 +184,7 @@ pub struct CheckFeesRequest { /// Melt Request [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MeltRequest { - pub proofs: Vec, + pub proofs: Proofs, /// bollt11 pub pr: Invoice, /// Blinded Message that can be used to return change [NUT-08] @@ -192,7 +205,7 @@ pub struct MeltResponse { pub struct Melted { pub paid: bool, pub preimage: Option, - pub change: Option>, + pub change: Option, } /// Split Request [NUT-06] @@ -200,7 +213,7 @@ pub struct Melted { pub struct SplitRequest { #[serde(with = "bitcoin::amount::serde::as_sat")] pub amount: Amount, - pub proofs: Vec, + pub proofs: Proofs, pub outputs: Vec, } @@ -216,7 +229,7 @@ pub struct SplitResponse { /// Check spendabale request [NUT-07] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckSpendableRequest { - pub proofs: Vec, + pub proofs: Proofs, } /// Check Spendable Response [NUT-07] @@ -229,14 +242,14 @@ pub struct CheckSpendableResponse { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProofsStatus { - pub spendable: Vec, - pub spent: Vec, + pub spendable: Proofs, + pub spent: Proofs, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct SendProofs { - pub change_proofs: Vec, - pub send_proofs: Vec, + pub change_proofs: Proofs, + pub send_proofs: Proofs, } /// Mint Version @@ -296,14 +309,14 @@ pub struct MintInfo { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Token { +pub struct MintProofs { #[serde(with = "serde_url")] pub mint: Url, - pub proofs: Vec, + pub proofs: Proofs, } -impl Token { - fn new(mint_url: Url, proofs: Vec) -> Self { +impl MintProofs { + fn new(mint_url: Url, proofs: Proofs) -> Self { Self { mint: mint_url, proofs, @@ -312,21 +325,33 @@ impl Token { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct TokenData { - pub token: Vec, +pub struct Token { + pub token: Vec, pub memo: Option, } -impl TokenData { - pub fn new(mint_url: Url, proofs: Vec, memo: Option) -> Self { +impl Token { + pub fn new(mint_url: Url, proofs: Proofs, memo: Option) -> Self { Self { - token: vec![Token::new(mint_url, proofs)], + token: vec![MintProofs::new(mint_url, proofs)], memo, } } + + pub fn token_info(&self) -> (u64, String) { + let mut amount = Amount::ZERO; + + for proofs in &self.token { + for proof in &proofs.proofs { + amount += proof.amount; + } + } + + (amount.to_sat(), self.token[0].mint.to_string()) + } } -impl FromStr for TokenData { +impl FromStr for Token { type Err = Error; fn from_str(s: &str) -> Result { @@ -338,12 +363,12 @@ impl FromStr for TokenData { let decoded = general_purpose::STANDARD.decode(s)?; let decoded_str = String::from_utf8(decoded)?; println!("decode: {:?}", decoded_str); - let token: TokenData = serde_json::from_str(&decoded_str)?; + let token: Token = serde_json::from_str(&decoded_str)?; Ok(token) } } -impl ToString for TokenData { +impl ToString for Token { fn to_string(&self) -> String { let json_string = serde_json::to_string(self).unwrap(); let encoded = general_purpose::STANDARD.encode(json_string); @@ -358,7 +383,7 @@ mod tests { #[test] fn test_proof_seralize() { let proof = "[{\"id\":\"DSAl9nvvyfva\",\"amount\":2,\"secret\":\"EhpennC9qB3iFlW8FZ_pZw\",\"C\":\"02c020067db727d586bc3183aecf97fcb800c3f4cc4759f69c626c9db5d8f5b5d4\"},{\"id\":\"DSAl9nvvyfva\",\"amount\":8,\"secret\":\"TmS6Cv0YT5PU_5ATVKnukw\",\"C\":\"02ac910bef28cbe5d7325415d5c263026f15f9b967a079ca9779ab6e5c2db133a7\"}]"; - let proof: Vec = serde_json::from_str(proof).unwrap(); + let proof: Proofs = serde_json::from_str(proof).unwrap(); assert_eq!(proof[0].clone().id.unwrap(), "DSAl9nvvyfva"); } @@ -366,7 +391,7 @@ mod tests { #[test] fn test_token_str_round_trip() { let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmt5b3UuIn0="; - let token = TokenData::from_str(token_str).unwrap(); + let token = Token::from_str(token_str).unwrap(); assert_eq!( token.token[0].mint, @@ -376,7 +401,7 @@ mod tests { let encoded = &token.to_string(); - let token_data = TokenData::from_str(encoded).unwrap(); + let token_data = Token::from_str(encoded).unwrap(); assert_eq!(token_data, token); }