cashu/nuts/nut00/
mod.rs

1//! NUT-00: Notation and Models
2//!
3//! <https://github.com/cashubtc/nuts/blob/main/00.md>
4
5use std::cmp::Ordering;
6use std::collections::{HashMap, HashSet};
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::str::FromStr;
10use std::string::FromUtf8Error;
11
12use serde::{de, Deserialize, Deserializer, Serialize};
13use thiserror::Error;
14
15use super::nut02::ShortKeysetId;
16#[cfg(feature = "wallet")]
17use super::nut10;
18#[cfg(feature = "wallet")]
19use super::nut11::SpendingConditions;
20#[cfg(feature = "wallet")]
21use crate::amount::SplitTarget;
22#[cfg(feature = "wallet")]
23use crate::dhke::blind_message;
24use crate::dhke::hash_to_curve;
25use crate::nuts::nut01::PublicKey;
26#[cfg(feature = "wallet")]
27use crate::nuts::nut01::SecretKey;
28use crate::nuts::nut11::{serde_p2pk_witness, P2PKWitness};
29use crate::nuts::nut12::BlindSignatureDleq;
30use crate::nuts::nut14::{serde_htlc_witness, HTLCWitness};
31use crate::nuts::{Id, ProofDleq};
32use crate::secret::Secret;
33use crate::Amount;
34
35pub mod token;
36pub use token::{Token, TokenV3, TokenV4};
37
38/// List of [Proof]
39pub type Proofs = Vec<Proof>;
40
41/// Utility methods for [Proofs]
42pub trait ProofsMethods {
43    /// Count proofs by keyset
44    fn count_by_keyset(&self) -> HashMap<Id, u64>;
45
46    /// Sum proofs by keyset
47    fn sum_by_keyset(&self) -> HashMap<Id, Amount>;
48
49    /// Try to sum up the amounts of all [Proof]s
50    fn total_amount(&self) -> Result<Amount, Error>;
51
52    /// Try to fetch the pubkeys of all [Proof]s
53    fn ys(&self) -> Result<Vec<PublicKey>, Error>;
54
55    /// Create a copy of proofs without dleqs
56    fn without_dleqs(&self) -> Proofs;
57}
58
59impl ProofsMethods for Proofs {
60    fn count_by_keyset(&self) -> HashMap<Id, u64> {
61        count_by_keyset(self.iter())
62    }
63
64    fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
65        sum_by_keyset(self.iter())
66    }
67
68    fn total_amount(&self) -> Result<Amount, Error> {
69        total_amount(self.iter())
70    }
71
72    fn ys(&self) -> Result<Vec<PublicKey>, Error> {
73        ys(self.iter())
74    }
75
76    fn without_dleqs(&self) -> Proofs {
77        self.iter()
78            .map(|p| {
79                let mut p = p.clone();
80                p.dleq = None;
81                p
82            })
83            .collect()
84    }
85}
86
87impl ProofsMethods for HashSet<Proof> {
88    fn count_by_keyset(&self) -> HashMap<Id, u64> {
89        count_by_keyset(self.iter())
90    }
91
92    fn sum_by_keyset(&self) -> HashMap<Id, Amount> {
93        sum_by_keyset(self.iter())
94    }
95
96    fn total_amount(&self) -> Result<Amount, Error> {
97        total_amount(self.iter())
98    }
99
100    fn ys(&self) -> Result<Vec<PublicKey>, Error> {
101        ys(self.iter())
102    }
103
104    fn without_dleqs(&self) -> Proofs {
105        self.iter()
106            .map(|p| {
107                let mut p = p.clone();
108                p.dleq = None;
109                p
110            })
111            .collect()
112    }
113}
114
115fn count_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, u64> {
116    let mut counts = HashMap::new();
117    for proof in proofs {
118        *counts.entry(proof.keyset_id).or_insert(0) += 1;
119    }
120    counts
121}
122
123fn sum_by_keyset<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> HashMap<Id, Amount> {
124    let mut sums = HashMap::new();
125    for proof in proofs {
126        *sums.entry(proof.keyset_id).or_insert(Amount::ZERO) += proof.amount;
127    }
128    sums
129}
130
131fn total_amount<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Amount, Error> {
132    Amount::try_sum(proofs.map(|p| p.amount)).map_err(Into::into)
133}
134
135fn ys<'a, I: Iterator<Item = &'a Proof>>(proofs: I) -> Result<Vec<PublicKey>, Error> {
136    proofs.map(|p| p.y()).collect::<Result<Vec<PublicKey>, _>>()
137}
138
139/// NUT00 Error
140#[derive(Debug, Error)]
141pub enum Error {
142    /// Proofs required
143    #[error("Proofs required in token")]
144    ProofsRequired,
145    /// Unsupported token
146    #[error("Unsupported token")]
147    UnsupportedToken,
148    /// Unsupported token
149    #[error("Unsupported unit")]
150    UnsupportedUnit,
151    /// Unsupported token
152    #[error("Unsupported payment method")]
153    UnsupportedPaymentMethod,
154    /// Duplicate proofs in token
155    #[error("Duplicate proofs in token")]
156    DuplicateProofs,
157    /// Serde Json error
158    #[error(transparent)]
159    SerdeJsonError(#[from] serde_json::Error),
160    /// Utf8 parse error
161    #[error(transparent)]
162    Utf8ParseError(#[from] FromUtf8Error),
163    /// Base64 error
164    #[error(transparent)]
165    Base64Error(#[from] bitcoin::base64::DecodeError),
166    /// Ciborium deserialization error
167    #[error(transparent)]
168    CiboriumError(#[from] ciborium::de::Error<std::io::Error>),
169    /// Ciborium serialization error
170    #[error(transparent)]
171    CiboriumSerError(#[from] ciborium::ser::Error<std::io::Error>),
172    /// Amount Error
173    #[error(transparent)]
174    Amount(#[from] crate::amount::Error),
175    /// Secret error
176    #[error(transparent)]
177    Secret(#[from] crate::secret::Error),
178    /// DHKE error
179    #[error(transparent)]
180    DHKE(#[from] crate::dhke::Error),
181    /// NUT10 error
182    #[error(transparent)]
183    NUT10(#[from] crate::nuts::nut10::Error),
184    /// NUT11 error
185    #[error(transparent)]
186    NUT11(#[from] crate::nuts::nut11::Error),
187    /// Short keyset id -> id error
188    #[error(transparent)]
189    NUT02(#[from] crate::nuts::nut02::Error),
190}
191
192/// Blinded Message (also called `output`)
193#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
194#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
195pub struct BlindedMessage {
196    /// Amount
197    ///
198    /// The value for the requested [BlindSignature]
199    pub amount: Amount,
200    /// Keyset ID
201    ///
202    /// ID from which we expect a signature.
203    #[serde(rename = "id")]
204    #[cfg_attr(feature = "swagger", schema(value_type = String))]
205    pub keyset_id: Id,
206    /// Blinded secret message (B_)
207    ///
208    /// The blinded secret message generated by the sender.
209    #[serde(rename = "B_")]
210    #[cfg_attr(feature = "swagger", schema(value_type = String))]
211    pub blinded_secret: PublicKey,
212    /// Witness
213    ///
214    /// <https://github.com/cashubtc/nuts/blob/main/11.md>
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub witness: Option<Witness>,
217}
218
219impl BlindedMessage {
220    /// Compose new blinded message
221    #[inline]
222    pub fn new(amount: Amount, keyset_id: Id, blinded_secret: PublicKey) -> Self {
223        Self {
224            amount,
225            keyset_id,
226            blinded_secret,
227            witness: None,
228        }
229    }
230
231    /// Add witness
232    #[inline]
233    pub fn witness(&mut self, witness: Witness) {
234        self.witness = Some(witness);
235    }
236}
237
238/// Blind Signature (also called `promise`)
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
241pub struct BlindSignature {
242    /// Amount
243    ///
244    /// The value of the blinded token.
245    pub amount: Amount,
246    /// Keyset ID
247    ///
248    /// ID of the mint keys that signed the token.
249    #[serde(rename = "id")]
250    #[cfg_attr(feature = "swagger", schema(value_type = String))]
251    pub keyset_id: Id,
252    /// Blinded signature (C_)
253    ///
254    /// The blinded signature on the secret message `B_` of [BlindedMessage].
255    #[serde(rename = "C_")]
256    #[cfg_attr(feature = "swagger", schema(value_type = String))]
257    pub c: PublicKey,
258    /// DLEQ Proof
259    ///
260    /// <https://github.com/cashubtc/nuts/blob/main/12.md>
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub dleq: Option<BlindSignatureDleq>,
263}
264
265impl Ord for BlindSignature {
266    fn cmp(&self, other: &Self) -> Ordering {
267        self.amount.cmp(&other.amount)
268    }
269}
270
271impl PartialOrd for BlindSignature {
272    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
273        Some(self.cmp(other))
274    }
275}
276
277/// Witness
278#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
279#[serde(untagged)]
280#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
281pub enum Witness {
282    /// P2PK Witness
283    #[serde(with = "serde_p2pk_witness")]
284    P2PKWitness(P2PKWitness),
285    /// HTLC Witness
286    #[serde(with = "serde_htlc_witness")]
287    HTLCWitness(HTLCWitness),
288}
289
290impl From<P2PKWitness> for Witness {
291    fn from(witness: P2PKWitness) -> Self {
292        Self::P2PKWitness(witness)
293    }
294}
295
296impl From<HTLCWitness> for Witness {
297    fn from(witness: HTLCWitness) -> Self {
298        Self::HTLCWitness(witness)
299    }
300}
301
302impl Witness {
303    /// Add signatures to [`Witness`]
304    pub fn add_signatures(&mut self, signatues: Vec<String>) {
305        match self {
306            Self::P2PKWitness(p2pk_witness) => p2pk_witness.signatures.extend(signatues),
307            Self::HTLCWitness(htlc_witness) => {
308                htlc_witness.signatures = htlc_witness.signatures.clone().map(|sigs| {
309                    let mut sigs = sigs;
310                    sigs.extend(signatues);
311                    sigs
312                });
313            }
314        }
315    }
316
317    /// Get signatures on [`Witness`]
318    pub fn signatures(&self) -> Option<Vec<String>> {
319        match self {
320            Self::P2PKWitness(witness) => Some(witness.signatures.clone()),
321            Self::HTLCWitness(witness) => witness.signatures.clone(),
322        }
323    }
324
325    /// Get preimage from [`Witness`]
326    pub fn preimage(&self) -> Option<String> {
327        match self {
328            Self::P2PKWitness(_witness) => None,
329            Self::HTLCWitness(witness) => Some(witness.preimage.clone()),
330        }
331    }
332}
333
334/// Proofs
335#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
336#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
337pub struct Proof {
338    /// Amount
339    pub amount: Amount,
340    /// `Keyset id`
341    #[serde(rename = "id")]
342    #[cfg_attr(feature = "swagger", schema(value_type = String))]
343    pub keyset_id: Id,
344    /// Secret message
345    #[cfg_attr(feature = "swagger", schema(value_type = String))]
346    pub secret: Secret,
347    /// Unblinded signature
348    #[serde(rename = "C")]
349    #[cfg_attr(feature = "swagger", schema(value_type = String))]
350    pub c: PublicKey,
351    /// Witness
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub witness: Option<Witness>,
354    /// DLEQ Proof
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub dleq: Option<ProofDleq>,
357}
358
359impl Proof {
360    /// Create new [`Proof`]
361    pub fn new(amount: Amount, keyset_id: Id, secret: Secret, c: PublicKey) -> Self {
362        Proof {
363            amount,
364            keyset_id,
365            secret,
366            c,
367            witness: None,
368            dleq: None,
369        }
370    }
371
372    /// Check if proof is in active keyset `Id`s
373    pub fn is_active(&self, active_keyset_ids: &[Id]) -> bool {
374        active_keyset_ids.contains(&self.keyset_id)
375    }
376
377    /// Get y from proof
378    ///
379    /// Where y is `hash_to_curve(secret)`
380    pub fn y(&self) -> Result<PublicKey, Error> {
381        Ok(hash_to_curve(self.secret.as_bytes())?)
382    }
383}
384
385impl Hash for Proof {
386    fn hash<H: Hasher>(&self, state: &mut H) {
387        self.secret.hash(state);
388    }
389}
390
391impl Ord for Proof {
392    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
393        self.amount.cmp(&other.amount)
394    }
395}
396
397impl PartialOrd for Proof {
398    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
399        Some(self.cmp(other))
400    }
401}
402
403/// Proof V4
404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub struct ProofV4 {
406    /// Amount in satoshi
407    #[serde(rename = "a")]
408    pub amount: Amount,
409    /// Secret message
410    #[serde(rename = "s")]
411    pub secret: Secret,
412    /// Unblinded signature
413    #[serde(
414        serialize_with = "serialize_v4_pubkey",
415        deserialize_with = "deserialize_v4_pubkey"
416    )]
417    pub c: PublicKey,
418    /// Witness
419    #[serde(default)]
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub witness: Option<Witness>,
422    /// DLEQ Proof
423    #[serde(rename = "d")]
424    pub dleq: Option<ProofDleq>,
425}
426
427impl ProofV4 {
428    /// [`ProofV4`] into [`Proof`]
429    pub fn into_proof(&self, keyset_id: &Id) -> Proof {
430        Proof {
431            amount: self.amount,
432            keyset_id: *keyset_id,
433            secret: self.secret.clone(),
434            c: self.c,
435            witness: self.witness.clone(),
436            dleq: self.dleq.clone(),
437        }
438    }
439}
440
441impl Hash for ProofV4 {
442    fn hash<H: Hasher>(&self, state: &mut H) {
443        self.secret.hash(state);
444    }
445}
446
447impl From<Proof> for ProofV4 {
448    fn from(proof: Proof) -> ProofV4 {
449        let Proof {
450            amount,
451            keyset_id: _,
452            secret,
453            c,
454            witness,
455            dleq,
456        } = proof;
457        ProofV4 {
458            amount,
459            secret,
460            c,
461            witness,
462            dleq,
463        }
464    }
465}
466
467impl From<ProofV3> for ProofV4 {
468    fn from(proof: ProofV3) -> Self {
469        Self {
470            amount: proof.amount,
471            secret: proof.secret,
472            c: proof.c,
473            witness: proof.witness,
474            dleq: proof.dleq,
475        }
476    }
477}
478
479/// Proof v3 with short keyset id
480#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
481pub struct ProofV3 {
482    /// Amount
483    pub amount: Amount,
484    /// Short keyset id
485    #[serde(rename = "id")]
486    pub keyset_id: ShortKeysetId,
487    /// Secret message
488    pub secret: Secret,
489    /// Unblinded signature
490    #[serde(rename = "C")]
491    pub c: PublicKey,
492    /// Witness
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub witness: Option<Witness>,
495    /// DLEQ Proof
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub dleq: Option<ProofDleq>,
498}
499
500impl ProofV3 {
501    /// [`ProofV3`] into [`Proof`]
502    pub fn into_proof(&self, keyset_id: &Id) -> Proof {
503        Proof {
504            amount: self.amount,
505            keyset_id: *keyset_id,
506            secret: self.secret.clone(),
507            c: self.c,
508            witness: self.witness.clone(),
509            dleq: self.dleq.clone(),
510        }
511    }
512}
513
514impl From<Proof> for ProofV3 {
515    fn from(proof: Proof) -> ProofV3 {
516        let Proof {
517            amount,
518            keyset_id,
519            secret,
520            c,
521            witness,
522            dleq,
523        } = proof;
524        ProofV3 {
525            amount,
526            secret,
527            c,
528            witness,
529            dleq,
530            keyset_id: keyset_id.into(),
531        }
532    }
533}
534
535impl Hash for ProofV3 {
536    fn hash<H: Hasher>(&self, state: &mut H) {
537        self.secret.hash(state);
538    }
539}
540
541fn serialize_v4_pubkey<S>(key: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
542where
543    S: serde::Serializer,
544{
545    serializer.serialize_bytes(&key.to_bytes())
546}
547
548fn deserialize_v4_pubkey<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
549where
550    D: serde::Deserializer<'de>,
551{
552    let bytes = Vec::<u8>::deserialize(deserializer)?;
553    PublicKey::from_slice(&bytes).map_err(serde::de::Error::custom)
554}
555
556/// Currency Unit
557#[non_exhaustive]
558#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
559#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
560pub enum CurrencyUnit {
561    /// Sat
562    #[default]
563    Sat,
564    /// Msat
565    Msat,
566    /// Usd
567    Usd,
568    /// Euro
569    Eur,
570    /// Auth
571    Auth,
572    /// Custom currency unit
573    Custom(String),
574}
575
576#[cfg(feature = "mint")]
577impl CurrencyUnit {
578    /// Derivation index mint will use for unit
579    pub fn derivation_index(&self) -> Option<u32> {
580        match self {
581            Self::Sat => Some(0),
582            Self::Msat => Some(1),
583            Self::Usd => Some(2),
584            Self::Eur => Some(3),
585            Self::Auth => Some(4),
586            _ => None,
587        }
588    }
589}
590
591impl FromStr for CurrencyUnit {
592    type Err = Error;
593    fn from_str(value: &str) -> Result<Self, Self::Err> {
594        let value = &value.to_uppercase();
595        match value.as_str() {
596            "SAT" => Ok(Self::Sat),
597            "MSAT" => Ok(Self::Msat),
598            "USD" => Ok(Self::Usd),
599            "EUR" => Ok(Self::Eur),
600            "AUTH" => Ok(Self::Auth),
601            c => Ok(Self::Custom(c.to_string())),
602        }
603    }
604}
605
606impl fmt::Display for CurrencyUnit {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        let s = match self {
609            CurrencyUnit::Sat => "SAT",
610            CurrencyUnit::Msat => "MSAT",
611            CurrencyUnit::Usd => "USD",
612            CurrencyUnit::Eur => "EUR",
613            CurrencyUnit::Auth => "AUTH",
614            CurrencyUnit::Custom(unit) => unit,
615        };
616        if let Some(width) = f.width() {
617            write!(f, "{:width$}", s.to_lowercase(), width = width)
618        } else {
619            write!(f, "{}", s.to_lowercase())
620        }
621    }
622}
623
624impl Serialize for CurrencyUnit {
625    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
626    where
627        S: serde::Serializer,
628    {
629        serializer.serialize_str(&self.to_string())
630    }
631}
632
633impl<'de> Deserialize<'de> for CurrencyUnit {
634    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
635    where
636        D: Deserializer<'de>,
637    {
638        let currency: String = String::deserialize(deserializer)?;
639        Self::from_str(&currency).map_err(|_| serde::de::Error::custom("Unsupported unit"))
640    }
641}
642
643/// Payment Method
644#[non_exhaustive]
645#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
646#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
647pub enum PaymentMethod {
648    /// Bolt11 payment type
649    #[default]
650    Bolt11,
651    /// Custom
652    Custom(String),
653}
654
655impl FromStr for PaymentMethod {
656    type Err = Error;
657    fn from_str(value: &str) -> Result<Self, Self::Err> {
658        match value.to_lowercase().as_str() {
659            "bolt11" => Ok(Self::Bolt11),
660            c => Ok(Self::Custom(c.to_string())),
661        }
662    }
663}
664
665impl fmt::Display for PaymentMethod {
666    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
667        match self {
668            PaymentMethod::Bolt11 => write!(f, "bolt11"),
669            PaymentMethod::Custom(p) => write!(f, "{p}"),
670        }
671    }
672}
673
674impl Serialize for PaymentMethod {
675    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
676    where
677        S: serde::Serializer,
678    {
679        serializer.serialize_str(&self.to_string())
680    }
681}
682
683impl<'de> Deserialize<'de> for PaymentMethod {
684    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
685    where
686        D: Deserializer<'de>,
687    {
688        let payment_method: String = String::deserialize(deserializer)?;
689        Self::from_str(&payment_method).map_err(|_| de::Error::custom("Unsupported payment method"))
690    }
691}
692
693/// PreMint
694#[cfg(feature = "wallet")]
695#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
696pub struct PreMint {
697    /// Blinded message
698    pub blinded_message: BlindedMessage,
699    /// Secret
700    pub secret: Secret,
701    /// R
702    pub r: SecretKey,
703    /// Amount
704    pub amount: Amount,
705}
706
707#[cfg(feature = "wallet")]
708impl Ord for PreMint {
709    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
710        self.amount.cmp(&other.amount)
711    }
712}
713
714#[cfg(feature = "wallet")]
715impl PartialOrd for PreMint {
716    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
717        Some(self.cmp(other))
718    }
719}
720
721/// Premint Secrets
722#[cfg(feature = "wallet")]
723#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
724pub struct PreMintSecrets {
725    /// Secrets
726    pub secrets: Vec<PreMint>,
727    /// Keyset Id
728    pub keyset_id: Id,
729}
730
731#[cfg(feature = "wallet")]
732impl PreMintSecrets {
733    /// Create new [`PreMintSecrets`]
734    pub fn new(keyset_id: Id) -> Self {
735        Self {
736            secrets: Vec::new(),
737            keyset_id,
738        }
739    }
740
741    /// Outputs for speceifed amount with random secret
742    pub fn random(
743        keyset_id: Id,
744        amount: Amount,
745        amount_split_target: &SplitTarget,
746    ) -> Result<Self, Error> {
747        let amount_split = amount.split_targeted(amount_split_target)?;
748
749        let mut output = Vec::with_capacity(amount_split.len());
750
751        for amount in amount_split {
752            let secret = Secret::generate();
753            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
754
755            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
756
757            output.push(PreMint {
758                secret,
759                blinded_message,
760                r,
761                amount,
762            });
763        }
764
765        Ok(PreMintSecrets {
766            secrets: output,
767            keyset_id,
768        })
769    }
770
771    /// Outputs from pre defined secrets
772    pub fn from_secrets(
773        keyset_id: Id,
774        amounts: Vec<Amount>,
775        secrets: Vec<Secret>,
776    ) -> Result<Self, Error> {
777        let mut output = Vec::with_capacity(secrets.len());
778
779        for (secret, amount) in secrets.into_iter().zip(amounts) {
780            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
781
782            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
783
784            output.push(PreMint {
785                secret,
786                blinded_message,
787                r,
788                amount,
789            });
790        }
791
792        Ok(PreMintSecrets {
793            secrets: output,
794            keyset_id,
795        })
796    }
797
798    /// Blank Outputs used for NUT-08 change
799    pub fn blank(keyset_id: Id, fee_reserve: Amount) -> Result<Self, Error> {
800        let count = ((u64::from(fee_reserve) as f64).log2().ceil() as u64).max(1);
801
802        let mut output = Vec::with_capacity(count as usize);
803
804        for _i in 0..count {
805            let secret = Secret::generate();
806            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
807
808            let blinded_message = BlindedMessage::new(Amount::ZERO, keyset_id, blinded);
809
810            output.push(PreMint {
811                secret,
812                blinded_message,
813                r,
814                amount: Amount::ZERO,
815            })
816        }
817
818        Ok(PreMintSecrets {
819            secrets: output,
820            keyset_id,
821        })
822    }
823
824    /// Outputs with specific spending conditions
825    pub fn with_conditions(
826        keyset_id: Id,
827        amount: Amount,
828        amount_split_target: &SplitTarget,
829        conditions: &SpendingConditions,
830    ) -> Result<Self, Error> {
831        let amount_split = amount.split_targeted(amount_split_target)?;
832
833        let mut output = Vec::with_capacity(amount_split.len());
834
835        for amount in amount_split {
836            let secret: nut10::Secret = conditions.clone().into();
837
838            let secret: Secret = secret.try_into()?;
839            let (blinded, r) = blind_message(&secret.to_bytes(), None)?;
840
841            let blinded_message = BlindedMessage::new(amount, keyset_id, blinded);
842
843            output.push(PreMint {
844                secret,
845                blinded_message,
846                r,
847                amount,
848            });
849        }
850
851        Ok(PreMintSecrets {
852            secrets: output,
853            keyset_id,
854        })
855    }
856
857    /// Iterate over secrets
858    #[inline]
859    pub fn iter(&self) -> impl Iterator<Item = &PreMint> {
860        self.secrets.iter()
861    }
862
863    /// Length of secrets
864    #[inline]
865    pub fn len(&self) -> usize {
866        self.secrets.len()
867    }
868
869    /// If secrets is empty
870    #[inline]
871    pub fn is_empty(&self) -> bool {
872        self.secrets.is_empty()
873    }
874
875    /// Totoal amount of secrets
876    pub fn total_amount(&self) -> Result<Amount, Error> {
877        Ok(Amount::try_sum(
878            self.secrets.iter().map(|PreMint { amount, .. }| *amount),
879        )?)
880    }
881
882    /// [`BlindedMessage`]s from [`PreMintSecrets`]
883    #[inline]
884    pub fn blinded_messages(&self) -> Vec<BlindedMessage> {
885        self.iter().map(|pm| pm.blinded_message.clone()).collect()
886    }
887
888    /// [`Secret`]s from [`PreMintSecrets`]
889    #[inline]
890    pub fn secrets(&self) -> Vec<Secret> {
891        self.iter().map(|pm| pm.secret.clone()).collect()
892    }
893
894    /// Blinding factor from [`PreMintSecrets`]
895    #[inline]
896    pub fn rs(&self) -> Vec<SecretKey> {
897        self.iter().map(|pm| pm.r.clone()).collect()
898    }
899
900    /// Amounts from [`PreMintSecrets`]
901    #[inline]
902    pub fn amounts(&self) -> Vec<Amount> {
903        self.iter().map(|pm| pm.amount).collect()
904    }
905
906    /// Combine [`PreMintSecrets`]
907    #[inline]
908    pub fn combine(&mut self, mut other: Self) {
909        self.secrets.append(&mut other.secrets)
910    }
911
912    /// Sort [`PreMintSecrets`] by [`Amount`]
913    #[inline]
914    pub fn sort_secrets(&mut self) {
915        self.secrets.sort();
916    }
917}
918
919// Implement Iterator for PreMintSecrets
920#[cfg(feature = "wallet")]
921impl Iterator for PreMintSecrets {
922    type Item = PreMint;
923
924    fn next(&mut self) -> Option<Self::Item> {
925        // Use the iterator of the vector
926        self.secrets.pop()
927    }
928}
929
930#[cfg(feature = "wallet")]
931impl Ord for PreMintSecrets {
932    fn cmp(&self, other: &Self) -> Ordering {
933        self.secrets.cmp(&other.secrets)
934    }
935}
936
937#[cfg(feature = "wallet")]
938impl PartialOrd for PreMintSecrets {
939    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
940        Some(self.cmp(other))
941    }
942}
943
944#[cfg(test)]
945mod tests {
946    use std::str::FromStr;
947
948    use super::*;
949
950    #[test]
951    fn test_proof_serialize() {
952        let proof = "[{\"id\":\"009a1f293253e41e\",\"amount\":2,\"secret\":\"407915bc212be61a77e3e6d2aeb4c727980bda51cd06a6afc29e2861768a7837\",\"C\":\"02bc9097997d81afb2cc7346b5e4345a9346bd2a506eb7958598a72f0cf85163ea\"},{\"id\":\"009a1f293253e41e\",\"amount\":8,\"secret\":\"fe15109314e61d7756b0f8ee0f23a624acaa3f4e042f61433c728c7057b931be\",\"C\":\"029e8e5050b890a7d6c0968db16bc1d5d5fa040ea1de284f6ec69d61299f671059\"}]";
953        let proof: Proofs = serde_json::from_str(proof).unwrap();
954
955        assert_eq!(
956            proof[0].clone().keyset_id,
957            Id::from_str("009a1f293253e41e").unwrap()
958        );
959
960        assert_eq!(proof.len(), 2);
961    }
962
963    #[test]
964    #[cfg(feature = "wallet")]
965    fn test_blank_blinded_messages() {
966        let b = PreMintSecrets::blank(
967            Id::from_str("009a1f293253e41e").unwrap(),
968            Amount::from(1000),
969        )
970        .unwrap();
971        assert_eq!(b.len(), 10);
972
973        let b = PreMintSecrets::blank(Id::from_str("009a1f293253e41e").unwrap(), Amount::from(1))
974            .unwrap();
975        assert_eq!(b.len(), 1);
976    }
977}