Remove SlashVote per #349

This commit is contained in:
Luke Parker
2023-08-21 23:48:33 -04:00
parent 718c44d50e
commit c65bb70741
9 changed files with 59 additions and 369 deletions

View File

@@ -6,7 +6,7 @@ use async_trait::async_trait;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, Zeroizing};
use rand::{SeedableRng, seq::SliceRandom, rngs::OsRng};
use rand::{SeedableRng, seq::SliceRandom};
use rand_chacha::ChaCha12Rng;
use transcript::{Transcript, RecommendedTranscript};
@@ -43,11 +43,11 @@ use tokio::{
use crate::{
TENDERMINT_MESSAGE, TRANSACTION_MESSAGE, BLOCK_MESSAGE, ReadWrite,
transaction::Transaction as TransactionTrait, Transaction, BlockHeader, Block, BlockError,
Blockchain, P2p, tendermint::tx::SlashVote,
Blockchain, P2p,
};
pub mod tx;
use tx::{TendermintTx, VoteSignature};
use tx::TendermintTx;
const DST: &[u8] = b"Tributary Tendermint Commit Aggregator";
@@ -312,21 +312,17 @@ impl<D: Db, T: TransactionTrait, P: P2p> Network for TendermintNetwork<D, T, P>
);
let signer = self.signer();
let tx = match slash_event {
let Some(tx) = (match slash_event {
SlashEvent::WithEvidence(m1, m2) => {
// create an unsigned evidence tx
TendermintTx::SlashEvidence((m1, m2).encode())
Some(TendermintTx::SlashEvidence((m1, m2).encode()))
}
SlashEvent::Id(reason, block, round) => {
// create a signed vote tx
let mut tx = TendermintTx::SlashVote(SlashVote {
id: (reason, block, round).encode().try_into().unwrap(),
target: validator.encode().try_into().unwrap(),
sig: VoteSignature::default(),
});
tx.sign(&mut OsRng, signer.genesis, &signer.key);
tx
SlashEvent::Id(_reason, _block, _round) => {
// TODO: Increase locally observed slash points
None
}
}) else {
return;
};
// add tx to blockchain and broadcast to peers

View File

@@ -1,19 +1,10 @@
use core::ops::Deref;
use std::{io, vec, default::Default};
use std::io;
use scale::Decode;
use zeroize::Zeroizing;
use blake2::{Digest, Blake2s256};
use blake2::{Digest, Blake2s256, Blake2b512};
use rand::{RngCore, CryptoRng};
use ciphersuite::{
group::{GroupEncoding, ff::Field},
Ciphersuite, Ristretto,
};
use schnorr::SchnorrSignature;
use ciphersuite::{Ciphersuite, Ristretto};
use crate::{
transaction::{Transaction, TransactionKind, TransactionError},
@@ -28,72 +19,10 @@ use tendermint::{
ext::{Network, Commit, RoundNumber, SignatureScheme},
};
/// Signing data for a slash vote.
///
/// The traditional Signed uses a nonce, whereas votes aren't required/expected to be ordered.
/// Accordingly, a simple uniqueness check works instead.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct VoteSignature {
pub signer: <Ristretto as Ciphersuite>::G,
pub signature: SchnorrSignature<Ristretto>,
}
impl ReadWrite for VoteSignature {
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let signer = Ristretto::read_G(reader)?;
let signature = SchnorrSignature::<Ristretto>::read(reader)?;
Ok(VoteSignature { signer, signature })
}
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.signer.to_bytes())?;
self.signature.write(writer)
}
}
impl Default for VoteSignature {
fn default() -> Self {
VoteSignature {
signer: Ristretto::generator(),
signature: SchnorrSignature::<Ristretto>::read(&mut [0; 64].as_slice()).unwrap(),
}
}
}
/// A vote to slash a malicious validator.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SlashVote {
pub id: [u8; 13], // vote id(slash event id)
pub target: [u8; 32], // who to slash
pub sig: VoteSignature, // signature
}
impl ReadWrite for SlashVote {
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let mut id = [0; 13];
let mut target = [0; 32];
reader.read_exact(&mut id)?;
reader.read_exact(&mut target)?;
let sig = VoteSignature::read(reader)?;
Ok(SlashVote { id, target, sig })
}
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.id)?;
writer.write_all(&self.target)?;
self.sig.write(writer)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TendermintTx {
SlashEvidence(Vec<u8>),
// TODO: should the SlashVote.sig be directly in the enum
// like as in (SlashVote, sig) since the sig is sig of the tx.
SlashVote(SlashVote),
}
impl ReadWrite for TendermintTx {
@@ -126,10 +55,6 @@ impl ReadWrite for TendermintTx {
}
Ok(TendermintTx::SlashEvidence(data))
}
1 => {
let vote = SlashVote::read(reader)?;
Ok(TendermintTx::SlashVote(vote))
}
_ => Err(io::Error::new(io::ErrorKind::Other, "invalid transaction type")),
}
}
@@ -141,10 +66,6 @@ impl ReadWrite for TendermintTx {
writer.write_all(&u32::try_from(ev.len()).unwrap().to_le_bytes())?;
writer.write_all(ev)
}
TendermintTx::SlashVote(vote) => {
writer.write_all(&[1])?;
vote.write(writer)
}
}
}
}
@@ -157,32 +78,12 @@ impl Transaction for TendermintTx {
}
fn hash(&self) -> [u8; 32] {
let mut tx = self.serialize();
if let TendermintTx::SlashVote(vote) = self {
// Make sure the part we're cutting off is the signature
assert_eq!(tx.drain((tx.len() - 64) ..).collect::<Vec<_>>(), vote.sig.signature.serialize());
}
Blake2s256::digest(tx).into()
Blake2s256::digest(self.serialize()).into()
}
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
fn sig_hash(&self, _genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
match self {
TendermintTx::SlashEvidence(_) => panic!("sig_hash called on slash evidence transaction"),
TendermintTx::SlashVote(vote) => {
let signature = &vote.sig.signature;
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
&Blake2b512::digest(
[
b"Tributary Slash Vote",
genesis.as_ref(),
&self.hash(),
signature.R.to_bytes().as_ref(),
]
.concat(),
)
.into(),
)
}
}
}
@@ -191,34 +92,6 @@ impl Transaction for TendermintTx {
}
}
impl TendermintTx {
// Sign a transaction
pub fn sign<R: RngCore + CryptoRng>(
&mut self,
rng: &mut R,
genesis: [u8; 32],
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
) {
fn signature(tx: &mut TendermintTx) -> Option<&mut VoteSignature> {
match tx {
TendermintTx::SlashVote(vote) => Some(&mut vote.sig),
_ => None,
}
}
signature(self).unwrap().signer = Ristretto::generator() * key.deref();
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
signature(self).unwrap().signature.R =
<Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
let sig_hash = self.sig_hash(genesis);
signature(self).unwrap().signature =
SchnorrSignature::<Ristretto>::sign(key, sig_nonce, sig_hash);
}
}
pub fn decode_evidence<N: Network>(
mut ev: &[u8],
) -> Result<(SignedMessageFor<N>, Option<SignedMessageFor<N>>), TransactionError> {
@@ -234,13 +107,13 @@ pub fn decode_evidence<N: Network>(
// re-implements an entire foreign library's checks for malicious behavior).
pub(crate) fn verify_tendermint_tx<N: Network>(
tx: &TendermintTx,
genesis: [u8; 32],
schema: N::SignatureScheme,
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
) -> Result<(), TransactionError> {
tx.verify()?;
match tx {
// TODO: Only allow one evidence per validator, since evidence is fatal
TendermintTx::SlashEvidence(ev) => {
let (first, second) = decode_evidence::<N>(ev)?;
@@ -332,21 +205,6 @@ pub(crate) fn verify_tendermint_tx<N: Network>(
_ => Err(TransactionError::InvalidContent)?,
}
}
TendermintTx::SlashVote(vote) => {
// TODO: verify the target is actually one of our validators?
// this shouldn't be a problem because if the target isn't valid, no one else
// gonna vote on it. But we still have to think about spam votes.
// TODO: we need to check signer is a participant
// TODO: Move this into the standalone TendermintTx verify
let sig = &vote.sig;
// verify the tx signature
// TODO: Use Schnorr half-aggregation and a batch verification here
if !sig.signature.verify(sig.signer, tx.sig_hash(genesis)) {
Err(TransactionError::InvalidSignature)?;
}
}
}
Ok(())