fedimint_core/
transaction.rs

1use bitcoin::hashes::Hash;
2use fedimint_core::core::{DynInput, DynOutput};
3use fedimint_core::encoding::{Decodable, Encodable};
4use fedimint_core::module::SerdeModuleEncoding;
5use fedimint_core::{Amount, TransactionId};
6use thiserror::Error;
7
8use crate::config::ALEPH_BFT_UNIT_BYTE_LIMIT;
9use crate::core::{DynInputError, DynOutputError};
10
11/// An atomic value transfer operation within the Fedimint system and consensus
12///
13/// The mint enforces that the total value of the outputs equals the total value
14/// of the inputs, to prevent creating funds out of thin air. In some cases, the
15/// value of the inputs and outputs can both be 0 e.g. when creating an offer to
16/// a Lightning Gateway.
17#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
18pub struct Transaction {
19    /// [`DynInput`]s consumed by the transaction
20    pub inputs: Vec<DynInput>,
21    /// [`DynOutput`]s created as a result of the transaction
22    pub outputs: Vec<DynOutput>,
23    /// No defined meaning, can be used to send the otherwise exactly same
24    /// transaction multiple times if the module inputs and outputs don't
25    /// introduce enough entropy.
26    ///
27    /// In the future the nonce can be used for grinding a tx hash that fulfills
28    /// certain PoW requirements.
29    pub nonce: [u8; 8],
30    /// signatures for all the public keys of the inputs
31    pub signatures: TransactionSignature,
32}
33
34pub type SerdeTransaction = SerdeModuleEncoding<Transaction>;
35
36impl Transaction {
37    /// Maximum size that a transaction can have while still fitting into an
38    /// AlephBFT unit. Subtracting 32 bytes is overly conservative, even in the
39    /// worst case the CI serialization around the transaction should never add
40    /// that much overhead. But since the byte limit is 50kb right now a few
41    /// bytes more or less won't make a difference and we can afford the safety
42    /// margin.
43    ///
44    /// A realistic value would be 7:
45    ///  * 1 byte for length of vector of CIs
46    ///  * 1 byte for the CI enum variant
47    ///  * 5 byte for the CI enum variant length
48    pub const MAX_TX_SIZE: usize = ALEPH_BFT_UNIT_BYTE_LIMIT - 32;
49
50    /// Hash of the transaction (excluding the signature).
51    ///
52    /// Transaction signature commits to this hash.
53    /// To generate it without already having a signature use
54    /// [`Self::tx_hash_from_parts`].
55    pub fn tx_hash(&self) -> TransactionId {
56        Self::tx_hash_from_parts(&self.inputs, &self.outputs, self.nonce)
57    }
58
59    /// Generate the transaction hash.
60    pub fn tx_hash_from_parts(
61        inputs: &[DynInput],
62        outputs: &[DynOutput],
63        nonce: [u8; 8],
64    ) -> TransactionId {
65        let mut engine = TransactionId::engine();
66        inputs
67            .consensus_encode(&mut engine)
68            .expect("write to hash engine can't fail");
69        outputs
70            .consensus_encode(&mut engine)
71            .expect("write to hash engine can't fail");
72        nonce
73            .consensus_encode(&mut engine)
74            .expect("write to hash engine can't fail");
75        TransactionId::from_engine(engine)
76    }
77
78    /// Validate the schnorr signatures signed over the `tx_hash`
79    pub fn validate_signatures(
80        &self,
81        pub_keys: &[secp256k1::PublicKey],
82    ) -> Result<(), TransactionError> {
83        let signatures = match &self.signatures {
84            TransactionSignature::NaiveMultisig(sigs) => sigs,
85            TransactionSignature::Default { variant, .. } => {
86                return Err(TransactionError::UnsupportedSignatureScheme { variant: *variant })
87            }
88        };
89
90        if pub_keys.len() != signatures.len() {
91            return Err(TransactionError::InvalidWitnessLength);
92        }
93
94        let txid = self.tx_hash();
95        let msg = secp256k1::Message::from_digest_slice(&txid[..]).expect("txid has right length");
96
97        for (pk, signature) in pub_keys.iter().zip(signatures) {
98            if secp256k1::global::SECP256K1
99                .verify_schnorr(signature, &msg, &pk.x_only_public_key().0)
100                .is_err()
101            {
102                return Err(TransactionError::InvalidSignature {
103                    tx: self.consensus_encode_to_hex(),
104                    hash: self.tx_hash().consensus_encode_to_hex(),
105                    sig: signature.consensus_encode_to_hex(),
106                    key: pk.consensus_encode_to_hex(),
107                });
108            }
109        }
110
111        Ok(())
112    }
113}
114
115#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
116pub enum TransactionSignature {
117    NaiveMultisig(Vec<fedimint_core::secp256k1::schnorr::Signature>),
118    #[encodable_default]
119    Default {
120        variant: u64,
121        bytes: Vec<u8>,
122    },
123}
124
125#[derive(Debug, Error, Encodable, Decodable, Clone, Eq, PartialEq)]
126pub enum TransactionError {
127    #[error("The transaction is unbalanced (in={inputs}, out={outputs}, fee={fee})")]
128    UnbalancedTransaction {
129        inputs: Amount,
130        outputs: Amount,
131        fee: Amount,
132    },
133    #[error("The transaction's signature is invalid: tx={tx}, hash={hash}, sig={sig}, key={key}")]
134    InvalidSignature {
135        tx: String,
136        hash: String,
137        sig: String,
138        key: String,
139    },
140    #[error("The transaction's signature scheme is not supported: variant={variant}")]
141    UnsupportedSignatureScheme { variant: u64 },
142    #[error("The transaction did not have the correct number of signatures")]
143    InvalidWitnessLength,
144    #[error("The transaction had an invalid input: {}", .0)]
145    Input(DynInputError),
146    #[error("The transaction had an invalid output: {}", .0)]
147    Output(DynOutputError),
148}
149
150/// The transaction caused an overflow.
151///
152/// We can't add a new variant to transaction errors, so we define a special
153/// case for the retroactively added overflow error type. In a second iteration
154/// of the transaction submission API this should become a separate error
155/// variant.
156pub const TRANSACTION_OVERFLOW_ERROR: TransactionError = TransactionError::UnbalancedTransaction {
157    inputs: Amount::ZERO,
158    outputs: Amount::ZERO,
159    fee: Amount::ZERO,
160};
161
162#[derive(Debug, Encodable, Decodable, Clone, Eq, PartialEq)]
163pub struct TransactionSubmissionOutcome(pub Result<TransactionId, TransactionError>);