From 2c0e73b78c4ccbf0f2c24e7e101af7fc46aec885 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 20 Aug 2019 17:39:14 +0200 Subject: [PATCH] srml/authority-discovery: Introduce srml module to sign and verify (#3385) In order to have authorities (validators) discover each other, they need to publish their public addresses by their ip address on the Kademlia Dht indexed by their public key. This payload needs to be signed by a key identifying them as a valid authority. Code inside `/core` does not know the current set of authorities nor can it assume what kind of cryptography primitives are currently in use. Instead it can retrieve its public key and the current set of authorities from the runtime and have it sign and verify Dht payloads. This commit enables code in `/core` to do so by introducing a srml module and runtime api to: 1. Retrieve own public key. 2. Retrieve public keys of current authority set. 3. Sign a Dht payload. 4. Verify a Dht payload. This commit makes the logic from the previous commit (`core/consensus/common/primitives.ConsensusApi`) cf80af9255eae8ed4538df04d555702073d6acfe obsolete and thus removes it. --- substrate/Cargo.lock | 42 ++- substrate/Cargo.toml | 1 + .../primitives/Cargo.toml | 8 +- .../authority-discovery/primitives/src/lib.rs | 49 +++ .../consensus/common/primitives/src/lib.rs | 31 -- substrate/core/state-machine/src/testing.rs | 5 + substrate/node-template/runtime/Cargo.toml | 2 - substrate/node-template/runtime/src/lib.rs | 6 - substrate/node/cli/Cargo.toml | 1 + substrate/node/cli/src/chain_spec.rs | 13 +- substrate/node/runtime/Cargo.toml | 6 +- substrate/node/runtime/src/lib.rs | 30 +- substrate/node/testing/src/genesis.rs | 1 + substrate/srml/authority-discovery/Cargo.toml | 34 ++ substrate/srml/authority-discovery/src/lib.rs | 349 ++++++++++++++++++ 15 files changed, 509 insertions(+), 69 deletions(-) rename substrate/core/{consensus/common => authority-discovery}/primitives/Cargo.toml (54%) create mode 100644 substrate/core/authority-discovery/primitives/src/lib.rs delete mode 100644 substrate/core/consensus/common/primitives/src/lib.rs create mode 100644 substrate/srml/authority-discovery/Cargo.toml create mode 100644 substrate/srml/authority-discovery/src/lib.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 03e76eb979..592b39e08c 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -2296,6 +2296,7 @@ dependencies = [ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", "sr-primitives 2.0.0", + "srml-authority-discovery 0.1.0", "srml-balances 2.0.0", "srml-contracts 2.0.0", "srml-finality-tracker 2.0.0", @@ -2419,6 +2420,7 @@ dependencies = [ "sr-staking-primitives 2.0.0", "sr-std 2.0.0", "sr-version 2.0.0", + "srml-authority-discovery 0.1.0", "srml-authorship 0.1.0", "srml-babe 2.0.0", "srml-balances 2.0.0", @@ -2440,9 +2442,9 @@ dependencies = [ "srml-system 2.0.0", "srml-timestamp 2.0.0", "srml-treasury 2.0.0", + "substrate-authority-discovery-primitives 2.0.0", "substrate-client 2.0.0", "substrate-consensus-babe-primitives 2.0.0", - "substrate-consensus-common-primitives 2.0.0", "substrate-keyring 2.0.0", "substrate-offchain-primitives 2.0.0", "substrate-primitives 2.0.0", @@ -2503,7 +2505,6 @@ dependencies = [ "srml-timestamp 2.0.0", "substrate-client 2.0.0", "substrate-consensus-babe-primitives 2.0.0", - "substrate-consensus-common-primitives 2.0.0", "substrate-offchain-primitives 2.0.0", "substrate-primitives 2.0.0", "substrate-session 2.0.0", @@ -3805,6 +3806,23 @@ dependencies = [ "substrate-primitives 2.0.0", ] +[[package]] +name = "srml-authority-discovery" +version = "0.1.0" +dependencies = [ + "parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 2.0.0", + "sr-primitives 2.0.0", + "sr-std 2.0.0", + "srml-im-online 0.1.0", + "srml-session 2.0.0", + "srml-support 2.0.0", + "srml-system 2.0.0", + "substrate-application-crypto 2.0.0", + "substrate-primitives 2.0.0", +] + [[package]] name = "srml-authorship" version = "0.1.0" @@ -4381,6 +4399,16 @@ dependencies = [ "substrate-test-runtime-client 2.0.0", ] +[[package]] +name = "substrate-authority-discovery-primitives" +version = "2.0.0" +dependencies = [ + "parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-primitives 2.0.0", + "sr-std 2.0.0", + "substrate-client 2.0.0", +] + [[package]] name = "substrate-basic-authorship" version = "2.0.0" @@ -4619,16 +4647,6 @@ dependencies = [ "substrate-test-runtime-client 2.0.0", ] -[[package]] -name = "substrate-consensus-common-primitives" -version = "2.0.0" -dependencies = [ - "parity-scale-codec 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "sr-primitives 2.0.0", - "sr-std 2.0.0", - "substrate-client 2.0.0", -] - [[package]] name = "substrate-consensus-rhd" version = "2.0.0" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index eb75ac19f7..048bfb7629 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -82,6 +82,7 @@ members = [ "srml/generic-asset", "srml/grandpa", "srml/im-online", + "srml/authority-discovery", "srml/indices", "srml/membership", "srml/metadata", diff --git a/substrate/core/consensus/common/primitives/Cargo.toml b/substrate/core/authority-discovery/primitives/Cargo.toml similarity index 54% rename from substrate/core/consensus/common/primitives/Cargo.toml rename to substrate/core/authority-discovery/primitives/Cargo.toml index de59c3723d..aeae5cc61f 100644 --- a/substrate/core/consensus/common/primitives/Cargo.toml +++ b/substrate/core/authority-discovery/primitives/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "substrate-consensus-common-primitives" +name = "substrate-authority-discovery-primitives" version = "2.0.0" authors = ["Parity Technologies "] description = "Common consensus primitives" @@ -7,9 +7,9 @@ edition = "2018" [dependencies] codec = { package = "parity-scale-codec", default-features = false, version = "1.0.3" } -client = { package = "substrate-client", path = "../../../client", default-features = false } -sr-primitives = { path = "../../../sr-primitives", default-features = false } -rstd = { package = "sr-std", path = "../../../sr-std", default-features = false } +client = { package = "substrate-client", path = "../../client", default-features = false } +sr-primitives = { path = "../../sr-primitives", default-features = false } +rstd = { package = "sr-std", path = "../../sr-std", default-features = false } [features] default = ["std"] diff --git a/substrate/core/authority-discovery/primitives/src/lib.rs b/substrate/core/authority-discovery/primitives/src/lib.rs new file mode 100644 index 0000000000..556b758aa6 --- /dev/null +++ b/substrate/core/authority-discovery/primitives/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Runtime Api to help discover authorities. + +#![cfg_attr(not(feature = "std"), no_std)] + +use client::decl_runtime_apis; +use codec::Codec; +use rstd::vec::Vec; + +decl_runtime_apis! { + /// The authority discovery api. + /// + /// This api is used by the `core/authority-discovery` module to retrieve our + /// own authority identifier, to retrieve identifiers of the current authority + /// set, as well as sign and verify Kademlia Dht external address payloads + /// from and to other authorities. + pub trait AuthorityDiscoveryApi { + /// Returns own authority identifier iff it is part of the current authority + /// set, otherwise this function returns None. The restriction might be + /// softened in the future in case a consumer needs to learn own authority + /// identifier. + fn authority_id() -> Option; + + /// Retrieve authority identifiers of the current authority set. + fn authorities() -> Vec; + + /// Sign the given payload with the private key corresponding to the given authority id. + fn sign(payload: Vec, authority_id: AuthorityId) -> Option>; + + /// Verify the given signature for the given payload with the given + /// authority identifier. + fn verify(payload: Vec, signature: Vec, authority_id: AuthorityId) -> bool; + } +} diff --git a/substrate/core/consensus/common/primitives/src/lib.rs b/substrate/core/consensus/common/primitives/src/lib.rs deleted file mode 100644 index f6c1800081..0000000000 --- a/substrate/core/consensus/common/primitives/src/lib.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Common consensus primitives. - -#![cfg_attr(not(feature = "std"), no_std)] - -use codec::Codec; -use client::decl_runtime_apis; -use rstd::vec::Vec; - -decl_runtime_apis! { - /// Common consensus runtime api. - pub trait ConsensusApi { - /// Returns the set of authorities of the currently active consensus mechanism. - fn authorities() -> Vec; - } -} diff --git a/substrate/core/state-machine/src/testing.rs b/substrate/core/state-machine/src/testing.rs index 5026abcb2f..00a0208a1f 100644 --- a/substrate/core/state-machine/src/testing.rs +++ b/substrate/core/state-machine/src/testing.rs @@ -89,6 +89,11 @@ impl TestExternalities { self.offchain = Some(Box::new(offchain)); } + /// Set keystore. + pub fn set_keystore(&mut self, keystore: BareCryptoStorePtr) { + self.keystore = Some(keystore); + } + /// Get mutable reference to changes trie storage. pub fn changes_trie_storage(&mut self) -> &mut ChangesTrieInMemoryStorage { &mut self.changes_trie_storage diff --git a/substrate/node-template/runtime/Cargo.toml b/substrate/node-template/runtime/Cargo.toml index 54aee700f8..986feeb38c 100644 --- a/substrate/node-template/runtime/Cargo.toml +++ b/substrate/node-template/runtime/Cargo.toml @@ -25,7 +25,6 @@ timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default sudo = { package = "srml-sudo", path = "../../srml/sudo", default_features = false } sr-primitives = { path = "../../core/sr-primitives", default_features = false } client = { package = "substrate-client", path = "../../core/client", default_features = false } -consensus-primitives = { package = "substrate-consensus-common-primitives", path = "../../core/consensus/common/primitives", default-features = false } offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false } [build-dependencies] @@ -53,7 +52,6 @@ std = [ "version/std", "serde", "safe-mix/std", - "consensus-primitives/std", "offchain-primitives/std", "substrate-session/std", ] diff --git a/substrate/node-template/runtime/src/lib.rs b/substrate/node-template/runtime/src/lib.rs index 9f520971ad..6a73a47d36 100644 --- a/substrate/node-template/runtime/src/lib.rs +++ b/substrate/node-template/runtime/src/lib.rs @@ -400,12 +400,6 @@ impl_runtime_apis! { } } - impl consensus_primitives::ConsensusApi for Runtime { - fn authorities() -> Vec { - Babe::authorities().into_iter().map(|(a, _)| a).collect() - } - } - impl substrate_session::SessionKeys for Runtime { fn generate_session_keys(seed: Option>) -> Vec { let seed = seed.as_ref().map(|s| rstd::str::from_utf8(&s).expect("Seed is an utf8 string")); diff --git a/substrate/node/cli/Cargo.toml b/substrate/node/cli/Cargo.toml index c8aaf576dc..7b4ebb0c5f 100644 --- a/substrate/node/cli/Cargo.toml +++ b/substrate/node/cli/Cargo.toml @@ -46,6 +46,7 @@ system = { package = "srml-system", path = "../../srml/system" } balances = { package = "srml-balances", path = "../../srml/balances" } support = { package = "srml-support", path = "../../srml/support", default-features = false } im_online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false } +authority-discovery = { package = "srml-authority-discovery", path = "../../srml/authority-discovery", default-features = false } [dev-dependencies] keystore = { package = "substrate-keystore", path = "../../core/keystore" } diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index fe8df33f79..fca4c78b89 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -19,10 +19,9 @@ use primitives::{Pair, Public, crypto::UncheckedInto}; pub use node_primitives::{AccountId, Balance}; use node_runtime::{ - BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig, - ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, - SessionConfig, SessionKeys, StakerStatus, StakingConfig, SudoConfig, SystemConfig, - TechnicalCommitteeConfig, WASM_BINARY, + AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig, + ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, SessionConfig, SessionKeys, StakerStatus, + StakingConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig, WASM_BINARY, }; use node_runtime::constants::{time::*, currency::*}; pub use node_runtime::GenesisConfig; @@ -171,6 +170,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig { im_online: Some(ImOnlineConfig { keys: vec![], }), + authority_discovery: Some(AuthorityDiscoveryConfig{ + keys: vec![], + }), grandpa: Some(GrandpaConfig { authorities: vec![], }), @@ -302,6 +304,9 @@ pub fn testnet_genesis( im_online: Some(ImOnlineConfig { keys: vec![], }), + authority_discovery: Some(AuthorityDiscoveryConfig{ + keys: vec![], + }), grandpa: Some(GrandpaConfig { authorities: vec![], }), diff --git a/substrate/node/runtime/Cargo.toml b/substrate/node/runtime/Cargo.toml index 188d0c6f97..9424788e2f 100644 --- a/substrate/node/runtime/Cargo.toml +++ b/substrate/node/runtime/Cargo.toml @@ -15,12 +15,12 @@ rstd = { package = "sr-std", path = "../../core/sr-std", default-features = fals sr-primitives = { path = "../../core/sr-primitives", default-features = false } sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false } +authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives", default-features = false } version = { package = "sr-version", path = "../../core/sr-version", default-features = false } support = { package = "srml-support", path = "../../srml/support", default-features = false } authorship = { package = "srml-authorship", path = "../../srml/authorship", default-features = false } babe = { package = "srml-babe", path = "../../srml/babe", default-features = false } babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false } -consensus-primitives = { package = "substrate-consensus-common-primitives", path = "../../core/consensus/common/primitives", default-features = false } balances = { package = "srml-balances", path = "../../srml/balances", default-features = false } contracts = { package = "srml-contracts", path = "../../srml/contracts", default-features = false } collective = { package = "srml-collective", path = "../../srml/collective", default-features = false } @@ -38,6 +38,7 @@ timestamp = { package = "srml-timestamp", path = "../../srml/timestamp", default treasury = { package = "srml-treasury", path = "../../srml/treasury", default-features = false } sudo = { package = "srml-sudo", path = "../../srml/sudo", default-features = false } im-online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false } +authority-discovery = { package = "srml-authority-discovery", path = "../../srml/authority-discovery", default-features = false } offences = { package = "srml-offences", path = "../../srml/offences", default-features = false } node-primitives = { path = "../primitives", default-features = false } rustc-hex = { version = "2.0", optional = true } @@ -54,6 +55,8 @@ no_std = [ "contracts/core", ] std = [ + "authority-discovery-primitives/std", + "authority-discovery/std", "authorship/std", "babe-primitives/std", "babe/std", @@ -61,7 +64,6 @@ std = [ "client/std", "codec/std", "collective/std", - "consensus-primitives/std", "contracts/std", "democracy/std", "elections/std", diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 7507609b3b..b1e4065b5f 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -80,8 +80,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 147, - impl_version: 150, + spec_version: 148, + impl_version: 148, apis: RUNTIME_API_VERSIONS, }; @@ -407,6 +407,8 @@ impl offences::Trait for Runtime { type OnOffenceHandler = Staking; } +impl authority_discovery::Trait for Runtime {} + impl grandpa::Trait for Runtime { type Event = Event; } @@ -447,6 +449,7 @@ construct_runtime!( Contracts: contracts, Sudo: sudo, ImOnline: im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, + AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Offences: offences::{Module, Call, Storage, Event}, } ); @@ -576,15 +579,26 @@ impl_runtime_apis! { } } - impl node_primitives::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Index { - System::account_nonce(account) + impl authority_discovery_primitives::AuthorityDiscoveryApi for Runtime { + fn authority_id() -> Option { + AuthorityDiscovery::authority_id() + } + fn authorities() -> Vec { + AuthorityDiscovery::authorities() + } + + fn sign(payload: Vec, authority_id: im_online::AuthorityId) -> Option> { + AuthorityDiscovery::sign(payload, authority_id) + } + + fn verify(payload: Vec, signature: Vec, public_key: im_online::AuthorityId) -> bool { + AuthorityDiscovery::verify(payload, signature, public_key) } } - impl consensus_primitives::ConsensusApi for Runtime { - fn authorities() -> Vec { - Babe::authorities().into_iter().map(|(a, _)| a).collect() + impl node_primitives::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) } } diff --git a/substrate/node/testing/src/genesis.rs b/substrate/node/testing/src/genesis.rs index a7df99da14..35ff93d1a6 100644 --- a/substrate/node/testing/src/genesis.rs +++ b/substrate/node/testing/src/genesis.rs @@ -89,6 +89,7 @@ pub fn config(support_changes_trie: bool, code: Option<&[u8]>) -> GenesisConfig authorities: vec![], }), im_online: Some(Default::default()), + authority_discovery: Some(Default::default()), democracy: Some(Default::default()), collective_Instance1: Some(Default::default()), collective_Instance2: Some(Default::default()), diff --git a/substrate/srml/authority-discovery/Cargo.toml b/substrate/srml/authority-discovery/Cargo.toml new file mode 100644 index 0000000000..5d52bdb246 --- /dev/null +++ b/substrate/srml/authority-discovery/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "srml-authority-discovery" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +sr-primitives = { path = "../../core/sr-primitives", default-features = false } +primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } +app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } +rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } +serde = { version = "1.0", optional = true } +session = { package = "srml-session", path = "../session", default-features = false } +im-online = { package = "srml-im-online", path = "../im-online", default-features = false } +srml-support = { path = "../support", default-features = false } +sr-io = { package = "sr-io", path = "../../core/sr-io", default-features = false } +system = { package = "srml-system", path = "../system", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "sr-primitives/std", + "primitives/std", + "rstd/std", + "serde", + "session/std", + "im-online/std", + "srml-support/std", + "sr-io/std", + "system/std", + "app-crypto/std", +] diff --git a/substrate/srml/authority-discovery/src/lib.rs b/substrate/srml/authority-discovery/src/lib.rs new file mode 100644 index 0000000000..00912aeffe --- /dev/null +++ b/substrate/srml/authority-discovery/src/lib.rs @@ -0,0 +1,349 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! # Authority discovery module. +//! +//! This module is used by the `core/authority-discovery` to retrieve the +//! current set of authorities, learn its own authority id as well as sign and +//! verify messages to and from other authorities. +//! +//! ## Dependencies +//! +//! This module depends on the [I’m online module](../srml_im_online/index.html) +//! using its session key. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use app_crypto::RuntimeAppPublic; +use codec::{Decode, Encode}; +use rstd::prelude::*; +use srml_support::{decl_module, decl_storage, StorageValue}; + +pub trait Trait: system::Trait + session::Trait {} + +decl_storage! { + trait Store for Module as AuthorityDiscovery { + /// The current set of keys that may issue a heartbeat. + Keys get(keys): Vec; + } + add_extra_genesis { + config(keys): Vec; + build(| + storage: &mut (sr_primitives::StorageOverlay, sr_primitives::ChildrenStorageOverlay), + config: &GenesisConfig + | { + sr_io::with_storage( + storage, + || Module::::initialize_keys(&config.keys), + ); + }) + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + } +} + +impl Module { + /// Returns own authority identifier iff it is part of the current authority + /// set, otherwise this function returns None. The restriction might be + /// softened in the future in case a consumer needs to learn own authority + /// identifier. + pub fn authority_id() -> Option { + let authorities = Keys::get(); + + let local_keys = im_online::AuthorityId::all(); + + authorities.into_iter().find_map(|authority| { + if local_keys.contains(&authority) { + Some(authority) + } else { + None + } + }) + } + + /// Retrieve authority identifiers of the current authority set. + pub fn authorities() -> Vec { + Keys::get() + } + + /// Sign the given payload with the private key corresponding to the given authority id. + pub fn sign(payload: Vec, authority_id: im_online::AuthorityId) -> Option> { + authority_id.sign(&payload).map(|s| s.encode()) + } + + /// Verify the given signature for the given payload with the given + /// authority identifier. + pub fn verify( + payload: Vec, + signature: Vec, + authority_id: im_online::AuthorityId, + ) -> bool { + im_online::AuthoritySignature::decode(&mut &signature[..]) + .map(|s| authority_id.verify(&payload, &s)) + .unwrap_or(false) + } + + fn initialize_keys(keys: &[im_online::AuthorityId]) { + if !keys.is_empty() { + assert!(Keys::get().is_empty(), "Keys are already initialized!"); + Keys::put_ref(keys); + } + } +} + +impl session::OneSessionHandler for Module { + type Key = im_online::AuthorityId; + + fn on_genesis_session<'a, I: 'a>(validators: I) + where + I: Iterator, + { + let keys = validators.map(|x| x.1).collect::>(); + Self::initialize_keys(&keys); + } + + fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, next_validators: I) + where + I: Iterator, + { + // Remember who the authorities are for the new session. + Keys::put(next_validators.map(|x| x.1).collect::>()); + } + + fn on_disabled(_i: usize) { + // ignore + } +} + +#[cfg(test)] +mod tests { + use super::*; + use app_crypto::Pair; + use primitives::testing::KeyStore; + use primitives::{crypto::key_types, sr25519, traits::BareCryptoStore, H256}; + use sr_io::{with_externalities, TestExternalities}; + use sr_primitives::testing::{Header, UintAuthorityId}; + use sr_primitives::traits::{ConvertInto, IdentityLookup, OpaqueKeys}; + use sr_primitives::Perbill; + use srml_support::{impl_outer_origin, parameter_types}; + + type AuthorityDiscovery = Module; + type SessionIndex = u32; + + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + impl Trait for Test {} + + pub struct TestOnSessionEnding; + impl session::OnSessionEnding for TestOnSessionEnding { + fn on_session_ending( + _: SessionIndex, + _: SessionIndex, + ) -> Option> { + None + } + } + + impl session::Trait for Test { + type OnSessionEnding = TestOnSessionEnding; + type Keys = UintAuthorityId; + type ShouldEndSession = session::PeriodicSessions; + type SessionHandler = TestSessionHandler; + type Event = (); + type ValidatorId = im_online::AuthorityId; + type ValidatorIdOf = ConvertInto; + type SelectInitialValidators = (); + } + + pub type BlockNumber = u64; + + parameter_types! { + pub const Period: BlockNumber = 1; + pub const Offset: BlockNumber = 0; + pub const UncleGenerations: u64 = 0; + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + + impl system::Trait for Test { + type Origin = Origin; + type Index = u64; + type BlockNumber = BlockNumber; + type Call = (); + type Hash = H256; + type Hashing = ::sr_primitives::traits::BlakeTwo256; + type AccountId = im_online::AuthorityId; + type Lookup = IdentityLookup; + type Header = Header; + type WeightMultiplierUpdate = (); + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type AvailableBlockRatio = AvailableBlockRatio; + type MaximumBlockLength = MaximumBlockLength; + type Version = (); + } + + impl_outer_origin! { + pub enum Origin for Test {} + } + + pub struct TestSessionHandler; + impl session::SessionHandler for TestSessionHandler { + fn on_new_session( + _changed: bool, + _validators: &[(im_online::AuthorityId, Ks)], + _queued_validators: &[(im_online::AuthorityId, Ks)], + ) { + } + + fn on_disabled(_validator_index: usize) {} + + fn on_genesis_session(_validators: &[(im_online::AuthorityId, Ks)]) {} + } + + #[test] + fn authority_id_fn_returns_intersection_of_current_authorities_and_keys_in_key_store() { + // Create keystore and generate key. + let key_store = KeyStore::new(); + key_store + .write() + .sr25519_generate_new(key_types::IM_ONLINE, None) + .expect("Generates key."); + + // Retrieve key to later check if we got the right one. + let public_key = key_store + .read() + .sr25519_public_keys(key_types::IM_ONLINE) + .pop() + .unwrap(); + let authority_id = im_online::AuthorityId::from(public_key); + + // Build genesis. + let mut t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + GenesisConfig { + keys: vec![authority_id.clone()], + } + .assimilate_storage::(&mut t) + .unwrap(); + + // Create externalities. + let mut externalities = TestExternalities::new(t); + externalities.set_keystore(key_store); + + with_externalities(&mut externalities, || { + assert_eq!( + authority_id, + AuthorityDiscovery::authority_id().expect("Retrieving public key.") + ); + }); + } + + #[test] + fn authority_id_fn_does_not_return_key_outside_current_authority_set() { + // Create keystore and generate key. + let key_store = KeyStore::new(); + key_store + .write() + .sr25519_generate_new(key_types::IM_ONLINE, None) + .expect("Generates key."); + + // Build genesis. + let mut t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + // Generate random authority set. + let keys = vec![(); 5] + .iter() + .map(|_x| sr25519::Pair::generate_with_phrase(None).0.public()) + .map(im_online::AuthorityId::from) + .collect(); + + GenesisConfig { keys: keys } + .assimilate_storage::(&mut t) + .unwrap(); + + // Create externalities. + let mut externalities = TestExternalities::new(t); + externalities.set_keystore(key_store); + + with_externalities(&mut externalities, || { + assert_eq!(None, AuthorityDiscovery::authority_id()); + }); + } + + #[test] + fn sign_and_verify_workflow() { + // Create keystore and generate key. + let key_store = KeyStore::new(); + key_store + .write() + .sr25519_generate_new(key_types::IM_ONLINE, None) + .expect("Generates key."); + + // Retrieve key to later check if we got the right one. + let public_key = key_store + .read() + .sr25519_public_keys(key_types::IM_ONLINE) + .pop() + .unwrap(); + let authority_id = im_online::AuthorityId::from(public_key); + + // Build genesis. + let mut t = system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + GenesisConfig { + keys: vec![authority_id.clone()], + } + .assimilate_storage::(&mut t) + .unwrap(); + + // Create externalities. + let mut externalities = TestExternalities::new(t); + externalities.set_keystore(key_store); + + with_externalities(&mut externalities, || { + let authority_id = AuthorityDiscovery::authority_id().expect("authority id"); + let payload = String::from("test payload").into_bytes(); + let sig = + AuthorityDiscovery::sign(payload.clone(), authority_id.clone()).expect("signature"); + + assert!(AuthorityDiscovery::verify( + payload, + sig.clone(), + authority_id.clone() + )); + + assert!(!AuthorityDiscovery::verify( + String::from("other payload").into_bytes(), + sig, + authority_id + )) + }); + } +}