Application Crypto and BEEFY Support for paired (ECDSA,BLS) crypto (#1815)

Next step in process of making BEEFY being able to generate both ECDSA
and BLS signature after #1705. It allows BEEFY to use a pair of ECDSA
and BLS key as a AuthorityId.

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: Robert Hambrock <roberthambrock@gmail.com>
This commit is contained in:
drskalman
2023-10-24 18:37:34 +00:00
committed by GitHub
parent 8a79fb22db
commit fbd5777118
11 changed files with 273 additions and 17 deletions
+26 -1
View File
@@ -37,7 +37,7 @@ use sp_core::bandersnatch;
}
sp_keystore::bls_experimental_enabled! {
use sp_core::{bls377, bls381};
use sp_core::{bls377, bls381, ecdsa_bls377};
}
use crate::{Error, Result};
@@ -366,6 +366,31 @@ impl Keystore for LocalKeystore {
) -> std::result::Result<Option<bls377::Signature>, TraitError> {
self.sign::<bls377::Pair>(key_type, public, msg)
}
fn ecdsa_bls377_public_keys(&self, key_type: KeyTypeId) -> Vec<ecdsa_bls377::Public> {
self.public_keys::<ecdsa_bls377::Pair>(key_type)
}
/// Generate a new pair of paired-keys compatible with the '(ecdsa,bls377)' signature scheme.
///
/// If `[seed]` is `Some` then the key will be ephemeral and stored in memory.
fn ecdsa_bls377_generate_new(
&self,
key_type: KeyTypeId,
seed: Option<&str>,
) -> std::result::Result<ecdsa_bls377::Public, TraitError> {
self.generate_new::<ecdsa_bls377::Pair>(key_type, seed)
}
fn ecdsa_bls377_sign(
&self,
key_type: KeyTypeId,
public: &ecdsa_bls377::Public,
msg: &[u8],
) -> std::result::Result<Option<ecdsa_bls377::Signature>, TraitError> {
self.sign::<ecdsa_bls377::Pair>(key_type, public, msg)
}
}
}
@@ -0,0 +1,57 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! ECDSA and BLS12-377 paired crypto applications.
use crate::{KeyTypeId, RuntimePublic};
pub use sp_core::paired_crypto::ecdsa_bls377::*;
mod app {
crate::app_crypto!(super, sp_core::testing::ECDSA_BLS377);
}
#[cfg(feature = "full_crypto")]
pub use app::Pair as AppPair;
pub use app::{Public as AppPublic, Signature as AppSignature};
impl RuntimePublic for Public {
type Signature = Signature;
/// Dummy implementation. Returns an empty vector.
fn all(_key_type: KeyTypeId) -> Vec<Self> {
Vec::new()
}
fn generate_pair(key_type: KeyTypeId, seed: Option<Vec<u8>>) -> Self {
sp_io::crypto::ecdsa_bls377_generate(key_type, seed)
}
/// Dummy implementation. Returns `None`.
fn sign<M: AsRef<[u8]>>(&self, _key_type: KeyTypeId, _msg: &M) -> Option<Self::Signature> {
None
}
/// Dummy implementation. Returns `false`.
fn verify<M: AsRef<[u8]>>(&self, _msg: &M, _signature: &Self::Signature) -> bool {
false
}
fn to_raw_vec(&self) -> Vec<u8> {
sp_core::crypto::ByteArray::to_raw_vec(self)
}
}
@@ -50,6 +50,8 @@ pub mod bls377;
#[cfg(feature = "bls-experimental")]
pub mod bls381;
pub mod ecdsa;
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_bls377;
pub mod ed25519;
pub mod sr25519;
mod traits;
@@ -74,10 +74,11 @@ pub trait BeefyAuthorityId<MsgHash: Hash>: RuntimeAppPublic {
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
pub mod ecdsa_crypto {
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE as BEEFY_KEY_TYPE};
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use sp_application_crypto::{app_crypto, ecdsa};
use sp_core::crypto::Wraps;
app_crypto!(ecdsa, BEEFY_KEY_TYPE);
app_crypto!(ecdsa, KEY_TYPE);
/// Identity of a BEEFY authority using ECDSA as its crypto.
pub type AuthorityId = Public;
@@ -115,10 +116,11 @@ pub mod ecdsa_crypto {
#[cfg(feature = "bls-experimental")]
pub mod bls_crypto {
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE as BEEFY_KEY_TYPE};
use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
use sp_application_crypto::{app_crypto, bls377};
use sp_core::{bls377::Pair as BlsPair, crypto::Wraps, Pair as _};
app_crypto!(bls377, BEEFY_KEY_TYPE);
app_crypto!(bls377, KEY_TYPE);
/// Identity of a BEEFY authority using BLS as its crypto.
pub type AuthorityId = Public;
@@ -140,6 +142,46 @@ pub mod bls_crypto {
}
}
}
/// BEEFY cryptographic types for (ECDSA,BLS) crypto pair
///
/// This module basically introduces four crypto types:
/// - `ecdsa_bls_crypto::Pair`
/// - `ecdsa_bls_crypto::Public`
/// - `ecdsa_bls_crypto::Signature`
/// - `ecdsa_bls_crypto::AuthorityId`
///
/// Your code should use the above types as concrete types for all crypto related
/// functionality.
#[cfg(feature = "bls-experimental")]
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 _};
app_crypto!(ecdsa_bls377, KEY_TYPE);
/// Identity of a BEEFY authority using (ECDSA,BLS) as its crypto.
pub type AuthorityId = Public;
/// Signature for a BEEFY authority using (ECDSA,BLS) as its crypto.
pub type AuthoritySignature = Signature;
impl<MsgHash: Hash> BeefyAuthorityId<MsgHash> for AuthorityId
where
<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())
}
}
}
/// The `ConsensusEngineId` of BEEFY.
pub const BEEFY_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"BEEF";
@@ -458,4 +500,20 @@ mod tests {
let (other_pair, _) = bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
}
#[test]
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls_beefy_verify_works() {
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();
// Verification works if same hashing function is used when signing and verifying.
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));
// Other public key doesn't work
let (other_pair, _) = ecdsa_bls_crypto::Pair::generate();
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
}
}
+1 -1
View File
@@ -134,7 +134,7 @@ impl<T> Eq for Public<T> {}
impl<T> PartialOrd for Public<T> {
fn partial_cmp(&self, other: &Self) -> Option<sp_std::cmp::Ordering> {
self.inner.partial_cmp(&other.inner)
Some(self.cmp(other))
}
}
+2
View File
@@ -75,6 +75,8 @@ pub mod uint;
#[cfg(feature = "bls-experimental")]
pub use bls::{bls377, bls381};
#[cfg(feature = "bls-experimental")]
pub use paired_crypto::ecdsa_bls377;
pub use self::{
hash::{convert_hash, H160, H256, H512},
+12 -8
View File
@@ -33,12 +33,12 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(all(not(feature = "std"), feature = "serde"))]
use sp_std::alloc::{format, string::String};
use sp_runtime_interface::pass_by::PassByInner;
use sp_runtime_interface::pass_by::{self, PassBy, PassByInner};
use sp_std::convert::TryFrom;
/// ECDSA and BLS12-377 paired crypto scheme
#[cfg(feature = "bls-experimental")]
pub mod ecdsa_n_bls377 {
pub mod ecdsa_bls377 {
use crate::{bls377, crypto::CryptoTypeId, ecdsa};
/// An identifier used to match public keys against BLS12-377 keys
@@ -49,12 +49,12 @@ pub mod ecdsa_n_bls377 {
const SIGNATURE_LEN: usize =
ecdsa::SIGNATURE_SERIALIZED_SIZE + bls377::SIGNATURE_SERIALIZED_SIZE;
/// (ECDSA, BLS12-377) key-pair pair.
/// (ECDSA,BLS12-377) key-pair pair.
#[cfg(feature = "full_crypto")]
pub type Pair = super::Pair<ecdsa::Pair, bls377::Pair, PUBLIC_KEY_LEN, SIGNATURE_LEN>;
/// (ECDSA, BLS12-377) public key pair.
/// (ECDSA,BLS12-377) public key pair.
pub type Public = super::Public<PUBLIC_KEY_LEN>;
/// (ECDSA, BLS12-377) signature pair.
/// (ECDSA,BLS12-377) signature pair.
pub type Signature = super::Signature<SIGNATURE_LEN>;
impl super::CryptoType for Public {
@@ -151,6 +151,10 @@ impl<const LEFT_PLUS_RIGHT_LEN: usize> PassByInner for Public<LEFT_PLUS_RIGHT_LE
}
}
impl<const LEFT_PLUS_RIGHT_LEN: usize> PassBy for Public<LEFT_PLUS_RIGHT_LEN> {
type PassBy = pass_by::Inner<Self, [u8; LEFT_PLUS_RIGHT_LEN]>;
}
#[cfg(feature = "full_crypto")]
impl<
LeftPair: PairT,
@@ -244,7 +248,7 @@ pub trait SignatureBound: ByteArray {}
impl<T: ByteArray> SignatureBound for T {}
/// A pair of signatures of different types
#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
pub struct Signature<const LEFT_PLUS_RIGHT_LEN: usize>([u8; LEFT_PLUS_RIGHT_LEN]);
#[cfg(feature = "full_crypto")]
@@ -447,12 +451,12 @@ where
}
}
// Test set exercising the (ECDSA, BLS12-377) implementation
// Test set exercising the (ECDSA,BLS12-377) implementation
#[cfg(all(test, feature = "bls-experimental"))]
mod test {
use super::*;
use crate::crypto::DEV_PHRASE;
use ecdsa_n_bls377::{Pair, Signature};
use ecdsa_bls377::{Pair, Signature};
use crate::{bls377, ecdsa};
#[test]
+2
View File
@@ -31,6 +31,8 @@ pub const BANDERSNATCH: KeyTypeId = KeyTypeId(*b"band");
pub const BLS377: KeyTypeId = KeyTypeId(*b"bls7");
/// Key type for generic BLS12-381 key.
pub const BLS381: KeyTypeId = KeyTypeId(*b"bls8");
/// Key type for (ECDSA,BLS12-377) key pair
pub const ECDSA_BLS377: KeyTypeId = KeyTypeId(*b"ecb7");
/// Macro for exporting functions from wasm in with the expected signature for using it with the
/// wasm executor. This is useful for tests where you need to call a function in wasm.
+20 -1
View File
@@ -106,7 +106,7 @@ use sp_core::{
};
#[cfg(feature = "bls-experimental")]
use sp_core::bls377;
use sp_core::{bls377, ecdsa_bls377};
#[cfg(feature = "std")]
use sp_trie::{LayoutV0, LayoutV1, TrieConfiguration};
@@ -1207,6 +1207,25 @@ pub trait Crypto {
.expect("`bls377_generate` failed")
}
/// Generate an `(ecdsa,bls12-377)` key for the given key type using an optional `seed` and
/// store it in the keystore.
///
/// The `seed` needs to be a valid utf8.
///
/// Returns the public key.
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_generate(
&mut self,
id: KeyTypeId,
seed: Option<Vec<u8>>,
) -> ecdsa_bls377::Public {
let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!"));
self.extension::<KeystoreExt>()
.expect("No `keystore` associated for the current context!")
.ecdsa_bls377_generate_new(id, seed)
.expect("`ecdsa_bls377_generate` failed")
}
/// Generate a `bandersnatch` key pair for the given key type using an optional
/// `seed` and store it in the keystore.
///
+64 -1
View File
@@ -23,7 +23,7 @@ pub mod testing;
#[cfg(feature = "bandersnatch-experimental")]
use sp_core::bandersnatch;
#[cfg(feature = "bls-experimental")]
use sp_core::{bls377, bls381};
use sp_core::{bls377, bls381, ecdsa_bls377};
use sp_core::{
crypto::{ByteArray, CryptoTypeId, KeyTypeId},
ecdsa, ed25519, sr25519,
@@ -270,6 +270,10 @@ pub trait Keystore: Send + Sync {
#[cfg(feature = "bls-experimental")]
fn bls377_public_keys(&self, id: KeyTypeId) -> Vec<bls377::Public>;
/// Returns all (ecdsa,bls12-377) paired public keys for the given key type.
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_public_keys(&self, id: KeyTypeId) -> Vec<ecdsa_bls377::Public>;
/// Generate a new bls381 key pair for the given key type and an optional seed.
///
/// Returns an `bls381::Public` key of the generated key pair or an `Err` if
@@ -292,6 +296,17 @@ pub trait Keystore: Send + Sync {
seed: Option<&str>,
) -> Result<bls377::Public, Error>;
/// Generate a new (ecdsa,bls377) key pair for the given key type and an optional seed.
///
/// Returns an `ecdsa_bls377::Public` key of the generated key pair or an `Err` if
/// something failed during key generation.
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_generate_new(
&self,
key_type: KeyTypeId,
seed: Option<&str>,
) -> Result<ecdsa_bls377::Public, Error>;
/// Generate a bls381 signature for a given message.
///
/// Receives [`KeyTypeId`] and a [`bls381::Public`] key to be able to map
@@ -324,6 +339,22 @@ pub trait Keystore: Send + Sync {
msg: &[u8],
) -> Result<Option<bls377::Signature>, Error>;
/// Generate a (ecdsa,bls377) signature pair for a given message.
///
/// Receives [`KeyTypeId`] and a [`ecdsa_bls377::Public`] key to be able to map
/// them to a private key that exists in the keystore.
///
/// Returns an [`ecdsa_bls377::Signature`] or `None` in case the given `key_type`
/// and `public` combination doesn't exist in the keystore.
/// An `Err` will be returned if generating the signature itself failed.
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_sign(
&self,
key_type: KeyTypeId,
public: &ecdsa_bls377::Public,
msg: &[u8],
) -> Result<Option<ecdsa_bls377::Signature>, Error>;
/// Insert a new secret key.
fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>;
@@ -349,6 +380,7 @@ pub trait Keystore: Send + Sync {
/// - bandersnatch
/// - bls381
/// - bls377
/// - (ecdsa,bls377) paired keys
///
/// To support more schemes you can overwrite this method.
///
@@ -398,6 +430,13 @@ pub trait Keystore: Send + Sync {
.map_err(|_| Error::ValidationError("Invalid public key format".into()))?;
self.bls377_sign(id, &public, msg)?.map(|s| s.encode())
},
#[cfg(feature = "bls-experimental")]
ecdsa_bls377::CRYPTO_ID => {
let public = ecdsa_bls377::Public::from_slice(public)
.map_err(|_| Error::ValidationError("Invalid public key format".into()))?;
self.ecdsa_bls377_sign(id, &public, msg)?.map(|s| s.encode())
},
_ => return Err(Error::KeyNotSupported(id)),
};
Ok(signature)
@@ -560,6 +599,11 @@ impl<T: Keystore + ?Sized> Keystore for Arc<T> {
(**self).bls377_public_keys(id)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_public_keys(&self, id: KeyTypeId) -> Vec<ecdsa_bls377::Public> {
(**self).ecdsa_bls377_public_keys(id)
}
#[cfg(feature = "bls-experimental")]
fn bls381_generate_new(
&self,
@@ -578,6 +622,15 @@ impl<T: Keystore + ?Sized> Keystore for Arc<T> {
(**self).bls377_generate_new(key_type, seed)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_generate_new(
&self,
key_type: KeyTypeId,
seed: Option<&str>,
) -> Result<ecdsa_bls377::Public, Error> {
(**self).ecdsa_bls377_generate_new(key_type, seed)
}
#[cfg(feature = "bls-experimental")]
fn bls381_sign(
&self,
@@ -598,6 +651,16 @@ impl<T: Keystore + ?Sized> Keystore for Arc<T> {
(**self).bls377_sign(key_type, public, msg)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_sign(
&self,
key_type: KeyTypeId,
public: &ecdsa_bls377::Public,
msg: &[u8],
) -> Result<Option<ecdsa_bls377::Signature>, Error> {
(**self).ecdsa_bls377_sign(key_type, public, msg)
}
fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> {
(**self).insert(key_type, suri, public)
}
+25 -1
View File
@@ -22,7 +22,7 @@ use crate::{Error, Keystore, KeystorePtr};
#[cfg(feature = "bandersnatch-experimental")]
use sp_core::bandersnatch;
#[cfg(feature = "bls-experimental")]
use sp_core::{bls377, bls381};
use sp_core::{bls377, bls381, ecdsa_bls377};
use sp_core::{
crypto::{ByteArray, KeyTypeId, Pair, VrfSecret},
ecdsa, ed25519, sr25519,
@@ -322,6 +322,30 @@ impl Keystore for MemoryKeystore {
self.sign::<bls377::Pair>(key_type, public, msg)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_public_keys(&self, key_type: KeyTypeId) -> Vec<ecdsa_bls377::Public> {
self.public_keys::<ecdsa_bls377::Pair>(key_type)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_generate_new(
&self,
key_type: KeyTypeId,
seed: Option<&str>,
) -> Result<ecdsa_bls377::Public, Error> {
self.generate_new::<ecdsa_bls377::Pair>(key_type, seed)
}
#[cfg(feature = "bls-experimental")]
fn ecdsa_bls377_sign(
&self,
key_type: KeyTypeId,
public: &ecdsa_bls377::Public,
msg: &[u8],
) -> Result<Option<ecdsa_bls377::Signature>, Error> {
self.sign::<ecdsa_bls377::Pair>(key_type, public, msg)
}
fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> {
self.keys
.write()