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