Skip to main content

fedimint_core/core/
backup.rs

1use std::fmt::{self, Debug};
2
3use bitcoin::hashes::{Hash, sha256};
4use fedimint_core::encoding::{
5    Decodable, DecodeError, Encodable, decode_field_from_finite_reader,
6    decode_legacy_system_time_from_finite_reader, encode_legacy_system_time, with_decoding_context,
7};
8use fedimint_core::module::registry::ModuleDecoderRegistry;
9use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification};
10use serde::{Deserialize, Serialize};
11
12/// Maximum payload size of a backup request
13///
14/// Note: this is just a current hard limit,
15/// that could be changed in the future versions.
16///
17/// For comparison - at the time of writing, ecash module
18/// backup with 52 notes is around 5.1K.
19pub const BACKUP_REQUEST_MAX_PAYLOAD_SIZE_BYTES: usize = 128 * 1024;
20
21#[derive(Serialize, Deserialize)]
22pub struct BackupRequest {
23    pub id: secp256k1::PublicKey,
24    #[serde(with = "fedimint_core::hex::serde")]
25    pub payload: Vec<u8>,
26    pub timestamp: std::time::SystemTime,
27}
28
29impl Encodable for BackupRequest {
30    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
31        self.id.consensus_encode(writer)?;
32        self.payload.consensus_encode(writer)?;
33        encode_legacy_system_time(&self.timestamp, writer)
34    }
35}
36
37impl Decodable for BackupRequest {
38    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
39        decoder: &mut D,
40        modules: &ModuleDecoderRegistry,
41    ) -> Result<Self, DecodeError> {
42        Ok(Self {
43            id: decode_field_from_finite_reader(
44                decoder,
45                modules,
46                "Decoding named block field: BackupRequest{ ... id ... }",
47            )?,
48            payload: decode_field_from_finite_reader(
49                decoder,
50                modules,
51                "Decoding named block field: BackupRequest{ ... payload ... }",
52            )?,
53            timestamp: with_decoding_context(
54                decode_legacy_system_time_from_finite_reader(decoder, modules),
55                "Decoding named block field: BackupRequest{ ... timestamp ... }",
56            )?,
57        })
58    }
59}
60
61impl BackupRequest {
62    fn hash(&self) -> sha256::Hash {
63        self.consensus_hash()
64    }
65
66    pub fn sign(self, keypair: &Keypair) -> anyhow::Result<SignedBackupRequest> {
67        let signature = secp256k1::SECP256K1
68            .sign_schnorr(&Message::from_digest(*self.hash().as_ref()), keypair);
69
70        Ok(SignedBackupRequest {
71            request: self,
72            signature,
73        })
74    }
75}
76
77impl fmt::Debug for BackupRequest {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("BackupRequest")
80            .field("id", &self.id)
81            .field("timestamp", &self.timestamp)
82            .field("payload_len", &self.payload.len())
83            .finish()
84    }
85}
86
87#[derive(Debug, Serialize, Deserialize)]
88pub struct SignedBackupRequest {
89    #[serde(flatten)]
90    request: BackupRequest,
91    pub signature: secp256k1::schnorr::Signature,
92}
93
94impl SignedBackupRequest {
95    pub fn verify_valid<C>(&self, ctx: &Secp256k1<C>) -> Result<&BackupRequest, secp256k1::Error>
96    where
97        C: Signing + Verification,
98    {
99        ctx.verify_schnorr(
100            &self.signature,
101            &secp256k1::Message::from_digest_slice(&self.request.hash().to_byte_array())
102                .expect("Can't fail"),
103            &self.request.id.x_only_public_key().0,
104        )?;
105
106        Ok(&self.request)
107    }
108}