mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-23 03:05:07 +00:00
Bind the signature scheme for tendermint-machine
This commit is contained in:
parent
8c8232516d
commit
86cbf6e02e
3 changed files with 135 additions and 1 deletions
|
@ -8,12 +8,15 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
thiserror = "1"
|
||||
|
||||
subtle = "^2"
|
||||
zeroize = { version = "^1.5", optional = true }
|
||||
rand_core = { version = "0.6", optional = true }
|
||||
|
||||
blake2 = "0.10"
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", features = ["recommended"] }
|
||||
|
||||
ciphersuite = { package = "ciphersuite", path = "../../crypto/ciphersuite", features = ["ristretto"] }
|
||||
schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr" }
|
||||
|
|
|
@ -7,7 +7,7 @@ mod transaction;
|
|||
pub use transaction::*;
|
||||
|
||||
mod provided;
|
||||
pub use provided::*;
|
||||
pub(crate) use provided::*;
|
||||
|
||||
mod block;
|
||||
pub use block::*;
|
||||
|
@ -18,6 +18,9 @@ pub use blockchain::*;
|
|||
mod mempool;
|
||||
pub use mempool::*;
|
||||
|
||||
mod tendermint;
|
||||
pub use crate::tendermint::*;
|
||||
|
||||
#[cfg(any(test, feature = "tests"))]
|
||||
pub mod tests;
|
||||
|
||||
|
|
128
coordinator/tributary/src/tendermint.rs
Normal file
128
coordinator/tributary/src/tendermint.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use subtle::ConstantTimeEq;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use transcript::{Transcript, RecommendedTranscript};
|
||||
|
||||
use ciphersuite::{
|
||||
group::{
|
||||
GroupEncoding,
|
||||
ff::{Field, PrimeField},
|
||||
},
|
||||
Ciphersuite, Ristretto,
|
||||
};
|
||||
use schnorr::SchnorrSignature;
|
||||
|
||||
use tendermint::ext::{Signer as SignerTrait, SignatureScheme as SignatureSchemeTrait};
|
||||
|
||||
fn challenge(
|
||||
genesis: [u8; 32],
|
||||
key: [u8; 32],
|
||||
nonce: &[u8],
|
||||
msg: &[u8],
|
||||
) -> <Ristretto as Ciphersuite>::F {
|
||||
let mut transcript = RecommendedTranscript::new(b"Tributary Chain Tendermint Message");
|
||||
transcript.append_message(b"genesis", genesis);
|
||||
transcript.append_message(b"key", key);
|
||||
transcript.append_message(b"nonce", nonce);
|
||||
transcript.append_message(b"message", msg);
|
||||
|
||||
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(&transcript.challenge(b"schnorr").into())
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
struct Signer {
|
||||
genesis: [u8; 32],
|
||||
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SignerTrait for Signer {
|
||||
type ValidatorId = [u8; 32];
|
||||
type Signature = [u8; 64];
|
||||
|
||||
/// Returns the validator's current ID. Returns None if they aren't a current validator.
|
||||
async fn validator_id(&self) -> Option<Self::ValidatorId> {
|
||||
Some((Ristretto::generator() * self.key.deref()).to_bytes())
|
||||
}
|
||||
|
||||
/// Sign a signature with the current validator's private key.
|
||||
async fn sign(&self, msg: &[u8]) -> Self::Signature {
|
||||
let mut nonce = Zeroizing::new(RecommendedTranscript::new(b"Tributary Chain Tendermint Nonce"));
|
||||
nonce.append_message(b"genesis", self.genesis);
|
||||
nonce.append_message(b"key", Zeroizing::new(self.key.deref().to_repr()).as_ref());
|
||||
nonce.append_message(b"message", msg);
|
||||
let mut nonce = nonce.challenge(b"nonce");
|
||||
|
||||
let mut nonce_arr = [0; 64];
|
||||
nonce_arr.copy_from_slice(nonce.as_ref());
|
||||
|
||||
let nonce_ref: &mut [u8] = nonce.as_mut();
|
||||
nonce_ref.zeroize();
|
||||
let nonce_ref: &[u8] = nonce.as_ref();
|
||||
assert_eq!(nonce_ref, [0; 64].as_ref());
|
||||
|
||||
let nonce =
|
||||
Zeroizing::new(<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(&nonce_arr));
|
||||
nonce_arr.zeroize();
|
||||
|
||||
assert!(!bool::from(nonce.ct_eq(&<Ristretto as Ciphersuite>::F::ZERO)));
|
||||
|
||||
let challenge = challenge(
|
||||
self.genesis,
|
||||
(Ristretto::generator() * self.key.deref()).to_bytes(),
|
||||
(Ristretto::generator() * nonce.deref()).to_bytes().as_ref(),
|
||||
msg,
|
||||
);
|
||||
|
||||
let sig = SchnorrSignature::<Ristretto>::sign(&self.key, nonce, challenge).serialize();
|
||||
|
||||
let mut res = [0; 64];
|
||||
res.copy_from_slice(&sig);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
struct SignatureScheme {
|
||||
genesis: [u8; 32],
|
||||
}
|
||||
|
||||
impl SignatureSchemeTrait for SignatureScheme {
|
||||
type ValidatorId = [u8; 32];
|
||||
type Signature = [u8; 64];
|
||||
// TODO: Use half-aggregation.
|
||||
type AggregateSignature = Vec<[u8; 64]>;
|
||||
type Signer = Signer;
|
||||
|
||||
#[must_use]
|
||||
fn verify(&self, validator: Self::ValidatorId, msg: &[u8], sig: &Self::Signature) -> bool {
|
||||
let Ok(validator_point) = Ristretto::read_G::<&[u8]>(&mut validator.as_ref()) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(actual_sig) = SchnorrSignature::<Ristretto>::read::<&[u8]>(&mut sig.as_ref()) else {
|
||||
return false;
|
||||
};
|
||||
actual_sig.verify(validator_point, challenge(self.genesis, validator, &sig[.. 32], msg))
|
||||
}
|
||||
|
||||
fn aggregate(sigs: &[Self::Signature]) -> Self::AggregateSignature {
|
||||
sigs.to_vec()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn verify_aggregate(
|
||||
&self,
|
||||
signers: &[Self::ValidatorId],
|
||||
msg: &[u8],
|
||||
sig: &Self::AggregateSignature,
|
||||
) -> bool {
|
||||
for (signer, sig) in signers.iter().zip(sig.iter()) {
|
||||
if !self.verify(*signer, msg, sig) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue