Introduce sign_with method in keystore (#4925)

* Add KEY_KIND_ID to the public trait

This change is being introduced for the purpose of identifying a public
key with it's identifier and algorithm "kind".

* Use `sign_with` as implemented in BareCryptoStore

* Implement `sign_with` in sc_keystore

* Fix inconsistencies, use *_KIND_ID in sp_core testing

* Rename KeyKindId to CryptoTypeId

* Remove pair-returning functions from BareCryptoStore trait

* Define CryptoTypeId in app-crypto macros

* Add functions to get keys supported by keystore

* Fix sign_with signature to include CryptoTypePublicPair

* Add `sign_with_any` and `sign_with_all`

* Use keystore.sign_with in auth_discovery

* Rename get_supported_keys -> supported_keys

* Added headers to function docstrings

* Use chain instead of extending a temp vector

* Fixed some code formatting

* Restrict size of CryptoTypeId

This is to be able to use Encode/Decode derives and the overcome having
the size being unknown at compile-time.

* Implement sign_with in the trait itself

* Remove whitespace

* Use key_type also as a CryptoTypeId in app_crypto macros

* Rename `get_keys` to `keys` in BareCryptoStore

* Remove usage of key_pair funcs in tests

* Adjust docstring for *_CYPTO_ID constants

* Fix failures

* Simplify mapping on keys

* Remove one let

* Fixed typo

* PR feedback

* remove whitespace

* Zip keys and signatures

* Use into_iter & remove cloned

* Pass index to MissingSignature

* Use typed errors instead of strings for BareCryptoStore

* Implement Debug for trait error

* Use hashsets for better performance for supported_keys

* Make sure keys are inserted into the keystore

* Make sign_with_all return type consistent with `sign_with`

* Rename Error to BareCryptoStoreError

* Rename CRYPT_TYPE_ID -> CRYPTO_ID

* Remove unnecessary CRYPTO_ID declaration in Public trait

* Convert pub key to CryptoTypePublicPair

* Fix use

* Fix code style

* Implement From on CryptoTypePublicPair in app_crypto macros

* Change CryptoTypePublicPair to a struct

* Implement Display on CryptoTypePublicPair

* Pass CryptoTypePublicPair to MissingSignature error

* Adjust docs according to function signature

* Unify keys implementation

* Fix RPC author tests

* Fix stackoverflow

* Tabify spaces

* Pass KeyTypeId to error for easier debugging

* Fix asserts

* Use ToHex to format public key

* Use constants from sp_core

* Rename testing KeyTypeId constants

* Please compiler

* Restore KeyTypeId names

apparently, they're not only used in tests

* Use BareCryptoStoreError instead of String

* Document return value

* Fix borrow check

* Convert to hashset internally

* WIP - iter_keys

* Return raw_public_keys

* Address PR feedback

* Address PR Feedback

* Fix hexdisplay import error

* Update primitives/core/src/traits.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Rakan Alhneiti
2020-03-30 13:18:59 +02:00
committed by GitHub
parent 462eaa3f41
commit e17a23e907
17 changed files with 474 additions and 152 deletions
@@ -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),
}
+22 -20
View File
@@ -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
+126 -50
View File
@@ -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())
}
/// 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)
}
}
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<_>>())
}
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))
}
}
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(),
);
}
}
+10 -17
View File
@@ -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 ed25519_public_keys = setup.keystore.read().keys(ED25519).unwrap();
let sr25519_public_keys = setup.keystore.read().keys(SR25519).unwrap();
let sr25519_key_pair = setup.keystore.read().sr25519_key_pair(
SR25519,
&session_keys.sr25519.clone().into(),
).expect("sr25519 key exists in store");
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]
@@ -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};
@@ -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;
@@ -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};
@@ -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)));
}
@@ -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)));
}
@@ -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! {
+27 -2
View File
@@ -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.
///
+4 -1
View File
@@ -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]>.
+16 -1
View File
@@ -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 {
+17 -2
View File
@@ -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
+84 -32
View File
@@ -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()));
}
}
+92 -10
View File
@@ -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.
+6 -4
View File
@@ -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.