Files
pezkuwi-subxt/substrate/client/consensus/beefy/src/keystore.rs
T
drskalman 3fef703e30 Support for multiple signature scheme for BEEFY primitves (#14373)
* Merged BEEFY primitives with generic signature and keyset commitment support from old pull to current code

* - Add bls-experimental feature to application-crypto and beefy primitives
- Fix remaining crypto -> ecdsa_crypto
- code build but not tests

* Make beefy primitive tests compile

* move bls related beefy primitives code and test behind bls-experimental flag

* Make BEEFY clients complies with BEEFY API depending on AuthorityId

* - Rename `BeefyAuthoritySet.root` → `BeefyAuthoritySet.keyset_commitment`.
- Remove apk proof keyset_commitment from `BeefyAuthoritySet`.
- Fix failing signed commitment and signature to witness test.
- Make client compatible with BeefyAPI generic on AuthorityId.
- `crypto` → `ecdsa_crypto` in BEEFY client and frame.

* Commit Cargo lock remove ark-serialize from BEEFY primitives

* Use Codec instead of Encode + Decode in primitives/consensus/beefy/src/lib.rs

Co-authored-by: Davide Galassi <davxy@datawok.net>

* - Make `BeefyApi` generic over Signature type.
- Make new `BeeyApi` functinos also generic over AuthorityId and Signature

* Unmake BeefyAPI generic over Signature. Recover Signature type from AuthId.

* - dont use hex or hex-literal use array-bytes instead in beefy primitives and bls crypto.
- CamelCase ECDSA and BLS everywhere.

* Move the definition of BEEFY key type from `primitives/beefy` to `crypto.rs` according to new convention.

* - Add bls377_generate_new to `sp-io` and `application_crypto::bls`.
- Add `bls-experimental` to `sp-io`

Does not compile because PassByCodec can not derive PassBy using customly implemented PassByIner.

* Implement PassBy for `bls::Public` manually

* fix Beefy `KEY_TYPE` in `frame/beefy` tests to come from `sp-core::key_types` enum

* specify both generic for `hex2array_unchecked` in `sp-core/bls.rs`

* Rename `crypto`→`ecdsa_crypto` in `primitives/consensus/beefy/src/test_utils.rs` docs

* remove commented-out code in `primitives/consensus/beefy/src/commitment.rs`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Fix inconsistency in panic message in  `primitives/io/src/lib.rs`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Remove redundant feature activation in `primitives/io/Cargo.toml`

Co-authored-by: Davide Galassi <davxy@datawok.net>

* - make `w3f-bls` a dev-dependancy only for beefy primitives.

- clean up comments.

Co-authored-by: Davide Galassi <davxy@datawok.net>

* export BEEFY KEY_TYPE from primitives/consensus/beefy
make `frame/consensus/beefy` in dependent of sp_crypto_app
use consistent naming in the beefy primitive tests.

* - implement `BeefyAuthorityId` for `bls_crypto::AuthorityId`.
- implement `bls_verify_works` test for BEEFY `bls_crypto`.

* Remove BEEFY `ecdsa_n_bls_crypto` for now for later re-introduction

* Make commitment and witness BEEFY tests not use Keystore.

* put `bls_beefy_verify_works` test under `bls-experimental` flag.

* bump up Runtime `BeefyAPI` to version 3 due to introducing generic AuthorityId.

* reuse code and encapsulate w3f-bls backend in sp-core as most as possible

Co-authored-by: Davide Galassi <davxy@datawok.net>

* Make comments in primities BEEFY `commitment.rs` and `witness.rs``tests convention conforming

* Use master dep versions

* Trivial change. Mostly to trigger CI

* Apply suggestions from code review

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>

* Fix Cargo.toml

* Trigger CI with cumulus companion

* Trigger CI after polkadot companion change

---------

Co-authored-by: Davide Galassi <davxy@datawok.net>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
2023-08-02 13:42:04 +00:00

344 lines
9.5 KiB
Rust

// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, RuntimeAppPublic};
use sp_core::keccak_256;
use sp_keystore::KeystorePtr;
use log::warn;
use sp_consensus_beefy::{
ecdsa_crypto::{Public, Signature},
BeefyAuthorityId,
};
use crate::{error, LOG_TARGET};
/// Hasher used for BEEFY signatures.
pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256;
/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a
/// wrapper around [`sp_keystore::Keystore`] and allows to customize
/// common cryptographic functionality.
pub(crate) struct BeefyKeystore(Option<KeystorePtr>);
impl BeefyKeystore {
/// Check if the keystore contains a private key for one of the public keys
/// contained in `keys`. A public key with a matching private key is known
/// as a local authority id.
///
/// Return the public key for which we also do have a private key. If no
/// matching private key is found, `None` will be returned.
pub fn authority_id(&self, keys: &[Public]) -> Option<Public> {
let store = self.0.clone()?;
// we do check for multiple private keys as a key store sanity check.
let public: Vec<Public> = keys
.iter()
.filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)]))
.cloned()
.collect();
if public.len() > 1 {
warn!(
target: LOG_TARGET,
"🥩 Multiple private keys found for: {:?} ({})",
public,
public.len()
);
}
public.get(0).cloned()
}
/// Sign `message` with the `public` key.
///
/// Note that `message` usually will be pre-hashed before being signed.
///
/// Return the message signature or an error in case of failure.
pub fn sign(&self, public: &Public, message: &[u8]) -> Result<Signature, error::Error> {
let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?;
let msg = keccak_256(message);
let public = public.as_ref();
let sig = store
.ecdsa_sign_prehashed(BEEFY_KEY_TYPE, public, &msg)
.map_err(|e| error::Error::Keystore(e.to_string()))?
.ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?;
// check that `sig` has the expected result type
let sig = sig.clone().try_into().map_err(|_| {
error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public))
})?;
Ok(sig)
}
/// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently
/// supported (i.e. found in the keystore).
pub fn public_keys(&self) -> Result<Vec<Public>, error::Error> {
let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?;
let pk: Vec<Public> =
store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect();
Ok(pk)
}
/// Use the `public` key to verify that `sig` is a valid signature for `message`.
///
/// Return `true` if the signature is authentic, `false` otherwise.
pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool {
BeefyAuthorityId::<BeefySignatureHasher>::verify(public, sig, message)
}
}
impl From<Option<KeystorePtr>> for BeefyKeystore {
fn from(store: Option<KeystorePtr>) -> BeefyKeystore {
BeefyKeystore(store)
}
}
#[cfg(test)]
pub mod tests {
use sp_consensus_beefy::{ecdsa_crypto, Keyring};
use sp_core::{ecdsa, Pair};
use sp_keystore::testing::MemoryKeystore;
use super::*;
use crate::error::Error;
fn keystore() -> KeystorePtr {
MemoryKeystore::new().into()
}
#[test]
fn verify_should_work() {
let msg = keccak_256(b"I am Alice!");
let sig = Keyring::Alice.sign(b"I am Alice!");
assert!(ecdsa::Pair::verify_prehashed(
&sig.clone().into(),
&msg,
&Keyring::Alice.public().into(),
));
// different public key -> fail
assert!(!ecdsa::Pair::verify_prehashed(
&sig.clone().into(),
&msg,
&Keyring::Bob.public().into(),
));
let msg = keccak_256(b"I am not Alice!");
// different msg -> fail
assert!(
!ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),)
);
}
#[test]
fn pair_works() {
let want = ecdsa_crypto::Pair::from_string("//Alice", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Alice.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Bob", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Bob.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Charlie", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Charlie.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Dave", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Dave.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Eve", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Eve.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Ferdie", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Ferdie.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//One", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::One.pair().to_raw_vec();
assert_eq!(want, got);
let want = ecdsa_crypto::Pair::from_string("//Two", None)
.expect("Pair failed")
.to_raw_vec();
let got = Keyring::Two.pair().to_raw_vec();
assert_eq!(want, got);
}
#[test]
fn authority_id_works() {
let store = keystore();
let alice: ecdsa_crypto::Public = store
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed()))
.ok()
.unwrap()
.into();
let bob = Keyring::Bob.public();
let charlie = Keyring::Charlie.public();
let store: BeefyKeystore = Some(store).into();
let mut keys = vec![bob, charlie];
let id = store.authority_id(keys.as_slice());
assert!(id.is_none());
keys.push(alice.clone());
let id = store.authority_id(keys.as_slice()).unwrap();
assert_eq!(id, alice);
}
#[test]
fn sign_works() {
let store = keystore();
let alice: ecdsa_crypto::Public = store
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed()))
.ok()
.unwrap()
.into();
let store: BeefyKeystore = Some(store).into();
let msg = b"are you involved or commited?";
let sig1 = store.sign(&alice, msg).unwrap();
let sig2 = Keyring::Alice.sign(msg);
assert_eq!(sig1, sig2);
}
#[test]
fn sign_error() {
let store = keystore();
store
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed()))
.ok()
.unwrap();
let store: BeefyKeystore = Some(store).into();
let alice = Keyring::Alice.public();
let msg = b"are you involved or commited?";
let sig = store.sign(&alice, msg).err().unwrap();
let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string());
assert_eq!(sig, err);
}
#[test]
fn sign_no_keystore() {
let store: BeefyKeystore = None.into();
let alice = Keyring::Alice.public();
let msg = b"are you involved or commited";
let sig = store.sign(&alice, msg).err().unwrap();
let err = Error::Keystore("no Keystore".to_string());
assert_eq!(sig, err);
}
#[test]
fn verify_works() {
let store = keystore();
let alice: ecdsa_crypto::Public = store
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed()))
.ok()
.unwrap()
.into();
let store: BeefyKeystore = Some(store).into();
// `msg` and `sig` match
let msg = b"are you involved or commited?";
let sig = store.sign(&alice, msg).unwrap();
assert!(BeefyKeystore::verify(&alice, &sig, msg));
// `msg and `sig` don't match
let msg = b"you are just involved";
assert!(!BeefyKeystore::verify(&alice, &sig, msg));
}
// Note that we use keys with and without a seed for this test.
#[test]
fn public_keys_works() {
const TEST_TYPE: sp_application_crypto::KeyTypeId =
sp_application_crypto::KeyTypeId(*b"test");
let store = keystore();
let add_key =
|key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap();
// test keys
let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str()));
let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str()));
let _ = add_key(TEST_TYPE, None);
let _ = add_key(TEST_TYPE, None);
// BEEFY keys
let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Dave.to_seed().as_str()));
let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Eve.to_seed().as_str()));
let key1: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into();
let key2: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into();
let store: BeefyKeystore = Some(store).into();
let keys = store.public_keys().ok().unwrap();
assert!(keys.len() == 4);
assert!(keys.contains(&Keyring::Dave.public()));
assert!(keys.contains(&Keyring::Eve.public()));
assert!(keys.contains(&key1));
assert!(keys.contains(&key2));
}
}