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 {
|
2023-04-12 22:04:28 +00:00
|
|
|
/// A provided transaction wasn't locally provided.
|
|
|
|
#[error("provided transaction wasn't locally provided")]
|
|
|
|
MissingProvided([u8; 32]),
|
|
|
|
/// This transaction's signer isn't a participant.
|
|
|
|
#[error("invalid signer")]
|
|
|
|
InvalidSigner,
|
|
|
|
/// This transaction's nonce isn't the prior nonce plus one.
|
|
|
|
#[error("invalid nonce")]
|
|
|
|
InvalidNonce,
|
|
|
|
/// This transaction's signature is invalid.
|
|
|
|
#[error("invalid signature")]
|
|
|
|
InvalidSignature,
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
|
|
|
|
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);
|
2023-04-12 16:15:38 +00:00
|
|
|
if nonce >= (u32::MAX - 1) {
|
|
|
|
Err(io::Error::new(io::ErrorKind::Other, "nonce exceeded limit"))?;
|
|
|
|
}
|
2023-04-11 23:03:36 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
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-13 22:43:03 +00:00
|
|
|
pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
2023-04-12 15:13:48 +00:00
|
|
|
/// Return what type of transaction this is.
|
2023-04-12 13:38:20 +00:00
|
|
|
fn kind(&self) -> TransactionKind<'_>;
|
2023-04-12 15:13:48 +00:00
|
|
|
|
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];
|
|
|
|
|
2023-04-12 15:13:48 +00:00
|
|
|
/// Perform transaction-specific verification.
|
2023-04-11 17:42:18 +00:00
|
|
|
fn verify(&self) -> Result<(), TransactionError>;
|
2023-04-11 23:03:36 +00:00
|
|
|
|
2023-04-12 15:13:48 +00:00
|
|
|
/// Obtain the challenge for this transaction's signature.
|
|
|
|
///
|
|
|
|
/// Do not override this unless you know what you're doing.
|
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
|
|
|
}
|
|
|
|
|
2023-04-12 16:42:23 +00:00
|
|
|
// This will only cause mutations when the transaction is valid
|
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> {
|
2023-04-12 16:15:38 +00:00
|
|
|
tx.verify()?;
|
|
|
|
|
2023-04-11 17:42:18 +00:00
|
|
|
match tx.kind() {
|
|
|
|
TransactionKind::Provided => {
|
2023-04-12 22:04:28 +00:00
|
|
|
let hash = tx.hash();
|
|
|
|
if !locally_provided.remove(&hash) {
|
|
|
|
Err(TransactionError::MissingProvided(hash))?;
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
TransactionKind::Unsigned => {}
|
2023-04-11 23:03:36 +00:00
|
|
|
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
2023-04-12 16:42:23 +00:00
|
|
|
if let Some(next_nonce) = next_nonces.get(signer) {
|
|
|
|
if nonce != next_nonce {
|
2023-04-12 22:04:28 +00:00
|
|
|
Err(TransactionError::InvalidNonce)?;
|
2023-04-12 16:42:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Not a participant
|
2023-04-12 22:04:28 +00:00
|
|
|
Err(TransactionError::InvalidSigner)?;
|
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-12 22:04:28 +00:00
|
|
|
Err(TransactionError::InvalidSignature)?;
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
2023-04-12 16:15:38 +00:00
|
|
|
|
|
|
|
next_nonces.insert(*signer, nonce + 1);
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-12 16:15:38 +00:00
|
|
|
Ok(())
|
2023-04-11 17:42:18 +00:00
|
|
|
}
|