fedimint_core/
transaction.rs1use 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#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
18pub struct Transaction {
19 pub inputs: Vec<DynInput>,
21 pub outputs: Vec<DynOutput>,
23 pub nonce: [u8; 8],
30 pub signatures: TransactionSignature,
32}
33
34pub type SerdeTransaction = SerdeModuleEncoding<Transaction>;
35
36impl Transaction {
37 pub const MAX_TX_SIZE: usize = ALEPH_BFT_UNIT_BYTE_LIMIT - 32;
49
50 pub fn tx_hash(&self) -> TransactionId {
56 Self::tx_hash_from_parts(&self.inputs, &self.outputs, self.nonce)
57 }
58
59 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 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
150pub 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>);