fedimint_core/
transaction.rsuse bitcoin::hashes::Hash;
use fedimint_core::core::{DynInput, DynOutput};
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::module::SerdeModuleEncoding;
use fedimint_core::{Amount, TransactionId};
use thiserror::Error;
use crate::config::ALEPH_BFT_UNIT_BYTE_LIMIT;
use crate::core::{DynInputError, DynOutputError};
#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
pub struct Transaction {
pub inputs: Vec<DynInput>,
pub outputs: Vec<DynOutput>,
pub nonce: [u8; 8],
pub signatures: TransactionSignature,
}
pub type SerdeTransaction = SerdeModuleEncoding<Transaction>;
impl Transaction {
pub const MAX_TX_SIZE: usize = ALEPH_BFT_UNIT_BYTE_LIMIT - 32;
pub fn tx_hash(&self) -> TransactionId {
Self::tx_hash_from_parts(&self.inputs, &self.outputs, self.nonce)
}
pub fn tx_hash_from_parts(
inputs: &[DynInput],
outputs: &[DynOutput],
nonce: [u8; 8],
) -> TransactionId {
let mut engine = TransactionId::engine();
inputs
.consensus_encode(&mut engine)
.expect("write to hash engine can't fail");
outputs
.consensus_encode(&mut engine)
.expect("write to hash engine can't fail");
nonce
.consensus_encode(&mut engine)
.expect("write to hash engine can't fail");
TransactionId::from_engine(engine)
}
pub fn validate_signatures(
&self,
pub_keys: &[secp256k1::PublicKey],
) -> Result<(), TransactionError> {
let signatures = match &self.signatures {
TransactionSignature::NaiveMultisig(sigs) => sigs,
TransactionSignature::Default { variant, .. } => {
return Err(TransactionError::UnsupportedSignatureScheme { variant: *variant })
}
};
if pub_keys.len() != signatures.len() {
return Err(TransactionError::InvalidWitnessLength);
}
let txid = self.tx_hash();
let msg = secp256k1::Message::from_digest_slice(&txid[..]).expect("txid has right length");
for (pk, signature) in pub_keys.iter().zip(signatures) {
if secp256k1::global::SECP256K1
.verify_schnorr(signature, &msg, &pk.x_only_public_key().0)
.is_err()
{
return Err(TransactionError::InvalidSignature {
tx: self.consensus_encode_to_hex(),
hash: self.tx_hash().consensus_encode_to_hex(),
sig: signature.consensus_encode_to_hex(),
key: pk.consensus_encode_to_hex(),
});
}
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Encodable, Decodable)]
pub enum TransactionSignature {
NaiveMultisig(Vec<fedimint_core::secp256k1::schnorr::Signature>),
#[encodable_default]
Default {
variant: u64,
bytes: Vec<u8>,
},
}
#[derive(Debug, Error, Encodable, Decodable, Clone, Eq, PartialEq)]
pub enum TransactionError {
#[error("The transaction is unbalanced (in={inputs}, out={outputs}, fee={fee})")]
UnbalancedTransaction {
inputs: Amount,
outputs: Amount,
fee: Amount,
},
#[error("The transaction's signature is invalid: tx={tx}, hash={hash}, sig={sig}, key={key}")]
InvalidSignature {
tx: String,
hash: String,
sig: String,
key: String,
},
#[error("The transaction's signature scheme is not supported: variant={variant}")]
UnsupportedSignatureScheme { variant: u64 },
#[error("The transaction did not have the correct number of signatures")]
InvalidWitnessLength,
#[error("The transaction had an invalid input: {}", .0)]
Input(DynInputError),
#[error("The transaction had an invalid output: {}", .0)]
Output(DynOutputError),
}
pub const TRANSACTION_OVERFLOW_ERROR: TransactionError = TransactionError::UnbalancedTransaction {
inputs: Amount::ZERO,
outputs: Amount::ZERO,
fee: Amount::ZERO,
};
#[derive(Debug, Encodable, Decodable, Clone, Eq, PartialEq)]
pub struct TransactionSubmissionOutcome(pub Result<TransactionId, TransactionError>);