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