use core::fmt::Debug; use std::io; use zeroize::Zeroize; use thiserror::Error; use blake2::{Digest, Blake2b512}; use ciphersuite::{ group::{Group, GroupEncoding}, Ciphersuite, Ristretto, }; use schnorr::SchnorrSignature; use crate::{TRANSACTION_SIZE_LIMIT, ReadWrite}; #[derive(Clone, PartialEq, Eq, Debug, Error)] pub enum TransactionError { /// Transaction exceeded the size limit. #[error("transaction is too large")] TooLargeTransaction, /// Transaction's signer isn't a participant. #[error("invalid signer")] InvalidSigner, /// Transaction's nonce isn't the prior nonce plus one. #[error("invalid nonce")] InvalidNonce, /// Transaction's signature is invalid. #[error("invalid signature")] InvalidSignature, /// Transaction's content is invalid. #[error("transaction content is invalid")] InvalidContent, /// Transaction's signer has too many transactions in the mempool. #[error("signer has too many transactions in the mempool")] TooManyInMempool, /// Provided Transaction added to mempool. #[error("provided transaction added to mempool")] ProvidedAddedToMempool, } /// Data for a signed transaction. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Signed { pub signer: ::G, pub nonce: u32, pub signature: SchnorrSignature, } impl ReadWrite for Signed { fn read(reader: &mut R) -> io::Result { let signer = Ristretto::read_G(reader)?; let mut nonce = [0; 4]; reader.read_exact(&mut nonce)?; let nonce = u32::from_le_bytes(nonce); if nonce >= (u32::MAX - 1) { Err(io::Error::other("nonce exceeded limit"))?; } let mut signature = SchnorrSignature::::read(reader)?; if signature.R.is_identity().into() { // Anyone malicious could remove this and try to find zero signatures // We should never produce zero signatures though meaning this should never come up // If it does somehow come up, this is a decent courtesy signature.zeroize(); Err(io::Error::other("signature nonce was identity"))?; } Ok(Signed { signer, nonce, signature }) } fn write(&self, writer: &mut W) -> io::Result<()> { // This is either an invalid signature or a private key leak if self.signature.R.is_identity().into() { Err(io::Error::other("signature nonce was identity"))?; } writer.write_all(&self.signer.to_bytes())?; writer.write_all(&self.nonce.to_le_bytes())?; self.signature.write(writer) } } impl Signed { pub fn read_without_nonce(reader: &mut R, nonce: u32) -> io::Result { let signer = Ristretto::read_G(reader)?; let mut signature = SchnorrSignature::::read(reader)?; if signature.R.is_identity().into() { // Anyone malicious could remove this and try to find zero signatures // We should never produce zero signatures though meaning this should never come up // If it does somehow come up, this is a decent courtesy signature.zeroize(); Err(io::Error::other("signature nonce was identity"))?; } Ok(Signed { signer, nonce, signature }) } pub fn write_without_nonce(&self, writer: &mut W) -> io::Result<()> { // This is either an invalid signature or a private key leak if self.signature.R.is_identity().into() { Err(io::Error::other("signature nonce was identity"))?; } writer.write_all(&self.signer.to_bytes())?; self.signature.write(writer) } } #[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Debug)] pub enum TransactionKind<'a> { /// This transaction should be provided by every validator, in an exact order. /// /// The contained static string names the orderer to use. This allows two distinct provided /// transaction kinds, without a synchronized order, to be ordered within their own kind without /// requiring ordering with each other. /// /// The only malleability is in when this transaction appears on chain. The block producer will /// include it when they have it. Block verification will fail for validators without it. /// /// If a supermajority of validators produce a commit for a block with a provided transaction /// which isn't locally held, the block will be added to the local chain. When the transaction is /// locally provided, it will be compared for correctness to the on-chain version /// /// In order to ensure TXs aren't accidentally provided multiple times, all provided transactions /// must have a unique hash which is also unique to all Unsigned transactions. Provided(&'static str), /// An unsigned transaction, only able to be included by the block producer. /// /// Once an Unsigned transaction is included on-chain, it may not be included again. In order to /// have multiple Unsigned transactions with the same values included on-chain, some distinct /// nonce must be included in order to cause a distinct hash. /// /// The hash must also be unique with all Provided transactions. Unsigned, /// A signed transaction. Signed(Vec, &'a Signed), } // TODO: Should this be renamed TransactionTrait now that a literal Transaction exists? // Or should the literal Transaction be renamed to Event? pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite { /// Return what type of transaction this is. fn kind(&self) -> TransactionKind<'_>; /// Return the hash of this transaction. /// /// The hash must NOT commit to the signature. fn hash(&self) -> [u8; 32]; /// Perform transaction-specific verification. fn verify(&self) -> Result<(), TransactionError>; /// Obtain the challenge for this transaction's signature. /// /// Do not override this unless you know what you're doing. /// /// Panics if called on non-signed transactions. fn sig_hash(&self, genesis: [u8; 32]) -> ::F { match self.kind() { TransactionKind::Signed(order, Signed { signature, .. }) => { ::F::from_bytes_mod_order_wide( &Blake2b512::digest( [ b"Tributary Signed Transaction", genesis.as_ref(), &self.hash(), order.as_ref(), signature.R.to_bytes().as_ref(), ] .concat(), ) .into(), ) } _ => panic!("sig_hash called on non-signed transaction"), } } } pub trait GAIN: FnMut(&::G, &[u8]) -> Option {} impl::G, &[u8]) -> Option> GAIN for F {} pub(crate) fn verify_transaction( tx: &T, genesis: [u8; 32], get_and_increment_nonce: &mut F, ) -> Result<(), TransactionError> { if tx.serialize().len() > TRANSACTION_SIZE_LIMIT { Err(TransactionError::TooLargeTransaction)?; } tx.verify()?; match tx.kind() { TransactionKind::Provided(_) | TransactionKind::Unsigned => {} TransactionKind::Signed(order, Signed { signer, nonce, signature }) => { if let Some(next_nonce) = get_and_increment_nonce(signer, &order) { if *nonce != next_nonce { Err(TransactionError::InvalidNonce)?; } } else { // Not a participant Err(TransactionError::InvalidSigner)?; } // TODO: Use a batch verification here if !signature.verify(*signer, tx.sig_hash(genesis)) { Err(TransactionError::InvalidSignature)?; } } } Ok(()) }