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
+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.