Skip to main content

fedimint_mint_common/
lib.rs

1#![deny(clippy::pedantic)]
2#![allow(clippy::doc_markdown)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::missing_panics_doc)]
5#![allow(clippy::module_name_repetitions)]
6#![allow(clippy::must_use_candidate)]
7
8use core::fmt;
9use std::hash::Hash;
10
11use bitcoin_hashes::Hash as _;
12use bitcoin_hashes::hex::DisplayHex;
13pub use common::{BackupRequest, SignedBackupRequest};
14use config::MintClientConfig;
15use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
16use fedimint_core::encoding::{Decodable, Encodable};
17use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
18use fedimint_core::{
19    Amount, extensible_associated_module_type, plugin_types_trait_impl_common, secp256k1,
20};
21use serde::{Deserialize, Serialize};
22use tbs::BlindedSignatureShare;
23use thiserror::Error;
24use tracing::error;
25
26pub mod common;
27pub mod config;
28pub mod endpoint_constants;
29
30pub const KIND: ModuleKind = ModuleKind::from_static_str("mint");
31pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(2, 0);
32
33/// By default, the maximum notes per denomination when change-making for users
34pub const DEFAULT_MAX_NOTES_PER_DENOMINATION: u16 = 3;
35
36/// The mint module currently doesn't define any consensus items and generally
37/// throws an error on encountering one. To allow old clients to still decode
38/// blocks in the future, should we decide to add consensus items, this has to
39/// be an enum with only a default variant.
40#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
41pub enum MintConsensusItem {
42    #[encodable_default]
43    Default { variant: u64, bytes: Vec<u8> },
44}
45
46impl std::fmt::Display for MintConsensusItem {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "MintConsensusItem")
49    }
50}
51
52/// Result of Federation members confirming [`MintOutput`] by contributing
53/// partial signatures via [`MintConsensusItem`]
54///
55/// A set of full blinded signatures.
56#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
57pub struct MintOutputBlindSignature(pub tbs::BlindedSignature);
58
59/// An verifiable one time use IOU from the mint.
60///
61/// Digital version of a "note of deposit" in a free-banking era.
62///
63/// Consist of a user-generated nonce and a threshold signature over it
64/// generated by the federated mint (while in a [`BlindNonce`] form).
65///
66/// As things are right now the denomination of each note is determined by the
67/// federation keys that signed over it, and needs to be tracked outside of this
68/// type.
69///
70/// In this form it can only be validated, not spent since for that the
71/// corresponding secret spend key is required.
72#[derive(Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
73pub struct Note {
74    pub nonce: Nonce,
75    pub signature: tbs::Signature,
76}
77
78impl fmt::Debug for Note {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        f.debug_struct("Note")
81            .field("nonce", &self.nonce)
82            .finish_non_exhaustive()
83    }
84}
85
86impl fmt::Display for Note {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        self.nonce.fmt(f)
89    }
90}
91
92/// Unique ID of a mint note.
93///
94/// User-generated, random or otherwise unpredictably generated
95/// (deterministically derived).
96///
97/// Internally a MuSig pub key so that transactions can be signed when being
98/// spent.
99#[derive(
100    Debug,
101    Copy,
102    Clone,
103    Eq,
104    PartialEq,
105    PartialOrd,
106    Ord,
107    Hash,
108    Deserialize,
109    Serialize,
110    Encodable,
111    Decodable,
112)]
113pub struct Nonce(pub secp256k1::PublicKey);
114
115impl fmt::Display for Nonce {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        self.0.fmt(f)
118    }
119}
120
121/// [`Nonce`] but blinded by the user key
122///
123/// Blinding prevents the Mint from being able to link the transaction spending
124/// [`Note`]s as an `Input`s of `Transaction` with new [`Note`]s being created
125/// in its `Output`s.
126///
127/// By signing it, the mint commits to the underlying (unblinded) [`Nonce`] as
128/// valid (until eventually spent).
129#[derive(Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
130pub struct BlindNonce(pub tbs::BlindedMessage);
131
132impl fmt::Debug for BlindNonce {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        f.write_fmt(format_args!(
135            "BlindNonce({})",
136            self.0.consensus_hash_sha256().as_byte_array()[0..8].as_hex()
137        ))
138    }
139}
140
141#[derive(Debug)]
142pub struct MintCommonInit;
143
144impl CommonModuleInit for MintCommonInit {
145    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
146    const KIND: ModuleKind = KIND;
147
148    type ClientConfig = MintClientConfig;
149
150    fn decoder() -> Decoder {
151        MintModuleTypes::decoder_builder().build()
152    }
153}
154
155extensible_associated_module_type!(MintInput, MintInputV0, UnknownMintInputVariantError);
156
157impl MintInput {
158    pub fn new_v0(amount: Amount, note: Note) -> MintInput {
159        MintInput::V0(MintInputV0 { amount, note })
160    }
161}
162
163#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
164pub struct MintInputV0 {
165    pub amount: Amount,
166    pub note: Note,
167}
168
169impl std::fmt::Display for MintInputV0 {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        write!(f, "Mint Note {}", self.amount)
172    }
173}
174
175extensible_associated_module_type!(MintOutput, MintOutputV0, UnknownMintOutputVariantError);
176
177impl MintOutput {
178    pub fn new_v0(amount: Amount, blind_nonce: BlindNonce) -> MintOutput {
179        MintOutput::V0(MintOutputV0 {
180            amount,
181            blind_nonce,
182        })
183    }
184}
185
186#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
187pub struct MintOutputV0 {
188    pub amount: Amount,
189    pub blind_nonce: BlindNonce,
190}
191
192impl std::fmt::Display for MintOutputV0 {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(f, "Mint Note {}", self.amount)
195    }
196}
197
198extensible_associated_module_type!(
199    MintOutputOutcome,
200    MintOutputOutcomeV0,
201    UnknownMintOutputOutcomeVariantError
202);
203
204impl MintOutputOutcome {
205    pub fn new_v0(blind_signature_share: BlindedSignatureShare) -> MintOutputOutcome {
206        MintOutputOutcome::V0(MintOutputOutcomeV0(blind_signature_share))
207    }
208}
209
210#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
211pub struct MintOutputOutcomeV0(pub tbs::BlindedSignatureShare);
212
213impl std::fmt::Display for MintOutputOutcomeV0 {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        write!(f, "MintOutputOutcome")
216    }
217}
218
219pub struct MintModuleTypes;
220
221impl Note {
222    /// Verify the note's validity under a mit key `pk`
223    pub fn verify(&self, pk: tbs::AggregatePublicKey) -> bool {
224        tbs::verify(self.nonce.to_message(), self.signature, pk)
225    }
226
227    /// Access the nonce as the public key to the spend key
228    pub fn spend_key(&self) -> &secp256k1::PublicKey {
229        &self.nonce.0
230    }
231}
232
233impl Nonce {
234    pub fn to_message(&self) -> tbs::Message {
235        tbs::Message::from_bytes(&self.0.serialize()[..])
236    }
237}
238
239plugin_types_trait_impl_common!(
240    KIND,
241    MintModuleTypes,
242    MintClientConfig,
243    MintInput,
244    MintOutput,
245    MintOutputOutcome,
246    MintConsensusItem,
247    MintInputError,
248    MintOutputError
249);
250
251#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
252pub enum MintInputError {
253    #[error("The note is already spent")]
254    SpentCoin,
255    #[error("The note has an invalid amount not issued by the mint: {0}")]
256    InvalidAmountTier(Amount),
257    #[error("The note has an invalid signature")]
258    InvalidSignature,
259    #[error("The mint input version is not supported by this federation")]
260    UnknownInputVariant(#[from] UnknownMintInputVariantError),
261}
262
263#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
264pub enum MintOutputError {
265    #[error("The note has an invalid amount not issued by the mint: {0}")]
266    InvalidAmountTier(Amount),
267    #[error("The mint output version is not supported by this federation")]
268    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
269    #[error("The mint output blind nonce was already used before")]
270    BlindNonceAlreadyUsed,
271}