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