From 4d17b922fe1e4f12e3be884b133ec8e36aec8b66 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 11 Apr 2023 19:03:36 -0400 Subject: [PATCH] Sign the genesis when signing transactions Prevents replaying across tributaries, which is a risk for BTC/ETH (regarding key gen). --- coordinator/tributary/src/block.rs | 3 +- coordinator/tributary/src/transaction.rs | 58 +++++++++++++++++++----- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/coordinator/tributary/src/block.rs b/coordinator/tributary/src/block.rs index b7d44e44..acab2c08 100644 --- a/coordinator/tributary/src/block.rs +++ b/coordinator/tributary/src/block.rs @@ -89,6 +89,7 @@ impl Block { pub fn verify( &self, + genesis: [u8; 32], last_block: [u8; 32], locally_provided: &mut HashSet<[u8; 32]>, next_nonces: &mut HashMap<::G, u32>, @@ -99,7 +100,7 @@ impl Block { let mut txs = Vec::with_capacity(self.transactions.len()); for tx in &self.transactions { - match verify_transaction(tx, locally_provided, next_nonces) { + match verify_transaction(tx, genesis, locally_provided, next_nonces) { Ok(()) => {} Err(e) => Err(BlockError::TransactionError(e))?, } diff --git a/coordinator/tributary/src/transaction.rs b/coordinator/tributary/src/transaction.rs index be2105c5..f21bc9f3 100644 --- a/coordinator/tributary/src/transaction.rs +++ b/coordinator/tributary/src/transaction.rs @@ -1,8 +1,13 @@ -use std::collections::{HashSet, HashMap}; +use std::{ + io, + collections::{HashSet, HashMap}, +}; use thiserror::Error; -use ciphersuite::{Ciphersuite, Ristretto}; +use blake2::{Digest, Blake2b512}; + +use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto}; use schnorr::SchnorrSignature; use crate::ReadWrite; @@ -17,6 +22,35 @@ pub enum TransactionError { Fatal, } +/// 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); + + let signature = SchnorrSignature::::read(reader)?; + + Ok(Signed { signer, nonce, signature }) + } + + fn 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)] #[derive(Clone, PartialEq, Eq, Debug)] pub enum TransactionKind { /// This tranaction should be provided by every validator, solely ordered by the block producer. @@ -28,11 +62,7 @@ pub enum TransactionKind { Unsigned, /// A signed transaction. - Signed { - signer: ::G, - nonce: u32, - signature: SchnorrSignature, - }, + Signed(Signed), } pub trait Transaction: Send + Sync + Clone + Eq + ReadWrite { @@ -40,10 +70,17 @@ pub trait Transaction: Send + Sync + Clone + Eq + ReadWrite { fn hash(&self) -> [u8; 32]; fn verify(&self) -> Result<(), TransactionError>; + + fn sig_hash(&self, genesis: [u8; 32]) -> ::F { + ::F::from_bytes_mod_order_wide( + &Blake2b512::digest([genesis, self.hash()].concat()).into(), + ) + } } pub(crate) fn verify_transaction( tx: &T, + genesis: [u8; 32], locally_provided: &mut HashSet<[u8; 32]>, next_nonces: &mut HashMap<::G, u32>, ) -> Result<(), TransactionError> { @@ -54,17 +91,14 @@ pub(crate) fn verify_transaction( } } TransactionKind::Unsigned => {} - TransactionKind::Signed { signer, nonce, signature } => { + TransactionKind::Signed(Signed { signer, nonce, signature }) => { if next_nonces.get(&signer).cloned().unwrap_or(0) != nonce { Err(TransactionError::Temporal)?; } next_nonces.insert(signer, nonce + 1); // TODO: Use Schnorr half-aggregation and a batch verification here - let mut wide = [0; 64]; - wide[.. 32].copy_from_slice(&tx.hash()); - if !signature.verify(signer, ::F::from_bytes_mod_order_wide(&wide)) - { + if !signature.verify(signer, tx.sig_hash(genesis)) { Err(TransactionError::Fatal)?; } }