|  | @@ -1,20 +1,31 @@
 | 
	
		
			
				|  |  | -//! Notation and Models
 | 
	
		
			
				|  |  | -// https://github.com/cashubtc/nuts/blob/main/00.md
 | 
	
		
			
				|  |  | +//! NUT-00: Notation and Models
 | 
	
		
			
				|  |  | +//!
 | 
	
		
			
				|  |  | +//! <https://github.com/cashubtc/nuts/blob/main/00.md>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +use std::cmp::Ordering;
 | 
	
		
			
				|  |  |  use std::fmt;
 | 
	
		
			
				|  |  | -use std::hash::{self, Hasher};
 | 
	
		
			
				|  |  | +use std::hash::{Hash, Hasher};
 | 
	
		
			
				|  |  |  use std::str::FromStr;
 | 
	
		
			
				|  |  |  use std::string::FromUtf8Error;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -use serde::{Deserialize, Serialize};
 | 
	
		
			
				|  |  | +use base64::engine::{general_purpose, GeneralPurpose};
 | 
	
		
			
				|  |  | +use base64::{alphabet, Engine as _};
 | 
	
		
			
				|  |  | +use serde::{Deserialize, Deserializer, Serialize};
 | 
	
		
			
				|  |  |  use thiserror::Error;
 | 
	
		
			
				|  |  | +use url::Url;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -use super::{BlindSignatureDleq, Id, ProofDleq, Proofs, PublicKey, Signatures};
 | 
	
		
			
				|  |  | -use crate::nuts::nut11::{witness_deserialize, witness_serialize};
 | 
	
		
			
				|  |  | +use crate::dhke::blind_message;
 | 
	
		
			
				|  |  | +use crate::nuts::nut01::{PublicKey, SecretKey};
 | 
	
		
			
				|  |  | +use crate::nuts::nut11::{witness_deserialize, witness_serialize, Signatures};
 | 
	
		
			
				|  |  | +use crate::nuts::nut12::BlindSignatureDleq;
 | 
	
		
			
				|  |  | +use crate::nuts::{Id, P2PKConditions, ProofDleq};
 | 
	
		
			
				|  |  |  use crate::secret::Secret;
 | 
	
		
			
				|  |  |  use crate::url::UncheckedUrl;
 | 
	
		
			
				|  |  |  use crate::Amount;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/// List of [Proof]
 | 
	
		
			
				|  |  | +pub type Proofs = Vec<Proof>;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  #[derive(Debug, Error)]
 | 
	
		
			
				|  |  |  pub enum Error {
 | 
	
		
			
				|  |  |      /// Proofs required
 | 
	
	
		
			
				|  | @@ -46,18 +57,26 @@ pub enum Error {
 | 
	
		
			
				|  |  |      NUT11(#[from] crate::nuts::nut11::Error),
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/// Blinded Message [NUT-00]
 | 
	
		
			
				|  |  | -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | +/// Blinded Message (also called `output`)
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 | 
	
		
			
				|  |  |  pub struct BlindedMessage {
 | 
	
		
			
				|  |  |      /// Amount
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// The value for the requested [BlindSignature]
 | 
	
		
			
				|  |  |      pub amount: Amount,
 | 
	
		
			
				|  |  | -    /// Keyset Id
 | 
	
		
			
				|  |  | +    /// Keyset ID
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// ID from which we expect a signature.
 | 
	
		
			
				|  |  |      #[serde(rename = "id")]
 | 
	
		
			
				|  |  |      pub keyset_id: Id,
 | 
	
		
			
				|  |  |      /// Blinded secret message (B_)
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// The blinded secret message generated by the sender.
 | 
	
		
			
				|  |  |      #[serde(rename = "B_")]
 | 
	
		
			
				|  |  | -    pub b: PublicKey,
 | 
	
		
			
				|  |  | +    pub blinded_secret: PublicKey,
 | 
	
		
			
				|  |  |      /// Witness
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// <https://github.com/cashubtc/nuts/blob/main/11.md>
 | 
	
		
			
				|  |  |      #[serde(default)]
 | 
	
		
			
				|  |  |      #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  |      //#[serde(serialize_with = "witness_serialize")]
 | 
	
	
		
			
				|  | @@ -66,18 +85,104 @@ pub struct BlindedMessage {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl BlindedMessage {
 | 
	
		
			
				|  |  | -    pub fn new(amount: Amount, keyset_id: Id, b: PublicKey) -> Self {
 | 
	
		
			
				|  |  | +    /// Compose new blinded message
 | 
	
		
			
				|  |  | +    #[inline]
 | 
	
		
			
				|  |  | +    pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
 | 
	
		
			
				|  |  |          Self {
 | 
	
		
			
				|  |  |              amount,
 | 
	
		
			
				|  |  |              keyset_id,
 | 
	
		
			
				|  |  | -            b,
 | 
	
		
			
				|  |  | +            blinded_secret,
 | 
	
		
			
				|  |  |              witness: None,
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /// Add witness
 | 
	
		
			
				|  |  | +    pub fn witness(mut self, witness: Signatures) -> Self {
 | 
	
		
			
				|  |  | +        self.witness = Some(witness);
 | 
	
		
			
				|  |  | +        self
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Blind Signature (also called `promise`)
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | +pub struct BlindSignature {
 | 
	
		
			
				|  |  | +    /// Amount
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// The value of the blinded token.
 | 
	
		
			
				|  |  | +    pub amount: Amount,
 | 
	
		
			
				|  |  | +    /// Keyset ID
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// ID of the mint keys that signed the token.
 | 
	
		
			
				|  |  | +    #[serde(rename = "id")]
 | 
	
		
			
				|  |  | +    pub keyset_id: Id,
 | 
	
		
			
				|  |  | +    /// Blinded signature (C_)
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// The blinded signature on the secret message `B_` of [BlindedMessage].
 | 
	
		
			
				|  |  | +    #[serde(rename = "C_")]
 | 
	
		
			
				|  |  | +    pub c: PublicKey,
 | 
	
		
			
				|  |  | +    /// DLEQ Proof
 | 
	
		
			
				|  |  | +    ///
 | 
	
		
			
				|  |  | +    /// <https://github.com/cashubtc/nuts/blob/main/12.md>
 | 
	
		
			
				|  |  | +    #[serde(default)]
 | 
	
		
			
				|  |  | +    #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | +    pub dleq: Option<BlindSignatureDleq>,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/// Proofs
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | +pub struct Proof {
 | 
	
		
			
				|  |  | +    /// Amount
 | 
	
		
			
				|  |  | +    pub amount: Amount,
 | 
	
		
			
				|  |  | +    /// `Keyset id`
 | 
	
		
			
				|  |  | +    #[serde(rename = "id")]
 | 
	
		
			
				|  |  | +    pub keyset_id: Id,
 | 
	
		
			
				|  |  | +    /// Secret message
 | 
	
		
			
				|  |  | +    pub secret: Secret,
 | 
	
		
			
				|  |  | +    /// Unblinded signature
 | 
	
		
			
				|  |  | +    #[serde(rename = "C")]
 | 
	
		
			
				|  |  | +    pub c: PublicKey,
 | 
	
		
			
				|  |  | +    /// Witness
 | 
	
		
			
				|  |  | +    #[serde(default)]
 | 
	
		
			
				|  |  | +    #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | +    #[serde(serialize_with = "witness_serialize")]
 | 
	
		
			
				|  |  | +    #[serde(deserialize_with = "witness_deserialize")]
 | 
	
		
			
				|  |  | +    pub witness: Option<Signatures>,
 | 
	
		
			
				|  |  | +    /// DLEQ Proof
 | 
	
		
			
				|  |  | +    pub dleq: Option<ProofDleq>,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Proof {
 | 
	
		
			
				|  |  | +    pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
 | 
	
		
			
				|  |  | +        Proof {
 | 
	
		
			
				|  |  | +            amount,
 | 
	
		
			
				|  |  | +            keyset_id,
 | 
	
		
			
				|  |  | +            secret,
 | 
	
		
			
				|  |  | +            c,
 | 
	
		
			
				|  |  | +            witness: None,
 | 
	
		
			
				|  |  | +            dleq: None,
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Hash for Proof {
 | 
	
		
			
				|  |  | +    fn hash<H: Hasher>(&self, state: &mut H) {
 | 
	
		
			
				|  |  | +        self.secret.hash(state);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Ord for Proof {
 | 
	
		
			
				|  |  | +    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
	
		
			
				|  |  | +        self.amount.cmp(&other.amount)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl PartialOrd for Proof {
 | 
	
		
			
				|  |  | +    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
	
		
			
				|  |  | +        Some(self.cmp(other))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, hash::Hash)]
 | 
	
		
			
				|  |  | -#[serde(rename_all = "lowercase")]
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
 | 
	
		
			
				|  |  |  pub enum CurrencyUnit {
 | 
	
		
			
				|  |  |      #[default]
 | 
	
		
			
				|  |  |      Sat,
 | 
	
	
		
			
				|  | @@ -85,14 +190,15 @@ pub enum CurrencyUnit {
 | 
	
		
			
				|  |  |      Custom(String),
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -impl FromStr for CurrencyUnit {
 | 
	
		
			
				|  |  | -    type Err = Error;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
	
		
			
				|  |  | -        match s {
 | 
	
		
			
				|  |  | -            "sat" => Ok(Self::Sat),
 | 
	
		
			
				|  |  | -            "usd" => Ok(Self::Usd),
 | 
	
		
			
				|  |  | -            _ => Ok(Self::Custom(s.to_string())),
 | 
	
		
			
				|  |  | +impl<S> From<S> for CurrencyUnit
 | 
	
		
			
				|  |  | +where
 | 
	
		
			
				|  |  | +    S: AsRef<str>,
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    fn from(currency: S) -> Self {
 | 
	
		
			
				|  |  | +        match currency.as_ref() {
 | 
	
		
			
				|  |  | +            "sat" => Self::Sat,
 | 
	
		
			
				|  |  | +            "usd" => Self::Usd,
 | 
	
		
			
				|  |  | +            o => Self::Custom(o.to_string()),
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -102,26 +208,45 @@ impl fmt::Display for CurrencyUnit {
 | 
	
		
			
				|  |  |          match self {
 | 
	
		
			
				|  |  |              CurrencyUnit::Sat => write!(f, "sat"),
 | 
	
		
			
				|  |  |              CurrencyUnit::Usd => write!(f, "usd"),
 | 
	
		
			
				|  |  | -            CurrencyUnit::Custom(unit) => write!(f, "{}", unit),
 | 
	
		
			
				|  |  | +            CurrencyUnit::Custom(unit) => write!(f, "{unit}"),
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#[derive(Default, Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Hash)]
 | 
	
		
			
				|  |  | -#[serde(rename_all = "lowercase")]
 | 
	
		
			
				|  |  | +impl Serialize for CurrencyUnit {
 | 
	
		
			
				|  |  | +    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
	
		
			
				|  |  | +    where
 | 
	
		
			
				|  |  | +        S: serde::Serializer,
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        serializer.serialize_str(&self.to_string())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl<'de> Deserialize<'de> for CurrencyUnit {
 | 
	
		
			
				|  |  | +    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 | 
	
		
			
				|  |  | +    where
 | 
	
		
			
				|  |  | +        D: Deserializer<'de>,
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        let currency: String = String::deserialize(deserializer)?;
 | 
	
		
			
				|  |  | +        Ok(Self::from(currency))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
 | 
	
		
			
				|  |  |  pub enum PaymentMethod {
 | 
	
		
			
				|  |  |      #[default]
 | 
	
		
			
				|  |  |      Bolt11,
 | 
	
		
			
				|  |  |      Custom(String),
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -impl FromStr for PaymentMethod {
 | 
	
		
			
				|  |  | -    type Err = Error;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
	
		
			
				|  |  | -        match s {
 | 
	
		
			
				|  |  | -            "bolt11" => Ok(Self::Bolt11),
 | 
	
		
			
				|  |  | -            _ => Ok(Self::Custom(s.to_string())),
 | 
	
		
			
				|  |  | +impl<S> From<S> for PaymentMethod
 | 
	
		
			
				|  |  | +where
 | 
	
		
			
				|  |  | +    S: AsRef<str>,
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +    fn from(method: S) -> Self {
 | 
	
		
			
				|  |  | +        match method.as_ref() {
 | 
	
		
			
				|  |  | +            "bolt11" => Self::Bolt11,
 | 
	
		
			
				|  |  | +            o => Self::Custom(o.to_string()),
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
	
		
			
				|  | @@ -135,285 +260,285 @@ impl fmt::Display for PaymentMethod {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  | -pub mod wallet {
 | 
	
		
			
				|  |  | -    use std::cmp::Ordering;
 | 
	
		
			
				|  |  | -    use std::fmt;
 | 
	
		
			
				|  |  | -    use std::str::FromStr;
 | 
	
		
			
				|  |  | +impl Serialize for PaymentMethod {
 | 
	
		
			
				|  |  | +    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
	
		
			
				|  |  | +    where
 | 
	
		
			
				|  |  | +        S: serde::Serializer,
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        serializer.serialize_str(&self.to_string())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    use base64::engine::{general_purpose, GeneralPurpose};
 | 
	
		
			
				|  |  | -    use base64::{alphabet, Engine as _};
 | 
	
		
			
				|  |  | -    use serde::{Deserialize, Serialize};
 | 
	
		
			
				|  |  | -    use url::Url;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    use super::{CurrencyUnit, MintProofs, *};
 | 
	
		
			
				|  |  | -    use crate::dhke::blind_message;
 | 
	
		
			
				|  |  | -    use crate::nuts::{BlindedMessage, Id, P2PKConditions, Proofs, SecretKey};
 | 
	
		
			
				|  |  | -    use crate::secret::Secret;
 | 
	
		
			
				|  |  | -    use crate::url::UncheckedUrl;
 | 
	
		
			
				|  |  | -    use crate::Amount;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
 | 
	
		
			
				|  |  | -    pub struct PreMint {
 | 
	
		
			
				|  |  | -        /// Blinded message
 | 
	
		
			
				|  |  | -        pub blinded_message: BlindedMessage,
 | 
	
		
			
				|  |  | -        /// Secret
 | 
	
		
			
				|  |  | -        pub secret: Secret,
 | 
	
		
			
				|  |  | -        /// R
 | 
	
		
			
				|  |  | -        pub r: SecretKey,
 | 
	
		
			
				|  |  | -        /// Amount
 | 
	
		
			
				|  |  | -        pub amount: Amount,
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    impl Ord for PreMint {
 | 
	
		
			
				|  |  | -        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
	
		
			
				|  |  | -            self.amount.cmp(&other.amount)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +impl<'de> Deserialize<'de> for PaymentMethod {
 | 
	
		
			
				|  |  | +    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 | 
	
		
			
				|  |  | +    where
 | 
	
		
			
				|  |  | +        D: Deserializer<'de>,
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        let payment_method: String = String::deserialize(deserializer)?;
 | 
	
		
			
				|  |  | +        Ok(Self::from(payment_method))
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl PartialOrd for PreMint {
 | 
	
		
			
				|  |  | -        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
	
		
			
				|  |  | -            Some(self.cmp(other))
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
 | 
	
		
			
				|  |  | +pub struct PreMint {
 | 
	
		
			
				|  |  | +    /// Blinded message
 | 
	
		
			
				|  |  | +    pub blinded_message: BlindedMessage,
 | 
	
		
			
				|  |  | +    /// Secret
 | 
	
		
			
				|  |  | +    pub secret: Secret,
 | 
	
		
			
				|  |  | +    /// R
 | 
	
		
			
				|  |  | +    pub r: SecretKey,
 | 
	
		
			
				|  |  | +    /// Amount
 | 
	
		
			
				|  |  | +    pub amount: Amount,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Ord for PreMint {
 | 
	
		
			
				|  |  | +    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
	
		
			
				|  |  | +        self.amount.cmp(&other.amount)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
 | 
	
		
			
				|  |  | -    pub struct PreMintSecrets {
 | 
	
		
			
				|  |  | -        pub secrets: Vec<PreMint>,
 | 
	
		
			
				|  |  | +impl PartialOrd for PreMint {
 | 
	
		
			
				|  |  | +    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
	
		
			
				|  |  | +        Some(self.cmp(other))
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl PreMintSecrets {
 | 
	
		
			
				|  |  | -        /// Outputs for speceifed amount with random secret
 | 
	
		
			
				|  |  | -        pub fn random(keyset_id: Id, amount: Amount) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | -            let amount_split = amount.split();
 | 
	
		
			
				|  |  | +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
 | 
	
		
			
				|  |  | +pub struct PreMintSecrets {
 | 
	
		
			
				|  |  | +    pub secrets: Vec<PreMint>,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            let mut output = Vec::with_capacity(amount_split.len());
 | 
	
		
			
				|  |  | +impl PreMintSecrets {
 | 
	
		
			
				|  |  | +    /// Outputs for speceifed amount with random secret
 | 
	
		
			
				|  |  | +    pub fn random(keyset_id: Id, amount: Amount) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | +        let amount_split = amount.split();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            for amount in amount_split {
 | 
	
		
			
				|  |  | -                let secret = Secret::generate();
 | 
	
		
			
				|  |  | -                let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  | +        let mut output = Vec::with_capacity(amount_split.len());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  | +        for amount in amount_split {
 | 
	
		
			
				|  |  | +            let secret = Secret::generate();
 | 
	
		
			
				|  |  | +            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                output.push(PreMint {
 | 
	
		
			
				|  |  | -                    secret,
 | 
	
		
			
				|  |  | -                    blinded_message,
 | 
	
		
			
				|  |  | -                    r,
 | 
	
		
			
				|  |  | -                    amount,
 | 
	
		
			
				|  |  | -                });
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +            output.push(PreMint {
 | 
	
		
			
				|  |  | +                secret,
 | 
	
		
			
				|  |  | +                blinded_message,
 | 
	
		
			
				|  |  | +                r,
 | 
	
		
			
				|  |  | +                amount,
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn from_secrets(
 | 
	
		
			
				|  |  | -            keyset_id: Id,
 | 
	
		
			
				|  |  | -            amounts: Vec<Amount>,
 | 
	
		
			
				|  |  | -            secrets: Vec<Secret>,
 | 
	
		
			
				|  |  | -        ) -> Result<Self, wallet::Error> {
 | 
	
		
			
				|  |  | -            let mut output = Vec::with_capacity(secrets.len());
 | 
	
		
			
				|  |  | +        Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            for (secret, amount) in secrets.into_iter().zip(amounts) {
 | 
	
		
			
				|  |  | -                let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  | +    pub fn from_secrets(
 | 
	
		
			
				|  |  | +        keyset_id: Id,
 | 
	
		
			
				|  |  | +        amounts: Vec<Amount>,
 | 
	
		
			
				|  |  | +        secrets: Vec<Secret>,
 | 
	
		
			
				|  |  | +    ) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | +        let mut output = Vec::with_capacity(secrets.len());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  | +        for (secret, amount) in secrets.into_iter().zip(amounts) {
 | 
	
		
			
				|  |  | +            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                output.push(PreMint {
 | 
	
		
			
				|  |  | -                    secret,
 | 
	
		
			
				|  |  | -                    blinded_message,
 | 
	
		
			
				|  |  | -                    r,
 | 
	
		
			
				|  |  | -                    amount,
 | 
	
		
			
				|  |  | -                });
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +            output.push(PreMint {
 | 
	
		
			
				|  |  | +                secret,
 | 
	
		
			
				|  |  | +                blinded_message,
 | 
	
		
			
				|  |  | +                r,
 | 
	
		
			
				|  |  | +                amount,
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        /// Blank Outputs used for NUT-08 change
 | 
	
		
			
				|  |  | -        pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, wallet::Error> {
 | 
	
		
			
				|  |  | -            let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
 | 
	
		
			
				|  |  | +        Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            let mut output = Vec::with_capacity(count as usize);
 | 
	
		
			
				|  |  | +    /// Blank Outputs used for NUT-08 change
 | 
	
		
			
				|  |  | +    pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | +        let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            for _i in 0..count {
 | 
	
		
			
				|  |  | -                let secret = Secret::generate();
 | 
	
		
			
				|  |  | -                let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  | +        let mut output = Vec::with_capacity(count as usize);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
 | 
	
		
			
				|  |  | +        for _i in 0..count {
 | 
	
		
			
				|  |  | +            let secret = Secret::generate();
 | 
	
		
			
				|  |  | +            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                output.push(PreMint {
 | 
	
		
			
				|  |  | -                    secret,
 | 
	
		
			
				|  |  | -                    blinded_message,
 | 
	
		
			
				|  |  | -                    r,
 | 
	
		
			
				|  |  | -                    amount: Amount::ZERO,
 | 
	
		
			
				|  |  | -                })
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +            output.push(PreMint {
 | 
	
		
			
				|  |  | +                secret,
 | 
	
		
			
				|  |  | +                blinded_message,
 | 
	
		
			
				|  |  | +                r,
 | 
	
		
			
				|  |  | +                amount: Amount::ZERO,
 | 
	
		
			
				|  |  | +            })
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn with_p2pk_conditions(
 | 
	
		
			
				|  |  | -            keyset_id: Id,
 | 
	
		
			
				|  |  | -            amount: Amount,
 | 
	
		
			
				|  |  | -            conditions: P2PKConditions,
 | 
	
		
			
				|  |  | -        ) -> Result<Self, wallet::Error> {
 | 
	
		
			
				|  |  | -            let amount_split = amount.split();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            let mut output = Vec::with_capacity(amount_split.len());
 | 
	
		
			
				|  |  | +        Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            for amount in amount_split {
 | 
	
		
			
				|  |  | -                let secret: Secret = conditions.clone().try_into()?;
 | 
	
		
			
				|  |  | -                let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  | +    pub fn with_p2pk_conditions(
 | 
	
		
			
				|  |  | +        keyset_id: Id,
 | 
	
		
			
				|  |  | +        amount: Amount,
 | 
	
		
			
				|  |  | +        conditions: P2PKConditions,
 | 
	
		
			
				|  |  | +    ) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | +        let amount_split = amount.split();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  | +        let mut output = Vec::with_capacity(amount_split.len());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                output.push(PreMint {
 | 
	
		
			
				|  |  | -                    secret,
 | 
	
		
			
				|  |  | -                    blinded_message,
 | 
	
		
			
				|  |  | -                    r,
 | 
	
		
			
				|  |  | -                    amount,
 | 
	
		
			
				|  |  | -                });
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        for amount in amount_split {
 | 
	
		
			
				|  |  | +            let secret: Secret = conditions.clone().try_into()?;
 | 
	
		
			
				|  |  | +            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
 | 
	
		
			
				|  |  | -            self.secrets.iter()
 | 
	
		
			
				|  |  | +            output.push(PreMint {
 | 
	
		
			
				|  |  | +                secret,
 | 
	
		
			
				|  |  | +                blinded_message,
 | 
	
		
			
				|  |  | +                r,
 | 
	
		
			
				|  |  | +                amount,
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn len(&self) -> usize {
 | 
	
		
			
				|  |  | -            self.secrets.len()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +        Ok(PreMintSecrets { secrets: output })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn is_empty(&self) -> bool {
 | 
	
		
			
				|  |  | -            self.secrets.is_empty()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
 | 
	
		
			
				|  |  | +        self.secrets.iter()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn total_amount(&self) -> Amount {
 | 
	
		
			
				|  |  | -            self.secrets
 | 
	
		
			
				|  |  | -                .iter()
 | 
	
		
			
				|  |  | -                .map(|PreMint { amount, .. }| *amount)
 | 
	
		
			
				|  |  | -                .sum()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn len(&self) -> usize {
 | 
	
		
			
				|  |  | +        self.secrets.len()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
 | 
	
		
			
				|  |  | -            self.iter().map(|pm| pm.blinded_message.clone()).collect()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn is_empty(&self) -> bool {
 | 
	
		
			
				|  |  | +        self.secrets.is_empty()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn secrets(&self) -> Vec<Secret> {
 | 
	
		
			
				|  |  | -            self.iter().map(|pm| pm.secret.clone()).collect()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn total_amount(&self) -> Amount {
 | 
	
		
			
				|  |  | +        self.secrets
 | 
	
		
			
				|  |  | +            .iter()
 | 
	
		
			
				|  |  | +            .map(|PreMint { amount, .. }| *amount)
 | 
	
		
			
				|  |  | +            .sum()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn rs(&self) -> Vec<SecretKey> {
 | 
	
		
			
				|  |  | -            self.iter().map(|pm| pm.r.clone()).collect()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
 | 
	
		
			
				|  |  | +        self.iter().map(|pm| pm.blinded_message.clone()).collect()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn amounts(&self) -> Vec<Amount> {
 | 
	
		
			
				|  |  | -            self.iter().map(|pm| pm.amount).collect()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn secrets(&self) -> Vec<Secret> {
 | 
	
		
			
				|  |  | +        self.iter().map(|pm| pm.secret.clone()).collect()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn combine(&mut self, mut other: Self) {
 | 
	
		
			
				|  |  | -            self.secrets.append(&mut other.secrets)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn rs(&self) -> Vec<SecretKey> {
 | 
	
		
			
				|  |  | +        self.iter().map(|pm| pm.r.clone()).collect()
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn sort_secrets(&mut self) {
 | 
	
		
			
				|  |  | -            self.secrets.sort();
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn amounts(&self) -> Vec<Amount> {
 | 
	
		
			
				|  |  | +        self.iter().map(|pm| pm.amount).collect()
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // Implement Iterator for PreMintSecrets
 | 
	
		
			
				|  |  | -    impl Iterator for PreMintSecrets {
 | 
	
		
			
				|  |  | -        type Item = PreMint;
 | 
	
		
			
				|  |  | +    pub fn combine(&mut self, mut other: Self) {
 | 
	
		
			
				|  |  | +        self.secrets.append(&mut other.secrets)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        fn next(&mut self) -> Option<Self::Item> {
 | 
	
		
			
				|  |  | -            // Use the iterator of the vector
 | 
	
		
			
				|  |  | -            self.secrets.pop()
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    pub fn sort_secrets(&mut self) {
 | 
	
		
			
				|  |  | +        self.secrets.sort();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl Ord for PreMintSecrets {
 | 
	
		
			
				|  |  | -        fn cmp(&self, other: &Self) -> Ordering {
 | 
	
		
			
				|  |  | -            self.secrets.cmp(&other.secrets)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +// Implement Iterator for PreMintSecrets
 | 
	
		
			
				|  |  | +impl Iterator for PreMintSecrets {
 | 
	
		
			
				|  |  | +    type Item = PreMint;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn next(&mut self) -> Option<Self::Item> {
 | 
	
		
			
				|  |  | +        // Use the iterator of the vector
 | 
	
		
			
				|  |  | +        self.secrets.pop()
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl PartialOrd for PreMintSecrets {
 | 
	
		
			
				|  |  | -        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | 
	
		
			
				|  |  | -            Some(self.cmp(other))
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +impl Ord for PreMintSecrets {
 | 
	
		
			
				|  |  | +    fn cmp(&self, other: &Self) -> Ordering {
 | 
	
		
			
				|  |  | +        self.secrets.cmp(&other.secrets)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | -    pub struct Token {
 | 
	
		
			
				|  |  | -        pub token: Vec<MintProofs>,
 | 
	
		
			
				|  |  | -        /// Memo for token
 | 
	
		
			
				|  |  | -        #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | -        pub memo: Option<String>,
 | 
	
		
			
				|  |  | -        /// Token Unit
 | 
	
		
			
				|  |  | -        #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | -        pub unit: Option<CurrencyUnit>,
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    impl Token {
 | 
	
		
			
				|  |  | -        pub fn new(
 | 
	
		
			
				|  |  | -            mint_url: UncheckedUrl,
 | 
	
		
			
				|  |  | -            proofs: Proofs,
 | 
	
		
			
				|  |  | -            memo: Option<String>,
 | 
	
		
			
				|  |  | -            unit: Option<CurrencyUnit>,
 | 
	
		
			
				|  |  | -        ) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | -            if proofs.is_empty() {
 | 
	
		
			
				|  |  | -                return Err(Error::ProofsRequired);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +impl PartialOrd for PreMintSecrets {
 | 
	
		
			
				|  |  | +    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 | 
	
		
			
				|  |  | +        Some(self.cmp(other))
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            // Check Url is valid
 | 
	
		
			
				|  |  | -            let _: Url = (&mint_url).try_into().map_err(|_| Error::InvalidUrl)?;
 | 
	
		
			
				|  |  | +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | +pub struct Token {
 | 
	
		
			
				|  |  | +    pub token: Vec<MintProofs>,
 | 
	
		
			
				|  |  | +    /// Memo for token
 | 
	
		
			
				|  |  | +    #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | +    pub memo: Option<String>,
 | 
	
		
			
				|  |  | +    /// Token Unit
 | 
	
		
			
				|  |  | +    #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | +    pub unit: Option<CurrencyUnit>,
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            Ok(Self {
 | 
	
		
			
				|  |  | -                token: vec![MintProofs::new(mint_url, proofs)],
 | 
	
		
			
				|  |  | -                memo,
 | 
	
		
			
				|  |  | -                unit,
 | 
	
		
			
				|  |  | -            })
 | 
	
		
			
				|  |  | +impl Token {
 | 
	
		
			
				|  |  | +    pub fn new(
 | 
	
		
			
				|  |  | +        mint_url: UncheckedUrl,
 | 
	
		
			
				|  |  | +        proofs: Proofs,
 | 
	
		
			
				|  |  | +        memo: Option<String>,
 | 
	
		
			
				|  |  | +        unit: Option<CurrencyUnit>,
 | 
	
		
			
				|  |  | +    ) -> Result<Self, Error> {
 | 
	
		
			
				|  |  | +        if proofs.is_empty() {
 | 
	
		
			
				|  |  | +            return Err(Error::ProofsRequired);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        pub fn token_info(&self) -> (u64, String) {
 | 
	
		
			
				|  |  | -            let mut amount = Amount::ZERO;
 | 
	
		
			
				|  |  | +        // Check Url is valid
 | 
	
		
			
				|  |  | +        let _: Url = (&mint_url).try_into().map_err(|_| Error::InvalidUrl)?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            for proofs in &self.token {
 | 
	
		
			
				|  |  | -                for proof in &proofs.proofs {
 | 
	
		
			
				|  |  | -                    amount += proof.amount;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        Ok(Self {
 | 
	
		
			
				|  |  | +            token: vec![MintProofs::new(mint_url, proofs)],
 | 
	
		
			
				|  |  | +            memo,
 | 
	
		
			
				|  |  | +            unit,
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn token_info(&self) -> (u64, String) {
 | 
	
		
			
				|  |  | +        let mut amount = Amount::ZERO;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            (amount.into(), self.token[0].mint.to_string())
 | 
	
		
			
				|  |  | +        for proofs in &self.token {
 | 
	
		
			
				|  |  | +            for proof in &proofs.proofs {
 | 
	
		
			
				|  |  | +                amount += proof.amount;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl FromStr for Token {
 | 
	
		
			
				|  |  | -        type Err = Error;
 | 
	
		
			
				|  |  | +        (amount.into(), self.token[0].mint.to_string())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
	
		
			
				|  |  | -            let s = if s.starts_with("cashuA") {
 | 
	
		
			
				|  |  | -                s.replace("cashuA", "")
 | 
	
		
			
				|  |  | -            } else {
 | 
	
		
			
				|  |  | -                return Err(Error::UnsupportedToken);
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | +impl FromStr for Token {
 | 
	
		
			
				|  |  | +    type Err = Error;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            let decode_config = general_purpose::GeneralPurposeConfig::new()
 | 
	
		
			
				|  |  | -                .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent);
 | 
	
		
			
				|  |  | -            let decoded = GeneralPurpose::new(&alphabet::STANDARD, decode_config).decode(s)?;
 | 
	
		
			
				|  |  | -            let decoded_str = String::from_utf8(decoded)?;
 | 
	
		
			
				|  |  | -            let token: Token = serde_json::from_str(&decoded_str)?;
 | 
	
		
			
				|  |  | -            Ok(token)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    fn from_str(s: &str) -> Result<Self, Self::Err> {
 | 
	
		
			
				|  |  | +        let s = if s.starts_with("cashuA") {
 | 
	
		
			
				|  |  | +            s.replace("cashuA", "")
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            return Err(Error::UnsupportedToken);
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let decode_config = general_purpose::GeneralPurposeConfig::new()
 | 
	
		
			
				|  |  | +            .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent);
 | 
	
		
			
				|  |  | +        let decoded = GeneralPurpose::new(&alphabet::STANDARD, decode_config).decode(s)?;
 | 
	
		
			
				|  |  | +        let decoded_str = String::from_utf8(decoded)?;
 | 
	
		
			
				|  |  | +        let token: Token = serde_json::from_str(&decoded_str)?;
 | 
	
		
			
				|  |  | +        Ok(token)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    impl fmt::Display for Token {
 | 
	
		
			
				|  |  | -        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
	
		
			
				|  |  | -            let json_string = serde_json::to_string(self).map_err(|_| fmt::Error)?;
 | 
	
		
			
				|  |  | -            let encoded = general_purpose::STANDARD.encode(json_string);
 | 
	
		
			
				|  |  | -            write!(f, "cashuA{}", encoded)
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +impl fmt::Display for Token {
 | 
	
		
			
				|  |  | +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
	
		
			
				|  |  | +        let json_string = serde_json::to_string(self).map_err(|_| fmt::Error)?;
 | 
	
		
			
				|  |  | +        let encoded = general_purpose::STANDARD.encode(json_string);
 | 
	
		
			
				|  |  | +        write!(f, "cashuA{}", encoded)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -423,9 +548,8 @@ pub struct MintProofs {
 | 
	
		
			
				|  |  |      pub proofs: Proofs,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -#[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  |  impl MintProofs {
 | 
	
		
			
				|  |  | -    fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self {
 | 
	
		
			
				|  |  | +    pub fn new(mint_url: UncheckedUrl, proofs: Proofs) -> Self {
 | 
	
		
			
				|  |  |          Self {
 | 
	
		
			
				|  |  |              mint: mint_url,
 | 
	
		
			
				|  |  |              proofs,
 | 
	
	
		
			
				|  | @@ -433,80 +557,10 @@ impl MintProofs {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/// Promise (BlindSignature) [NUT-00]
 | 
	
		
			
				|  |  | -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | -pub struct BlindSignature {
 | 
	
		
			
				|  |  | -    pub amount: Amount,
 | 
	
		
			
				|  |  | -    /// Keyset Id
 | 
	
		
			
				|  |  | -    #[serde(rename = "id")]
 | 
	
		
			
				|  |  | -    pub keyset_id: Id,
 | 
	
		
			
				|  |  | -    /// blinded signature (C_) on the secret message `B_` of [BlindedMessage]
 | 
	
		
			
				|  |  | -    #[serde(rename = "C_")]
 | 
	
		
			
				|  |  | -    pub c: PublicKey,
 | 
	
		
			
				|  |  | -    /// DLEQ Proof
 | 
	
		
			
				|  |  | -    pub dleq: Option<BlindSignatureDleq>,
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/// Proofs [NUT-00]
 | 
	
		
			
				|  |  | -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 | 
	
		
			
				|  |  | -pub struct Proof {
 | 
	
		
			
				|  |  | -    /// Amount in satoshi
 | 
	
		
			
				|  |  | -    pub amount: Amount,
 | 
	
		
			
				|  |  | -    /// `Keyset id`
 | 
	
		
			
				|  |  | -    #[serde(rename = "id")]
 | 
	
		
			
				|  |  | -    pub keyset_id: Id,
 | 
	
		
			
				|  |  | -    /// Secret message
 | 
	
		
			
				|  |  | -    pub secret: Secret,
 | 
	
		
			
				|  |  | -    /// Unblinded signature
 | 
	
		
			
				|  |  | -    #[serde(rename = "C")]
 | 
	
		
			
				|  |  | -    pub c: PublicKey,
 | 
	
		
			
				|  |  | -    /// Witness
 | 
	
		
			
				|  |  | -    #[serde(default)]
 | 
	
		
			
				|  |  | -    #[serde(skip_serializing_if = "Option::is_none")]
 | 
	
		
			
				|  |  | -    #[serde(serialize_with = "witness_serialize")]
 | 
	
		
			
				|  |  | -    #[serde(deserialize_with = "witness_deserialize")]
 | 
	
		
			
				|  |  | -    pub witness: Option<Signatures>,
 | 
	
		
			
				|  |  | -    /// DLEQ Proof
 | 
	
		
			
				|  |  | -    pub dleq: Option<ProofDleq>,
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl Proof {
 | 
	
		
			
				|  |  | -    pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
 | 
	
		
			
				|  |  | -        Proof {
 | 
	
		
			
				|  |  | -            amount,
 | 
	
		
			
				|  |  | -            keyset_id,
 | 
	
		
			
				|  |  | -            secret,
 | 
	
		
			
				|  |  | -            c,
 | 
	
		
			
				|  |  | -            witness: None,
 | 
	
		
			
				|  |  | -            dleq: None,
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl hash::Hash for Proof {
 | 
	
		
			
				|  |  | -    fn hash<H: Hasher>(&self, state: &mut H) {
 | 
	
		
			
				|  |  | -        self.secret.hash(state);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl Ord for Proof {
 | 
	
		
			
				|  |  | -    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
	
		
			
				|  |  | -        self.amount.cmp(&other.amount)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -impl PartialOrd for Proof {
 | 
	
		
			
				|  |  | -    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
	
		
			
				|  |  | -        Some(self.cmp(other))
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  #[cfg(test)]
 | 
	
		
			
				|  |  |  mod tests {
 | 
	
		
			
				|  |  |      use std::str::FromStr;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    #[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  | -    use super::wallet::*;
 | 
	
		
			
				|  |  |      use super::*;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      #[test]
 | 
	
	
		
			
				|  | @@ -523,7 +577,6 @@ mod tests {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      #[test]
 | 
	
		
			
				|  |  | -    #[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  |      fn test_token_str_round_trip() {
 | 
	
		
			
				|  |  |          let token_str = "cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -547,7 +600,6 @@ mod tests {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      #[test]
 | 
	
		
			
				|  |  | -    #[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  |      fn test_blank_blinded_messages() {
 | 
	
		
			
				|  |  |          // TODO: Need to update id to new type in proof
 | 
	
		
			
				|  |  |          let b = PreMintSecrets::blank(
 | 
	
	
		
			
				|  | @@ -564,7 +616,6 @@ mod tests {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      #[test]
 | 
	
		
			
				|  |  | -    #[cfg(feature = "wallet")]
 | 
	
		
			
				|  |  |      fn incorrect_tokens() {
 | 
	
		
			
				|  |  |          let incorrect_prefix = "casshuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9";
 | 
	
		
			
				|  |  |  
 |