| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 | use std::collections::{HashMap, HashSet};use cashu::dhke::{sign_message, verify_message};use cashu::nuts::{    BlindedMessage, BlindedSignature, MeltBolt11Request, MeltBolt11Response, Proof, SwapRequest,    SwapResponse, *,};#[cfg(feature = "nut07")]use cashu::nuts::{CheckSpendableRequest, CheckSpendableResponse};use cashu::secret::Secret;use cashu::Amount;use serde::{Deserialize, Serialize};use thiserror::Error;use tracing::{debug, info};use crate::utils::unix_time;use crate::Mnemonic;mod localstore;use localstore::LocalStore;#[derive(Debug, Error)]pub enum Error {    /// Unknown Keyset    #[error("Unknown Keyset")]    UnknownKeySet,    /// Inactive Keyset    #[error("Inactive Keyset")]    InactiveKeyset,    #[error("No key for amount")]    AmountKey,    #[error("Amount")]    Amount,    #[error("Duplicate proofs")]    DuplicateProofs,    #[error("Token Spent")]    TokenSpent,    #[error("Token Pending")]    TokenPending,    #[error("`{0}`")]    Custom(String),    #[error("`{0}`")]    Cashu(#[from] cashu::error::mint::Error),    #[error("`{0}`")]    Localstore(#[from] localstore::Error),}pub struct Mint<L: LocalStore> {    //    pub pubkey: PublicKey    pub keysets_info: HashMap<Id, MintKeySetInfo>,    //    pub pubkey: PublicKey,    mnemonic: Mnemonic,    pub fee_reserve: FeeReserve,    localstore: L,}impl<L: LocalStore> Mint<L> {    pub async fn new(        localstore: L,        mnemonic: Mnemonic,        keysets_info: HashSet<MintKeySetInfo>,        min_fee_reserve: Amount,        percent_fee_reserve: f32,    ) -> Result<Self, Error> {        let mut info = HashMap::default();        let mut active_units: HashSet<CurrencyUnit> = HashSet::default();        // Check that there is only one active keyset per unit        for keyset_info in keysets_info {            if keyset_info.active && !active_units.insert(keyset_info.unit.clone()) {                // TODO: Handle Error                todo!()            }            let keyset = nut02::mint::KeySet::generate(                &mnemonic.to_seed_normalized(""),                keyset_info.unit.clone(),                &keyset_info.derivation_path.clone(),                keyset_info.max_order,            );            info.insert(keyset_info.id, keyset_info);            localstore.add_keyset(keyset).await?;        }        Ok(Self {            localstore,            mnemonic,            keysets_info: info,            fee_reserve: FeeReserve {                min_fee_reserve,                percent_fee_reserve,            },        })    }    /// Retrieve the public keys of the active keyset for distribution to    /// wallet clients    pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<Option<KeysResponse>, Error> {        let keyset = match self.localstore.get_keyset(keyset_id).await? {            Some(keyset) => keyset.clone(),            None => {                return Ok(None);            }        };        Ok(Some(KeysResponse {            keysets: vec![keyset.into()],        }))    }    /// Return a list of all supported keysets    pub fn keysets(&self) -> KeysetResponse {        let keysets = self            .keysets_info            .values()            .map(|k| k.clone().into())            .collect();        KeysetResponse { keysets }    }    pub async fn keyset(&self, id: &Id) -> Result<Option<KeySet>, Error> {        Ok(self            .localstore            .get_keyset(id)            .await?            .map(|ks| ks.clone().into()))    }    /// Add current keyset to inactive keysets    /// Generate new keyset    pub async fn rotate_keyset(        &mut self,        unit: CurrencyUnit,        derivation_path: &str,        max_order: u8,    ) -> Result<(), Error> {        let new_keyset = MintKeySet::generate(            &self.mnemonic.to_seed_normalized(""),            unit.clone(),            derivation_path,            max_order,        );        self.localstore.add_keyset(new_keyset.clone()).await?;        for mint_keyset_info in self.keysets_info.values_mut() {            if mint_keyset_info.active && mint_keyset_info.unit.eq(&unit) {                mint_keyset_info.active = false;            }        }        let mint_keyset_info = MintKeySetInfo {            id: new_keyset.id,            unit,            derivation_path: derivation_path.to_string(),            active: true,            valid_from: unix_time(),            valid_to: None,            max_order,        };        self.keysets_info.insert(new_keyset.id, mint_keyset_info);        Ok(())    }    pub async fn process_mint_request(        &mut self,        mint_request: nut04::MintBolt11Request,    ) -> Result<nut04::MintBolt11Response, Error> {        let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());        for blinded_message in mint_request.outputs {            blind_signatures.push(self.blind_sign(&blinded_message).await?);        }        Ok(nut04::MintBolt11Response {            signatures: blind_signatures,        })    }    async fn blind_sign(        &self,        blinded_message: &BlindedMessage,    ) -> Result<BlindedSignature, Error> {        let BlindedMessage {            amount,            b,            keyset_id,        } = blinded_message;        let keyset = self            .localstore            .get_keyset(keyset_id)            .await?            .ok_or(Error::UnknownKeySet)?;        // Check that the keyset is active and should be used to sign        if !self            .keysets_info            .get(keyset_id)            .ok_or(Error::UnknownKeySet)?            .active        {            return Err(Error::InactiveKeyset);        }        let Some(key_pair) = keyset.keys.0.get(amount) else {            // No key for amount            return Err(Error::AmountKey);        };        let c = sign_message(key_pair.secret_key.clone().into(), b.clone().into())?;        Ok(BlindedSignature {            amount: *amount,            c: c.into(),            keyset_id: keyset.id,        })    }    pub async fn process_swap_request(        &mut self,        swap_request: SwapRequest,    ) -> Result<SwapResponse, Error> {        let proofs_total = swap_request.input_amount();        let output_total = swap_request.output_amount();        if proofs_total != output_total {            return Err(Error::Amount);        }        let proof_count = swap_request.inputs.len();        let secrets: HashSet<Secret> = swap_request            .inputs            .iter()            .map(|p| p.secret.clone())            .collect();        // Check that there are no duplicate proofs in request        if secrets.len().ne(&proof_count) {            return Err(Error::DuplicateProofs);        }        for proof in &swap_request.inputs {            self.verify_proof(proof).await?        }        for (secret, proof) in secrets.iter().zip(swap_request.inputs) {            self.localstore                .add_spent_proof(secret.clone(), proof)                .await                .unwrap();        }        let mut promises = Vec::with_capacity(swap_request.outputs.len());        for output in swap_request.outputs {            let promise = self.blind_sign(&output).await?;            promises.push(promise);        }        Ok(SwapResponse::new(promises))    }    async fn verify_proof(&self, proof: &Proof) -> Result<(), Error> {        if self            .localstore            .get_spent_proof(&proof.secret)            .await?            .is_some()        {            return Err(Error::TokenSpent);        }        if self            .localstore            .get_pending_proof(&proof.secret)            .await?            .is_some()        {            return Err(Error::TokenPending);        }        let keyset = self            .localstore            .get_keyset(&proof.keyset_id)            .await?            .ok_or(Error::UnknownKeySet)?;        let Some(keypair) = keyset.keys.0.get(&proof.amount) else {            return Err(Error::AmountKey);        };        verify_message(            keypair.secret_key.clone().into(),            proof.c.clone().into(),            &proof.secret,        )?;        Ok(())    }    #[cfg(feature = "nut07")]    pub async fn check_spendable(        &self,        check_spendable: &CheckSpendableRequest,    ) -> Result<CheckSpendableResponse, Error> {        let mut spendable = Vec::with_capacity(check_spendable.proofs.len());        let mut pending = Vec::with_capacity(check_spendable.proofs.len());        for proof in &check_spendable.proofs {            spendable.push(                self.localstore                    .get_spent_proof(&proof.secret)                    .await                    .unwrap()                    .is_none(),            );            pending.push(                self.localstore                    .get_pending_proof(&proof.secret)                    .await                    .unwrap()                    .is_some(),            );        }        Ok(CheckSpendableResponse { spendable, pending })    }    pub async fn verify_melt_request(        &mut self,        melt_request: &MeltBolt11Request,    ) -> Result<(), Error> {        let quote = self            .localstore            .get_melt_quote(&melt_request.quote)            .await            .unwrap();        let quote = if let Some(quote) = quote {            quote        } else {            return Err(Error::Custom("Unknown Quote".to_string()));        };        let proofs_total = melt_request.proofs_amount();        let required_total = quote.amount + quote.fee_reserve;        if proofs_total < required_total {            debug!(                "Insufficient Proofs: Got: {}, Required: {}",                proofs_total, required_total            );            return Err(Error::Amount);        }        let secrets: HashSet<&Secret> = melt_request.inputs.iter().map(|p| &p.secret).collect();        // Ensure proofs are unique and not being double spent        if melt_request.inputs.len().ne(&secrets.len()) {            return Err(Error::DuplicateProofs);        }        for proof in &melt_request.inputs {            self.verify_proof(proof).await?        }        Ok(())    }    pub async fn process_melt_request(        &mut self,        melt_request: &MeltBolt11Request,        preimage: &str,        total_spent: Amount,    ) -> Result<MeltBolt11Response, Error> {        self.verify_melt_request(melt_request).await?;        for input in &melt_request.inputs {            self.localstore                .add_spent_proof(input.secret.clone(), input.clone())                .await                .unwrap();        }        let mut change = None;        if let Some(outputs) = melt_request.outputs.clone() {            let change_target = melt_request.proofs_amount() - total_spent;            let mut amounts = change_target.split();            let mut change_sigs = Vec::with_capacity(amounts.len());            if outputs.len().lt(&amounts.len()) {                debug!(                    "Providing change requires {} blinded messages, but only {} provided",                    amounts.len(),                    outputs.len()                );                // In the case that not enough outputs are provided to return all change                // Reverse sort the amounts so that the most amount of change possible is                // returned. The rest is burnt                amounts.sort_by(|a, b| b.cmp(a));            }            for (amount, blinded_message) in amounts.iter().zip(outputs) {                let mut blinded_message = blinded_message;                blinded_message.amount = *amount;                let signature = self.blind_sign(&blinded_message).await?;                change_sigs.push(signature)            }            change = Some(change_sigs);        } else {            info!(                "No change outputs provided. Burnt: {:?} sats",                (melt_request.proofs_amount() - total_spent)            );        }        Ok(MeltBolt11Response {            paid: true,            payment_preimage: Some(preimage.to_string()),            change,        })    }}pub struct FeeReserve {    pub min_fee_reserve: Amount,    pub percent_fee_reserve: f32,}#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)]pub struct MintKeySetInfo {    pub id: Id,    pub unit: CurrencyUnit,    pub active: bool,    pub valid_from: u64,    pub valid_to: Option<u64>,    pub derivation_path: String,    pub max_order: u8,}impl From<MintKeySetInfo> for KeySetInfo {    fn from(keyset_info: MintKeySetInfo) -> Self {        Self {            id: keyset_info.id,            unit: keyset_info.unit,            active: keyset_info.active,        }    }}
 |