[offchain] Support for sign & verify for crypto keys (#3023)

* Implement sign & verify.

* Use phrases and password.

* Sign & verify with authority keys.

* Fix tests.

* WiP

* WiP

* Allow the caller to decide on 'CryptoKind'.

* Remove TODO.

* Make seed private back.

* Fix non-std build and bump version.

* Use Into<u32> instead of asses.

* Add missing typedef.
This commit is contained in:
Tomasz Drwięga
2019-07-09 17:09:14 +02:00
committed by Gavin Wood
parent ed630e5eda
commit e729dbabbe
22 changed files with 647 additions and 178 deletions
+1
View File
@@ -4544,6 +4544,7 @@ dependencies = [
"tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"twox-hash 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmi 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"zeroize 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
+1 -1
View File
@@ -376,7 +376,7 @@ where
let spec = load_spec(&cli.shared_params, spec_factory)?;
let mut config = service::Configuration::default_with_spec(spec.clone());
if cli.interactive_password {
config.password = input_keystore_password()?
config.password = input_keystore_password()?.into()
}
config.impl_name = impl_name;
+17 -6
View File
@@ -79,7 +79,7 @@ impl client::backend::OffchainStorage for LocalStorage {
&mut self,
prefix: &[u8],
item_key: &[u8],
old_value: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
let key: Vec<u8> = prefix.iter().chain(item_key).cloned().collect();
@@ -91,11 +91,10 @@ impl client::backend::OffchainStorage for LocalStorage {
let is_set;
{
let _key_guard = key_lock.lock();
is_set = self.db.get(columns::OFFCHAIN, &key)
let val = self.db.get(columns::OFFCHAIN, &key)
.ok()
.and_then(|x| x)
.map(|v| &*v == old_value)
.unwrap_or(true);
.and_then(|x| x);
is_set = val.as_ref().map(|x| &**x) == old_value;
if is_set {
self.set(prefix, item_key, new_value)
@@ -130,8 +129,20 @@ mod tests {
storage.set(prefix, key, value);
assert_eq!(storage.get(prefix, key), Some(value.to_vec()));
assert_eq!(storage.compare_and_set(prefix, key, value, b"asd"), true);
assert_eq!(storage.compare_and_set(prefix, key, Some(value), b"asd"), true);
assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
}
#[test]
fn should_compare_and_set_on_empty_field() {
let mut storage = LocalStorage::new_test();
let prefix = b"prefix";
let key = b"key";
assert_eq!(storage.compare_and_set(prefix, key, None, b"asd"), true);
assert_eq!(storage.get(prefix, key), Some(b"asd".to_vec()));
assert!(storage.locks.lock().is_empty(), "Locks map should be empty!");
}
}
+1 -1
View File
@@ -213,7 +213,7 @@ pub trait OffchainStorage: Clone + Send + Sync {
&mut self,
prefix: &[u8],
key: &[u8],
old_value: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool;
}
+19 -11
View File
@@ -794,19 +794,23 @@ impl backend::OffchainStorage for OffchainStorage {
&mut self,
prefix: &[u8],
key: &[u8],
old_value: &[u8],
old_value: Option<&[u8]>,
new_value: &[u8],
) -> bool {
use std::collections::hash_map::Entry;
let key = prefix.iter().chain(key).cloned().collect();
let mut set = false;
self.storage.entry(key).and_modify(|val| {
if val.as_slice() == old_value {
*val = new_value.to_vec();
set = true;
}
});
set
match self.storage.entry(key) {
Entry::Vacant(entry) => if old_value.is_none() {
entry.insert(new_value.to_vec());
true
} else { false },
Entry::Occupied(ref mut entry) if Some(entry.get().as_slice()) == old_value => {
entry.insert(new_value.to_vec());
true
},
_ => false,
}
}
}
@@ -845,9 +849,13 @@ mod tests {
assert_eq!(storage.get(b"A", b"B"), Some(b"C".to_vec()));
assert_eq!(storage.get(b"B", b"A"), None);
storage.compare_and_set(b"A", b"B", b"X", b"D");
storage.compare_and_set(b"A", b"B", Some(b"X"), b"D");
assert_eq!(storage.get(b"A", b"B"), Some(b"C".to_vec()));
storage.compare_and_set(b"A", b"B", b"C", b"D");
storage.compare_and_set(b"A", b"B", Some(b"C"), b"D");
assert_eq!(storage.get(b"A", b"B"), Some(b"D".to_vec()));
assert!(!storage.compare_and_set(b"B", b"A", Some(b""), b"Y"));
assert!(storage.compare_and_set(b"B", b"A", None, b"X"));
assert_eq!(storage.get(b"B", b"A"), Some(b"X".to_vec()));
}
}
+38 -11
View File
@@ -729,18 +729,26 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
.ok_or_else(|| "Calling unavailable API ext_new_crypto_key: wasm")?;
match res {
Ok(key_id) => Ok(key_id.0 as u32),
Ok(key_id) => Ok(key_id.into()),
Err(()) => Ok(u32::max_value()),
}
},
ext_encrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8 => {
ext_encrypt(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
msg_len: *mut u32
) -> *mut u8 => {
let key = u32_to_key(key)
.map_err(|_| "Key OOB while ext_encrypt: wasm")?;
let kind = offchain::CryptoKind::try_from(kind)
.map_err(|_| "crypto kind OOB while ext_encrypt: wasm")?;
let message = this.memory.get(data, data_len as usize)
.map_err(|_| "OOB while ext_encrypt: wasm")?;
let res = this.ext.offchain()
.map(|api| api.encrypt(key, &*message))
.map(|api| api.encrypt(key, kind, &*message))
.ok_or_else(|| "Calling unavailable API ext_encrypt: wasm")?;
let (offset,len) = match res {
@@ -759,14 +767,22 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(offset)
},
ext_decrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8 => {
ext_decrypt(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
msg_len: *mut u32
) -> *mut u8 => {
let key = u32_to_key(key)
.map_err(|_| "Key OOB while ext_decrypt: wasm")?;
let kind = offchain::CryptoKind::try_from(kind)
.map_err(|_| "crypto kind OOB while ext_decrypt: wasm")?;
let message = this.memory.get(data, data_len as usize)
.map_err(|_| "OOB while ext_decrypt: wasm")?;
let res = this.ext.offchain()
.map(|api| api.decrypt(key, &*message))
.map(|api| api.decrypt(key, kind, &*message))
.ok_or_else(|| "Calling unavailable API ext_decrypt: wasm")?;
let (offset,len) = match res {
@@ -785,14 +801,22 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(offset)
},
ext_sign(key: u32, data: *const u8, data_len: u32, sig_data_len: *mut u32) -> *mut u8 => {
ext_sign(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
sig_data_len: *mut u32
) -> *mut u8 => {
let key = u32_to_key(key)
.map_err(|_| "Key OOB while ext_sign: wasm")?;
let kind = offchain::CryptoKind::try_from(kind)
.map_err(|_| "crypto kind OOB while ext_sign: wasm")?;
let message = this.memory.get(data, data_len as usize)
.map_err(|_| "OOB while ext_sign: wasm")?;
let res = this.ext.offchain()
.map(|api| api.sign(key, &*message))
.map(|api| api.sign(key, kind, &*message))
.ok_or_else(|| "Calling unavailable API ext_sign: wasm")?;
let (offset,len) = match res {
@@ -813,6 +837,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
},
ext_verify(
key: u32,
kind: u32,
msg: *const u8,
msg_len: u32,
signature: *const u8,
@@ -820,13 +845,15 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
) -> u32 => {
let key = u32_to_key(key)
.map_err(|_| "Key OOB while ext_verify: wasm")?;
let kind = offchain::CryptoKind::try_from(kind)
.map_err(|_| "crypto kind OOB while ext_verify: wasm")?;
let message = this.memory.get(msg, msg_len as usize)
.map_err(|_| "OOB while ext_verify: wasm")?;
let signature = this.memory.get(signature, signature_len as usize)
.map_err(|_| "OOB while ext_verify: wasm")?;
let res = this.ext.offchain()
.map(|api| api.verify(key, &*message, &*signature))
.map(|api| api.verify(key, kind, &*message, &*signature))
.ok_or_else(|| "Calling unavailable API ext_verify: wasm")?;
match res {
@@ -945,7 +972,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
.ok_or_else(|| "Calling unavailable API ext_http_request_start: wasm")?;
if let Ok(id) = id {
Ok(id.0 as u32)
Ok(id.into())
} else {
Ok(u32::max_value())
}
@@ -996,7 +1023,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
Ok(match res {
Ok(()) => 0,
Err(e) => e as u8 as u32,
Err(e) => e.into(),
})
},
ext_http_response_wait(
@@ -1074,7 +1101,7 @@ impl_function_executor!(this: FunctionExecutor<'e, E>,
read as u32
},
Err(err) => {
u32::max_value() - err as u8 as u32 + 1
u32::max_value() - u32::from(err) + 1
}
})
},
+8 -8
View File
@@ -23,7 +23,7 @@ use std::path::PathBuf;
use std::fs::{self, File};
use std::io::{self, Write};
use substrate_primitives::crypto::{KeyTypeId, Pair, Public, TypedKey};
use substrate_primitives::crypto::{KeyTypeId, Pair, Public};
/// Keystore error.
#[derive(Debug, derive_more::Display, derive_more::From)]
@@ -69,7 +69,7 @@ impl Store {
Ok(Store { path, additional: HashMap::new() })
}
fn get_pair<TPair: Pair + TypedKey>(&self, public: &TPair::Public) -> Result<Option<TPair>> {
fn get_pair<TPair: Pair>(&self, public: &TPair::Public) -> Result<Option<TPair>> {
let key = (TPair::KEY_TYPE, public.to_raw_vec());
if let Some(bytes) = self.additional.get(&key) {
let pair = TPair::from_seed_slice(bytes)
@@ -79,13 +79,13 @@ impl Store {
Ok(None)
}
fn insert_pair<TPair: Pair + TypedKey>(&mut self, pair: &TPair) {
fn insert_pair<TPair: Pair>(&mut self, pair: &TPair) {
let key = (TPair::KEY_TYPE, pair.public().to_raw_vec());
self.additional.insert(key, pair.to_raw_vec());
}
/// Generate a new key, placing it into the store.
pub fn generate<TPair: Pair + TypedKey>(&self, password: &str) -> Result<TPair> {
pub fn generate<TPair: Pair>(&self, password: &str) -> Result<TPair> {
let (pair, phrase, _) = TPair::generate_with_phrase(Some(password));
let mut file = File::create(self.key_file_path::<TPair>(&pair.public()))?;
::serde_json::to_writer(&file, &phrase)?;
@@ -94,7 +94,7 @@ impl Store {
}
/// Create a new key from seed. Do not place it into the store.
pub fn generate_from_seed<TPair: Pair + TypedKey>(&mut self, seed: &str) -> Result<TPair> {
pub fn generate_from_seed<TPair: Pair>(&mut self, seed: &str) -> Result<TPair> {
let pair = TPair::from_string(seed, None)
.ok().ok_or(Error::InvalidSeed)?;
self.insert_pair(&pair);
@@ -102,7 +102,7 @@ impl Store {
}
/// Load a key file with given public key.
pub fn load<TPair: Pair + TypedKey>(&self, public: &TPair::Public, password: &str) -> Result<TPair> {
pub fn load<TPair: Pair>(&self, public: &TPair::Public, password: &str) -> Result<TPair> {
if let Some(pair) = self.get_pair(public)? {
return Ok(pair)
}
@@ -120,7 +120,7 @@ impl Store {
}
/// Get public keys of all stored keys.
pub fn contents<TPublic: Public + TypedKey>(&self) -> Result<Vec<TPublic>> {
pub fn contents<TPublic: Public>(&self) -> Result<Vec<TPublic>> {
let mut public_keys: Vec<TPublic> = self.additional.keys()
.filter_map(|(ty, public)| {
if *ty != TPublic::KEY_TYPE {
@@ -151,7 +151,7 @@ impl Store {
Ok(public_keys)
}
fn key_file_path<TPair: Pair + TypedKey>(&self, public: &TPair::Public) -> PathBuf {
fn key_file_path<TPair: Pair>(&self, public: &TPair::Public) -> PathBuf {
let mut buf = self.path.clone();
let bytes: [u8; 4] = TPair::KEY_TYPE.to_le_bytes();
let key_type = hex::encode(bytes);
+181 -20
View File
@@ -16,15 +16,18 @@
use std::sync::Arc;
use client::backend::OffchainStorage;
use crate::AuthorityKeyProvider;
use futures::{Stream, Future, sync::mpsc};
use log::{info, debug, warn, error};
use parity_codec::Decode;
use parity_codec::{Encode, Decode};
use primitives::offchain::{
Timestamp, HttpRequestId, HttpRequestStatus, HttpError,
Externalities as OffchainExt,
CryptoKind, CryptoKeyId,
StorageKind,
};
use primitives::crypto::{Pair, Protected};
use primitives::{ed25519, sr25519};
use runtime_primitives::{
generic::BlockId,
traits::{self, Extrinsic},
@@ -36,12 +39,26 @@ enum ExtMessage {
SubmitExtrinsic(Vec<u8>),
}
/// A persisted key seed.
#[derive(Encode, Decode)]
struct CryptoKey {
kind: CryptoKind,
phrase: String,
}
enum Key {
Sr25519(sr25519::Pair),
Ed25519(ed25519::Pair),
}
/// Asynchronous offchain API.
///
/// NOTE this is done to prevent recursive calls into the runtime (which are not supported currently).
pub(crate) struct Api<S> {
pub(crate) struct Api<Storage, KeyProvider> {
sender: mpsc::UnboundedSender<ExtMessage>,
db: S,
db: Storage,
keys_password: Protected<String>,
key_provider: KeyProvider,
}
fn unavailable_yet<R: Default>(name: &str) -> R {
@@ -52,8 +69,60 @@ fn unavailable_yet<R: Default>(name: &str) -> R {
const LOCAL_DB: &str = "LOCAL (fork-aware) DB";
const STORAGE_PREFIX: &[u8] = b"storage";
const KEYS_PREFIX: &[u8] = b"keys";
impl<S: OffchainStorage> OffchainExt for Api<S> {
const NEXT_ID: &[u8] = b"crypto_key_id";
impl<Storage, KeyProvider> Api<Storage, KeyProvider> where
Storage: OffchainStorage,
KeyProvider: AuthorityKeyProvider,
{
fn keypair<P: Pair>(&self, phrase: &str) -> Result<P, ()> {
P::from_phrase(phrase, Some(self.keys_password.as_ref()))
.map_err(|e| {
warn!("Error recovering Offchain Worker key. Password invalid? {:?}", e);
()
})
.map(|x| x.0)
}
fn read_key(&self, id: Option<CryptoKeyId>, kind: CryptoKind) -> Result<Key, ()> {
if let Some(id) = id {
let key = self.db.get(KEYS_PREFIX, &id.0.encode())
.and_then(|key| CryptoKey::decode(&mut &*key))
.ok_or(())?;
if key.kind != kind {
warn!(
"Invalid crypto kind (got: {:?}, expected: {:?}), when requesting key {:?}",
key.kind,
kind,
id
);
return Err(())
}
Ok(match key.kind {
CryptoKind::Sr25519 => Key::Sr25519(self.keypair(&key.phrase)?),
CryptoKind::Ed25519 => Key::Ed25519(self.keypair(&key.phrase)?),
})
} else {
let key = match kind {
CryptoKind::Sr25519 => self.key_provider.authority_key().map(Key::Sr25519),
CryptoKind::Ed25519 => self.key_provider.authority_key().map(Key::Ed25519),
};
key.ok_or_else(|| {
warn!("AuthorityKey is not configured, yet offchain worker tried to access it.");
()
})
}
}
}
impl<Storage, KeyProvider> OffchainExt for Api<Storage, KeyProvider> where
Storage: OffchainStorage,
KeyProvider: AuthorityKeyProvider,
{
fn submit_transaction(&mut self, ext: Vec<u8>) -> Result<(), ()> {
self.sender
.unbounded_send(ExtMessage::SubmitExtrinsic(ext))
@@ -61,29 +130,61 @@ impl<S: OffchainStorage> OffchainExt for Api<S> {
.map_err(|_| ())
}
fn new_crypto_key(&mut self, _crypto: CryptoKind) -> Result<CryptoKeyId, ()> {
unavailable_yet::<()>("new_crypto_key");
Err(())
fn new_crypto_key(&mut self, kind: CryptoKind) -> Result<CryptoKeyId, ()> {
let phrase = match kind {
CryptoKind::Ed25519 => {
ed25519::Pair::generate_with_phrase(Some(self.keys_password.as_ref())).1
},
CryptoKind::Sr25519 => {
sr25519::Pair::generate_with_phrase(Some(self.keys_password.as_ref())).1
},
};
let (id, id_encoded) = loop {
let encoded = self.db.get(KEYS_PREFIX, NEXT_ID);
let encoded_slice = encoded.as_ref().map(|x| x.as_slice());
let new_id = encoded_slice.and_then(|mut x| u16::decode(&mut x)).unwrap_or_default()
.checked_add(1)
.ok_or(())?;
let new_id_encoded = new_id.encode();
if self.db.compare_and_set(KEYS_PREFIX, NEXT_ID, encoded_slice, &new_id_encoded) {
break (new_id, new_id_encoded);
}
};
self.db.set(KEYS_PREFIX, &id_encoded, &CryptoKey { phrase, kind } .encode());
Ok(CryptoKeyId(id))
}
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _kind: CryptoKind, _data: &[u8]) -> Result<Vec<u8>, ()> {
unavailable_yet::<()>("encrypt");
Err(())
}
fn decrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn decrypt(&mut self, _key: Option<CryptoKeyId>, _kind: CryptoKind, _data: &[u8]) -> Result<Vec<u8>, ()> {
unavailable_yet::<()>("decrypt");
Err(())
}
fn sign(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
unavailable_yet::<()>("sign");
Err(())
fn sign(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
let key = self.read_key(key, kind)?;
Ok(match key {
Key::Sr25519(pair) => pair.sign(data).0.to_vec(),
Key::Ed25519(pair) => pair.sign(data).0.to_vec(),
})
}
fn verify(&mut self, _key: Option<CryptoKeyId>, _msg: &[u8], _signature: &[u8]) -> Result<bool, ()> {
unavailable_yet::<()>("verify");
Err(())
fn verify(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
let key = self.read_key(key, kind)?;
Ok(match key {
Key::Sr25519(pair) => sr25519::Pair::verify_weak(signature, msg, pair.public()),
Key::Ed25519(pair) => ed25519::Pair::verify_weak(signature, msg, pair.public()),
})
}
fn timestamp(&mut self) -> Timestamp {
@@ -114,7 +215,7 @@ impl<S: OffchainStorage> OffchainExt for Api<S> {
) -> bool {
match kind {
StorageKind::PERSISTENT => {
self.db.compare_and_set(STORAGE_PREFIX, key, old_value, new_value)
self.db.compare_and_set(STORAGE_PREFIX, key, Some(old_value), new_value)
},
StorageKind::LOCAL => unavailable_yet(LOCAL_DB),
}
@@ -195,16 +296,20 @@ pub(crate) struct AsyncApi<A: ChainApi> {
impl<A: ChainApi> AsyncApi<A> {
/// Creates new Offchain extensions API implementation an the asynchronous processing part.
pub fn new<S: OffchainStorage>(
pub fn new<S: OffchainStorage, P: AuthorityKeyProvider>(
transaction_pool: Arc<Pool<A>>,
db: S,
keys_password: Protected<String>,
key_provider: P,
at: BlockId<A::Block>,
) -> (Api<S>, AsyncApi<A>) {
) -> (Api<S, P>, AsyncApi<A>) {
let (sender, rx) = mpsc::unbounded();
let api = Api {
sender,
db,
keys_password,
key_provider,
};
let async_api = AsyncApi {
@@ -251,8 +356,9 @@ impl<A: ChainApi> AsyncApi<A> {
mod tests {
use super::*;
use client_db::offchain::LocalStorage;
use crate::tests::TestProvider;
fn offchain_api() -> (Api<LocalStorage>, AsyncApi<impl ChainApi>) {
fn offchain_api() -> (Api<LocalStorage, TestProvider>, AsyncApi<impl ChainApi>) {
let _ = env_logger::try_init();
let db = LocalStorage::new_test();
let client = Arc::new(test_client::new());
@@ -260,7 +366,7 @@ mod tests {
Pool::new(Default::default(), transaction_pool::ChainApi::new(client.clone()))
);
AsyncApi::new(pool, db, BlockId::Number(0))
AsyncApi::new(pool, db, "pass".to_owned().into(), TestProvider::default(), BlockId::Number(0))
}
#[test]
@@ -294,4 +400,59 @@ mod tests {
assert_eq!(api.local_storage_compare_and_set(kind, key, b"value", b"xxx"), true);
assert_eq!(api.local_storage_get(kind, key), Some(b"xxx".to_vec()));
}
#[test]
fn should_create_a_new_key_and_sign_and_verify_stuff() {
let test = |kind: CryptoKind| {
// given
let mut api = offchain_api().0;
let msg = b"Hello world!";
// when
let key_id = api.new_crypto_key(kind).unwrap();
let signature = api.sign(Some(key_id), kind, msg).unwrap();
// then
let res = api.verify(Some(key_id), kind, msg, &signature).unwrap();
assert_eq!(res, true);
let res = api.verify(Some(key_id), kind, msg, &[]).unwrap();
assert_eq!(res, false);
let res = api.verify(Some(key_id), kind, b"Different msg", &signature).unwrap();
assert_eq!(res, false);
assert_eq!(
api.verify(Some(key_id), CryptoKind::Sr25519, msg, &signature).is_err(),
kind != CryptoKind::Sr25519
);
};
test(CryptoKind::Ed25519);
test(CryptoKind::Sr25519);
}
#[test]
fn should_sign_and_verify_with_authority_key() {
// given
let mut api = offchain_api().0;
api.key_provider.ed_key = Some(ed25519::Pair::generate().0);
let msg = b"Hello world!";
let kind = CryptoKind::Ed25519;
// when
let signature = api.sign(None, kind, msg).unwrap();
// then
let res = api.verify(None, kind, msg, &signature).unwrap();
assert_eq!(res, true);
let res = api.verify(None, kind, msg, &[]).unwrap();
assert_eq!(res, false);
let res = api.verify(None, kind, b"Different msg", &signature).unwrap();
assert_eq!(res, false);
assert!(
api.verify(None, CryptoKind::Sr25519, msg, &signature).is_err(),
"Invalid kind should trigger a missing key error."
);
}
}
+68 -13
View File
@@ -41,7 +41,10 @@ use std::{
use client::runtime_api::ApiExt;
use log::{debug, warn};
use primitives::ExecutionContext;
use primitives::{
ExecutionContext,
crypto,
};
use runtime_primitives::{
generic::BlockId,
traits::{self, ProvideRuntimeApi},
@@ -55,38 +58,71 @@ pub mod testing;
pub use offchain_primitives::OffchainWorkerApi;
/// Provides currently configured authority key.
pub trait AuthorityKeyProvider: Clone + 'static {
/// Returns currently configured authority key.
fn authority_key<TPair: crypto::Pair>(&self) -> Option<TPair>;
}
/// An offchain workers manager.
pub struct OffchainWorkers<C, S, Block: traits::Block> {
client: Arc<C>,
db: S,
pub struct OffchainWorkers<
Client,
Storage,
KeyProvider,
Block: traits::Block,
> {
client: Arc<Client>,
db: Storage,
authority_key: KeyProvider,
keys_password: crypto::Protected<String>,
_block: PhantomData<Block>,
}
impl<C, S, Block: traits::Block> OffchainWorkers<C, S, Block> {
impl<Client, Storage, KeyProvider, Block: traits::Block> OffchainWorkers<
Client,
Storage,
KeyProvider,
Block,
> {
/// Creates new `OffchainWorkers`.
pub fn new(
client: Arc<C>,
db: S,
client: Arc<Client>,
db: Storage,
authority_key: KeyProvider,
keys_password: crypto::Protected<String>,
) -> Self {
Self {
client,
db,
authority_key,
keys_password,
_block: PhantomData,
}
}
}
impl<C, S, Block: traits::Block> fmt::Debug for OffchainWorkers<C, S, Block> {
impl<Client, Storage, KeyProvider, Block: traits::Block> fmt::Debug for OffchainWorkers<
Client,
Storage,
KeyProvider,
Block,
> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("OffchainWorkers").finish()
}
}
impl<C, S, Block> OffchainWorkers<C, S, Block> where
impl<Client, Storage, KeyProvider, Block> OffchainWorkers<
Client,
Storage,
KeyProvider,
Block,
> where
Block: traits::Block,
S: client::backend::OffchainStorage + 'static,
C: ProvideRuntimeApi,
C::Api: OffchainWorkerApi<Block>,
Client: ProvideRuntimeApi,
Client::Api: OffchainWorkerApi<Block>,
KeyProvider: AuthorityKeyProvider,
Storage: client::backend::OffchainStorage + 'static,
{
/// Start the offchain workers after given block.
#[must_use]
@@ -106,6 +142,8 @@ impl<C, S, Block> OffchainWorkers<C, S, Block> where
let (api, runner) = api::AsyncApi::new(
pool.clone(),
self.db.clone(),
self.keys_password.clone(),
self.authority_key.clone(),
at.clone(),
);
debug!("Running offchain workers at {:?}", at);
@@ -122,6 +160,23 @@ impl<C, S, Block> OffchainWorkers<C, S, Block> where
mod tests {
use super::*;
use futures::Future;
use primitives::{ed25519, sr25519, crypto::{TypedKey, Pair}};
#[derive(Clone, Default)]
pub(crate) struct TestProvider {
pub(crate) sr_key: Option<sr25519::Pair>,
pub(crate) ed_key: Option<ed25519::Pair>,
}
impl AuthorityKeyProvider for TestProvider {
fn authority_key<TPair: crypto::Pair>(&self) -> Option<TPair> {
TPair::from_seed_slice(&match TPair::KEY_TYPE {
sr25519::Pair::KEY_TYPE => self.sr_key.as_ref().map(|key| key.to_raw_vec()),
ed25519::Pair::KEY_TYPE => self.ed_key.as_ref().map(|key| key.to_raw_vec()),
_ => None,
}?).ok()
}
}
#[test]
fn should_call_into_runtime_and_produce_extrinsic() {
@@ -133,7 +188,7 @@ mod tests {
let db = client_db::offchain::LocalStorage::new_test();
// when
let offchain = OffchainWorkers::new(client, db);
let offchain = OffchainWorkers::new(client, db, TestProvider::default(), "".to_owned().into());
runtime.executor().spawn(offchain.on_block_imported(&0u64, &pool));
// then
+26 -5
View File
@@ -143,19 +143,40 @@ impl offchain::Externalities for TestOffchainExt {
unimplemented!("not needed in tests so far")
}
fn encrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn encrypt(
&mut self,
_key: Option<CryptoKeyId>,
_kind: CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unimplemented!("not needed in tests so far")
}
fn decrypt(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn decrypt(
&mut self,
_key: Option<CryptoKeyId>,
_kind: CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unimplemented!("not needed in tests so far")
}
fn sign(&mut self, _key: Option<CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn sign(
&mut self,
_key: Option<CryptoKeyId>,
_kind: CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unimplemented!("not needed in tests so far")
}
fn verify(&mut self, _key: Option<CryptoKeyId>, _msg: &[u8], _signature: &[u8]) -> Result<bool, ()> {
fn verify(
&mut self,
_key: Option<CryptoKeyId>,
_kind: CryptoKind,
_msg: &[u8],
_signature: &[u8],
) -> Result<bool, ()> {
unimplemented!("not needed in tests so far")
}
@@ -190,7 +211,7 @@ impl offchain::Externalities for TestOffchainExt {
match kind {
StorageKind::LOCAL => &mut state.local_storage,
StorageKind::PERSISTENT => &mut state.persistent_storage,
}.compare_and_set(b"", key, old_value, new_value)
}.compare_and_set(b"", key, Some(old_value), new_value)
}
fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option<Vec<u8>> {
+2
View File
@@ -27,6 +27,7 @@ tiny-bip39 = { version = "0.6.1", optional = true }
hex = { version = "0.3", optional = true }
regex = { version = "1.1", optional = true }
num-traits = { version = "0.2", default-features = false }
zeroize = { version = "0.9.2", default-features = false }
[dev-dependencies]
substrate-serializer = { path = "../serializer" }
@@ -72,4 +73,5 @@ std = [
"schnorrkel",
"regex",
"num-traits/std",
"zeroize/std"
]
+51 -8
View File
@@ -28,6 +28,7 @@ use regex::Regex;
use base58::{FromBase58, ToBase58};
#[cfg(feature = "std")]
use std::hash::Hash;
use zeroize::Zeroize;
/// The root phrase for our publicly known keys.
pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
@@ -66,6 +67,43 @@ impl<S, T: UncheckedFrom<S>> UncheckedInto<T> for S {
}
}
/// A store for sensitive data.
///
/// Calls `Zeroize::zeroize` upon `Drop`.
#[derive(Clone)]
pub struct Protected<T: Zeroize>(T);
impl<T: Zeroize> AsRef<T> for Protected<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
#[cfg(feature = "std")]
impl<T: Zeroize> std::fmt::Debug for Protected<T> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "<protected>")
}
}
impl<T: Zeroize> From<T> for Protected<T> {
fn from(t: T) -> Self {
Protected(t)
}
}
impl<T: Zeroize> Zeroize for Protected<T> {
fn zeroize(&mut self) {
self.0.zeroize()
}
}
impl<T: Zeroize> Drop for Protected<T> {
fn drop(&mut self) {
self.zeroize()
}
}
/// An error with the interpretation of a secret.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(feature = "std")]
@@ -289,7 +327,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T {
}
/// Trait suitable for typical cryptographic PKI key public type.
pub trait Public: PartialEq + Eq {
pub trait Public: TypedKey + PartialEq + Eq {
/// A new instance from the given slice that should be 32 bytes long.
///
/// NOTE: No checking goes on to ensure this is a real public key. Only use it if
@@ -308,9 +346,8 @@ pub trait Public: PartialEq + Eq {
///
/// For now it just specifies how to create a key from a phrase and derivation path.
#[cfg(feature = "std")]
pub trait Pair: Sized + 'static
{
/// TThe type which is used to encode a public key.
pub trait Pair: TypedKey + Sized + 'static {
/// The type which is used to encode a public key.
type Public: Public + Hash;
/// The type used to (minimally) encode the data required to securely create
@@ -319,7 +356,7 @@ pub trait Pair: Sized + 'static
/// The type used to represent a signature. Can be created from a key pair and a message
/// and verified with the message and a public key.
type Signature;
type Signature: AsRef<[u8]>;
/// Error returned from the `derive` function.
type DeriveError;
@@ -476,7 +513,7 @@ mod tests {
#[derive(PartialEq, Eq, Hash)]
struct TestPublic;
impl Public for TestPublic {
fn from_slice(bytes: &[u8]) -> Self {
fn from_slice(_bytes: &[u8]) -> Self {
Self
}
fn as_slice(&self) -> &[u8] {
@@ -486,10 +523,13 @@ mod tests {
vec![]
}
}
impl TypedKey for TestPublic {
const KEY_TYPE: u32 = 4242;
}
impl Pair for TestPair {
type Public = TestPublic;
type Seed = [u8; 0];
type Signature = ();
type Signature = [u8; 0];
type DeriveError = ();
fn generate() -> (Self, <Self as Pair>::Seed) { (TestPair::Generated, []) }
@@ -510,7 +550,7 @@ mod tests {
Err(())
}
fn from_seed(_seed: &<TestPair as Pair>::Seed) -> Self { TestPair::Seed(vec![]) }
fn sign(&self, _message: &[u8]) -> Self::Signature { () }
fn sign(&self, _message: &[u8]) -> Self::Signature { [] }
fn verify<P: AsRef<Self::Public>, M: AsRef<[u8]>>(
_sig: &Self::Signature,
_message: M,
@@ -542,6 +582,9 @@ mod tests {
vec![]
}
}
impl TypedKey for TestPair {
const KEY_TYPE: u32 = 4242;
}
#[test]
fn interpret_std_seed_should_work() {
+60 -25
View File
@@ -16,11 +16,13 @@
//! Offchain workers types
use crate::crypto;
use parity_codec::{Encode, Decode};
use rstd::prelude::{Vec, Box};
use rstd::convert::TryFrom;
/// A type of supported crypto.
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
#[repr(C)]
pub enum StorageKind {
@@ -50,15 +52,21 @@ impl TryFrom<u32> for StorageKind {
}
}
impl From<StorageKind> for u32 {
fn from(c: StorageKind) -> Self {
c as u8 as u32
}
}
/// A type of supported crypto.
#[derive(Clone, Copy, PartialEq, Eq)]
#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
#[repr(C)]
pub enum CryptoKind {
/// SR25519 crypto (Schnorrkel)
Sr25519 = 1,
Sr25519 = crypto::key_types::SR25519 as isize,
/// ED25519 crypto (Edwards)
Ed25519 = 2,
Ed25519 = crypto::key_types::ED25519 as isize,
}
impl TryFrom<u32> for CryptoKind {
@@ -66,23 +74,41 @@ impl TryFrom<u32> for CryptoKind {
fn try_from(kind: u32) -> Result<Self, Self::Error> {
match kind {
e if e == u32::from(CryptoKind::Sr25519 as u8) => Ok(CryptoKind::Sr25519),
e if e == u32::from(CryptoKind::Ed25519 as u8) => Ok(CryptoKind::Ed25519),
e if e == CryptoKind::Sr25519 as isize as u32 => Ok(CryptoKind::Sr25519),
e if e == CryptoKind::Ed25519 as isize as u32 => Ok(CryptoKind::Ed25519),
_ => Err(()),
}
}
}
impl From<CryptoKind> for u32 {
fn from(c: CryptoKind) -> Self {
c as isize as u32
}
}
/// Opaque type for created crypto keys.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct CryptoKeyId(pub u16);
impl From<CryptoKeyId> for u32 {
fn from(c: CryptoKeyId) -> Self {
c.0 as u32
}
}
/// Opaque type for offchain http requests.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct HttpRequestId(pub u16);
impl From<HttpRequestId> for u32 {
fn from(c: HttpRequestId) -> Self {
c.0 as u32
}
}
/// An error enum returned by some http methods.
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
@@ -106,6 +132,12 @@ impl TryFrom<u32> for HttpError {
}
}
impl From<HttpError> for u32 {
fn from(c: HttpError) -> Self {
c as u8 as u32
}
}
/// Status of the HTTP request
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
@@ -216,31 +248,34 @@ pub trait Externalities {
/// Encrypt a piece of data using given crypto key.
///
/// If `key` is `None`, it will attempt to use current authority key.
/// If `key` is `None`, it will attempt to use current authority key of `CryptoKind`.
///
/// Returns an error if `key` is not available or does not exist.
fn encrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Returns an error if `key` is not available or does not exist,
/// or the expected `CryptoKind` does not match.
fn encrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Decrypt a piece of data using given crypto key.
///
/// If `key` is `None`, it will attempt to use current authority key.
/// If `key` is `None`, it will attempt to use current authority key of `CryptoKind`.
///
/// Returns an error if data cannot be decrypted or the `key` is not available or does not exist.
fn decrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Returns an error if data cannot be decrypted or the `key` is not available or does not exist,
/// or the expected `CryptoKind` does not match.
fn decrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Sign a piece of data using given crypto key.
///
/// If `key` is `None`, it will attempt to use current authority key.
/// If `key` is `None`, it will attempt to use current authority key of `CryptoKind`.
///
/// Returns an error if `key` is not available or does not exist.
fn sign(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Returns an error if `key` is not available or does not exist,
/// or the expected `CryptoKind` does not match.
fn sign(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Verifies that `signature` for `msg` matches given `key`.
///
/// Returns an `Ok` with `true` in case it does, `false` in case it doesn't.
/// Returns an error in case the key is not available or does not exist or the parameters
/// lengths are incorrect.
fn verify(&mut self, key: Option<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()>;
/// lengths are incorrect or `CryptoKind` does not match.
fn verify(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, msg: &[u8], signature: &[u8]) -> Result<bool, ()>;
/// Returns current UNIX timestamp (in millis)
fn timestamp(&mut self) -> Timestamp;
@@ -359,20 +394,20 @@ impl<T: Externalities + ?Sized> Externalities for Box<T> {
(&mut **self).new_crypto_key(crypto)
}
fn encrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).encrypt(key, data)
fn encrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).encrypt(key, kind, data)
}
fn decrypt(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).decrypt(key, data)
fn decrypt(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).decrypt(key, kind, data)
}
fn sign(&mut self, key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).sign(key, data)
fn sign(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()> {
(&mut **self).sign(key, kind, data)
}
fn verify(&mut self, key: Option<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
(&mut **self).verify(key, msg, signature)
fn verify(&mut self, key: Option<CryptoKeyId>, kind: CryptoKind, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
(&mut **self).verify(key, kind, msg, signature)
}
fn timestamp(&mut self) -> Timestamp {
+1
View File
@@ -379,6 +379,7 @@ fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> Sec
secret.hard_derive_mini_secret_key(Some(ChainCode(cc.clone())), b"").0.expand()
}
/// The raw secret seed, which can be used to recreate the `Pair`.
#[cfg(feature = "std")]
type Seed = [u8; MINI_SECRET_KEY_LENGTH];
+4 -2
View File
@@ -21,7 +21,7 @@ use serde::{Serialize, de::DeserializeOwned};
use crate::chain_spec::ChainSpec;
use client_db;
use client::{self, Client, runtime_api};
use crate::{error, Service};
use crate::{error, Service, AuthorityKeyProvider};
use consensus_common::{import_queue::ImportQueue, SelectChain};
use network::{self, OnDemand, FinalityProofProvider};
use substrate_executor::{NativeExecutor, NativeExecutionDispatch};
@@ -191,7 +191,7 @@ fn maintain_transaction_pool<Api, Backend, Block, Executor, PoolApi>(
client: &Client<Backend, Executor, Block, Api>,
transaction_pool: &TransactionPool<PoolApi>,
) -> error::Result<()> where
Block: BlockT<Hash = <Blake2Hasher as ::primitives::Hasher>::Out>,
Block: BlockT<Hash = <Blake2Hasher as primitives::Hasher>::Out>,
Backend: client::backend::Backend<Block, Blake2Hasher>,
Client<Backend, Executor, Block, Api>: ProvideRuntimeApi,
<Client<Backend, Executor, Block, Api> as ProvideRuntimeApi>::Api: runtime_api::TaggedTransactionQueue<Block>,
@@ -231,6 +231,7 @@ pub trait OffchainWorker<C: Components> {
offchain: &offchain::OffchainWorkers<
ComponentClient<C>,
ComponentOffchainStorage<C>,
AuthorityKeyProvider,
ComponentBlock<C>
>,
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
@@ -246,6 +247,7 @@ impl<C: Components> OffchainWorker<Self> for C where
offchain: &offchain::OffchainWorkers<
ComponentClient<C>,
ComponentOffchainStorage<C>,
AuthorityKeyProvider,
ComponentBlock<C>
>,
pool: &Arc<TransactionPool<C::TransactionPoolApi>>,
+7 -5
View File
@@ -16,12 +16,14 @@
//! Service configuration.
use std::{path::PathBuf, net::SocketAddr};
use transaction_pool;
use crate::chain_spec::ChainSpec;
pub use client::ExecutionStrategies;
pub use client_db::PruningMode;
pub use network::config::{ExtTransport, NetworkConfiguration, Roles};
use std::{path::PathBuf, net::SocketAddr};
use transaction_pool;
use crate::chain_spec::ChainSpec;
use primitives::crypto::Protected;
use runtime_primitives::BuildStorage;
use serde::{Serialize, de::DeserializeOwned};
use target_info::Target;
@@ -86,7 +88,7 @@ pub struct Configuration<C, G: Serialize + DeserializeOwned + BuildStorage> {
/// Disable GRANDPA when running in validator mode
pub disable_grandpa: bool,
/// Node keystore's password
pub password: String,
pub password: Protected<String>,
}
impl<C: Default, G: Serialize + DeserializeOwned + BuildStorage> Configuration<C, G> {
@@ -120,7 +122,7 @@ impl<C: Default, G: Serialize + DeserializeOwned + BuildStorage> Configuration<C
offchain_worker: Default::default(),
force_authoring: false,
disable_grandpa: false,
password: "".to_string(),
password: "".to_string().into(),
};
configuration.network.boot_nodes = configuration.chain_spec.boot_nodes().to_vec();
+51 -22
View File
@@ -39,7 +39,7 @@ use keystore::Store as Keystore;
use network::NetworkState;
use log::{info, warn, debug, error};
use parity_codec::{Encode, Decode};
use primitives::{Pair, Public, crypto::TypedKey, ed25519};
use primitives::{Pair, ed25519, crypto};
use runtime_primitives::generic::BlockId;
use runtime_primitives::traits::{Header, NumberFor, SaturatedConversion};
use substrate_executor::NativeExecutor;
@@ -74,14 +74,14 @@ const DEFAULT_PROTOCOL_ID: &str = "sup";
/// Substrate service.
pub struct Service<Components: components::Components> {
client: Arc<ComponentClient<Components>>,
select_chain: Option<<Components as components::Components>::SelectChain>,
select_chain: Option<Components::SelectChain>,
network: Arc<components::NetworkService<Components>>,
/// Sinks to propagate network status updates.
network_status_sinks: Arc<Mutex<Vec<mpsc::UnboundedSender<(
NetworkStatus<ComponentBlock<Components>>, NetworkState
)>>>>,
transaction_pool: Arc<TransactionPool<Components::TransactionPoolApi>>,
keystore: Option<Keystore>,
keystore: AuthorityKeyProvider,
exit: ::exit_future::Exit,
signal: Option<Signal>,
/// Sender for futures that must be spawned as background tasks.
@@ -101,6 +101,7 @@ pub struct Service<Components: components::Components> {
_offchain_workers: Option<Arc<offchain::OffchainWorkers<
ComponentClient<Components>,
ComponentOffchainStorage<Components>,
AuthorityKeyProvider,
ComponentBlock<Components>>
>>,
}
@@ -192,7 +193,7 @@ impl<Components: components::Components> Service<Components> {
public_key = match keystore.contents::<ed25519::Public>()?.get(0) {
Some(public_key) => public_key.to_string(),
None => {
let key: ed25519::Pair = keystore.generate(&config.password)?;
let key: ed25519::Pair = keystore.generate(&config.password.as_ref())?;
let public_key = key.public();
info!("Generated a new keypair: {:?}", public_key);
public_key.to_string()
@@ -259,6 +260,12 @@ impl<Components: components::Components> Service<Components> {
let network = network_mut.service().clone();
let network_status_sinks = Arc::new(Mutex::new(Vec::new()));
let keystore_authority_key = AuthorityKeyProvider {
roles: config.roles,
password: config.password.clone(),
keystore: keystore.map(Arc::new),
};
#[allow(deprecated)]
let offchain_storage = client.backend().offchain_storage();
let offchain_workers = match (config.offchain_worker, offchain_storage) {
@@ -266,6 +273,8 @@ impl<Components: components::Components> Service<Components> {
Some(Arc::new(offchain::OffchainWorkers::new(
client.clone(),
db,
keystore_authority_key.clone(),
config.password.clone(),
)))
},
(true, None) => {
@@ -469,7 +478,7 @@ impl<Components: components::Components> Service<Components> {
to_spawn_tx,
to_spawn_rx,
to_poll: Vec::new(),
keystore,
keystore: keystore_authority_key,
config,
exit,
rpc_handlers,
@@ -481,23 +490,10 @@ impl<Components: components::Components> Service<Components> {
}
/// give the authority key, if we are an authority and have a key
pub fn authority_key<TPair>(&self) -> Option<TPair>
where
TPair: Pair + TypedKey,
<TPair as Pair>::Public: Public + TypedKey,
{
if self.config.roles != Roles::AUTHORITY { return None }
if let Some(keystore) = &self.keystore {
if let Ok(Some(Ok(key))) = keystore.contents::<TPair::Public>().map(|keys| keys.get(0)
.map(|k| keystore.load::<TPair>(k, &self.config.password)))
{
Some(key)
} else {
None
}
} else {
None
}
pub fn authority_key<TPair: Pair>(&self) -> Option<TPair> {
use offchain::AuthorityKeyProvider;
self.keystore.authority_key()
}
/// return a shared instance of Telemetry (if enabled)
@@ -870,6 +866,39 @@ impl<C: Components> network::TransactionPool<ComponentExHash<C>, ComponentBlock<
}
}
/// A provider of current authority key.
#[derive(Clone)]
pub struct AuthorityKeyProvider {
roles: Roles,
keystore: Option<Arc<Keystore>>,
password: crypto::Protected<String>,
}
impl offchain::AuthorityKeyProvider for AuthorityKeyProvider {
fn authority_key<TPair: Pair>(&self) -> Option<TPair> {
if self.roles != Roles::AUTHORITY {
return None
}
let keystore = match self.keystore {
Some(ref keystore) => keystore,
None => return None
};
let loaded_key = keystore
.contents()
.map(|keys| keys.get(0)
.map(|k| keystore.load(k, self.password.as_ref()))
);
if let Ok(Some(Ok(key))) = loaded_key {
Some(key)
} else {
None
}
}
}
/// Constructs a service factory with the given name that implements the `ServiceFactory` trait.
/// The required parameters are required to be given in the exact order. Some parameters are followed
/// by `{}` blocks. These blocks are required and used to initialize the given parameter.
+1 -1
View File
@@ -194,7 +194,7 @@ fn node_config<F: ServiceFactory> (
offchain_worker: false,
force_authoring: false,
disable_grandpa: false,
password: "".to_string(),
password: "".to_string().into(),
}
}
+9 -4
View File
@@ -249,28 +249,33 @@ export_api! {
/// If `key` is `None`, it will attempt to use current authority key.
///
/// Returns an error if `key` is not available or does not exist.
fn encrypt(key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
fn encrypt(key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Decrypt a piece of data using given crypto key.
///
/// If `key` is `None`, it will attempt to use current authority key.
///
/// Returns an error if data cannot be decrypted or the `key` is not available or does not exist.
fn decrypt(key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
fn decrypt(key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Sign a piece of data using given crypto key.
///
/// If `key` is `None`, it will attempt to use current authority key.
///
/// Returns an error if `key` is not available or does not exist.
fn sign(key: Option<CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()>;
fn sign(key: Option<CryptoKeyId>, kind: CryptoKind, data: &[u8]) -> Result<Vec<u8>, ()>;
/// Verifies that `signature` for `msg` matches given `key`.
///
/// Returns an `Ok` with `true` in case it does, `false` in case it doesn't.
/// Returns an error in case the key is not available or does not exist or the parameters
/// lengths are incorrect.
fn verify(key: Option<CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()>;
fn verify(
key: Option<CryptoKeyId>,
kind: CryptoKind,
msg: &[u8],
signature: &[u8]
) -> Result<bool, ()>;
/// Returns current UNIX timestamp (in millis)
fn timestamp() -> Timestamp;
+25 -8
View File
@@ -275,27 +275,44 @@ impl OffchainApi for () {
}, "new_crypto_key can be called only in the offchain worker context")
}
fn encrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
fn encrypt(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
with_offchain(|ext| {
ext.encrypt(key, data)
ext.encrypt(key, kind, data)
}, "encrypt can be called only in the offchain worker context")
}
fn decrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
fn decrypt(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
with_offchain(|ext| {
ext.decrypt(key, data)
ext.decrypt(key, kind, data)
}, "decrypt can be called only in the offchain worker context")
}
fn sign(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
fn sign(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
with_offchain(|ext| {
ext.sign(key, data)
ext.sign(key, kind, data)
}, "sign can be called only in the offchain worker context")
}
fn verify(key: Option<offchain::CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
fn verify(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
msg: &[u8],
signature: &[u8],
) -> Result<bool, ()> {
with_offchain(|ext| {
ext.verify(key, msg, signature)
ext.verify(key, kind, msg, signature)
}, "verify can be called only in the offchain worker context")
}
+67 -26
View File
@@ -395,18 +395,24 @@ pub mod ext {
/// Encrypt a piece of data using given crypto key.
///
/// If `key` is `0`, it will attempt to use current authority key.
/// If `key` is `0`, it will attempt to use current authority key of given `kind`.
///
/// # Returns
///
/// - `0` in case the key is invalid, `msg_len` is set to `u32::max_value`
/// - Otherwise, pointer to the encrypted message in memory,
/// `msg_len` contains the length of the message.
fn ext_encrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8;
fn ext_encrypt(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
msg_len: *mut u32
) -> *mut u8;
/// Decrypt a piece of data using given crypto key.
///
/// If `key `is `0`, it will attempt to use current authority key.
/// If `key` is `0`, it will attempt to use current authority key of given `kind`.
///
/// # Returns
///
@@ -414,11 +420,17 @@ pub mod ext {
/// `msg_len` is set to `u32::max_value`
/// - Otherwise, pointer to the decrypted message in memory,
/// `msg_len` contains the length of the message.
fn ext_decrypt(key: u32, data: *const u8, data_len: u32, msg_len: *mut u32) -> *mut u8;
fn ext_decrypt(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
msg_len: *mut u32
) -> *mut u8;
/// Sign a piece of data using given crypto key.
///
/// If `key` is `0`, it will attempt to use current authority key.
/// If `key` is `0`, it will attempt to use current authority key of given `kind`.
///
/// # Returns
///
@@ -426,11 +438,17 @@ pub mod ext {
/// `sig_data_len` is set to `u32::max_value`
/// - Otherwise, pointer to the signature in memory,
/// `sig_data_len` contains the length of the signature.
fn ext_sign(key: u32, data: *const u8, data_len: u32, sig_data_len: *mut u32) -> *mut u8;
fn ext_sign(
key: u32,
kind: u32,
data: *const u8,
data_len: u32,
sig_data_len: *mut u32
) -> *mut u8;
/// Verifies that `signature` for `msg` matches given `key`.
///
/// If `key` is `0`, it will attempt to use current authority key.
/// If `key` is `0`, it will attempt to use current authority key of given `kind`.
///
/// # Returns
/// - `0` in case the signature is correct
@@ -438,6 +456,7 @@ pub mod ext {
/// - `u32::max_value` if the key is invalid.
fn ext_verify(
key: u32,
kind: u32,
msg: *const u8,
msg_len: u32,
signature: *const u8,
@@ -870,7 +889,7 @@ impl OffchainApi for () {
}
fn new_crypto_key(crypto: offchain::CryptoKind) -> Result<offchain::CryptoKeyId, ()> {
let crypto = crypto as u8 as u32;
let crypto = crypto.into();
let ret = unsafe {
ext_new_crypto_key.get()(crypto)
};
@@ -882,41 +901,63 @@ impl OffchainApi for () {
}
}
fn encrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
let key = key.map(|x| x.0 as u32).unwrap_or(0);
fn encrypt(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
let key = key.map(Into::into).unwrap_or(0);
let kind = kind.into();
let mut len = 0_u32;
unsafe {
let ptr = ext_encrypt.get()(key, data.as_ptr(), data.len() as u32, &mut len);
let ptr = ext_encrypt.get()(key, kind, data.as_ptr(), data.len() as u32, &mut len);
from_raw_parts(ptr, len).ok_or(())
}
}
fn decrypt(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
let key = key.map(|x| x.0 as u32).unwrap_or(0);
fn decrypt(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
let key = key.map(Into::into).unwrap_or(0);
let kind = kind.into();
let mut len = 0_u32;
unsafe {
let ptr = ext_decrypt.get()(key, data.as_ptr(), data.len() as u32, &mut len);
let ptr = ext_decrypt.get()(key, kind, data.as_ptr(), data.len() as u32, &mut len);
from_raw_parts(ptr, len).ok_or(())
}
}
fn sign(key: Option<offchain::CryptoKeyId>, data: &[u8]) -> Result<Vec<u8>, ()> {
let key = key.map(|x| x.0 as u32).unwrap_or(0);
fn sign(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
data: &[u8],
) -> Result<Vec<u8>, ()> {
let key = key.map(Into::into).unwrap_or(0);
let kind = kind.into();
let mut len = 0_u32;
unsafe {
let ptr = ext_sign.get()(key, data.as_ptr(), data.len() as u32, &mut len);
let ptr = ext_sign.get()(key, kind, data.as_ptr(), data.len() as u32, &mut len);
from_raw_parts(ptr, len).ok_or(())
}
}
fn verify(key: Option<offchain::CryptoKeyId>, msg: &[u8], signature: &[u8]) -> Result<bool, ()> {
let key = key.map(|x| x.0 as u32).unwrap_or(0);
fn verify(
key: Option<offchain::CryptoKeyId>,
kind: offchain::CryptoKind,
msg: &[u8],
signature: &[u8],
) -> Result<bool, ()> {
let key = key.map(Into::into).unwrap_or(0);
let kind = kind.into();
let val = unsafe {
ext_verify.get()(
key,
kind,
msg.as_ptr(),
msg.len() as u32,
signature.as_ptr(),
@@ -954,7 +995,7 @@ impl OffchainApi for () {
fn local_storage_set(kind: offchain::StorageKind, key: &[u8], value: &[u8]) {
unsafe {
ext_local_storage_set.get()(
kind as u8 as u32,
kind.into(),
key.as_ptr(),
key.len() as u32,
value.as_ptr(),
@@ -966,7 +1007,7 @@ impl OffchainApi for () {
fn local_storage_compare_and_set(kind: offchain::StorageKind, key: &[u8], old_value: &[u8], new_value: &[u8]) -> bool {
unsafe {
ext_local_storage_compare_and_set.get()(
kind as u8 as u32,
kind.into(),
key.as_ptr(),
key.len() as u32,
old_value.as_ptr(),
@@ -981,7 +1022,7 @@ impl OffchainApi for () {
let mut len = 0u32;
unsafe {
let ptr = ext_local_storage_get.get()(
kind as u8 as u32,
kind.into(),
key.as_ptr(),
key.len() as u32,
&mut len,
@@ -1019,7 +1060,7 @@ impl OffchainApi for () {
let result = unsafe {
ext_http_request_add_header.get()(
request_id.0 as u32,
request_id.into(),
name.as_ptr(),
name.len() as u32,
value.as_ptr(),
@@ -1041,7 +1082,7 @@ impl OffchainApi for () {
) -> Result<(), offchain::HttpError> {
let res = unsafe {
ext_http_request_write_body.get()(
request_id.0 as u32,
request_id.into(),
chunk.as_ptr(),
chunk.len() as u32,
deadline.map_or(0, |x| x.unix_millis()),
@@ -1084,7 +1125,7 @@ impl OffchainApi for () {
let mut len = 0u32;
let raw_result = unsafe {
let ptr = ext_http_response_headers.get()(
request_id.0 as u32,
request_id.into(),
&mut len,
);
@@ -1101,7 +1142,7 @@ impl OffchainApi for () {
) -> Result<usize, offchain::HttpError> {
let res = unsafe {
ext_http_response_read_body.get()(
request_id.0 as u32,
request_id.into(),
buffer.as_mut_ptr(),
buffer.len() as u32,
deadline.map_or(0, |x| x.unix_millis()),
+9 -1
View File
@@ -250,6 +250,7 @@ impl offchain::Externalities for NeverOffchainExt {
fn encrypt(
&mut self,
_key: Option<offchain::CryptoKeyId>,
_kind: offchain::CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unreachable!()
@@ -258,18 +259,25 @@ impl offchain::Externalities for NeverOffchainExt {
fn decrypt(
&mut self,
_key: Option<offchain::CryptoKeyId>,
_kind: offchain::CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unreachable!()
}
fn sign(&mut self, _key: Option<offchain::CryptoKeyId>, _data: &[u8]) -> Result<Vec<u8>, ()> {
fn sign(
&mut self,
_key: Option<offchain::CryptoKeyId>,
_kind: offchain::CryptoKind,
_data: &[u8],
) -> Result<Vec<u8>, ()> {
unreachable!()
}
fn verify(
&mut self,
_key: Option<offchain::CryptoKeyId>,
_kind: offchain::CryptoKind,
_msg: &[u8],
_signature: &[u8],
) -> Result<bool, ()> {