mint.rs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. use std::collections::{HashMap, HashSet};
  2. use cashu::dhke::sign_message;
  3. use cashu::dhke::verify_message;
  4. pub use cashu::error::mint::Error;
  5. use cashu::nuts::nut00::BlindedMessage;
  6. use cashu::nuts::nut00::BlindedSignature;
  7. use cashu::nuts::nut00::Proof;
  8. use cashu::nuts::nut02::mint::KeySet;
  9. use cashu::nuts::nut02::Id;
  10. use cashu::nuts::nut06::SplitRequest;
  11. use cashu::nuts::nut06::SplitResponse;
  12. use cashu::nuts::nut07::CheckSpendableRequest;
  13. use cashu::nuts::nut07::CheckSpendableResponse;
  14. use cashu::nuts::nut08::MeltRequest;
  15. use cashu::nuts::nut08::MeltResponse;
  16. use cashu::nuts::*;
  17. use cashu::secret::Secret;
  18. use cashu::Amount;
  19. pub struct Mint {
  20. // pub pubkey: PublicKey,
  21. pub active_keyset: nut02::mint::KeySet,
  22. pub inactive_keysets: HashMap<Id, nut02::mint::KeySet>,
  23. pub spent_secrets: HashSet<Secret>,
  24. pub pending_secrets: HashSet<Secret>,
  25. pub fee_reserve: FeeReserve,
  26. }
  27. impl Mint {
  28. pub fn new(
  29. secret: &str,
  30. derivation_path: &str,
  31. inactive_keysets: HashMap<Id, nut02::mint::KeySet>,
  32. spent_secrets: HashSet<Secret>,
  33. max_order: u8,
  34. min_fee_reserve: Amount,
  35. percent_fee_reserve: f32,
  36. ) -> Self {
  37. Self {
  38. active_keyset: nut02::mint::KeySet::generate(secret, derivation_path, max_order),
  39. inactive_keysets,
  40. spent_secrets,
  41. pending_secrets: HashSet::new(),
  42. fee_reserve: FeeReserve {
  43. min_fee_reserve,
  44. percent_fee_reserve,
  45. },
  46. }
  47. }
  48. /// Retrieve the public keys of the active keyset for distribution to
  49. /// wallet clients
  50. pub fn active_keyset_pubkeys(&self) -> nut01::Response {
  51. nut01::Response {
  52. keys: nut02::KeySet::from(self.active_keyset.clone()).keys,
  53. }
  54. }
  55. /// Return a list of all supported keysets
  56. pub fn keysets(&self) -> nut02::Response {
  57. let mut keysets: HashSet<_> = self.inactive_keysets.keys().cloned().collect();
  58. keysets.insert(self.active_keyset.id);
  59. nut02::Response { keysets }
  60. }
  61. pub fn active_keyset(&self) -> nut02::mint::KeySet {
  62. self.active_keyset.clone()
  63. }
  64. pub fn keyset(&self, id: &Id) -> Option<nut02::KeySet> {
  65. if self.active_keyset.id.eq(id) {
  66. return Some(self.active_keyset.clone().into());
  67. }
  68. self.inactive_keysets.get(id).map(|k| k.clone().into())
  69. }
  70. /// Add current keyset to inactive keysets
  71. /// Generate new keyset
  72. pub fn rotate_keyset(
  73. &mut self,
  74. secret: impl Into<String>,
  75. derivation_path: impl Into<String>,
  76. max_order: u8,
  77. ) {
  78. // Add current set to inactive keysets
  79. self.inactive_keysets
  80. .insert(self.active_keyset.id, self.active_keyset.clone());
  81. self.active_keyset = KeySet::generate(secret, derivation_path, max_order);
  82. }
  83. pub fn process_mint_request(
  84. &mut self,
  85. mint_request: nut04::MintRequest,
  86. ) -> Result<nut04::PostMintResponse, Error> {
  87. let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
  88. for blinded_message in mint_request.outputs {
  89. blind_signatures.push(self.blind_sign(&blinded_message)?);
  90. }
  91. Ok(nut04::PostMintResponse {
  92. promises: blind_signatures,
  93. })
  94. }
  95. fn blind_sign(&self, blinded_message: &BlindedMessage) -> Result<BlindedSignature, Error> {
  96. let BlindedMessage { amount, b } = blinded_message;
  97. let Some(key_pair) = self.active_keyset.keys.0.get(amount) else {
  98. // No key for amount
  99. return Err(Error::AmountKey);
  100. };
  101. let c = sign_message(key_pair.secret_key.clone().into(), b.clone().into())?;
  102. Ok(BlindedSignature {
  103. amount: *amount,
  104. c: c.into(),
  105. id: self.active_keyset.id,
  106. })
  107. }
  108. pub fn process_split_request(
  109. &mut self,
  110. split_request: SplitRequest,
  111. ) -> Result<SplitResponse, Error> {
  112. let proofs_total = split_request.proofs_amount();
  113. let output_total = split_request.output_amount();
  114. if proofs_total != output_total {
  115. return Err(Error::Amount);
  116. }
  117. let proof_count = split_request.proofs.len();
  118. let secrets: HashSet<Secret> = split_request
  119. .proofs
  120. .iter()
  121. .map(|p| p.secret.clone())
  122. .collect();
  123. // Check that there are no duplicate proofs in request
  124. if secrets.len().ne(&proof_count) {
  125. return Err(Error::DuplicateProofs);
  126. }
  127. for secret in secrets {
  128. self.spent_secrets.insert(secret);
  129. }
  130. for proof in &split_request.proofs {
  131. self.verify_proof(proof)?
  132. }
  133. match &split_request.amount {
  134. None => {
  135. let promises: Vec<BlindedSignature> = split_request
  136. .outputs
  137. .iter()
  138. .map(|b| self.blind_sign(b).unwrap())
  139. .collect();
  140. Ok(SplitResponse::new(promises))
  141. }
  142. Some(amount) => {
  143. let outs_fst = (proofs_total.to_owned() - amount.to_owned()).split();
  144. // Blinded change messages
  145. let b_fst = split_request.outputs[0..outs_fst.len()].to_vec();
  146. let b_snd = split_request.outputs[outs_fst.len()..].to_vec();
  147. let fst: Vec<BlindedSignature> =
  148. b_fst.iter().map(|b| self.blind_sign(b).unwrap()).collect();
  149. let snd: Vec<BlindedSignature> =
  150. b_snd.iter().map(|b| self.blind_sign(b).unwrap()).collect();
  151. let split_response = SplitResponse::new_from_amount(fst, snd);
  152. if split_response.target_amount() != split_request.amount {
  153. return Err(Error::CustomError("Output order".to_string()));
  154. }
  155. Ok(split_response)
  156. }
  157. }
  158. }
  159. fn verify_proof(&self, proof: &Proof) -> Result<(), Error> {
  160. if self.spent_secrets.contains(&proof.secret) {
  161. return Err(Error::TokenSpent);
  162. }
  163. let keyset = proof.id.as_ref().map_or_else(
  164. || &self.active_keyset,
  165. |id| {
  166. if let Some(keyset) = self.inactive_keysets.get(id) {
  167. keyset
  168. } else {
  169. &self.active_keyset
  170. }
  171. },
  172. );
  173. let Some(keypair) = keyset.keys.0.get(&proof.amount) else {
  174. return Err(Error::AmountKey);
  175. };
  176. verify_message(
  177. keypair.secret_key.clone().into(),
  178. proof.c.clone().into(),
  179. &proof.secret,
  180. )?;
  181. Ok(())
  182. }
  183. pub fn check_spendable(
  184. &self,
  185. check_spendable: &CheckSpendableRequest,
  186. ) -> Result<CheckSpendableResponse, Error> {
  187. let mut spendable = Vec::with_capacity(check_spendable.proofs.len());
  188. let mut pending = Vec::with_capacity(check_spendable.proofs.len());
  189. for proof in &check_spendable.proofs {
  190. spendable.push(!self.spent_secrets.contains(&proof.secret));
  191. pending.push(!self.pending_secrets.contains(&proof.secret));
  192. }
  193. Ok(CheckSpendableResponse { spendable, pending })
  194. }
  195. pub fn verify_melt_request(&mut self, melt_request: &MeltRequest) -> Result<(), Error> {
  196. let proofs_total = melt_request.proofs_amount();
  197. let percent_fee_reserve = Amount::from_sat(
  198. (proofs_total.to_sat() as f32 * self.fee_reserve.percent_fee_reserve) as u64,
  199. );
  200. let fee_reserve = if percent_fee_reserve > self.fee_reserve.min_fee_reserve {
  201. percent_fee_reserve
  202. } else {
  203. self.fee_reserve.min_fee_reserve
  204. };
  205. if proofs_total
  206. < melt_request
  207. .invoice_amount()
  208. .map_err(|_| Error::InvoiceAmountUndefined)?
  209. + fee_reserve
  210. {
  211. return Err(Error::Amount);
  212. }
  213. let secrets: HashSet<&Secret> = melt_request.proofs.iter().map(|p| &p.secret).collect();
  214. // Ensure proofs are unique and not being double spent
  215. if melt_request.proofs.len().ne(&secrets.len()) {
  216. return Err(Error::DuplicateProofs);
  217. }
  218. for proof in &melt_request.proofs {
  219. self.verify_proof(proof)?
  220. }
  221. Ok(())
  222. }
  223. pub fn process_melt_request(
  224. &mut self,
  225. melt_request: &MeltRequest,
  226. preimage: &str,
  227. total_spent: Amount,
  228. ) -> Result<MeltResponse, Error> {
  229. let secrets = Vec::with_capacity(melt_request.proofs.len());
  230. for secret in secrets {
  231. self.spent_secrets.insert(secret);
  232. }
  233. let change_target = melt_request.proofs_amount() - total_spent;
  234. let amounts = change_target.split();
  235. let mut change = Vec::with_capacity(amounts.len());
  236. if let Some(outputs) = &melt_request.outputs {
  237. for (i, amount) in amounts.iter().enumerate() {
  238. let mut message = outputs[i].clone();
  239. message.amount = *amount;
  240. let signature = self.blind_sign(&message)?;
  241. change.push(signature)
  242. }
  243. }
  244. Ok(MeltResponse {
  245. paid: true,
  246. preimage: Some(preimage.to_string()),
  247. change: Some(change),
  248. })
  249. }
  250. }
  251. pub struct FeeReserve {
  252. pub min_fee_reserve: Amount,
  253. pub percent_fee_reserve: f32,
  254. }