fedimint_core/
transaction.rs1use std::fmt;
2
3use bitcoin::hashes::Hash;
4use bitcoin::hex::DisplayHex as _;
5use fedimint_core::core::{DynInput, DynOutput};
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::module::SerdeModuleEncoding;
8use fedimint_core::{Amount, TransactionId};
9use thiserror::Error;
10
11use crate::config::ALEPH_BFT_UNIT_BYTE_LIMIT;
12use crate::core::{DynInputError, DynOutputError};
13
14#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
21pub struct Transaction {
22 pub inputs: Vec<DynInput>,
24 pub outputs: Vec<DynOutput>,
26 pub nonce: [u8; 8],
33 pub signatures: TransactionSignature,
35}
36
37pub type SerdeTransaction = SerdeModuleEncoding<Transaction>;
38
39impl Transaction {
40 pub const MAX_TX_SIZE: usize = ALEPH_BFT_UNIT_BYTE_LIMIT - 32;
52
53 pub fn tx_hash(&self) -> TransactionId {
59 Self::tx_hash_from_parts(&self.inputs, &self.outputs, self.nonce)
60 }
61
62 pub fn tx_hash_from_parts(
64 inputs: &[DynInput],
65 outputs: &[DynOutput],
66 nonce: [u8; 8],
67 ) -> TransactionId {
68 let mut engine = TransactionId::engine();
69 inputs
70 .consensus_encode(&mut engine)
71 .expect("write to hash engine can't fail");
72 outputs
73 .consensus_encode(&mut engine)
74 .expect("write to hash engine can't fail");
75 nonce
76 .consensus_encode(&mut engine)
77 .expect("write to hash engine can't fail");
78 TransactionId::from_engine(engine)
79 }
80
81 pub fn validate_signatures(
83 &self,
84 pub_keys: &[secp256k1::PublicKey],
85 ) -> Result<(), TransactionError> {
86 let signatures = match &self.signatures {
87 TransactionSignature::NaiveMultisig(sigs) => sigs,
88 TransactionSignature::Default { variant, .. } => {
89 return Err(TransactionError::UnsupportedSignatureScheme { variant: *variant })
90 }
91 };
92
93 if pub_keys.len() != signatures.len() {
94 return Err(TransactionError::InvalidWitnessLength);
95 }
96
97 let txid = self.tx_hash();
98 let msg = secp256k1::Message::from_digest_slice(&txid[..]).expect("txid has right length");
99
100 for (pk, signature) in pub_keys.iter().zip(signatures) {
101 if secp256k1::global::SECP256K1
102 .verify_schnorr(signature, &msg, &pk.x_only_public_key().0)
103 .is_err()
104 {
105 return Err(TransactionError::InvalidSignature {
106 tx: self.consensus_encode_to_hex(),
107 hash: self.tx_hash().consensus_encode_to_hex(),
108 sig: signature.consensus_encode_to_hex(),
109 key: pk.consensus_encode_to_hex(),
110 });
111 }
112 }
113
114 Ok(())
115 }
116}
117
118#[derive(Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
119pub enum TransactionSignature {
120 NaiveMultisig(Vec<fedimint_core::secp256k1::schnorr::Signature>),
121 #[encodable_default]
122 Default {
123 variant: u64,
124 bytes: Vec<u8>,
125 },
126}
127
128impl fmt::Debug for TransactionSignature {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 match self {
131 Self::NaiveMultisig(multi) => {
132 f.debug_struct("NaiveMultisig")
133 .field("len", &multi.len())
134 .finish()?;
135 }
136 Self::Default { variant, bytes } => {
137 f.debug_struct(stringify!($name))
138 .field("variant", variant)
139 .field("bytes", &bytes.as_hex())
140 .finish()?;
141 }
142 }
143 Ok(())
144 }
145}
146
147#[derive(Debug, Error, Encodable, Decodable, Clone, Eq, PartialEq)]
148pub enum TransactionError {
149 #[error("The transaction is unbalanced (in={inputs}, out={outputs}, fee={fee})")]
150 UnbalancedTransaction {
151 inputs: Amount,
152 outputs: Amount,
153 fee: Amount,
154 },
155 #[error("The transaction's signature is invalid: tx={tx}, hash={hash}, sig={sig}, key={key}")]
156 InvalidSignature {
157 tx: String,
158 hash: String,
159 sig: String,
160 key: String,
161 },
162 #[error("The transaction's signature scheme is not supported: variant={variant}")]
163 UnsupportedSignatureScheme { variant: u64 },
164 #[error("The transaction did not have the correct number of signatures")]
165 InvalidWitnessLength,
166 #[error("The transaction had an invalid input: {}", .0)]
167 Input(DynInputError),
168 #[error("The transaction had an invalid output: {}", .0)]
169 Output(DynOutputError),
170}
171
172pub const TRANSACTION_OVERFLOW_ERROR: TransactionError = TransactionError::UnbalancedTransaction {
179 inputs: Amount::ZERO,
180 outputs: Amount::ZERO,
181 fee: Amount::ZERO,
182};
183
184#[derive(Debug, Encodable, Decodable, Clone, Eq, PartialEq)]
185pub struct TransactionSubmissionOutcome(pub Result<TransactionId, TransactionError>);