Skip to main content

fedimint_mintv2_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 std::fmt;
9use std::hash::Hash;
10
11use bitcoin_hashes::hash160;
12use config::MintClientConfig;
13pub use fedimint_core::config::ExcessiveRelativeFeeError;
14use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
15use fedimint_core::encoding::{Decodable, Encodable};
16use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
17use fedimint_core::secp256k1::PublicKey;
18use fedimint_core::{Amount, extensible_associated_module_type, plugin_types_trait_impl_common};
19use serde::{Deserialize, Serialize};
20use tbs::{BlindedMessage, Message};
21use thiserror::Error;
22
23pub mod config;
24pub mod endpoint_constants;
25
26pub const KIND: ModuleKind = ModuleKind::from_static_str("mintv2");
27pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(1, 0);
28
29/// Compact representation of a power-of-2 amount denomination
30/// Represents 2^denomination msats
31#[derive(
32    Debug,
33    Copy,
34    Clone,
35    Eq,
36    PartialEq,
37    Hash,
38    Ord,
39    PartialOrd,
40    Serialize,
41    Deserialize,
42    Encodable,
43    Decodable,
44)]
45pub struct Denomination(pub u8);
46
47impl Denomination {
48    /// Convert to Amount (only call at boundaries)
49    pub fn amount(self) -> Amount {
50        Amount::from_msats(1 << self.0 as usize)
51    }
52}
53
54impl fmt::Display for Denomination {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "2^{} msats", self.0)
57    }
58}
59
60/// The mint module currently doesn't define any consensus items and generally
61/// throws an error on encountering one. To allow old clients to still decode
62/// blocks in the future, should we decide to add consensus items, this has to
63/// be an enum with only a default variant.
64#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
65pub enum MintConsensusItem {
66    #[encodable_default]
67    Default { variant: u64, bytes: Vec<u8> },
68}
69
70impl std::fmt::Display for MintConsensusItem {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "MintConsensusItem")
73    }
74}
75
76#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
77pub struct MintOutputBlindSignature(pub tbs::BlindedSignature);
78
79/// A verifiable one time use IOU from the mint.
80///
81/// Digital version of a "note of deposit" in a free-banking era.
82///
83/// Consist of a user-generated nonce and a threshold signature over it
84/// generated by the federated mint.
85///
86/// In this form it can only be validated, not spent since for that the
87/// corresponding secret spend key is required.
88#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
89pub struct Note {
90    pub denomination: Denomination,
91    pub nonce: PublicKey,
92    pub signature: tbs::Signature,
93}
94
95impl Note {
96    pub fn amount(&self) -> Amount {
97        self.denomination.amount()
98    }
99}
100
101#[derive(Debug)]
102pub struct MintCommonInit;
103
104impl CommonModuleInit for MintCommonInit {
105    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
106    const KIND: ModuleKind = KIND;
107
108    type ClientConfig = MintClientConfig;
109
110    fn decoder() -> Decoder {
111        MintModuleTypes::decoder_builder().build()
112    }
113}
114
115extensible_associated_module_type!(MintInput, MintInputV0, UnknownMintInputVariantError);
116
117impl MintInput {
118    pub fn new_v0(note: Note) -> Self {
119        Self::V0(MintInputV0 { note })
120    }
121}
122
123#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
124pub struct MintInputV0 {
125    pub note: Note,
126}
127
128impl std::fmt::Display for MintInputV0 {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(f, "Mint Note {}", self.note.denomination)
131    }
132}
133
134extensible_associated_module_type!(MintOutput, MintOutputV0, UnknownMintOutputVariantError);
135
136impl MintOutput {
137    pub fn new_v0(denomination: Denomination, nonce: BlindedMessage, tweak: [u8; 16]) -> Self {
138        MintOutput::V0(MintOutputV0 {
139            denomination,
140            nonce,
141            tweak,
142        })
143    }
144}
145
146#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
147pub struct MintOutputV0 {
148    pub denomination: Denomination,
149    pub nonce: BlindedMessage,
150    pub tweak: [u8; 16],
151}
152
153impl MintOutputV0 {
154    pub fn amount(&self) -> Amount {
155        self.denomination.amount()
156    }
157}
158
159impl std::fmt::Display for MintOutputV0 {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "Mint Note {}", self.denomination)
162    }
163}
164
165/// Recovery item for stateless ecash recovery
166#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
167pub enum RecoveryItem {
168    Output {
169        denomination: Denomination,
170        nonce_hash: hash160::Hash,
171        tweak: [u8; 16],
172    },
173    Input {
174        nonce_hash: hash160::Hash,
175    },
176}
177
178#[derive(Debug, Clone, PartialEq, Hash, Encodable, Decodable)]
179pub struct MintOutputOutcome;
180
181impl std::fmt::Display for MintOutputOutcome {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        write!(f, "MintOutputOutcome")
184    }
185}
186
187pub struct MintModuleTypes;
188
189pub fn verify_note(note: Note, pk: tbs::AggregatePublicKey) -> bool {
190    tbs::verify(nonce_message(note.nonce), note.signature, pk)
191}
192
193pub fn nonce_message(nonce: PublicKey) -> Message {
194    tbs::Message::from_bytes_sha256(&nonce.serialize())
195}
196
197plugin_types_trait_impl_common!(
198    KIND,
199    MintModuleTypes,
200    MintClientConfig,
201    MintInput,
202    MintOutput,
203    MintOutputOutcome,
204    MintConsensusItem,
205    MintInputError,
206    MintOutputError
207);
208
209#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
210pub enum MintInputError {
211    #[error("The mint input version is not supported by this federation")]
212    UnknownInputVariant(#[from] UnknownMintInputVariantError),
213    #[error("The note is already spent")]
214    SpentCoin,
215    #[error("The note has an invalid amount not issued by the mint")]
216    InvalidDenomination,
217    #[error("The note has an invalid signature")]
218    InvalidSignature,
219}
220
221#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
222pub enum MintOutputError {
223    #[error("The mint output version is not supported by this federation")]
224    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
225    #[error("The note has an invalid amount not issued by the mint")]
226    InvalidDenomination,
227}