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
188impl Note {
189    pub fn verify(&self, pk: tbs::AggregatePublicKey) -> bool {
190        tbs::verify(nonce_message(self.nonce), self.signature, pk)
191    }
192}
193
194pub fn nonce_message(nonce: PublicKey) -> Message {
195    tbs::Message::from_bytes_sha256(&nonce.serialize())
196}
197
198plugin_types_trait_impl_common!(
199    KIND,
200    MintModuleTypes,
201    MintClientConfig,
202    MintInput,
203    MintOutput,
204    MintOutputOutcome,
205    MintConsensusItem,
206    MintInputError,
207    MintOutputError
208);
209
210#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
211pub enum MintInputError {
212    #[error("The mint input version is not supported by this federation")]
213    UnknownInputVariant(#[from] UnknownMintInputVariantError),
214    #[error("The note is already spent")]
215    SpentCoin,
216    #[error("The note has an invalid amount not issued by the mint")]
217    InvalidAmountTier,
218    #[error("The note has an invalid signature")]
219    InvalidSignature,
220}
221
222#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
223pub enum MintOutputError {
224    #[error("The mint output version is not supported by this federation")]
225    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
226    #[error("The note has an invalid amount not issued by the mint")]
227    InvalidAmountTier,
228}