types.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. //! Types for `cashu-rs`
  2. use std::str::FromStr;
  3. use base64::{engine::general_purpose, Engine as _};
  4. use bitcoin::Amount;
  5. use k256::{PublicKey, SecretKey};
  6. use serde::{Deserialize, Deserializer, Serialize, Serializer};
  7. use url::Url;
  8. use crate::utils::generate_secret;
  9. pub use crate::Invoice;
  10. use crate::{
  11. dhke::blind_message, error::Error, serde_utils, serde_utils::serde_url, utils::split_amount,
  12. };
  13. /// Blinded Message [NUT-00]
  14. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  15. pub struct BlindedMessage {
  16. /// Amount in satoshi
  17. #[serde(with = "bitcoin::amount::serde::as_sat")]
  18. pub amount: Amount,
  19. /// encrypted secret message (B_)
  20. #[serde(rename = "B_")]
  21. #[serde(with = "serde_utils::serde_public_key")]
  22. pub b: PublicKey,
  23. }
  24. /// Blinded Messages [NUT-00]
  25. #[derive(Debug, Default, Clone, PartialEq, Eq)]
  26. pub struct BlindedMessages {
  27. /// Blinded messages
  28. pub blinded_messages: Vec<BlindedMessage>,
  29. /// Secrets
  30. pub secrets: Vec<String>,
  31. /// Rs
  32. pub rs: Vec<SecretKey>,
  33. /// Amounts
  34. pub amounts: Vec<Amount>,
  35. }
  36. impl BlindedMessages {
  37. /// Outputs for speceifed amount with random secret
  38. pub fn random(amount: Amount) -> Result<Self, Error> {
  39. let mut blinded_messages = BlindedMessages::default();
  40. for amount in split_amount(amount) {
  41. let secret = generate_secret();
  42. let (blinded, r) = blind_message(secret.as_bytes(), None)?;
  43. let blinded_message = BlindedMessage { amount, b: blinded };
  44. blinded_messages.secrets.push(secret);
  45. blinded_messages.blinded_messages.push(blinded_message);
  46. blinded_messages.rs.push(r);
  47. blinded_messages.amounts.push(amount);
  48. }
  49. Ok(blinded_messages)
  50. }
  51. /// Blank Outputs used for NUT-08 change
  52. pub fn blank() -> Result<Self, Error> {
  53. let mut blinded_messages = BlindedMessages::default();
  54. for _i in 0..4 {
  55. let secret = generate_secret();
  56. let (blinded, r) = blind_message(secret.as_bytes(), None)?;
  57. let blinded_message = BlindedMessage {
  58. amount: Amount::ZERO,
  59. b: blinded,
  60. };
  61. blinded_messages.secrets.push(secret);
  62. blinded_messages.blinded_messages.push(blinded_message);
  63. blinded_messages.rs.push(r);
  64. blinded_messages.amounts.push(Amount::ZERO);
  65. }
  66. Ok(blinded_messages)
  67. }
  68. }
  69. #[derive(Debug, Clone, PartialEq, Eq)]
  70. pub struct SplitPayload {
  71. pub keep_blinded_messages: BlindedMessages,
  72. pub send_blinded_messages: BlindedMessages,
  73. pub split_payload: SplitRequest,
  74. }
  75. /// Promise (BlindedSignature) [NUT-00]
  76. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  77. pub struct Promise {
  78. pub id: String,
  79. #[serde(with = "bitcoin::amount::serde::as_sat")]
  80. pub amount: Amount,
  81. /// blinded signature (C_) on the secret message `B_` of [BlindedMessage]
  82. #[serde(rename = "C_")]
  83. #[serde(with = "serde_utils::serde_public_key")]
  84. pub c: PublicKey,
  85. }
  86. /// Proofs [NUT-00]
  87. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  88. pub struct Proof {
  89. /// Amount in satoshi
  90. #[serde(with = "bitcoin::amount::serde::as_sat")]
  91. pub amount: Amount,
  92. /// Secret message
  93. // #[serde(with = "crate::serde_utils::bytes_base64")]
  94. pub secret: String,
  95. /// Unblinded signature
  96. #[serde(rename = "C")]
  97. #[serde(with = "serde_utils::serde_public_key")]
  98. pub c: PublicKey,
  99. /// `Keyset id`
  100. pub id: Option<String>,
  101. #[serde(skip_serializing_if = "Option::is_none")]
  102. /// P2SHScript that specifies the spending condition for this Proof
  103. pub script: Option<String>,
  104. }
  105. /// List of proofs
  106. pub type Proofs = Vec<Proof>;
  107. /// Mint request response [NUT-03]
  108. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  109. pub struct RequestMintResponse {
  110. /// Bolt11 payment request
  111. pub pr: Invoice,
  112. /// Random hash MUST not be the hash of invoice
  113. pub hash: String,
  114. }
  115. /// Post Mint Request [NUT-04]
  116. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  117. pub struct MintRequest {
  118. pub outputs: Vec<BlindedMessage>,
  119. }
  120. /// Post Mint Response [NUT-05]
  121. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  122. pub struct PostMintResponse {
  123. pub promises: Vec<Promise>,
  124. }
  125. /// Check Fees Response [NUT-05]
  126. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  127. pub struct CheckFeesResponse {
  128. /// Expected Mac Fee in satoshis
  129. #[serde(with = "bitcoin::amount::serde::as_sat")]
  130. pub fee: Amount,
  131. }
  132. /// Check Fees request [NUT-05]
  133. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  134. pub struct CheckFeesRequest {
  135. /// Lighting Invoice
  136. pub pr: Invoice,
  137. }
  138. /// Melt Request [NUT-05]
  139. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  140. pub struct MeltRequest {
  141. pub proofs: Proofs,
  142. /// bollt11
  143. pub pr: Invoice,
  144. /// Blinded Message that can be used to return change [NUT-08]
  145. /// Amount field of blindedMessages `SHOULD` be set to zero
  146. pub outputs: Option<Vec<BlindedMessage>>,
  147. }
  148. /// Melt Response [NUT-05]
  149. /// Lightning fee return [NUT-08] if change is defined
  150. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  151. pub struct MeltResponse {
  152. pub paid: bool,
  153. pub preimage: Option<String>,
  154. pub change: Option<Vec<Promise>>,
  155. }
  156. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  157. pub struct Melted {
  158. pub paid: bool,
  159. pub preimage: Option<String>,
  160. pub change: Option<Proofs>,
  161. }
  162. /// Split Request [NUT-06]
  163. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  164. pub struct SplitRequest {
  165. #[serde(with = "bitcoin::amount::serde::as_sat")]
  166. pub amount: Amount,
  167. pub proofs: Proofs,
  168. pub outputs: Vec<BlindedMessage>,
  169. }
  170. /// Split Response [NUT-06]
  171. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  172. pub struct SplitResponse {
  173. /// Promises to keep
  174. pub fst: Vec<Promise>,
  175. /// Promises to send
  176. pub snd: Vec<Promise>,
  177. }
  178. /// Check spendabale request [NUT-07]
  179. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  180. pub struct CheckSpendableRequest {
  181. pub proofs: Proofs,
  182. }
  183. /// Check Spendable Response [NUT-07]
  184. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  185. pub struct CheckSpendableResponse {
  186. /// booleans indicating whether the provided Proof is still spendable.
  187. /// In same order as provided proofs
  188. pub spendable: Vec<bool>,
  189. }
  190. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  191. pub struct ProofsStatus {
  192. pub spendable: Proofs,
  193. pub spent: Proofs,
  194. }
  195. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
  196. pub struct SendProofs {
  197. pub change_proofs: Proofs,
  198. pub send_proofs: Proofs,
  199. }
  200. /// Mint Version
  201. #[derive(Debug, Clone, PartialEq, Eq)]
  202. pub struct MintVersion {
  203. pub name: String,
  204. pub version: String,
  205. }
  206. impl Serialize for MintVersion {
  207. fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  208. where
  209. S: Serializer,
  210. {
  211. let combined = format!("{}/{}", self.name, self.version);
  212. serializer.serialize_str(&combined)
  213. }
  214. }
  215. impl<'de> Deserialize<'de> for MintVersion {
  216. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  217. where
  218. D: Deserializer<'de>,
  219. {
  220. let combined = String::deserialize(deserializer)?;
  221. let parts: Vec<&str> = combined.split('/').collect();
  222. if parts.len() != 2 {
  223. return Err(serde::de::Error::custom("Invalid input string"));
  224. }
  225. Ok(MintVersion {
  226. name: parts[0].to_string(),
  227. version: parts[1].to_string(),
  228. })
  229. }
  230. }
  231. /// Mint Info [NIP-09]
  232. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  233. pub struct MintInfo {
  234. /// name of the mint and should be recognizable
  235. pub name: Option<String>,
  236. /// hex pubkey of the mint
  237. #[serde(with = "serde_utils::serde_public_key::opt")]
  238. pub pubkey: Option<PublicKey>,
  239. /// implementation name and the version running
  240. pub version: Option<MintVersion>,
  241. /// short description of the mint
  242. pub description: Option<String>,
  243. /// long description
  244. pub description_long: Option<String>,
  245. /// contact methods to reach the mint operator
  246. pub contact: Vec<Vec<String>>,
  247. /// shows which NUTs the mint supports
  248. pub nuts: Vec<String>,
  249. /// message of the day that the wallet must display to the user
  250. pub motd: Option<String>,
  251. }
  252. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  253. pub struct MintProofs {
  254. #[serde(with = "serde_url")]
  255. pub mint: Url,
  256. pub proofs: Proofs,
  257. }
  258. impl MintProofs {
  259. fn new(mint_url: Url, proofs: Proofs) -> Self {
  260. Self {
  261. mint: mint_url,
  262. proofs,
  263. }
  264. }
  265. }
  266. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  267. pub struct Token {
  268. pub token: Vec<MintProofs>,
  269. pub memo: Option<String>,
  270. }
  271. impl Token {
  272. pub fn new(mint_url: Url, proofs: Proofs, memo: Option<String>) -> Self {
  273. Self {
  274. token: vec![MintProofs::new(mint_url, proofs)],
  275. memo,
  276. }
  277. }
  278. pub fn token_info(&self) -> (u64, String) {
  279. let mut amount = Amount::ZERO;
  280. for proofs in &self.token {
  281. for proof in &proofs.proofs {
  282. amount += proof.amount;
  283. }
  284. }
  285. (amount.to_sat(), self.token[0].mint.to_string())
  286. }
  287. }
  288. impl FromStr for Token {
  289. type Err = Error;
  290. fn from_str(s: &str) -> Result<Self, Self::Err> {
  291. if !s.starts_with("cashuA") {
  292. return Err(Error::UnsupportedToken);
  293. }
  294. let s = s.replace("cashuA", "");
  295. let decoded = general_purpose::STANDARD.decode(s)?;
  296. let decoded_str = String::from_utf8(decoded)?;
  297. println!("decode: {:?}", decoded_str);
  298. let token: Token = serde_json::from_str(&decoded_str)?;
  299. Ok(token)
  300. }
  301. }
  302. impl Token {
  303. pub fn convert_to_string(&self) -> Result<String, Error> {
  304. let json_string = serde_json::to_string(self)?;
  305. let encoded = general_purpose::STANDARD.encode(json_string);
  306. Ok(format!("cashuA{}", encoded))
  307. }
  308. }
  309. #[cfg(test)]
  310. mod tests {
  311. use super::*;
  312. #[test]
  313. fn test_proof_seralize() {
  314. let proof = "[{\"id\":\"DSAl9nvvyfva\",\"amount\":2,\"secret\":\"EhpennC9qB3iFlW8FZ_pZw\",\"C\":\"02c020067db727d586bc3183aecf97fcb800c3f4cc4759f69c626c9db5d8f5b5d4\"},{\"id\":\"DSAl9nvvyfva\",\"amount\":8,\"secret\":\"TmS6Cv0YT5PU_5ATVKnukw\",\"C\":\"02ac910bef28cbe5d7325415d5c263026f15f9b967a079ca9779ab6e5c2db133a7\"}]";
  315. let proof: Proofs = serde_json::from_str(proof).unwrap();
  316. assert_eq!(proof[0].clone().id.unwrap(), "DSAl9nvvyfva");
  317. }
  318. #[test]
  319. fn test_token_str_round_trip() {
  320. let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJpZCI6IkRTQWw5bnZ2eWZ2YSIsImFtb3VudCI6Miwic2VjcmV0IjoiRWhwZW5uQzlxQjNpRmxXOEZaX3BadyIsIkMiOiIwMmMwMjAwNjdkYjcyN2Q1ODZiYzMxODNhZWNmOTdmY2I4MDBjM2Y0Y2M0NzU5ZjY5YzYyNmM5ZGI1ZDhmNWI1ZDQifSx7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50Ijo4LCJzZWNyZXQiOiJUbVM2Q3YwWVQ1UFVfNUFUVktudWt3IiwiQyI6IjAyYWM5MTBiZWYyOGNiZTVkNzMyNTQxNWQ1YzI2MzAyNmYxNWY5Yjk2N2EwNzljYTk3NzlhYjZlNWMyZGIxMzNhNyJ9XX1dLCJtZW1vIjoiVGhhbmt5b3UuIn0=";
  321. let token = Token::from_str(token_str).unwrap();
  322. assert_eq!(
  323. token.token[0].mint,
  324. Url::from_str("https://8333.space:3338").unwrap()
  325. );
  326. assert_eq!(token.token[0].proofs[0].clone().id.unwrap(), "DSAl9nvvyfva");
  327. let encoded = &token.convert_to_string().unwrap();
  328. let token_data = Token::from_str(encoded).unwrap();
  329. assert_eq!(token_data, token);
  330. }
  331. }