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#[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 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#[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#[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#[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}