//! Types for `cashu-rs` 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 { /// Amount in satoshi #[serde(with = "bitcoin::amount::serde::as_sat")] pub amount: Amount, /// encrypted secret message (B_) #[serde(rename = "B_")] 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] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Promise { pub id: String, #[serde(with = "bitcoin::amount::serde::as_sat")] pub amount: Amount, /// blinded signature (C_) on the secret message `B_` of [BlindedMessage] #[serde(rename = "C_")] pub c: String, } /// Proofs [NUT-00] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Proof { /// Amount in satoshi #[serde(with = "bitcoin::amount::serde::as_sat")] pub amount: Amount, /// Secret message pub secret: String, /// Unblinded signature #[serde(rename = "C")] pub c: String, /// `Keyset id` pub id: Option, /// P2SHScript that specifies the spending condition for this Proof pub script: Option, } /// Mint Keys [NIP-01] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeys(pub HashMap); /// Mint Keysets [NIP-02] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintKeySets { /// set of public keys that the mint generates pub keysets: Vec, } /// Mint request response [NUT-03] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RequestMintResponse { /// Bolt11 payment request pub pr: Invoice, /// Hash of Invoice pub hash: String, } /// Post Mint Request [NIP-04] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintRequest { pub outputs: Vec, } /// Post Mint Response [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PostMintResponse { pub promises: Vec, } /// Check Fees Response [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckFeesResponse { /// Expected Mac Fee in satoshis #[serde(with = "bitcoin::amount::serde::as_sat")] pub fee: Amount, } /// Check Fees request [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckFeesRequest { /// Lighting Invoice pub pr: Invoice, } /// Melt Request [NUT-05] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MeltRequest { pub proofs: Vec, /// bollt11 pub pr: Invoice, /// Blinded Message that can be used to return change [NUT-08] /// Amount feild of blindedMessages `SHOULD` be set to zero pub outputs: Option>, } /// Melt Response [NUT-05] /// Lightning fee return [NUT-08] if change is defined #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MeltResposne { pub paid: bool, pub preimage: String, pub change: Option, } /// Split Request [NUT-06] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SplitRequest { #[serde(with = "bitcoin::amount::serde::as_sat")] pub amount: Amount, pub proofs: Vec, pub outputs: Vec, } /// Split Response [NUT-06] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SplitResponse { /// Promises to keep pub fst: Vec, /// Promises to send pub snd: Vec, } /// Check spendabale request [NUT-07] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckSpendableRequest { pub proofs: Vec, } /// Check Spendable Response [NUT-07] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CheckSpendableResponse { /// booleans indicating whether the provided Proof is still spendable. /// In same order as provided proofs pub spendable: Vec, } /// Mint Version #[derive(Debug, Clone, PartialEq, Eq)] pub struct MintVersion { name: String, version: String, } impl Serialize for MintVersion { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let combined = format!("{}/{}", self.name, self.version); serializer.serialize_str(&combined) } } impl<'de> Deserialize<'de> for MintVersion { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let combined = String::deserialize(deserializer)?; let parts: Vec<&str> = combined.split('/').collect(); if parts.len() != 2 { return Err(serde::de::Error::custom("Invalid input string")); } Ok(MintVersion { name: parts[0].to_string(), version: parts[1].to_string(), }) } } /// Mint Info [NIP-09] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct MintInfo { /// name of the mint and should be recognizable pub name: String, /// hex pubkey of the mint pub pubkey: String, /// implementation name and the version running pub version: MintVersion, /// short description of the mint pub description: String, /// long description pub description_long: String, /// contact methods to reach the mint operator pub contact: HashMap, /// shows which NUTs the mint supports pub nuts: Vec, /// message of the day that the wallet must display to the user pub motd: String, }