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(Debug, 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::Display for Note {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        self.nonce.fmt(f)
81    }
82}
83
84/// Unique ID of a mint note.
85///
86/// User-generated, random or otherwise unpredictably generated
87/// (deterministically derived).
88///
89/// Internally a MuSig pub key so that transactions can be signed when being
90/// spent.
91#[derive(
92    Debug,
93    Copy,
94    Clone,
95    Eq,
96    PartialEq,
97    PartialOrd,
98    Ord,
99    Hash,
100    Deserialize,
101    Serialize,
102    Encodable,
103    Decodable,
104)]
105pub struct Nonce(pub secp256k1::PublicKey);
106
107impl fmt::Display for Nonce {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        self.0.fmt(f)
110    }
111}
112
113/// [`Nonce`] but blinded by the user key
114///
115/// Blinding prevents the Mint from being able to link the transaction spending
116/// [`Note`]s as an `Input`s of `Transaction` with new [`Note`]s being created
117/// in its `Output`s.
118///
119/// By signing it, the mint commits to the underlying (unblinded) [`Nonce`] as
120/// valid (until eventually spent).
121#[derive(Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
122pub struct BlindNonce(pub tbs::BlindedMessage);
123
124impl fmt::Debug for BlindNonce {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.write_fmt(format_args!(
127            "BlindNonce({})",
128            self.0.consensus_hash_sha256().as_byte_array()[0..8].as_hex()
129        ))
130    }
131}
132
133#[derive(Debug)]
134pub struct MintCommonInit;
135
136impl CommonModuleInit for MintCommonInit {
137    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
138    const KIND: ModuleKind = KIND;
139
140    type ClientConfig = MintClientConfig;
141
142    fn decoder() -> Decoder {
143        MintModuleTypes::decoder_builder().build()
144    }
145}
146
147extensible_associated_module_type!(MintInput, MintInputV0, UnknownMintInputVariantError);
148
149impl MintInput {
150    pub fn new_v0(amount: Amount, note: Note) -> MintInput {
151        MintInput::V0(MintInputV0 { amount, note })
152    }
153}
154
155#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
156pub struct MintInputV0 {
157    pub amount: Amount,
158    pub note: Note,
159}
160
161impl std::fmt::Display for MintInputV0 {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        write!(f, "Mint Note {}", self.amount)
164    }
165}
166
167extensible_associated_module_type!(MintOutput, MintOutputV0, UnknownMintOutputVariantError);
168
169impl MintOutput {
170    pub fn new_v0(amount: Amount, blind_nonce: BlindNonce) -> MintOutput {
171        MintOutput::V0(MintOutputV0 {
172            amount,
173            blind_nonce,
174        })
175    }
176}
177
178#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
179pub struct MintOutputV0 {
180    pub amount: Amount,
181    pub blind_nonce: BlindNonce,
182}
183
184impl std::fmt::Display for MintOutputV0 {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(f, "Mint Note {}", self.amount)
187    }
188}
189
190extensible_associated_module_type!(
191    MintOutputOutcome,
192    MintOutputOutcomeV0,
193    UnknownMintOutputOutcomeVariantError
194);
195
196impl MintOutputOutcome {
197    pub fn new_v0(blind_signature_share: BlindedSignatureShare) -> MintOutputOutcome {
198        MintOutputOutcome::V0(MintOutputOutcomeV0(blind_signature_share))
199    }
200}
201
202#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
203pub struct MintOutputOutcomeV0(pub tbs::BlindedSignatureShare);
204
205impl std::fmt::Display for MintOutputOutcomeV0 {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        write!(f, "MintOutputOutcome")
208    }
209}
210
211pub struct MintModuleTypes;
212
213impl Note {
214    /// Verify the note's validity under a mit key `pk`
215    pub fn verify(&self, pk: tbs::AggregatePublicKey) -> bool {
216        tbs::verify(self.nonce.to_message(), self.signature, pk)
217    }
218
219    /// Access the nonce as the public key to the spend key
220    pub fn spend_key(&self) -> &secp256k1::PublicKey {
221        &self.nonce.0
222    }
223}
224
225impl Nonce {
226    pub fn to_message(&self) -> tbs::Message {
227        tbs::Message::from_bytes(&self.0.serialize()[..])
228    }
229}
230
231plugin_types_trait_impl_common!(
232    KIND,
233    MintModuleTypes,
234    MintClientConfig,
235    MintInput,
236    MintOutput,
237    MintOutputOutcome,
238    MintConsensusItem,
239    MintInputError,
240    MintOutputError
241);
242
243#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
244pub enum MintInputError {
245    #[error("The note is already spent")]
246    SpentCoin,
247    #[error("The note has an invalid amount not issued by the mint: {0}")]
248    InvalidAmountTier(Amount),
249    #[error("The note has an invalid signature")]
250    InvalidSignature,
251    #[error("The mint input version is not supported by this federation")]
252    UnknownInputVariant(#[from] UnknownMintInputVariantError),
253}
254
255#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
256pub enum MintOutputError {
257    #[error("The note has an invalid amount not issued by the mint: {0}")]
258    InvalidAmountTier(Amount),
259    #[error("The mint output version is not supported by this federation")]
260    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
261    #[error("The mint output blind nonce was already used before")]
262    BlindNonceAlreadyUsed,
263}