Skip to content

Commit

Permalink
Remove SlashVote per #349
Browse files Browse the repository at this point in the history
  • Loading branch information
kayabaNerve committed Aug 22, 2023
1 parent 718c44d commit c65bb70
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 369 deletions.
14 changes: 0 additions & 14 deletions coordinator/src/tributary/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,6 @@ async fn handle_block<

// TODO: disconnect the node from network/ban from further participation in Tributary
}
TributaryTransaction::Tendermint(TendermintTx::SlashVote(vote)) => {
// TODO: make sure same signer doesn't vote twice

// increment the counter for this vote
let vote_key = TributaryDb::<D>::slash_vote_key(genesis, vote.id, vote.target);
let mut count = txn.get(&vote_key).map_or(0, |c| u32::from_le_bytes(c.try_into().unwrap()));
count += 1; // TODO: Increase by weight, not by 1
txn.put(vote_key, count.to_le_bytes());

// TODO: Check if a supermajority of validators, by weight, voted, and increment slash
// points if so
// If a node has a certain number more than the median slash points, the node should be
// removed
}
TributaryTransaction::Application(tx) => {
handle_application_tx::<D, _, _, _>(
tx,
Expand Down
2 changes: 1 addition & 1 deletion coordinator/tributary/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl<T: TransactionTrait> Block<T> {
// use this pattern of verifying tendermint Txs and app txs differently?
match tx {
Transaction::Tendermint(tx) => {
match verify_tendermint_tx::<N>(tx, genesis, schema.clone(), &commit) {
match verify_tendermint_tx::<N>(tx, schema.clone(), &commit) {
Ok(()) => {}
Err(e) => Err(BlockError::TransactionError(e))?,
}
Expand Down
2 changes: 1 addition & 1 deletion coordinator/tributary/src/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
}

// verify the tx
if verify_tendermint_tx::<N>(tendermint_tx, self.genesis, schema, commit).is_err() {
if verify_tendermint_tx::<N>(tendermint_tx, schema, commit).is_err() {
return false;
}
}
Expand Down
24 changes: 10 additions & 14 deletions coordinator/tributary/src/tendermint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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
Expand Down
154 changes: 6 additions & 148 deletions coordinator/tributary/src/tendermint/tx.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -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 {
Expand Down Expand Up @@ -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")),
}
}
Expand All @@ -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)
}
}
}
}
Expand All @@ -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(),
)
}
}
}

Expand All @@ -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> {
Expand All @@ -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)?;

Expand Down Expand Up @@ -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(())
Expand Down
Loading

0 comments on commit c65bb70

Please sign in to comment.