mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-10 04:44:40 +00:00
Sign the genesis when signing transactions
Prevents replaying across tributaries, which is a risk for BTC/ETH (regarding key gen).
This commit is contained in:
parent
7488d23e0d
commit
4d17b922fe
2 changed files with 48 additions and 13 deletions
|
@ -89,6 +89,7 @@ impl<T: Transaction> Block<T> {
|
||||||
|
|
||||||
pub fn verify(
|
pub fn verify(
|
||||||
&self,
|
&self,
|
||||||
|
genesis: [u8; 32],
|
||||||
last_block: [u8; 32],
|
last_block: [u8; 32],
|
||||||
locally_provided: &mut HashSet<[u8; 32]>,
|
locally_provided: &mut HashSet<[u8; 32]>,
|
||||||
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
|
@ -99,7 +100,7 @@ impl<T: Transaction> Block<T> {
|
||||||
|
|
||||||
let mut txs = Vec::with_capacity(self.transactions.len());
|
let mut txs = Vec::with_capacity(self.transactions.len());
|
||||||
for tx in &self.transactions {
|
for tx in &self.transactions {
|
||||||
match verify_transaction(tx, locally_provided, next_nonces) {
|
match verify_transaction(tx, genesis, locally_provided, next_nonces) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) => Err(BlockError::TransactionError(e))?,
|
Err(e) => Err(BlockError::TransactionError(e))?,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::{
|
||||||
|
io,
|
||||||
|
collections::{HashSet, HashMap},
|
||||||
|
};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use ciphersuite::{Ciphersuite, Ristretto};
|
use blake2::{Digest, Blake2b512};
|
||||||
|
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
use schnorr::SchnorrSignature;
|
use schnorr::SchnorrSignature;
|
||||||
|
|
||||||
use crate::ReadWrite;
|
use crate::ReadWrite;
|
||||||
|
@ -17,6 +22,35 @@ pub enum TransactionError {
|
||||||
Fatal,
|
Fatal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum TransactionKind {
|
pub enum TransactionKind {
|
||||||
/// This tranaction should be provided by every validator, solely ordered by the block producer.
|
/// This tranaction should be provided by every validator, solely ordered by the block producer.
|
||||||
|
@ -28,11 +62,7 @@ pub enum TransactionKind {
|
||||||
Unsigned,
|
Unsigned,
|
||||||
|
|
||||||
/// A signed transaction.
|
/// A signed transaction.
|
||||||
Signed {
|
Signed(Signed),
|
||||||
signer: <Ristretto as Ciphersuite>::G,
|
|
||||||
nonce: u32,
|
|
||||||
signature: SchnorrSignature<Ristretto>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Transaction: Send + Sync + Clone + Eq + ReadWrite {
|
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 hash(&self) -> [u8; 32];
|
||||||
|
|
||||||
fn verify(&self) -> Result<(), TransactionError>;
|
fn verify(&self) -> Result<(), TransactionError>;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn verify_transaction<T: Transaction>(
|
pub(crate) fn verify_transaction<T: Transaction>(
|
||||||
tx: &T,
|
tx: &T,
|
||||||
|
genesis: [u8; 32],
|
||||||
locally_provided: &mut HashSet<[u8; 32]>,
|
locally_provided: &mut HashSet<[u8; 32]>,
|
||||||
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
) -> Result<(), TransactionError> {
|
) -> Result<(), TransactionError> {
|
||||||
|
@ -54,17 +91,14 @@ pub(crate) fn verify_transaction<T: Transaction>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {}
|
TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed { signer, nonce, signature } => {
|
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
||||||
if next_nonces.get(&signer).cloned().unwrap_or(0) != nonce {
|
if next_nonces.get(&signer).cloned().unwrap_or(0) != nonce {
|
||||||
Err(TransactionError::Temporal)?;
|
Err(TransactionError::Temporal)?;
|
||||||
}
|
}
|
||||||
next_nonces.insert(signer, nonce + 1);
|
next_nonces.insert(signer, nonce + 1);
|
||||||
|
|
||||||
// TODO: Use Schnorr half-aggregation and a batch verification here
|
// TODO: Use Schnorr half-aggregation and a batch verification here
|
||||||
let mut wide = [0; 64];
|
if !signature.verify(signer, tx.sig_hash(genesis)) {
|
||||||
wide[.. 32].copy_from_slice(&tx.hash());
|
|
||||||
if !signature.verify(signer, <Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(&wide))
|
|
||||||
{
|
|
||||||
Err(TransactionError::Fatal)?;
|
Err(TransactionError::Fatal)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue