Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ecdsa_bls verify in BEEFY primitives #2066

Merged
26 changes: 17 additions & 9 deletions substrate/primitives/consensus/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub mod bls_crypto {
<MsgHash as Hash>::Output: Into<[u8; 32]>,
{
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
// `w3f-bls` library uses IETF hashing standard and as such does not exposes
// `w3f-bls` library uses IETF hashing standard and as such does not expose
// a choice of hash to field function.
// We are directly calling into the library to avoid introducing new host call.
// and because BeefyAuthorityId::verify is being called in the runtime so we don't have
Expand All @@ -157,7 +157,7 @@ pub mod bls_crypto {
pub mod ecdsa_bls_crypto {
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use sp_application_crypto::{app_crypto, ecdsa_bls377};
use sp_core::{crypto::Wraps, ecdsa_bls377::Pair as EcdsaBlsPair, Pair as _};
use sp_core::{crypto::Wraps, ecdsa_bls377::Pair as EcdsaBlsPair};

app_crypto!(ecdsa_bls377, KEY_TYPE);

Expand All @@ -172,12 +172,15 @@ pub mod ecdsa_bls_crypto {
<MsgHash as Hash>::Output: Into<[u8; 32]>,
{
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
// `w3f-bls` library uses IETF hashing standard and as such does not exposes
// a choice of hash to field function.
// We are directly calling into the library to avoid introducing new host call.
// and because BeefyAuthorityId::verify is being called in the runtime so we don't have

EcdsaBlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())
// We can not simply call
// `EcdsaBlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())`
// because that invokes ecdsa default verification which perfoms blake2 hash
// which we don't want.
drskalman marked this conversation as resolved.
Show resolved Hide resolved
EcdsaBlsPair::verify_with_hasher::<MsgHash>(
signature.as_inner_ref(),
msg,
self.as_inner_ref(),
)
}
}
}
Expand Down Expand Up @@ -257,6 +260,7 @@ pub enum ConsensusLog<AuthorityId: Codec> {
///
/// A vote message is a direct vote created by a BEEFY node on every voting round
/// and is gossiped to its peers.
// TODO: Remove `Signature` generic type, instead get it from `Id::Signature`.
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
pub struct VoteMessage<Number, Id, Signature> {
/// Commit to information extracted from a finalized block
Expand Down Expand Up @@ -507,11 +511,15 @@ mod tests {
let msg = &b"test-message"[..];
let (pair, _) = ecdsa_bls_crypto::Pair::generate();

let signature: ecdsa_bls_crypto::Signature = pair.as_inner_ref().sign(&msg).into();
let signature: ecdsa_bls_crypto::Signature =
pair.as_inner_ref().sign_with_hasher::<Keccak256>(&msg).into();

// Verification works if same hashing function is used when signing and verifying.
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));

// Verification doesn't works if we verify function provided by pair_crypto implementation
drskalman marked this conversation as resolved.
Show resolved Hide resolved
assert!(!ecdsa_bls_crypto::Pair::verify(&signature, msg, &pair.public()));

// Other public key doesn't work
let (other_pair, _) = ecdsa_bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
Expand Down
74 changes: 71 additions & 3 deletions substrate/primitives/core/src/paired_crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ use sp_std::convert::TryFrom;
/// ECDSA and BLS12-377 paired crypto scheme
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls377 {
use crate::{bls377, crypto::CryptoTypeId, ecdsa};
use crate::{
bls377,
crypto::{CryptoTypeId, Pair as PairT, UncheckedFrom},
ecdsa,
};

/// An identifier used to match public keys against BLS12-377 keys
pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecb7");
Expand Down Expand Up @@ -71,6 +75,60 @@ pub mod ecdsa_bls377 {
impl super::CryptoType for Pair {
type Pair = Pair;
}

#[cfg(feature = "full_crypto")]
impl Pair {
/// hashes the `message` with the specified `MsgHasher` and then signs it using ECDSA
/// algorithm. It does not affect the behavoir of BLS12-377 component. It generates
/// BLS12-377 Signature according to IETF standard and disregard the hasher for the
drskalman marked this conversation as resolved.
Show resolved Hide resolved
/// BLS12-377 component
pub fn sign_with_hasher<MsgHasher: crate::Hasher>(&self, message: &[u8]) -> Signature
where
<MsgHasher as crate::Hasher>::Out: Into<[u8; 32]>,
davxy marked this conversation as resolved.
Show resolved Hide resolved
{
let msg_hash = <MsgHasher as crate::Hasher>::hash(message).into();

let mut raw: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
raw[..ecdsa::SIGNATURE_SERIALIZED_SIZE]
.copy_from_slice(self.left.sign_prehashed(&msg_hash).as_ref());
raw[ecdsa::SIGNATURE_SERIALIZED_SIZE..]
.copy_from_slice(self.right.sign(message).as_ref());
<Self as PairT>::Signature::unchecked_from(raw)
}

/// hashes the `message` with the specified `MsgHasher` and then verifys if the resulting
/// hash was signed by the provided ECDSA public key.. It does not affect the behavoir of
/// BLS12-377 component. It Verify the BLS12-377 signature as it was hashed and signed
/// according to IETF standrad
drskalman marked this conversation as resolved.
Show resolved Hide resolved
pub fn verify_with_hasher<MsgHasher: crate::Hasher>(
sig: &Signature,
message: &[u8],
public: &Public,
) -> bool
where
<MsgHasher as crate::Hasher>::Out: Into<[u8; 32]>,
{
let msg_hash = <MsgHasher as crate::Hasher>::hash(message).into();

let Ok(left_pub) = public.0[..ecdsa::PUBLIC_KEY_SERIALIZED_SIZE].try_into() else {
return false
};
let Ok(left_sig) = sig.0[0..ecdsa::SIGNATURE_SERIALIZED_SIZE].try_into() else {
return false
};
if !ecdsa::Pair::verify_prehashed(&left_sig, &msg_hash, &left_pub) {
return false
}

let Ok(right_pub) = public.0[ecdsa::PUBLIC_KEY_SERIALIZED_SIZE..].try_into() else {
return false
};
let Ok(right_sig) = sig.0[ecdsa::SIGNATURE_SERIALIZED_SIZE..].try_into() else {
return false
};
bls377::Pair::verify(&right_sig, message.as_ref(), &right_pub)
}
}
}

/// Secure seed length.
Expand Down Expand Up @@ -455,12 +513,12 @@ where
#[cfg(all(test, feature = "bls-experimental"))]
mod test {
use super::*;
use crate::crypto::DEV_PHRASE;
use crate::{crypto::DEV_PHRASE, KeccakHasher};
use ecdsa_bls377::{Pair, Signature};

use crate::{bls377, ecdsa};
#[test]

#[test]
fn test_length_of_paired_ecdsa_and_bls377_public_key_and_signature_is_correct() {
assert_eq!(
<Pair as PairT>::Public::LEN,
Expand Down Expand Up @@ -617,6 +675,16 @@ mod test {
assert_eq!(cmp, public);
}

#[test]
fn sign_and_verify_with_hasher_works() {
let pair =
Pair::from_seed(&(b"12345678901234567890123456789012".as_slice().try_into().unwrap()));
let message = b"Something important";
let signature = pair.sign_with_hasher::<KeccakHasher>(&message[..]);

assert!(Pair::verify_with_hasher::<KeccakHasher>(&signature, &message[..], &pair.public()));
}

#[test]
fn signature_serialization_works() {
let pair =
Expand Down
Loading