Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions bin/node-template/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
50 changes: 28 additions & 22 deletions client/keystore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
}

Expand All @@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs for additional say it should be raw public key, but here it seems we just put seed in.

Also we can just put bulshit, cause we don't check that the pair is derived from that seed, do we?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say: (KeyTypeId, Raw public key) -> Key phrase/seed
Key -> Value

(the key is build above in exactly this way.

}

/// Insert a new key with anonymous crypto.
Expand Down Expand Up @@ -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)
}

Expand All @@ -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),
Expand Down Expand Up @@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions client/rpc-api/src/author/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 16 additions & 1 deletion client/rpc-api/src/author/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>>;
Expand Down
16 changes: 16 additions & 0 deletions client/rpc/src/author/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
62 changes: 59 additions & 3 deletions client/rpc/src/author/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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)),
);
}
}
4 changes: 4 additions & 0 deletions primitives/application-crypto/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
4 changes: 4 additions & 0 deletions primitives/application-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions primitives/application-crypto/src/sr25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
8 changes: 7 additions & 1 deletion primitives/application-crypto/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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`.
Expand Down
4 changes: 1 addition & 3 deletions primitives/core/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() }
Expand Down
4 changes: 4 additions & 0 deletions primitives/core/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions primitives/core/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions primitives/offchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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]
Expand Down
4 changes: 4 additions & 0 deletions primitives/runtime/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading