diff --git a/substrate/client/authority-discovery/src/error.rs b/substrate/client/authority-discovery/src/error.rs index 74b7043c29ba0c3fb8439c6dde47c619183831ed..216f8c26bf07e397a80612c17ca8ea82cd2275da 100644 --- a/substrate/client/authority-discovery/src/error.rs +++ b/substrate/client/authority-discovery/src/error.rs @@ -16,6 +16,8 @@ //! Authority discovery errors. +use sp_core::crypto::CryptoTypePublicPair; + /// AuthorityDiscovery Result. pub type Result<T> = std::result::Result<T, Error>; @@ -46,6 +48,10 @@ pub enum Error { EncodingDecodingScale(codec::Error), /// Failed to parse a libp2p multi address. ParsingMultiaddress(libp2p::core::multiaddr::Error), + /// Failed to sign using a specific public key + MissingSignature(CryptoTypePublicPair), + /// Failed to sign using all public keys + Signing, /// Failed to register Prometheus metric. Prometheus(prometheus_endpoint::PrometheusError), } diff --git a/substrate/client/authority-discovery/src/lib.rs b/substrate/client/authority-discovery/src/lib.rs index 171a401bbbf8b5d00bb80d06ee0dbe2ad7d482ce..ec103ab8a0ea1f2048358cca9515decb464dfe84 100644 --- a/substrate/client/authority-discovery/src/lib.rs +++ b/substrate/client/authority-discovery/src/lib.rs @@ -63,7 +63,7 @@ use prost::Message; use sc_client_api::blockchain::HeaderBackend; use sc_network::{DhtEvent, ExHashT, NetworkStateInfo}; use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId, AuthoritySignature, AuthorityPair}; -use sp_core::crypto::{key_types, Pair}; +use sp_core::crypto::{key_types, CryptoTypePublicPair, Pair}; use sp_core::traits::BareCryptoStorePtr; use sp_runtime::{traits::Block as BlockT, generic::BlockId}; use sp_api::ProvideRuntimeApi; @@ -283,19 +283,36 @@ where .encode(&mut serialized_addresses) .map_err(Error::EncodingProto)?; - for key in self.get_priv_keys_within_authority_set()?.into_iter() { - let signature = key.sign(&serialized_addresses); + let keys: Vec<CryptoTypePublicPair> = self.get_own_public_keys_within_authority_set()? + .into_iter() + .map(Into::into) + .collect(); + let signatures = self.key_store + .read() + .sign_with_all( + key_types::AUTHORITY_DISCOVERY, + keys.clone(), + serialized_addresses.as_slice(), + ) + .map_err(|_| Error::Signing)?; + + for (sign_result, key) in signatures.iter().zip(keys) { let mut signed_addresses = vec![]; + + // sign_with_all returns Result<Signature, Error> signature + // is generated for a public key that is supported. + // Verify that all signatures exist for all provided keys. + let signature = sign_result.as_ref().map_err(|_| Error::MissingSignature(key.clone()))?; schema::SignedAuthorityAddresses { addresses: serialized_addresses.clone(), - signature: signature.encode(), + signature: Encode::encode(&signature), } .encode(&mut signed_addresses) .map_err(Error::EncodingProto)?; self.network.put_value( - hash_authority_id(key.public().as_ref())?, + hash_authority_id(key.1.as_ref())?, signed_addresses, ); } @@ -446,21 +463,6 @@ where Ok(()) } - /// Retrieve all local authority discovery private keys that are within the current authority - /// set. - fn get_priv_keys_within_authority_set(&mut self) -> Result<Vec<AuthorityPair>> { - let keys = self.get_own_public_keys_within_authority_set()? - .into_iter() - .map(std::convert::Into::into) - .filter_map(|pub_key| { - self.key_store.read().sr25519_key_pair(key_types::AUTHORITY_DISCOVERY, &pub_key) - }) - .map(std::convert::Into::into) - .collect(); - - Ok(keys) - } - /// Retrieve our public keys within the current authority set. // // A node might have multiple authority discovery keys within its keystore, e.g. an old one and diff --git a/substrate/client/keystore/src/lib.rs b/substrate/client/keystore/src/lib.rs index 087ddf326de0141cc011cf4b046779193b4ba2a1..f8bc93097113b300bc85373918021da742700221 100644 --- a/substrate/client/keystore/src/lib.rs +++ b/substrate/client/keystore/src/lib.rs @@ -17,10 +17,11 @@ //! Keystore (and session key management) for ed25519 based chains like Polkadot. #![warn(missing_docs)] - -use std::{collections::HashMap, path::PathBuf, fs::{self, File}, io::{self, Write}, sync::Arc}; +use std::{collections::{HashMap, HashSet}, path::PathBuf, fs::{self, File}, io::{self, Write}, sync::Arc}; use sp_core::{ - crypto::{KeyTypeId, Pair as PairT, Public, IsWrappedBy, Protected}, traits::BareCryptoStore, + crypto::{IsWrappedBy, CryptoTypePublicPair, KeyTypeId, Pair as PairT, Protected, Public}, + traits::{BareCryptoStore, BareCryptoStoreError as TraitError}, + Encode, }; use sp_application_crypto::{AppKey, AppPublic, AppPair, ed25519, sr25519}; use parking_lot::RwLock; @@ -44,6 +45,12 @@ pub enum Error { /// Invalid seed #[display(fmt="Invalid seed")] InvalidSeed, + /// Public key type is not supported + #[display(fmt="Key crypto type is not supported")] + KeyNotSupported(KeyTypeId), + /// Pair not found for public key and KeyTypeId + #[display(fmt="Pair not found for {} public key", "_0")] + PairNotFound(String), /// Keystore unavailable #[display(fmt="Keystore unavailable")] Unavailable, @@ -52,6 +59,21 @@ pub enum Error { /// Keystore Result pub type Result<T> = std::result::Result<T, Error>; +impl From<Error> for TraitError { + fn from(error: Error) -> Self { + match error { + Error::KeyNotSupported(id) => TraitError::KeyNotSupported(id), + Error::PairNotFound(e) => TraitError::PairNotFound(e), + Error::InvalidSeed | Error::InvalidPhrase | Error::InvalidPassword => { + TraitError::ValidationError(error.to_string()) + }, + Error::Unavailable => TraitError::Unavailable, + Error::Io(e) => TraitError::Other(e.to_string()), + Error::Json(e) => TraitError::Other(e.to_string()), + } + } +} + impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { @@ -220,16 +242,35 @@ impl Store { self.key_pair_by_type::<Pair::Generic>(IsWrappedBy::from_ref(public), Pair::ID).map(Into::into) } - /// Get public keys of all stored keys that match the given key type. - pub fn public_keys_by_type<TPublic: Public>(&self, key_type: KeyTypeId) -> Result<Vec<TPublic>> { - let mut public_keys: Vec<TPublic> = self.additional.keys() - .filter_map(|(ty, public)| { - if *ty == key_type { - Some(TPublic::from_slice(public)) - } else { - None - } + /// Get public keys of all stored keys that match the key type. + /// + /// This will just use the type of the public key (a list of which to be returned) in order + /// to determine the key type. Unless you use a specialized application-type public key, then + /// this only give you keys registered under generic cryptography, and will not return keys + /// registered under the application type. + pub fn public_keys<Public: AppPublic>(&self) -> Result<Vec<Public>> { + self.raw_public_keys(Public::ID) + .map(|v| { + v.into_iter() + .map(|k| Public::from_slice(k.as_slice())) + .collect() }) + } + + /// Returns the file path for the given public key and key type. + fn key_file_path(&self, public: &[u8], key_type: KeyTypeId) -> Option<PathBuf> { + let mut buf = self.path.as_ref()?.clone(); + let key_type = hex::encode(key_type.0); + let key = hex::encode(public); + buf.push(key_type + key.as_str()); + Some(buf) + } + + /// Returns a list of raw public keys filtered by `KeyTypeId` + fn raw_public_keys(&self, id: KeyTypeId) -> Result<Vec<Vec<u8>>> { + let mut public_keys: Vec<Vec<u8>> = self.additional.keys() + .into_iter() + .filter_map(|k| if k.0 == id { Some(k.1.clone()) } else { None }) .collect(); if let Some(path) = &self.path { @@ -241,8 +282,10 @@ impl Store { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { match hex::decode(name) { Ok(ref hex) if hex.len() > 4 => { - if &hex[0..4] != &key_type.0 { continue } - let public = TPublic::from_slice(&hex[4..]); + if &hex[0..4] != &id.0 { + continue; + } + let public = hex[4..].to_vec(); public_keys.push(public); } _ => continue, @@ -253,71 +296,104 @@ impl Store { Ok(public_keys) } +} - /// Get public keys of all stored keys that match the key type. - /// - /// This will just use the type of the public key (a list of which to be returned) in order - /// to determine the key type. Unless you use a specialized application-type public key, then - /// this only give you keys registered under generic cryptography, and will not return keys - /// registered under the application type. - pub fn public_keys<Public: AppPublic>(&self) -> Result<Vec<Public>> { - self.public_keys_by_type::<Public::Generic>(Public::ID) - .map(|v| v.into_iter().map(Into::into).collect()) +impl BareCryptoStore for Store { + fn keys( + &self, + id: KeyTypeId + ) -> std::result::Result<Vec<CryptoTypePublicPair>, TraitError> { + let raw_keys = self.raw_public_keys(id)?; + Ok(raw_keys.into_iter() + .fold(Vec::new(), |mut v, k| { + v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); + v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); + v + })) + } + + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec<CryptoTypePublicPair> + ) -> std::result::Result<Vec<CryptoTypePublicPair>, TraitError> { + let all_keys = self.keys(id)?.into_iter().collect::<HashSet<_>>(); + Ok(keys.into_iter() + .filter(|key| all_keys.contains(key)) + .collect::<Vec<_>>()) } - /// Returns the file path for the given public key and key type. - fn key_file_path(&self, public: &[u8], key_type: KeyTypeId) -> Option<PathBuf> { - let mut buf = self.path.as_ref()?.clone(); - let key_type = hex::encode(key_type.0); - let key = hex::encode(public); - buf.push(key_type + key.as_str()); - Some(buf) + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> std::result::Result<Vec<u8>, TraitError> { + match key.0 { + ed25519::CRYPTO_ID => { + let pub_key = ed25519::Public::from_slice(key.1.as_slice()); + let key_pair: ed25519::Pair = self + .key_pair_by_type::<ed25519::Pair>(&pub_key, id) + .map_err(|e| TraitError::from(e))?; + Ok(key_pair.sign(msg).encode()) + } + sr25519::CRYPTO_ID => { + let pub_key = sr25519::Public::from_slice(key.1.as_slice()); + let key_pair: sr25519::Pair = self + .key_pair_by_type::<sr25519::Pair>(&pub_key, id) + .map_err(|e| TraitError::from(e))?; + Ok(key_pair.sign(msg).encode()) + } + _ => Err(TraitError::KeyNotSupported(id)) + } } -} -impl BareCryptoStore for Store { fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec<sr25519::Public> { - self.public_keys_by_type::<sr25519::Public>(key_type).unwrap_or_default() + self.raw_public_keys(key_type) + .map(|v| { + v.into_iter() + .map(|k| sr25519::Public::from_slice(k.as_slice())) + .collect() + }) + .unwrap_or_default() } fn sr25519_generate_new( &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result<sr25519::Public, String> { + ) -> std::result::Result<sr25519::Public, TraitError> { let pair = match seed { Some(seed) => self.insert_ephemeral_from_seed_by_type::<sr25519::Pair>(seed, id), None => self.generate_by_type::<sr25519::Pair>(id), - }.map_err(|e| e.to_string())?; + }.map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option<sr25519::Pair> { - self.key_pair_by_type::<sr25519::Pair>(pub_key, id).ok() - } - fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public> { - self.public_keys_by_type::<ed25519::Public>(key_type).unwrap_or_default() + self.raw_public_keys(key_type) + .map(|v| { + v.into_iter() + .map(|k| ed25519::Public::from_slice(k.as_slice())) + .collect() + }) + .unwrap_or_default() } fn ed25519_generate_new( &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result<ed25519::Public, String> { + ) -> std::result::Result<ed25519::Public, TraitError> { let pair = match seed { Some(seed) => self.insert_ephemeral_from_seed_by_type::<ed25519::Pair>(seed, id), None => self.generate_by_type::<ed25519::Pair>(id), - }.map_err(|e| e.to_string())?; + }.map_err(|e| -> TraitError { e.into() })?; Ok(pair.public()) } - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option<ed25519::Pair> { - self.key_pair_by_type::<ed25519::Pair>(pub_key, id).ok() - } - fn insert_unknown(&mut self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> std::result::Result<(), ()> { @@ -337,7 +413,7 @@ impl BareCryptoStore for Store { mod tests { use super::*; use tempfile::TempDir; - use sp_core::{testing::{SR25519}, crypto::{Ss58Codec}}; + use sp_core::{testing::SR25519, crypto::Ss58Codec}; #[test] fn basic_store() { @@ -451,7 +527,7 @@ mod tests { fs::write(file_name, "test").expect("Invalid file is written"); assert!( - store.read().public_keys_by_type::<sr25519::AppPublic>(SR25519).unwrap().is_empty(), + store.read().sr25519_public_keys(SR25519).is_empty(), ); } } diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index 3093cd9d3b759b08d887eb10165e26f697a9a073..8b956c23a5e660cd685c9d6381395068decb4ebc 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -21,7 +21,8 @@ use assert_matches::assert_matches; use codec::Encode; use sp_core::{ H256, blake2_256, hexdisplay::HexDisplay, testing::{ED25519, SR25519, KeyStore}, - traits::BareCryptoStorePtr, ed25519, crypto::{Pair, Public}, + traits::BareCryptoStorePtr, ed25519, sr25519, + crypto::{CryptoTypePublicPair, Pair, Public}, }; use rpc::futures::Stream as _; use substrate_test_runtime_client::{ @@ -173,7 +174,7 @@ fn should_return_pending_extrinsics() { let ex = uxt(AccountKeyring::Alice, 0); AuthorApi::submit_extrinsic(&p, ex.encode().into()).wait().unwrap(); - assert_matches!( + assert_matches!( p.pending_extrinsics(), Ok(ref expected) if *expected == vec![Bytes(ex.encode())] ); @@ -199,7 +200,7 @@ fn should_remove_extrinsics() { hash::ExtrinsicOrHash::Extrinsic(ex1.encode().into()), ]).unwrap(); - assert_eq!(removed.len(), 3); + assert_eq!(removed.len(), 3); } #[test] @@ -215,10 +216,9 @@ fn should_insert_key() { key_pair.public().0.to_vec().into(), ).expect("Insert key"); - let store_key_pair = setup.keystore.read() - .ed25519_key_pair(ED25519, &key_pair.public()).expect("Key exists in store"); + let public_keys = setup.keystore.read().keys(ED25519).unwrap(); - assert_eq!(key_pair.public(), store_key_pair.public()); + assert!(public_keys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, key_pair.public().to_raw_vec()))); } #[test] @@ -231,18 +231,11 @@ fn should_rotate_keys() { let session_keys = SessionKeys::decode(&mut &new_public_keys[..]) .expect("SessionKeys decode successfully"); - let ed25519_key_pair = setup.keystore.read().ed25519_key_pair( - ED25519, - &session_keys.ed25519.clone().into(), - ).expect("ed25519 key exists in store"); - - let sr25519_key_pair = setup.keystore.read().sr25519_key_pair( - SR25519, - &session_keys.sr25519.clone().into(), - ).expect("sr25519 key exists in store"); + let ed25519_public_keys = setup.keystore.read().keys(ED25519).unwrap(); + let sr25519_public_keys = setup.keystore.read().keys(SR25519).unwrap(); - assert_eq!(session_keys.ed25519, ed25519_key_pair.public().into()); - assert_eq!(session_keys.sr25519, sr25519_key_pair.public().into()); + assert!(ed25519_public_keys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, session_keys.ed25519.to_raw_vec()))); + assert!(sr25519_public_keys.contains(&CryptoTypePublicPair(sr25519::CRYPTO_ID, session_keys.sr25519.to_raw_vec()))); } #[test] diff --git a/substrate/primitives/application-crypto/src/ed25519.rs b/substrate/primitives/application-crypto/src/ed25519.rs index 414715a10684c18b49a33fe6bb1fc08c20639d4a..5be79ff4f7985a54e9eb0c34fb3552f3cf062bb1 100644 --- a/substrate/primitives/application-crypto/src/ed25519.rs +++ b/substrate/primitives/application-crypto/src/ed25519.rs @@ -23,12 +23,27 @@ use sp_std::vec::Vec; pub use sp_core::ed25519::*; mod app { + use sp_core::crypto::{CryptoTypePublicPair, Public as TraitPublic}; use sp_core::testing::ED25519; + use sp_core::ed25519::CRYPTO_ID; + crate::app_crypto!(super, ED25519); impl crate::traits::BoundToRuntimeAppPublic for Public { type Public = Self; } + + impl From<Public> for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } + } } pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/substrate/primitives/application-crypto/src/lib.rs b/substrate/primitives/application-crypto/src/lib.rs index b7c9ccaa9821aa49b9f14e70f841e05e1ef9853f..07e2b45106ee9690c841a5d842eebdc4325afd59 100644 --- a/substrate/primitives/application-crypto/src/lib.rs +++ b/substrate/primitives/application-crypto/src/lib.rs @@ -21,11 +21,11 @@ #![cfg_attr(not(feature = "std"), no_std)] #[doc(hidden)] -pub use sp_core::{self, crypto::{CryptoType, Public, Derive, IsWrappedBy, Wraps}, RuntimeDebug}; +pub use sp_core::{self, crypto::{CryptoType, CryptoTypePublicPair, Public, Derive, IsWrappedBy, Wraps}, RuntimeDebug}; #[doc(hidden)] #[cfg(feature = "full_crypto")] pub use sp_core::crypto::{SecretStringError, DeriveJunction, Ss58Codec, Pair}; -pub use sp_core::{crypto::{KeyTypeId, key_types}}; +pub use sp_core::crypto::{CryptoTypeId, KeyTypeId, key_types}; #[doc(hidden)] pub use codec; diff --git a/substrate/primitives/application-crypto/src/sr25519.rs b/substrate/primitives/application-crypto/src/sr25519.rs index 59c6f19b6f2668939054127347f5a87ed6989907..a0f2cef1c4e45b6150be335ac271e3dd592febbe 100644 --- a/substrate/primitives/application-crypto/src/sr25519.rs +++ b/substrate/primitives/application-crypto/src/sr25519.rs @@ -23,12 +23,27 @@ use sp_std::vec::Vec; pub use sp_core::sr25519::*; mod app { + use sp_core::crypto::{CryptoTypePublicPair, Public as TraitPublic}; use sp_core::testing::SR25519; + use sp_core::sr25519::CRYPTO_ID; + crate::app_crypto!(super, SR25519); impl crate::traits::BoundToRuntimeAppPublic for Public { type Public = Self; } + + impl From<Public> for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } + } } pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/substrate/primitives/application-crypto/test/src/ed25519.rs b/substrate/primitives/application-crypto/test/src/ed25519.rs index 400edfd6ae4402f9ab77fc0fe4fde9f61dfd89ce..1d72962829a53309ed4146a5f8657820ba15b697 100644 --- a/substrate/primitives/application-crypto/test/src/ed25519.rs +++ b/substrate/primitives/application-crypto/test/src/ed25519.rs @@ -17,7 +17,10 @@ //! Integration tests for ed25519 use sp_runtime::generic::BlockId; -use sp_core::{testing::{KeyStore, ED25519}, crypto::Pair}; +use sp_core::{ + crypto::Pair, + testing::{KeyStore, ED25519}, +}; use substrate_test_runtime_client::{ TestClientBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt, runtime::TestAPI, @@ -33,8 +36,7 @@ fn ed25519_works_in_runtime() { .test_ed25519_crypto(&BlockId::Number(0)) .expect("Tests `ed25519` crypto."); - let key_pair = keystore.read().ed25519_key_pair(ED25519, &public.as_ref()) - .expect("There should be at a `ed25519` key in the keystore for the given public key."); - - assert!(AppPair::verify(&signature, "ed25519", &AppPublic::from(key_pair.public()))); + let supported_keys = keystore.read().keys(ED25519).unwrap(); + assert!(supported_keys.contains(&public.clone().into())); + assert!(AppPair::verify(&signature, "ed25519", &AppPublic::from(public))); } diff --git a/substrate/primitives/application-crypto/test/src/sr25519.rs b/substrate/primitives/application-crypto/test/src/sr25519.rs index 49bb3c2a8364027c1dd4eceef607deb49ca09469..f2c7c48b2bc91c0329ecaad7541be5f13f3f754c 100644 --- a/substrate/primitives/application-crypto/test/src/sr25519.rs +++ b/substrate/primitives/application-crypto/test/src/sr25519.rs @@ -18,7 +18,10 @@ use sp_runtime::generic::BlockId; -use sp_core::{testing::{KeyStore, SR25519}, crypto::Pair}; +use sp_core::{ + crypto::Pair, + testing::{KeyStore, SR25519}, +}; use substrate_test_runtime_client::{ TestClientBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt, runtime::TestAPI, @@ -34,8 +37,7 @@ fn sr25519_works_in_runtime() { .test_sr25519_crypto(&BlockId::Number(0)) .expect("Tests `sr25519` crypto."); - let key_pair = keystore.read().sr25519_key_pair(SR25519, public.as_ref()) - .expect("There should be at a `sr25519` key in the keystore for the given public key."); - - assert!(AppPair::verify(&signature, "sr25519", &AppPublic::from(key_pair.public()))); + let supported_keys = keystore.read().keys(SR25519).unwrap(); + assert!(supported_keys.contains(&public.clone().into())); + assert!(AppPair::verify(&signature, "sr25519", &AppPublic::from(public))); } diff --git a/substrate/primitives/authority-discovery/src/lib.rs b/substrate/primitives/authority-discovery/src/lib.rs index 8926825525976f9d284bc1b6969ddfde936aff99..68680ad75946545f93d8d0c5ef5c3004a3623efc 100644 --- a/substrate/primitives/authority-discovery/src/lib.rs +++ b/substrate/primitives/authority-discovery/src/lib.rs @@ -21,8 +21,25 @@ use sp_std::vec::Vec; mod app { - use sp_application_crypto::{app_crypto, key_types::AUTHORITY_DISCOVERY, sr25519}; + use sp_application_crypto::{ + CryptoTypePublicPair, + key_types::AUTHORITY_DISCOVERY, + Public as _, + app_crypto, + sr25519}; app_crypto!(sr25519, AUTHORITY_DISCOVERY); + + impl From<Public> for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } + } + + impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(sr25519::CRYPTO_ID, key.to_raw_vec()) + } + } } sp_application_crypto::with_pair! { diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index 6301600921ac32bc3cace163eb2cdad591379c5a..f16c1609603c9f9726721c19d7f7e8384ae762ac 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -21,6 +21,7 @@ use crate::{sr25519, ed25519}; use sp_std::hash::Hash; use sp_std::vec::Vec; +use sp_std::str; #[cfg(feature = "std")] use sp_std::convert::TryInto; use sp_std::convert::TryFrom; @@ -33,7 +34,8 @@ use codec::{Encode, Decode}; use regex::Regex; #[cfg(feature = "std")] use base58::{FromBase58, ToBase58}; - +#[cfg(feature = "std")] +use crate::hexdisplay::HexDisplay; use zeroize::Zeroize; #[doc(hidden)] pub use sp_std::ops::Deref; @@ -539,7 +541,9 @@ impl<T: Sized + AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T { } /// Trait suitable for typical cryptographic PKI key public type. -pub trait Public: AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync { +pub trait Public: + AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync +{ /// A new instance from the given slice. /// /// NOTE: No checking goes on to ensure this is a real public key. Only use it if @@ -955,6 +959,27 @@ impl<'a> TryFrom<&'a str> for KeyTypeId { } } +/// An identifier for a specific cryptographic algorithm used by a key pair +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +pub struct CryptoTypeId(pub [u8; 4]); + +/// A type alias of CryptoTypeId & a public key +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +pub struct CryptoTypePublicPair(pub CryptoTypeId, pub Vec<u8>); + +#[cfg(feature = "std")] +impl sp_std::fmt::Display for CryptoTypePublicPair { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + let id = match str::from_utf8(&(self.0).0[..]) { + Ok(id) => id.to_string(), + Err(_) => { + format!("{:#?}", self.0) + } + }; + write!(f, "{}-{}", id, HexDisplay::from(&self.1)) + } +} + /// Known key types; this also functions as a global registry of key types for projects wishing to /// avoid collisions with each other. /// diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 548ae49b55487d31779a150909456beb74be04c8..8a45157844f3af20ce5ded4ce3dc28f6008a5983 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -36,10 +36,13 @@ use crate::{hashing::blake2_256, crypto::{Pair as TraitPair, DeriveJunction, Sec use crate::crypto::Ss58Codec; #[cfg(feature = "std")] use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; -use crate::crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}; +use crate::crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; #[cfg(feature = "full_crypto")] use secp256k1::{PublicKey, SecretKey}; +/// An identifier used to match public keys against ecdsa keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecds"); + /// A secret seed (which is bytewise essentially equivalent to a SecretKey). /// /// We need it as a different type because `Seed` is expected to be AsRef<[u8]>. diff --git a/substrate/primitives/core/src/ed25519.rs b/substrate/primitives/core/src/ed25519.rs index 5e04dcceffb395fe5433bfcb28a217f8c44f06b5..abeac05388d2773e7ba80f60d6b4e9193ebe2fcd 100644 --- a/substrate/primitives/core/src/ed25519.rs +++ b/substrate/primitives/core/src/ed25519.rs @@ -38,10 +38,13 @@ use crate::crypto::{Pair as TraitPair, DeriveJunction, SecretStringError}; use crate::crypto::Ss58Codec; #[cfg(feature = "std")] use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; -use crate::{crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}}; +use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; use sp_runtime_interface::pass_by::PassByInner; use sp_std::ops::Deref; +/// An identifier used to match public keys against ed25519 keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ed25"); + /// A secret seed. It's not called a "secret key" because ring doesn't expose the secret keys /// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we /// will need it later (such as for HDKD). @@ -378,6 +381,18 @@ impl TraitPublic for Public { impl Derive for Public {} +impl From<Public> for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } +} + +impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } +} + /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index de643dbb0a861d79e3d971b87670e2d15551d836..717952eb01c71b7cb398c5c57efc040704e07d24 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -39,7 +39,7 @@ use crate::crypto::{ #[cfg(feature = "std")] use crate::crypto::Ss58Codec; -use crate::{crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}}; +use crate::crypto::{Public as TraitPublic, CryptoTypePublicPair, UncheckedFrom, CryptoType, Derive, CryptoTypeId}; use crate::hash::{H256, H512}; use codec::{Encode, Decode}; use sp_std::ops::Deref; @@ -54,6 +54,9 @@ use sp_runtime_interface::pass_by::PassByInner; #[cfg(feature = "full_crypto")] const SIGNING_CTX: &[u8] = b"substrate"; +/// An identifier used to match public keys against sr25519 keys +pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"sr25"); + /// An Schnorrkel/Ristretto x25519 ("sr25519") public key. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Encode, Decode, Default, PassByInner)] @@ -390,6 +393,18 @@ impl TraitPublic for Public { } } +impl From<Public> for CryptoTypePublicPair { + fn from(key: Public) -> Self { + (&key).into() + } +} + +impl From<&Public> for CryptoTypePublicPair { + fn from(key: &Public) -> Self { + CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) + } +} + #[cfg(feature = "std")] impl From<MiniSecretKey> for Pair { fn from(sec: MiniSecretKey) -> Pair { @@ -599,7 +614,7 @@ impl CryptoType for Pair { #[cfg(test)] mod compatibility_test { use super::*; - use crate::crypto::{DEV_PHRASE}; + use crate::crypto::DEV_PHRASE; use hex_literal::hex; // NOTE: tests to ensure addresses that are created with the `0.1.x` version (pre-audit) are diff --git a/substrate/primitives/core/src/testing.rs b/substrate/primitives/core/src/testing.rs index 247323f9703b41ecf5a22897da4e4b0c6de1fb12..b5e6f4c7aff37427573de40587b9bfe6bf311281 100644 --- a/substrate/primitives/core/src/testing.rs +++ b/substrate/primitives/core/src/testing.rs @@ -16,10 +16,16 @@ //! Types that should only be used for testing! +use crate::crypto::{KeyTypeId, CryptoTypePublicPair}; #[cfg(feature = "std")] -use crate::{ed25519, sr25519, crypto::{Public, Pair}}; -use crate::crypto::KeyTypeId; - +use crate::{ + crypto::{Pair, Public}, + ed25519, sr25519, + traits::BareCryptoStoreError +}; +#[cfg(feature = "std")] +use std::collections::HashSet; +use codec::Encode; /// Key type for generic Ed25519 key. pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25"); /// Key type for generic Sr 25519 key. @@ -39,10 +45,41 @@ impl KeyStore { pub fn new() -> crate::traits::BareCryptoStorePtr { std::sync::Arc::new(parking_lot::RwLock::new(Self::default())) } + + fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option<sr25519::Pair> { + self.keys.get(&id) + .and_then(|inner| + inner.get(pub_key.as_slice()) + .map(|s| sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid")) + ) + } + + fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option<ed25519::Pair> { + self.keys.get(&id) + .and_then(|inner| + inner.get(pub_key.as_slice()) + .map(|s| ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid")) + ) + } + } #[cfg(feature = "std")] impl crate::traits::BareCryptoStore for KeyStore { + fn keys(&self, id: KeyTypeId) -> Result<Vec<CryptoTypePublicPair>, BareCryptoStoreError> { + self.keys + .get(&id) + .map(|map| { + Ok(map.keys() + .fold(Vec::new(), |mut v, k| { + v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); + v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); + v + })) + }) + .unwrap_or(Ok(vec![])) + } + fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec<sr25519::Public> { self.keys.get(&id) .map(|keys| @@ -58,10 +95,11 @@ impl crate::traits::BareCryptoStore for KeyStore { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result<sr25519::Public, String> { + ) -> Result<sr25519::Public, BareCryptoStoreError> { match seed { Some(seed) => { - let pair = sr25519::Pair::from_string(seed, None).expect("Generates an `sr25519` pair."); + let pair = sr25519::Pair::from_string(seed, None) + .map_err(|_| BareCryptoStoreError::ValidationError("Generates an `sr25519` pair.".to_owned()))?; self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), seed.into()); Ok(pair.public()) }, @@ -73,14 +111,6 @@ impl crate::traits::BareCryptoStore for KeyStore { } } - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option<sr25519::Pair> { - self.keys.get(&id) - .and_then(|inner| - inner.get(pub_key.as_slice()) - .map(|s| sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid")) - ) - } - fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec<ed25519::Public> { self.keys.get(&id) .map(|keys| @@ -96,10 +126,11 @@ impl crate::traits::BareCryptoStore for KeyStore { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result<ed25519::Public, String> { + ) -> Result<ed25519::Public, BareCryptoStoreError> { match seed { Some(seed) => { - let pair = ed25519::Pair::from_string(seed, None).expect("Generates an `ed25519` pair."); + let pair = ed25519::Pair::from_string(seed, None) + .map_err(|_| BareCryptoStoreError::ValidationError("Generates an `ed25519` pair.".to_owned()))?; self.keys.entry(id).or_default().insert(pair.public().to_raw_vec(), seed.into()); Ok(pair.public()) }, @@ -111,14 +142,6 @@ impl crate::traits::BareCryptoStore for KeyStore { } } - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option<ed25519::Pair> { - self.keys.get(&id) - .and_then(|inner| - inner.get(pub_key.as_slice()) - .map(|s| ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid")) - ) - } - fn insert_unknown(&mut self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys.entry(id).or_default().insert(public.to_owned(), suri.to_string()); Ok(()) @@ -131,6 +154,40 @@ impl crate::traits::BareCryptoStore for KeyStore { fn has_keys(&self, public_keys: &[(Vec<u8>, KeyTypeId)]) -> bool { public_keys.iter().all(|(k, t)| self.keys.get(&t).and_then(|s| s.get(k)).is_some()) } + + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec<CryptoTypePublicPair>, + ) -> std::result::Result<Vec<CryptoTypePublicPair>, BareCryptoStoreError> { + let provided_keys = keys.into_iter().collect::<HashSet<_>>(); + let all_keys = self.keys(id)?.into_iter().collect::<HashSet<_>>(); + + Ok(provided_keys.intersection(&all_keys).cloned().collect()) + } + + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> Result<Vec<u8>, BareCryptoStoreError> { + match key.0 { + ed25519::CRYPTO_ID => { + let key_pair: ed25519::Pair = self + .ed25519_key_pair(id, &ed25519::Public::from_slice(key.1.as_slice())) + .ok_or(BareCryptoStoreError::PairNotFound("ed25519".to_owned()))?; + return Ok(key_pair.sign(msg).encode()); + } + sr25519::CRYPTO_ID => { + let key_pair: sr25519::Pair = self + .sr25519_key_pair(id, &sr25519::Public::from_slice(key.1.as_slice())) + .ok_or(BareCryptoStoreError::PairNotFound("sr25519".to_owned()))?; + return Ok(key_pair.sign(msg).encode()); + } + _ => Err(BareCryptoStoreError::KeyNotSupported(id)) + } + } } /// Macro for exporting functions from wasm in with the expected signature for using it with the @@ -247,11 +304,9 @@ mod tests { .ed25519_generate_new(ED25519, None) .expect("Generates key"); - let store_key_pair = store.read() - .ed25519_key_pair(ED25519, &public) - .expect("Key should exists in store"); + let public_keys = store.read().keys(ED25519).unwrap(); - assert_eq!(public, store_key_pair.public()); + assert!(public_keys.contains(&public.into())); } #[test] @@ -267,11 +322,8 @@ mod tests { key_pair.public().as_ref(), ).expect("Inserts unknown key"); - let store_key_pair = store.read().sr25519_key_pair( - SR25519, - &key_pair.public(), - ).expect("Gets key pair from keystore"); + let public_keys = store.read().keys(SR25519).unwrap(); - assert_eq!(key_pair.public(), store_key_pair.public()); + assert!(public_keys.contains(&key_pair.public().into())); } } diff --git a/substrate/primitives/core/src/traits.rs b/substrate/primitives/core/src/traits.rs index 21a0b77ca7ab4ca0d7345928bc03c2d2e569a674..14839fb58562a3ec1bcff7219e6dbfb7e1b47b4d 100644 --- a/substrate/primitives/core/src/traits.rs +++ b/substrate/primitives/core/src/traits.rs @@ -16,14 +16,35 @@ //! Shareable Substrate traits. -use crate::{crypto::KeyTypeId, ed25519, sr25519}; +use crate::{ + crypto::{KeyTypeId, CryptoTypePublicPair}, + ed25519, sr25519, +}; use std::{ - fmt::{Debug, Display}, panic::UnwindSafe, sync::Arc, borrow::Cow, + borrow::Cow, + fmt::{Debug, Display}, + panic::UnwindSafe, + sync::Arc, }; pub use sp_externalities::{Externalities, ExternalitiesExt}; +/// BareCryptoStore error +#[derive(Debug)] +pub enum BareCryptoStoreError { + /// Public key type is not supported + KeyNotSupported(KeyTypeId), + /// Pair not found for public key and KeyTypeId + PairNotFound(String), + /// Validation error + ValidationError(String), + /// Keystore unavailable + Unavailable, + /// Programming errors + Other(String) +} + /// Something that generates, stores and provides access to keys. pub trait BareCryptoStore: Send + Sync { /// Returns all sr25519 public keys for the given key type. @@ -37,10 +58,7 @@ pub trait BareCryptoStore: Send + Sync { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result<sr25519::Public, String>; - /// Returns the sr25519 key pair for the given key type and public key combination. - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option<sr25519::Pair>; - + ) -> Result<sr25519::Public, BareCryptoStoreError>; /// Returns all ed25519 public keys for the given key type. fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec<ed25519::Public>; /// Generate a new ed25519 key pair for the given key type and an optional seed. @@ -52,10 +70,7 @@ pub trait BareCryptoStore: Send + Sync { &mut self, id: KeyTypeId, seed: Option<&str>, - ) -> Result<ed25519::Public, String>; - - /// Returns the ed25519 key pair for the given key type and public key combination. - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option<ed25519::Pair>; + ) -> Result<ed25519::Public, BareCryptoStoreError>; /// Insert a new key. This doesn't require any known of the crypto; but a public key must be /// manually provided. @@ -67,11 +82,78 @@ pub trait BareCryptoStore: Send + Sync { /// Get the password for this store. fn password(&self) -> Option<&str>; + /// Find intersection between provided keys and supported keys + /// + /// Provided a list of (CryptoTypeId,[u8]) pairs, this would return + /// a filtered set of public keys which are supported by the keystore. + fn supported_keys( + &self, + id: KeyTypeId, + keys: Vec<CryptoTypePublicPair> + ) -> Result<Vec<CryptoTypePublicPair>, BareCryptoStoreError>; + /// List all supported keys + /// + /// Returns a set of public keys the signer supports. + fn keys(&self, id: KeyTypeId) -> Result<Vec<CryptoTypePublicPair>, BareCryptoStoreError>; /// Checks if the private keys for the given public key and key type combinations exist. /// /// Returns `true` iff all private keys could be found. fn has_keys(&self, public_keys: &[(Vec<u8>, KeyTypeId)]) -> bool; + + /// Sign with key + /// + /// Signs a message with the private key that matches + /// the public key passed. + /// + /// Returns the SCALE encoded signature if key is found & supported, + /// an error otherwise. + fn sign_with( + &self, + id: KeyTypeId, + key: &CryptoTypePublicPair, + msg: &[u8], + ) -> Result<Vec<u8>, BareCryptoStoreError>; + + /// Sign with any key + /// + /// Given a list of public keys, find the first supported key and + /// sign the provided message with that key. + /// + /// Returns a tuple of the used key and the signature + fn sign_with_any( + &self, + id: KeyTypeId, + keys: Vec<CryptoTypePublicPair>, + msg: &[u8] + ) -> Result<(CryptoTypePublicPair, Vec<u8>), BareCryptoStoreError> { + if keys.len() == 1 { + return self.sign_with(id, &keys[0], msg).map(|s| (keys[0].clone(), s)); + } else { + for k in self.supported_keys(id, keys)? { + if let Ok(sign) = self.sign_with(id, &k, msg) { + return Ok((k, sign)); + } + } + } + Err(BareCryptoStoreError::KeyNotSupported(id)) + } + + /// Sign with all keys + /// + /// Provided a list of public keys, sign a message with + /// each key given that the key is supported. + /// + /// Returns a list of `Result`s each representing the signature of each key or + /// a BareCryptoStoreError for non-supported keys. + fn sign_with_all( + &self, + id: KeyTypeId, + keys: Vec<CryptoTypePublicPair>, + msg: &[u8], + ) -> Result<Vec<Result<Vec<u8>, BareCryptoStoreError>>, ()>{ + Ok(keys.iter().map(|k| self.sign_with(id, k, msg)).collect()) + } } /// A pointer to the key store. diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 780065897474debb95bbc300cc7aada2ccaf19ad..bc49df159eb6f873d5a9ac7e011a529cc2422cc0 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -411,8 +411,9 @@ pub trait Crypto { self.extension::<KeystoreExt>() .expect("No `keystore` associated for the current context!") .read() - .ed25519_key_pair(id, &pub_key) - .map(|k| k.sign(msg)) + .sign_with(id, &pub_key.into(), msg) + .map(|sig| ed25519::Signature::from_slice(sig.as_slice())) + .ok() } /// Verify an `ed25519` signature. @@ -463,8 +464,9 @@ pub trait Crypto { self.extension::<KeystoreExt>() .expect("No `keystore` associated for the current context!") .read() - .sr25519_key_pair(id, &pub_key) - .map(|k| k.sign(msg)) + .sign_with(id, &pub_key.into(), msg) + .map(|sig| sr25519::Signature::from_slice(sig.as_slice())) + .ok() } /// Verify an `sr25519` signature.