fedimint_mint_common/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#![deny(clippy::pedantic)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]

use core::fmt;
use std::hash::Hash;

use bitcoin_hashes::hex::DisplayHex;
use bitcoin_hashes::Hash as _;
pub use common::{BackupRequest, SignedBackupRequest};
use config::MintClientConfig;
use fedimint_core::core::{Decoder, ModuleInstanceId, ModuleKind};
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::module::{CommonModuleInit, ModuleCommon, ModuleConsensusVersion};
use fedimint_core::{
    extensible_associated_module_type, plugin_types_trait_impl_common, secp256k1, Amount,
};
use serde::{Deserialize, Serialize};
use tbs::BlindedSignatureShare;
use thiserror::Error;
use tracing::error;

pub mod common;
pub mod config;
pub mod endpoint_constants;

pub const KIND: ModuleKind = ModuleKind::from_static_str("mint");
pub const MODULE_CONSENSUS_VERSION: ModuleConsensusVersion = ModuleConsensusVersion::new(2, 0);

/// By default, the maximum notes per denomination when change-making for users
pub const DEFAULT_MAX_NOTES_PER_DENOMINATION: u16 = 3;

/// The mint module currently doesn't define any consensus items and generally
/// throws an error on encountering one. To allow old clients to still decode
/// blocks in the future, should we decide to add consensus items, this has to
/// be an enum with only a default variant.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
pub enum MintConsensusItem {
    #[encodable_default]
    Default { variant: u64, bytes: Vec<u8> },
}

impl std::fmt::Display for MintConsensusItem {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MintConsensusItem")
    }
}

/// Result of Federation members confirming [`MintOutput`] by contributing
/// partial signatures via [`MintConsensusItem`]
///
/// A set of full blinded signatures.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct MintOutputBlindSignature(pub tbs::BlindedSignature);

/// An verifiable one time use IOU from the mint.
///
/// Digital version of a "note of deposit" in a free-banking era.
///
/// Consist of a user-generated nonce and a threshold signature over it
/// generated by the federated mint (while in a [`BlindNonce`] form).
///
/// As things are right now the denomination of each note is determined by the
/// federation keys that signed over it, and needs to be tracked outside of this
/// type.
///
/// In this form it can only be validated, not spent since for that the
/// corresponding secret spend key is required.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct Note {
    pub nonce: Nonce,
    pub signature: tbs::Signature,
}

impl fmt::Display for Note {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.nonce.fmt(f)
    }
}

/// Unique ID of a mint note.
///
/// User-generated, random or otherwise unpredictably generated
/// (deterministically derived).
///
/// Internally a MuSig pub key so that transactions can be signed when being
/// spent.
#[derive(
    Debug,
    Copy,
    Clone,
    Eq,
    PartialEq,
    PartialOrd,
    Ord,
    Hash,
    Deserialize,
    Serialize,
    Encodable,
    Decodable,
)]
pub struct Nonce(pub secp256k1::PublicKey);

impl fmt::Display for Nonce {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

/// [`Nonce`] but blinded by the user key
///
/// Blinding prevents the Mint from being able to link the transaction spending
/// [`Note`]s as an `Input`s of `Transaction` with new [`Note`]s being created
/// in its `Output`s.
///
/// By signing it, the mint commits to the underlying (unblinded) [`Nonce`] as
/// valid (until eventually spent).
#[derive(Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct BlindNonce(pub tbs::BlindedMessage);

impl fmt::Debug for BlindNonce {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!(
            "BlindNonce({})",
            self.0.consensus_hash_sha256().as_byte_array()[0..8].as_hex()
        ))
    }
}

#[derive(Debug)]
pub struct MintCommonInit;

impl CommonModuleInit for MintCommonInit {
    const CONSENSUS_VERSION: ModuleConsensusVersion = MODULE_CONSENSUS_VERSION;
    const KIND: ModuleKind = KIND;

    type ClientConfig = MintClientConfig;

    fn decoder() -> Decoder {
        MintModuleTypes::decoder_builder().build()
    }
}

extensible_associated_module_type!(MintInput, MintInputV0, UnknownMintInputVariantError);

impl MintInput {
    pub fn new_v0(amount: Amount, note: Note) -> MintInput {
        MintInput::V0(MintInputV0 { amount, note })
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct MintInputV0 {
    pub amount: Amount,
    pub note: Note,
}

impl std::fmt::Display for MintInputV0 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Mint Note {}", self.amount)
    }
}

extensible_associated_module_type!(MintOutput, MintOutputV0, UnknownMintOutputVariantError);

impl MintOutput {
    pub fn new_v0(amount: Amount, blind_nonce: BlindNonce) -> MintOutput {
        MintOutput::V0(MintOutputV0 {
            amount,
            blind_nonce,
        })
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct MintOutputV0 {
    pub amount: Amount,
    pub blind_nonce: BlindNonce,
}

impl std::fmt::Display for MintOutputV0 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Mint Note {}", self.amount)
    }
}

extensible_associated_module_type!(
    MintOutputOutcome,
    MintOutputOutcomeV0,
    UnknownMintOutputOutcomeVariantError
);

impl MintOutputOutcome {
    pub fn new_v0(blind_signature_share: BlindedSignatureShare) -> MintOutputOutcome {
        MintOutputOutcome::V0(MintOutputOutcomeV0(blind_signature_share))
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Encodable, Decodable)]
pub struct MintOutputOutcomeV0(pub tbs::BlindedSignatureShare);

impl std::fmt::Display for MintOutputOutcomeV0 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MintOutputOutcome")
    }
}

pub struct MintModuleTypes;

impl Note {
    /// Verify the note's validity under a mit key `pk`
    pub fn verify(&self, pk: tbs::AggregatePublicKey) -> bool {
        tbs::verify(self.nonce.to_message(), self.signature, pk)
    }

    /// Access the nonce as the public key to the spend key
    pub fn spend_key(&self) -> &secp256k1::PublicKey {
        &self.nonce.0
    }
}

impl Nonce {
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = vec![];
        bincode::serialize_into(&mut bytes, &self.0).unwrap();
        bytes
    }

    pub fn from_bytes(bytes: &[u8]) -> Self {
        // FIXME: handle errors or the client can be crashed
        bincode::deserialize(bytes).unwrap()
    }

    pub fn to_message(&self) -> tbs::Message {
        tbs::Message::from_bytes(&self.0.serialize()[..])
    }
}

plugin_types_trait_impl_common!(
    KIND,
    MintModuleTypes,
    MintClientConfig,
    MintInput,
    MintOutput,
    MintOutputOutcome,
    MintConsensusItem,
    MintInputError,
    MintOutputError
);

#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
pub enum MintInputError {
    #[error("The note is already spent")]
    SpentCoin,
    #[error("The note has an invalid amount not issued by the mint: {0}")]
    InvalidAmountTier(Amount),
    #[error("The note has an invalid signature")]
    InvalidSignature,
    #[error("The mint input version is not supported by this federation")]
    UnknownInputVariant(#[from] UnknownMintInputVariantError),
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Encodable, Decodable)]
pub enum MintOutputError {
    #[error("The note has an invalid amount not issued by the mint: {0}")]
    InvalidAmountTier(Amount),
    #[error("The mint output version is not supported by this federation")]
    UnknownOutputVariant(#[from] UnknownMintOutputVariantError),
}