2023-04-12 00:24:27 +00:00
|
|
|
use core::fmt::Debug;
|
2023-04-11 23:03:36 +00:00
|
|
|
use std::{
|
|
|
|
io,
|
|
|
|
collections::{HashSet, HashMap},
|
|
|
|
};
|
2023-04-11 17:42:18 +00:00
|
|
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2023-04-11 23:03:36 +00:00
|
|
|
use blake2::{Digest, Blake2b512};
|
|
|
|
|
|
|
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
2023-04-11 17:42:18 +00:00
|
|
|
use schnorr::SchnorrSignature;
|
|
|
|
|
|
|
|
use crate::ReadWrite;
|
|
|
|
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
|
|
|
pub enum TransactionError {
|
|
|
|
/// This transaction was perceived as invalid against the current state.
|
|
|
|
#[error("transaction temporally invalid")]
|
|
|
|
Temporal,
|
|
|
|
/// This transaction is definitively invalid.
|
|
|
|
#[error("transaction definitively invalid")]
|
|
|
|
Fatal,
|
|
|
|
}
|
|
|
|
|
2023-04-11 23:03:36 +00:00
|
|
|
/// Data for a signed transaction.
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
|
|
pub struct Signed {
|
|
|
|
pub signer: <Ristretto as Ciphersuite>::G,
|
|
|
|
pub nonce: u32,
|
|
|
|
pub signature: SchnorrSignature<Ristretto>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ReadWrite for Signed {
|
|
|
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
|
|
|
let signer = Ristretto::read_G(reader)?;
|
|
|
|
|
|
|
|
let mut nonce = [0; 4];
|
|
|
|
reader.read_exact(&mut nonce)?;
|
|
|
|
let nonce = u32::from_le_bytes(nonce);
|
|
|
|
|
|
|
|
let signature = SchnorrSignature::<Ristretto>::read(reader)?;
|
|
|
|
|
|
|
|
Ok(Signed { signer, nonce, signature })
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
|
|
|
writer.write_all(&self.signer.to_bytes())?;
|
|
|
|
writer.write_all(&self.nonce.to_le_bytes())?;
|
|
|
|
self.signature.write(writer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
2023-04-11 17:42:18 +00:00
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
2023-04-12 13:38:20 +00:00
|
|
|
pub enum TransactionKind<'a> {
|
2023-04-11 17:42:18 +00:00
|
|
|
/// This tranaction should be provided by every validator, solely ordered by the block producer.
|
|
|
|
///
|
|
|
|
/// This transaction is only valid if a supermajority of validators provided it.
|
|
|
|
Provided,
|
|
|
|
|
|
|
|
/// An unsigned transaction, only able to be included by the block producer.
|
|
|
|
Unsigned,
|
|
|
|
|
|
|
|
/// A signed transaction.
|
2023-04-12 13:38:20 +00:00
|
|
|
Signed(&'a Signed),
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
|
|
|
|
2023-04-12 00:24:27 +00:00
|
|
|
pub trait Transaction: Send + Sync + Clone + Eq + Debug + ReadWrite {
|
2023-04-12 13:38:20 +00:00
|
|
|
fn kind(&self) -> TransactionKind<'_>;
|
2023-04-12 00:24:27 +00:00
|
|
|
/// Return the hash of this transaction.
|
|
|
|
///
|
|
|
|
/// The hash must NOT commit to the signature.
|
2023-04-11 17:42:18 +00:00
|
|
|
fn hash(&self) -> [u8; 32];
|
|
|
|
|
|
|
|
fn verify(&self) -> Result<(), TransactionError>;
|
2023-04-11 23:03:36 +00:00
|
|
|
|
|
|
|
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
|
|
|
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
|
|
|
&Blake2b512::digest([genesis, self.hash()].concat()).into(),
|
|
|
|
)
|
|
|
|
}
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn verify_transaction<T: Transaction>(
|
|
|
|
tx: &T,
|
2023-04-11 23:03:36 +00:00
|
|
|
genesis: [u8; 32],
|
2023-04-11 17:42:18 +00:00
|
|
|
locally_provided: &mut HashSet<[u8; 32]>,
|
|
|
|
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
|
|
|
) -> Result<(), TransactionError> {
|
|
|
|
match tx.kind() {
|
|
|
|
TransactionKind::Provided => {
|
|
|
|
if !locally_provided.remove(&tx.hash()) {
|
|
|
|
Err(TransactionError::Temporal)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TransactionKind::Unsigned => {}
|
2023-04-11 23:03:36 +00:00
|
|
|
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
2023-04-12 12:51:40 +00:00
|
|
|
// TODO: Use presence as a whitelist, erroring on lack of
|
2023-04-12 13:38:20 +00:00
|
|
|
if next_nonces.get(signer).cloned().unwrap_or(0) != *nonce {
|
2023-04-11 17:42:18 +00:00
|
|
|
Err(TransactionError::Temporal)?;
|
|
|
|
}
|
2023-04-12 13:38:20 +00:00
|
|
|
next_nonces.insert(*signer, nonce + 1);
|
2023-04-11 17:42:18 +00:00
|
|
|
|
|
|
|
// TODO: Use Schnorr half-aggregation and a batch verification here
|
2023-04-12 13:38:20 +00:00
|
|
|
if !signature.verify(*signer, tx.sig_hash(genesis)) {
|
2023-04-11 17:42:18 +00:00
|
|
|
Err(TransactionError::Fatal)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.verify()
|
|
|
|
}
|