Crypto fixes/improvements (#2008)

* Crypto fixes:

- Use schnorrkel's HDKD derive
- Assume all key URIs beginning with `/` are prefixed with public
  root phrase.

* Remove commented code.

* Update README

* Update core/primitives/src/ed25519.rs

Co-Authored-By: gavofyork <github@gavwood.com>
This commit is contained in:
Gav Wood
2019-03-15 13:53:09 +01:00
committed by GitHub
parent ae1351cb79
commit 03d52fdbeb
10 changed files with 70 additions and 38 deletions
+2 -2
View File
@@ -2768,7 +2768,7 @@ dependencies = [
[[package]] [[package]]
name = "schnorrkel" name = "schnorrkel"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/w3f/schnorrkel#0876f3f2194426150efea99304a8d23a336744bc" source = "git+https://github.com/w3f/schnorrkel#0a0de4294b475ef6abdeebb50067f213ca79b3c7"
dependencies = [ dependencies = [
"clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"curve25519-dalek 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "curve25519-dalek 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -4673,7 +4673,7 @@ name = "twox-hash"
version = "1.1.2" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
+2 -2
View File
@@ -259,7 +259,7 @@ We'll start Alice's substrate node first on default TCP port 30333 with her chai
cargo run --release \-- \ cargo run --release \-- \
--base-path /tmp/alice \ --base-path /tmp/alice \
--chain=local \ --chain=local \
--key Alice \ --key //Alice \
--name "ALICE" \ --name "ALICE" \
--node-key 0000000000000000000000000000000000000000000000000000000000000001 \ --node-key 0000000000000000000000000000000000000000000000000000000000000001 \
--telemetry-url ws://telemetry.polkadot.io:1024 \ --telemetry-url ws://telemetry.polkadot.io:1024 \
@@ -272,7 +272,7 @@ cargo run --release \-- \
--base-path /tmp/bob \ --base-path /tmp/bob \
--bootnodes /ip4/127.0.0.1/tcp/30333/p2p/QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN \ --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN \
--chain=local \ --chain=local \
--key Bob \ --key //Bob \
--name "BOB" \ --name "BOB" \
--port 30334 \ --port 30334 \
--telemetry-url ws://telemetry.polkadot.io:1024 \ --telemetry-url ws://telemetry.polkadot.io:1024 \
+1 -4
View File
@@ -63,9 +63,6 @@ use substrate_telemetry::TelemetryEndpoints;
const MAX_NODE_NAME_LENGTH: usize = 32; const MAX_NODE_NAME_LENGTH: usize = 32;
/// The root phrase for our development network keys.
pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// Executable version. Used to pass version information from the root crate. /// Executable version. Used to pass version information from the root crate.
pub struct VersionInfo { pub struct VersionInfo {
/// Implemtation name. /// Implemtation name.
@@ -390,7 +387,7 @@ where
} }
if cli.shared_params.dev { if cli.shared_params.dev {
config.keys.push(format!("{}//Alice", DEV_PHRASE)); config.keys.push("//Alice".into());
} }
let rpc_interface: &str = if cli.rpc_external { "0.0.0.0" } else { "127.0.0.1" }; let rpc_interface: &str = if cli.rpc_external { "0.0.0.0" } else { "127.0.0.1" };
+1 -6
View File
@@ -22,11 +22,6 @@ use lazy_static::lazy_static;
use substrate_primitives::{ed25519::{Pair, Public, Signature}, Pair as _Pair, H256}; use substrate_primitives::{ed25519::{Pair, Public, Signature}, Pair as _Pair, H256};
pub use substrate_primitives::ed25519; pub use substrate_primitives::ed25519;
/// The root phrase for our test keys.
///
/// This is the same phrase that's in node::cli, but shouldn't need to be.
pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// Set of test accounts. /// Set of test accounts.
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyring { pub enum Keyring {
@@ -81,7 +76,7 @@ impl Keyring {
} }
pub fn pair(self) -> Pair { pub fn pair(self) -> Pair {
Pair::from_string(&format!("{}//{}", DEV_PHRASE, <&'static str>::from(self)), None) Pair::from_string(&format!("//{}", <&'static str>::from(self)), None)
.expect("static values are known good; qed") .expect("static values are known good; qed")
} }
} }
+1 -6
View File
@@ -22,11 +22,6 @@ use lazy_static::lazy_static;
use substrate_primitives::{sr25519::{Pair, Public, Signature}, Pair as _Pair, H256}; use substrate_primitives::{sr25519::{Pair, Public, Signature}, Pair as _Pair, H256};
pub use substrate_primitives::sr25519; pub use substrate_primitives::sr25519;
/// The root phrase for our test keys.
///
/// This is the same phrase that's in node::cli, but shouldn't need to be.
pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// Set of test accounts. /// Set of test accounts.
#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyring { pub enum Keyring {
@@ -81,7 +76,7 @@ impl Keyring {
} }
pub fn pair(self) -> Pair { pub fn pair(self) -> Pair {
Pair::from_string(&format!("{}//{}", DEV_PHRASE, <&'static str>::from(self)), None) Pair::from_string(&format!("//{}", <&'static str>::from(self)), None)
.expect("static values are known good; qed") .expect("static values are known good; qed")
} }
} }
+15 -4
View File
@@ -25,6 +25,12 @@ use regex::Regex;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use base58::{FromBase58, ToBase58}; use base58::{FromBase58, ToBase58};
/// The root phrase for our publically known keys.
pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// The address of the associated root phrase for our publically known keys.
pub const DEV_ADDRESS: &str = "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqAS7";
/// The infallible type. /// The infallible type.
#[derive(Debug)] #[derive(Debug)]
pub enum Infallible {} pub enum Infallible {}
@@ -243,14 +249,16 @@ impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T {
} }
fn from_string(s: &str) -> Result<Self, PublicError> { fn from_string(s: &str) -> Result<Self, PublicError> {
let re = Regex::new(r"^(?P<ss58>[\w\d]+)(?P<path>(//?[^/]+)*)$") let re = Regex::new(r"^(?P<ss58>[\w\d]+)?(?P<path>(//?[^/]+)*)$")
.expect("constructed from known-good static value; qed"); .expect("constructed from known-good static value; qed");
let cap = re.captures(s).ok_or(PublicError::InvalidFormat)?; let cap = re.captures(s).ok_or(PublicError::InvalidFormat)?;
let re_junction = Regex::new(r"/(/?[^/]+)") let re_junction = Regex::new(r"/(/?[^/]+)")
.expect("constructed from known-good static value; qed"); .expect("constructed from known-good static value; qed");
let path = re_junction.captures_iter(&cap["path"]) let path = re_junction.captures_iter(&cap["path"])
.map(|f| DeriveJunction::from(&f[1])); .map(|f| DeriveJunction::from(&f[1]));
Self::from_ss58check(&cap["ss58"])?.derive(path).ok_or(PublicError::InvalidPath) Self::from_ss58check(cap.name("ss58").map(|r| r.as_str()).unwrap_or(DEV_ADDRESS))?
.derive(path)
.ok_or(PublicError::InvalidPath)
} }
} }
@@ -335,6 +343,9 @@ pub trait Pair: Sized {
/// - the phrase may be followed by one or more items delimited by `/` characters. /// - the phrase may be followed by one or more items delimited by `/` characters.
/// - the path may be followed by `///`, in which case everything after the `///` is treated /// - the path may be followed by `///`, in which case everything after the `///` is treated
/// as a password. /// as a password.
/// - If `s` begins with a `/` character it is prefixed with the Substrate public `DEV_PHRASE` and
/// interpreted as above.
///
/// In this case they are interpreted as HDKD junctions; purely numeric items are interpreted as /// In this case they are interpreted as HDKD junctions; purely numeric items are interpreted as
/// integers, non-numeric items as strings. Junctions prefixed with `/` are interpreted as soft /// integers, non-numeric items as strings. Junctions prefixed with `/` are interpreted as soft
/// junctions, and with `//` as hard junctions. /// junctions, and with `//` as hard junctions.
@@ -359,7 +370,7 @@ pub trait Pair: Sized {
} }
} }
let re = Regex::new(r"^(?P<phrase>\w+( \w+)*)(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$") let re = Regex::new(r"^(?P<phrase>\w+( \w+)*)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$")
.expect("constructed from known-good static value; qed"); .expect("constructed from known-good static value; qed");
let cap = re.captures(s).ok_or(SecretStringError::InvalidFormat)?; let cap = re.captures(s).ok_or(SecretStringError::InvalidFormat)?;
let re_junction = Regex::new(r"/(/?[^/]+)") let re_junction = Regex::new(r"/(/?[^/]+)")
@@ -367,7 +378,7 @@ pub trait Pair: Sized {
let path = re_junction.captures_iter(&cap["path"]) let path = re_junction.captures_iter(&cap["path"])
.map(|f| DeriveJunction::from(&f[1])); .map(|f| DeriveJunction::from(&f[1]));
Self::from_standard_components( Self::from_standard_components(
&cap["phrase"], cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE),
password_override.or_else(|| cap.name("password").map(|m| m.as_str())), password_override.or_else(|| cap.name("password").map(|m| m.as_str())),
path, path,
) )
+9 -1
View File
@@ -523,7 +523,15 @@ impl Pair {
mod test { mod test {
use super::*; use super::*;
use hex_literal::{hex, hex_impl}; use hex_literal::{hex, hex_impl};
use crate::Pair as _Pair; use crate::{Pair as _Pair, crypto::DEV_PHRASE};
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")).unwrap().public(),
);
}
#[test] #[test]
fn test_vector_should_work() { fn test_vector_should_work() {
+35 -7
View File
@@ -366,11 +366,7 @@ impl AsRef<schnorrkel::Keypair> for Pair {
/// Derive a single hard junction. /// Derive a single hard junction.
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> SecretKey { fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> SecretKey {
("SchnorrRistrettoHDKD", &secret.to_bytes()[..], cc).using_encoded(|data| secret.hard_derive_mini_secret_key(signing_context(b"SchnorrRistrettoHDKD").bytes(&cc[..])).expand()
MiniSecretKey::from_bytes(blake2_rfc::blake2b::blake2b(32, &[], data).as_bytes())
.expect("all 32-byte crypto-hash results are valid MiniSecretKeys; qed")
.expand()
)
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
@@ -507,9 +503,41 @@ impl Pair {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::Pair as _Pair; use crate::{Pair as _Pair, crypto::{Ss58Codec, DEV_PHRASE, DEV_ADDRESS}};
use hex_literal::{hex, hex_impl}; use hex_literal::{hex, hex_impl};
#[test]
fn default_phrase_should_be_used() {
assert_eq!(
Pair::from_string("//Alice///password", None).unwrap().public(),
Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")).unwrap().public(),
);
assert_eq!(
Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None).as_ref().map(Pair::public),
Pair::from_string("/Alice", None).as_ref().map(Pair::public)
);
}
#[test]
fn default_address_should_be_used() {
assert_eq!(
Public::from_string(&format!("{}/Alice", DEV_ADDRESS)),
Public::from_string("/Alice")
);
}
#[test]
fn default_phrase_should_correspond_to_default_address() {
assert_eq!(
Pair::from_string(&format!("{}/Alice", DEV_PHRASE), None).unwrap().public(),
Public::from_string(&format!("{}/Alice", DEV_ADDRESS)).unwrap(),
);
assert_eq!(
Pair::from_string("/Alice", None).unwrap().public(),
Public::from_string("/Alice").unwrap()
);
}
#[test] #[test]
fn derive_soft_should_work() { fn derive_soft_should_work() {
let pair: Pair = Pair::from_seed(hex!( let pair: Pair = Pair::from_seed(hex!(
+2 -3
View File
@@ -3,7 +3,6 @@ use node_template_runtime::{
AccountId, GenesisConfig, ConsensusConfig, TimestampConfig, BalancesConfig, AccountId, GenesisConfig, ConsensusConfig, TimestampConfig, BalancesConfig,
SudoConfig, IndicesConfig, FeesConfig, SudoConfig, IndicesConfig, FeesConfig,
}; };
use substrate_cli::DEV_PHRASE;
use substrate_service; use substrate_service;
use ed25519::Public as AuthorityId; use ed25519::Public as AuthorityId;
@@ -26,13 +25,13 @@ pub enum Alternative {
} }
fn authority_key(s: &str) -> AuthorityId { fn authority_key(s: &str) -> AuthorityId {
ed25519::Pair::from_string(&format!("{}//{}", DEV_PHRASE, s), None) ed25519::Pair::from_string(&format!("//{}", s), None)
.expect("static values are valid; qed") .expect("static values are valid; qed")
.public() .public()
} }
fn account_key(s: &str) -> AccountId { fn account_key(s: &str) -> AccountId {
ed25519::Pair::from_string(&format!("{}//{}", DEV_PHRASE, s), None) ed25519::Pair::from_string(&format!("//{}", s), None)
.expect("static values are valid; qed") .expect("static values are valid; qed")
.public() .public()
} }
+2 -3
View File
@@ -25,7 +25,6 @@ pub use node_runtime::GenesisConfig;
use substrate_service; use substrate_service;
use hex_literal::{hex, hex_impl}; use hex_literal::{hex, hex_impl};
use substrate_telemetry::TelemetryEndpoints; use substrate_telemetry::TelemetryEndpoints;
use cli::DEV_PHRASE;
const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
@@ -188,14 +187,14 @@ pub fn staging_testnet_config() -> ChainSpec {
/// Helper function to generate AccountId from seed /// Helper function to generate AccountId from seed
pub fn get_account_id_from_seed(seed: &str) -> AccountId { pub fn get_account_id_from_seed(seed: &str) -> AccountId {
sr25519::Pair::from_string(&format!("{}//{}", DEV_PHRASE, seed), None) sr25519::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed") .expect("static values are valid; qed")
.public() .public()
} }
/// Helper function to generate AuthorityId from seed /// Helper function to generate AuthorityId from seed
pub fn get_session_key_from_seed(seed: &str) -> AuthorityId { pub fn get_session_key_from_seed(seed: &str) -> AuthorityId {
ed25519::Pair::from_string(&format!("{}//{}", DEV_PHRASE, seed), None) ed25519::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed") .expect("static values are valid; qed")
.public() .public()
} }