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;
15pub use fedimint_core::config::ExcessiveRelativeFeeError;
16use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
17use fedimint_core::encoding::{Decodable, Encodable};
18use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
19use fedimint_core::{
20    Amount, extensible_associated_module_type, plugin_types_trait_impl_common, secp256k1,
21};
22use serde::{Deserialize, Serialize};
23use tbs::BlindedSignatureShare;
24use thiserror::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        write!(f, "{}", self.nonce.fmt_short())
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    Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Deserialize, Serialize, Encodable, Decodable,
101)]
102pub struct Nonce(pub secp256k1::PublicKey);
103
104pub struct NonceShortFmt<'a>(&'a Nonce);
105pub struct NonceFullFmt<'a>(&'a Nonce);
106
107impl Nonce {
108    pub fn fmt_short(&self) -> NonceShortFmt<'_> {
109        NonceShortFmt(self)
110    }
111
112    pub fn fmt_full(&self) -> NonceFullFmt<'_> {
113        NonceFullFmt(self)
114    }
115}
116
117impl fmt::Display for NonceShortFmt<'_> {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let bytes = self.0.0.serialize();
120        fedimint_core::format_hex(&bytes[..4], f)?;
121        f.write_str("_")?;
122        fedimint_core::format_hex(&bytes[29..], f)
123    }
124}
125
126impl fmt::Display for NonceFullFmt<'_> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        let bytes = self.0.0.serialize();
129        fedimint_core::format_hex(&bytes, f)
130    }
131}
132
133impl fmt::Debug for Nonce {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(f, "Nonce({})", self.fmt_short())
136    }
137}
138
139/// [`Nonce`] but blinded by the user key
140///
141/// Blinding prevents the Mint from being able to link the transaction spending
142/// [`Note`]s as an `Input`s of `Transaction` with new [`Note`]s being created
143/// in its `Output`s.
144///
145/// By signing it, the mint commits to the underlying (unblinded) [`Nonce`] as
146/// valid (until eventually spent).
147#[derive(Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
148pub struct BlindNonce(pub tbs::BlindedMessage);
149
150pub struct BlindNonceShortFmt<'a>(&'a BlindNonce);
151pub struct BlindNonceFullFmt<'a>(&'a BlindNonce);
152
153impl BlindNonce {
154    pub fn fmt_short(&self) -> BlindNonceShortFmt<'_> {
155        BlindNonceShortFmt(self)
156    }
157
158    pub fn fmt_full(&self) -> BlindNonceFullFmt<'_> {
159        BlindNonceFullFmt(self)
160    }
161}
162
163impl fmt::Display for BlindNonceShortFmt<'_> {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        let bytes = self.0.0.consensus_hash_sha256().to_byte_array();
166        fedimint_core::format_hex(&bytes[..4], f)?;
167        f.write_str("_")?;
168        fedimint_core::format_hex(&bytes[28..], f)
169    }
170}
171
172impl fmt::Display for BlindNonceFullFmt<'_> {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        write!(
175            f,
176            "{}",
177            self.0.0.consensus_hash_sha256().as_byte_array().as_hex()
178        )
179    }
180}
181
182impl fmt::Debug for BlindNonce {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(f, "BlindNonce({})", self.fmt_short())
185    }
186}
187
188#[derive(Debug)]
189pub struct MintCommonInit;
190
191impl CommonModuleInit for MintCommonInit {
192    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
193    const KIND: ModuleKind = KIND;
194
195    type ClientConfig = MintClientConfig;
196
197    fn decoder() -> Decoder {
198        MintModuleTypes::decoder_builder().build()
199    }
200}
201
202extensible_associated_module_type!(MintInput, MintInputV0, UnknownMintInputVariantError);
203
204impl MintInput {
205    pub fn new_v0(amount: Amount, note: Note) -> MintInput {
206        MintInput::V0(MintInputV0 { amount, note })
207    }
208}
209
210#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
211pub struct MintInputV0 {
212    pub amount: Amount,
213    pub note: Note,
214}
215
216impl std::fmt::Display for MintInputV0 {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        write!(
219            f,
220            "Mint Note {} nonce={}",
221            self.amount,
222            self.note.nonce.fmt_short()
223        )
224    }
225}
226
227extensible_associated_module_type!(MintOutput, MintOutputV0, UnknownMintOutputVariantError);
228
229impl MintOutput {
230    pub fn new_v0(amount: Amount, blind_nonce: BlindNonce) -> MintOutput {
231        MintOutput::V0(MintOutputV0 {
232            amount,
233            blind_nonce,
234        })
235    }
236}
237
238#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
239pub struct MintOutputV0 {
240    pub amount: Amount,
241    pub blind_nonce: BlindNonce,
242}
243
244impl std::fmt::Display for MintOutputV0 {
245    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246        write!(
247            f,
248            "Mint Note {} blind_nonce={}",
249            self.amount,
250            self.blind_nonce.fmt_short()
251        )
252    }
253}
254
255extensible_associated_module_type!(
256    MintOutputOutcome,
257    MintOutputOutcomeV0,
258    UnknownMintOutputOutcomeVariantError
259);
260
261impl MintOutputOutcome {
262    pub fn new_v0(blind_signature_share: BlindedSignatureShare) -> MintOutputOutcome {
263        MintOutputOutcome::V0(MintOutputOutcomeV0(blind_signature_share))
264    }
265}
266
267#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
268pub struct MintOutputOutcomeV0(pub tbs::BlindedSignatureShare);
269
270impl std::fmt::Display for MintOutputOutcomeV0 {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        write!(f, "MintOutputOutcome")
273    }
274}
275
276pub struct MintModuleTypes;
277
278impl Note {
279    /// Verify the note's validity under a mit key `pk`
280    pub fn verify(&self, pk: tbs::AggregatePublicKey) -> bool {
281        tbs::verify(self.nonce.to_message(), self.signature, pk)
282    }
283
284    /// Access the nonce as the public key to the spend key
285    pub fn spend_key(&self) -> &secp256k1::PublicKey {
286        &self.nonce.0
287    }
288}
289
290impl Nonce {
291    pub fn to_message(&self) -> tbs::Message {
292        tbs::Message::from_bytes(&self.0.serialize()[..])
293    }
294}
295
296plugin_types_trait_impl_common!(
297    KIND,
298    MintModuleTypes,
299    MintClientConfig,
300    MintInput,
301    MintOutput,
302    MintOutputOutcome,
303    MintConsensusItem,
304    MintInputError,
305    MintOutputError
306);
307
308#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
309pub enum RecoveryItem {
310    /// A mint output was created (note issuance)
311    Output {
312        amount: Amount,
313        nonce: bitcoin_hashes::hash160::Hash,
314    },
315    /// A mint input was spent (note redemption)
316    Input {
317        nonce: bitcoin_hashes::hash160::Hash,
318    },
319}
320
321#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
322pub enum MintInputError {
323    #[error("The note is already spent")]
324    SpentCoin,
325    #[error("The note has an invalid amount not issued by the mint: {0}")]
326    InvalidAmountTier(Amount),
327    #[error("The note has an invalid signature")]
328    InvalidSignature,
329    #[error("The mint input version is not supported by this federation")]
330    UnknownInputVariant(#[from] UnknownMintInputVariantError),
331}
332
333#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
334pub enum MintOutputError {
335    #[error("The note has an invalid amount not issued by the mint: {0}")]
336    InvalidAmountTier(Amount),
337    #[error("The mint output version is not supported by this federation")]
338    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
339    #[error("The mint output blind nonce was already used before")]
340    BlindNonceAlreadyUsed,
341}