Introduces author_hasKey and author_hasSessionKeys rpc endpoints (#4720)

* Introduces `author_hasKey` and `author_hasSessionKeys` rpc endpoints

Both endpoints can be used to check if a key is present in the keystore.

- `hasKey` works on with an individual public key and key type. It
checks if a private key for the given combination exists in the
keystore.
- `hasSessionKeys` works with the full encoded session key blob stored
on-chain in `nextKeys`. This requires that the given blob can be decoded
by the runtime. It will return `true`, iff all public keys of the
session key exist in the storage.

Fixes: https://github.com/paritytech/substrate/issues/4696

* Update client/rpc-api/src/author/error.rs

Co-Authored-By: Nikolay Volf <nikvolf@gmail.com>

* Indentation

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
This commit is contained in:
Bastian Köcher
2020-01-24 15:20:45 +01:00
committed by GitHub
parent 1614ce0996
commit fc99887de0
21 changed files with 223 additions and 34 deletions
+1
View File
@@ -6642,6 +6642,7 @@ name = "sp-session"
version = "2.0.0"
dependencies = [
"sp-api 2.0.0",
"sp-core 2.0.0",
"sp-runtime 2.0.0",
"sp-std 2.0.0",
]
@@ -360,6 +360,12 @@ impl_runtime_apis! {
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
opaque::SessionKeys::generate(seed)
}
fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
opaque::SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
impl fg_primitives::GrandpaApi<Block> for Runtime {
+6
View File
@@ -798,6 +798,12 @@ impl_runtime_apis! {
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(seed)
}
fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
}
+28 -22
View File
@@ -72,7 +72,8 @@ impl std::error::Error for Error {
/// Every pair that is being generated by a `seed`, will be placed in memory.
pub struct Store {
path: Option<PathBuf>,
additional: HashMap<(KeyTypeId, Vec<u8>), Vec<u8>>,
/// Map over `(KeyTypeId, Raw public key)` -> `Key phrase/seed`
additional: HashMap<(KeyTypeId, Vec<u8>), String>,
password: Option<Protected<String>>,
}
@@ -97,25 +98,22 @@ impl Store {
}))
}
/// Get the public/private key pair for the given public key and key type.
fn get_additional_pair<Pair: PairT>(
/// Get the key phrase for the given public key and key type from the in-memory store.
fn get_additional_pair(
&self,
public: &Pair::Public,
public: &[u8],
key_type: KeyTypeId,
) -> Result<Option<Pair>> {
let key = (key_type, public.to_raw_vec());
self.additional
.get(&key)
.map(|bytes| Pair::from_seed_slice(bytes).map_err(|_| Error::InvalidSeed))
.transpose()
) -> Option<&String> {
let key = (key_type, public.to_vec());
self.additional.get(&key)
}
/// Insert the given public/private key pair with the given key type.
///
/// Does not place it into the file system store.
fn insert_ephemeral_pair<Pair: PairT>(&mut self, pair: &Pair, key_type: KeyTypeId) {
fn insert_ephemeral_pair<Pair: PairT>(&mut self, pair: &Pair, seed: &str, key_type: KeyTypeId) {
let key = (key_type, pair.public().to_raw_vec());
self.additional.insert(key, pair.to_raw_vec());
self.additional.insert(key, seed.into());
}
/// Insert a new key with anonymous crypto.
@@ -179,7 +177,7 @@ impl Store {
key_type: KeyTypeId,
) -> Result<Pair> {
let pair = Pair::from_string(seed, None).map_err(|_| Error::InvalidSeed)?;
self.insert_ephemeral_pair(&pair, key_type);
self.insert_ephemeral_pair(&pair, seed, key_type);
Ok(pair)
}
@@ -190,20 +188,24 @@ impl Store {
self.insert_ephemeral_from_seed_by_type::<Pair::Generic>(seed, Pair::ID).map(Into::into)
}
/// Get the key phrase for a given public key and key type.
fn key_phrase_by_type(&self, public: &[u8], key_type: KeyTypeId) -> Result<String> {
if let Some(phrase) = self.get_additional_pair(public, key_type) {
return Ok(phrase.clone())
}
let path = self.key_file_path(public, key_type).ok_or_else(|| Error::Unavailable)?;
let file = File::open(path)?;
serde_json::from_reader(&file).map_err(Into::into)
}
/// Get a key pair for the given public key and key type.
pub fn key_pair_by_type<Pair: PairT>(&self,
public: &Pair::Public,
key_type: KeyTypeId,
) -> Result<Pair> {
if let Some(pair) = self.get_additional_pair(public, key_type)? {
return Ok(pair)
}
let path = self.key_file_path(public.as_slice(), key_type)
.ok_or_else(|| Error::Unavailable)?;
let file = File::open(path)?;
let phrase: String = serde_json::from_reader(&file)?;
let phrase = self.key_phrase_by_type(public.as_slice(), key_type)?;
let pair = Pair::from_string(
&phrase,
self.password.as_ref().map(|p| &***p),
@@ -328,6 +330,10 @@ impl BareCryptoStore for Store {
fn password(&self) -> Option<&str> {
self.password.as_ref().map(|x| x.as_str())
}
fn has_keys(&self, public_keys: &[(Vec<u8>, KeyTypeId)]) -> bool {
public_keys.iter().all(|(p, t)| self.key_phrase_by_type(&p, *t).is_ok())
}
}
#[cfg(test)]
@@ -54,6 +54,9 @@ pub enum Error {
/// Some random issue with the key store. Shouldn't happen.
#[display(fmt="The key store is unavailable")]
KeyStoreUnavailable,
/// Invalid session keys encoding.
#[display(fmt="Session keys are not encoded correctly")]
InvalidSessionKeys,
}
impl std::error::Error for Error {
+16 -1
View File
@@ -39,7 +39,8 @@ pub trait AuthorApi<Hash, BlockHash> {
/// Insert a key into the keystore.
#[rpc(name = "author_insertKey")]
fn insert_key(&self,
fn insert_key(
&self,
key_type: String,
suri: String,
public: Bytes,
@@ -49,6 +50,20 @@ pub trait AuthorApi<Hash, BlockHash> {
#[rpc(name = "author_rotateKeys")]
fn rotate_keys(&self) -> Result<Bytes>;
/// Checks if the keystore has private keys for the given session public keys.
///
/// `session_keys` is the SCALE encoded session keys object from the runtime.
///
/// Returns `true` iff all private keys could be found.
#[rpc(name = "author_hasSessionKeys")]
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool>;
/// Checks if the keystore has private keys for the given public key and key type.
///
/// Returns `true` if a private key could be found.
#[rpc(name = "author_hasKey")]
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool>;
/// Returns all pending extrinsics, potentially grouped by sender.
#[rpc(name = "author_pendingExtrinsics")]
fn pending_extrinsics(&self) -> Result<Vec<Bytes>>;
+16
View File
@@ -112,6 +112,22 @@ where
).map(Into::into).map_err(|e| Error::Client(Box::new(e)))
}
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool> {
let best_block_hash = self.client.chain_info().best_hash;
let keys = self.client.runtime_api().decode_session_keys(
&generic::BlockId::Hash(best_block_hash),
session_keys.to_vec(),
).map_err(|e| Error::Client(Box::new(e)))?
.ok_or_else(|| Error::InvalidSessionKeys)?;
Ok(self.keystore.read().has_keys(&keys))
}
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool> {
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
Ok(self.keystore.read().has_keys(&[(public_key.to_vec(), key_type)]))
}
fn submit_extrinsic(&self, ext: Bytes) -> FutureResult<TxHash<P>> {
let xt = match Decode::decode(&mut &ext[..]) {
Ok(xt) => xt,
+59 -3
View File
@@ -16,12 +16,12 @@
use super::*;
use std::sync::Arc;
use std::{mem, sync::Arc};
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,
H256, blake2_256, hexdisplay::HexDisplay, testing::{ED25519, SR25519, KeyStore},
traits::BareCryptoStorePtr, ed25519, crypto::{Pair, Public},
};
use rpc::futures::Stream as _;
use substrate_test_runtime_client::{
@@ -237,3 +237,59 @@ fn should_rotate_keys() {
assert_eq!(session_keys.ed25519, ed25519_key_pair.public().into());
assert_eq!(session_keys.sr25519, sr25519_key_pair.public().into());
}
#[test]
fn test_has_session_keys() {
let setup = TestSetup::default();
let p = setup.author();
let non_existent_public_keys = TestSetup::default()
.author()
.rotate_keys()
.expect("Rotates the keys");
let public_keys = p.rotate_keys().expect("Rotates the keys");
let test_vectors = vec![
(public_keys, Ok(true)),
(vec![1, 2, 3].into(), Err(Error::InvalidSessionKeys)),
(non_existent_public_keys, Ok(false)),
];
for (keys, result) in test_vectors {
assert_eq!(
result.map_err(|e| mem::discriminant(&e)),
p.has_session_keys(keys).map_err(|e| mem::discriminant(&e)),
);
}
}
#[test]
fn test_has_key() {
let setup = TestSetup::default();
let p = setup.author();
let suri = "//Alice";
let alice_key_pair = ed25519::Pair::from_string(suri, None).expect("Generates keypair");
p.insert_key(
String::from_utf8(ED25519.0.to_vec()).expect("Keytype is a valid string"),
suri.to_string(),
alice_key_pair.public().0.to_vec().into(),
).expect("Insert key");
let bob_key_pair = ed25519::Pair::from_string("//Bob", None).expect("Generates keypair");
let test_vectors = vec![
(alice_key_pair.public().to_raw_vec().into(), ED25519, Ok(true)),
(alice_key_pair.public().to_raw_vec().into(), SR25519, Ok(false)),
(bob_key_pair.public().to_raw_vec().into(), ED25519, Ok(false)),
];
for (key, key_type, result) in test_vectors {
assert_eq!(
result.map_err(|e| mem::discriminant(&e)),
p.has_key(
key,
String::from_utf8(key_type.0.to_vec()).expect("Keytype is a valid string"),
).map_err(|e| mem::discriminant(&e)),
);
}
}
@@ -53,4 +53,8 @@ impl RuntimePublic for Public {
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool {
sp_io::crypto::ed25519_verify(&signature, msg.as_ref(), self)
}
fn to_raw_vec(&self) -> Vec<u8> {
sp_core::crypto::Public::to_raw_vec(self)
}
}
@@ -301,6 +301,10 @@ macro_rules! app_crypto_public_common {
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool {
<$public as $crate::RuntimePublic>::verify(self.as_ref(), msg, &signature.as_ref())
}
fn to_raw_vec(&self) -> $crate::Vec<u8> {
<$public as $crate::RuntimePublic>::to_raw_vec(&self.0)
}
}
}
}
@@ -53,4 +53,8 @@ impl RuntimePublic for Public {
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool {
sp_io::crypto::sr25519_verify(&signature, msg.as_ref(), self)
}
fn to_raw_vec(&self) -> Vec<u8> {
sp_core::crypto::Public::to_raw_vec(self)
}
}
@@ -106,10 +106,13 @@ pub trait RuntimePublic: Sized {
/// Verify that the given signature matches the given message using this public key.
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool;
/// Returns `Self` as raw vec.
fn to_raw_vec(&self) -> Vec<u8>;
}
/// A runtime interface for an application's public key.
pub trait RuntimeAppPublic: Sized {
pub trait RuntimeAppPublic: Sized {
/// An identifier for this application-specific key type.
const ID: KeyTypeId;
@@ -136,6 +139,9 @@ pub trait RuntimeAppPublic: Sized {
/// Verify that the given signature matches the given message using this public key.
fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool;
/// Returns `Self` as raw vec.
fn to_raw_vec(&self) -> Vec<u8>;
}
/// Something that bound to a fixed `RuntimeAppPublic`.
+1 -3
View File
@@ -19,7 +19,6 @@
// end::description[]
use sp_std::hash::Hash;
#[cfg(feature = "full_crypto")]
use sp_std::vec::Vec;
#[cfg(feature = "std")]
use sp_std::convert::TryInto;
@@ -520,8 +519,7 @@ pub trait Public: AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + Pa
fn from_slice(data: &[u8]) -> Self;
/// Return a `Vec<u8>` filled with raw data.
#[cfg(feature = "std")]
fn to_raw_vec(&self) -> Vec<u8> { self.as_slice().to_owned() }
fn to_raw_vec(&self) -> Vec<u8> { self.as_slice().to_vec() }
/// Return a slice filled with raw data.
fn as_slice(&self) -> &[u8] { self.as_ref() }
+4
View File
@@ -127,6 +127,10 @@ impl crate::traits::BareCryptoStore for KeyStore {
fn password(&self) -> Option<&str> {
None
}
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())
}
}
/// Macro for exporting functions from wasm in with the expected signature for using it with the
+5
View File
@@ -69,6 +69,11 @@ pub trait BareCryptoStore: Send + Sync {
/// Get the password for this store.
fn password(&self) -> Option<&str>;
/// 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;
}
/// A pointer to the key store.
+1 -3
View File
@@ -19,8 +19,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)]
use sp_runtime::traits::NumberFor;
/// Local Storage Prefix used by the Offchain Worker API to
pub const STORAGE_PREFIX: &[u8] = b"storage";
@@ -31,7 +29,7 @@ sp_api::decl_runtime_apis! {
/// Starts the off-chain task for given block number.
#[skip_initialize_block]
#[changed_in(2)]
fn offchain_worker(number: NumberFor<Block>);
fn offchain_worker(number: sp_runtime::traits::NumberFor<Block>);
/// Starts the off-chain task for given block header.
#[skip_initialize_block]
@@ -115,6 +115,10 @@ impl sp_application_crypto::RuntimeAppPublic for UintAuthorityId {
u64::from_le_bytes(msg_signature) == *signature
}
fn to_raw_vec(&self) -> Vec<u8> {
AsRef::<[u8]>::as_ref(self).to_vec()
}
}
impl OpaqueKeys for UintAuthorityId {
@@ -1106,12 +1106,14 @@ macro_rules! count {
#[macro_export]
macro_rules! impl_opaque_keys {
(
$( #[ $attr:meta ] )*
pub struct $name:ident {
$(
pub $field:ident: $type:ty,
)*
}
) => {
$( #[ $attr ] )*
#[derive(
Default, Clone, PartialEq, Eq,
$crate::codec::Encode,
@@ -1143,6 +1145,37 @@ macro_rules! impl_opaque_keys {
};
$crate::codec::Encode::encode(&keys)
}
/// Converts `Self` into a `Vec` of `(raw public key, KeyTypeId)`.
pub fn into_raw_public_keys(
self,
) -> $crate::sp_std::vec::Vec<($crate::sp_std::vec::Vec<u8>, $crate::KeyTypeId)> {
let mut keys = Vec::new();
$(
keys.push((
$crate::RuntimeAppPublic::to_raw_vec(&self.$field),
<
<
$type as $crate::BoundToRuntimeAppPublic
>::Public as $crate::RuntimeAppPublic
>::ID,
));
)*
keys
}
/// Decode `Self` from the given `encoded` slice and convert `Self` into the raw public
/// keys (see [`Self::into_raw_public_keys`]).
///
/// Returns `None` when the decoding failed, otherwise `Some(_)`.
pub fn decode_into_raw_public_keys(
encoded: &[u8],
) -> Option<$crate::sp_std::vec::Vec<($crate::sp_std::vec::Vec<u8>, $crate::KeyTypeId)>> {
<Self as $crate::codec::Decode>::decode(&mut &encoded[..])
.ok()
.map(|s| s.into_raw_public_keys())
}
}
impl $crate::traits::OpaqueKeys for $name {
+2 -1
View File
@@ -7,8 +7,9 @@ edition = "2018"
[dependencies]
sp-api = { version = "2.0.0", default-features = false, path = "../api" }
sp-std = { version = "2.0.0", default-features = false, path = "../std" }
sp-core = { version = "2.0.0", default-features = false, path = "../core" }
sp-runtime = { version = "2.0.0", optional = true, path = "../runtime" }
[features]
default = [ "std" ]
std = [ "sp-api/std", "sp-std/std", "sp-runtime" ]
std = [ "sp-api/std", "sp-std/std", "sp-runtime", "sp-core/std" ]
+7
View File
@@ -25,6 +25,8 @@ use sp_runtime::{generic::BlockId, traits::Block as BlockT};
#[cfg(feature = "std")]
use sp_api::ProvideRuntimeApi;
use sp_core::crypto::KeyTypeId;
sp_api::decl_runtime_apis! {
/// Session keys runtime api.
pub trait SessionKeys {
@@ -36,6 +38,11 @@ sp_api::decl_runtime_apis! {
///
/// Returns the concatenated SCALE encoded public keys.
fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8>;
/// Decode the given public session keys.
///
/// Returns the list of public raw public keys + key type.
fn decode_session_keys(encoded: Vec<u8>) -> Option<Vec<(Vec<u8>, KeyTypeId)>>;
}
}
+12
View File
@@ -631,6 +631,12 @@ cfg_if! {
fn generate_session_keys(_: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(None)
}
fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
@@ -847,6 +853,12 @@ cfg_if! {
fn generate_session_keys(_: Option<Vec<u8>>) -> Vec<u8> {
SessionKeys::generate(None)
}
fn decode_session_keys(
encoded: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {