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