From d97775542aa09a98fd8dab63b2232d45d8949431 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Thu, 24 Oct 2019 10:59:09 +0200 Subject: [PATCH] Add SECP256k1/ECDSA support for transaction signing (#3861) * Add SECP256k1/ECDSA support for transaction signing. * Refactoring and fixes * Fix for contracts * Avoid breaking runtime host function * Build fixes, make subkey work more generaically. * Fix tests * Dedpulicate a bit of code, remove unneeded code, docs * Bump runtime version * Fix a test and clean up some code. * Derivation can derive seed. * Whitespace * Bump runtime again. * Update core/primitives/src/crypto.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update core/primitives/src/ecdsa.rs Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fix AppVerify --- substrate/Cargo.lock | 2 + substrate/core/application-crypto/src/lib.rs | 13 +- .../core/application-crypto/src/traits.rs | 1 + substrate/core/executor/src/host_interface.rs | 82 ++- substrate/core/keyring/src/ed25519.rs | 15 + substrate/core/keyring/src/sr25519.rs | 15 + substrate/core/primitives/Cargo.toml | 4 + substrate/core/primitives/src/crypto.rs | 286 +++++--- substrate/core/primitives/src/ecdsa.rs | 608 ++++++++++++++++++ substrate/core/primitives/src/ed25519.rs | 20 +- substrate/core/primitives/src/hexdisplay.rs | 2 +- substrate/core/primitives/src/lib.rs | 1 + substrate/core/primitives/src/sr25519.rs | 53 +- substrate/core/sr-io/src/lib.rs | 9 +- substrate/core/sr-io/with_std.rs | 10 + substrate/core/sr-io/without_std.rs | 24 + .../src/generic/unchecked_extrinsic.rs | 22 +- substrate/core/sr-primitives/src/lib.rs | 60 +- substrate/core/sr-primitives/src/traits.rs | 73 ++- substrate/node/cli/src/chain_spec.rs | 67 +- substrate/node/cli/src/factory_impl.rs | 13 +- substrate/node/cli/src/service.rs | 17 +- substrate/node/primitives/src/lib.rs | 9 +- substrate/node/runtime/src/lib.rs | 59 +- substrate/node/testing/src/keyring.rs | 6 +- substrate/srml/system/src/offchain.rs | 71 +- substrate/subkey/src/cli.yml | 5 + substrate/subkey/src/main.rs | 146 +++-- substrate/subkey/src/vanity.rs | 6 +- .../test-utils/chain-spec-builder/src/main.rs | 6 +- 30 files changed, 1286 insertions(+), 419 deletions(-) create mode 100644 substrate/core/primitives/src/ecdsa.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a87da6586cc..14f8c87c1aa 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -5366,6 +5366,7 @@ dependencies = [ "hex-literal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "impl-serde 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libsecp256k1 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -5385,6 +5386,7 @@ dependencies = [ "substrate-primitives-storage 2.0.0", "substrate-serializer 2.0.0", "tiny-bip39 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tiny-keccak 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmi 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "zeroize 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/substrate/core/application-crypto/src/lib.rs b/substrate/core/application-crypto/src/lib.rs index 16eda532383..b4ada5425b4 100644 --- a/substrate/core/application-crypto/src/lib.rs +++ b/substrate/core/application-crypto/src/lib.rs @@ -88,22 +88,13 @@ macro_rules! app_crypto { } fn derive< Iter: Iterator<Item=$crate::DeriveJunction> - >(&self, path: Iter) -> Result<Self, Self::DeriveError> { - self.0.derive(path).map(Self) + >(&self, path: Iter, seed: Option<Self::Seed>) -> Result<(Self, Option<Self::Seed>), Self::DeriveError> { + self.0.derive(path, seed).map(|x| (Self(x.0), x.1)) } fn from_seed(seed: &Self::Seed) -> Self { Self(<$pair>::from_seed(seed)) } fn from_seed_slice(seed: &[u8]) -> Result<Self, $crate::SecretStringError> { <$pair>::from_seed_slice(seed).map(Self) } - fn from_standard_components< - I: Iterator<Item=$crate::DeriveJunction> - >( - seed: &str, - password: Option<&str>, - path: I, - ) -> Result<Self, $crate::SecretStringError> { - <$pair>::from_standard_components::<I>(seed, password, path).map(Self) - } fn sign(&self, msg: &[u8]) -> Self::Signature { Signature(self.0.sign(msg)) } diff --git a/substrate/core/application-crypto/src/traits.rs b/substrate/core/application-crypto/src/traits.rs index 49d3a44aee3..66e6cd6579b 100644 --- a/substrate/core/application-crypto/src/traits.rs +++ b/substrate/core/application-crypto/src/traits.rs @@ -126,3 +126,4 @@ pub trait RuntimeAppPublic: Sized { /// Verify that the given signature matches the given message using this public key. fn verify<M: AsRef<[u8]>>(&self, msg: &M, signature: &Self::Signature) -> bool; } + diff --git a/substrate/core/executor/src/host_interface.rs b/substrate/core/executor/src/host_interface.rs index e6386ff1acc..7d87d93333f 100644 --- a/substrate/core/executor/src/host_interface.rs +++ b/substrate/core/executor/src/host_interface.rs @@ -41,6 +41,40 @@ macro_rules! debug_trace { pub struct SubstrateExternals; +enum RecoverResult { + Invalid(u32), + Valid(secp256k1::PublicKey), +} + +fn secp256k1_recover( + context: &mut dyn FunctionContext, + msg_data: Pointer<u8>, + sig_data: Pointer<u8>, +) -> WResult<RecoverResult> { + let mut sig = [0u8; 65]; + context.read_memory_into(sig_data, &mut sig[..]) + .map_err(|_| "Invalid attempt to get signature in ext_secp256k1_ecdsa_recover")?; + let rs = match secp256k1::Signature::parse_slice(&sig[0..64]) { + Ok(rs) => rs, + _ => return Ok(RecoverResult::Invalid(1)), + }; + + let recovery_id = if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8; + let v = match secp256k1::RecoveryId::parse(recovery_id) { + Ok(v) => v, + _ => return Ok(RecoverResult::Invalid(2)), + }; + + let mut msg = [0u8; 32]; + context.read_memory_into(msg_data, &mut msg[..]) + .map_err(|_| "Invalid attempt to get message in ext_secp256k1_ecdsa_recover")?; + + Ok(match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { + Ok(pubkey) => RecoverResult::Valid(pubkey), + Err(_) => RecoverResult::Invalid(3), + }) +} + impl_wasm_host_interface! { impl SubstrateExternals where context { ext_malloc(size: WordSize) -> Pointer<u8> { @@ -781,33 +815,29 @@ impl_wasm_host_interface! { sig_data: Pointer<u8>, pubkey_data: Pointer<u8>, ) -> u32 { - let mut sig = [0u8; 65]; - context.read_memory_into(sig_data, &mut sig[..]) - .map_err(|_| "Invalid attempt to get signature in ext_secp256k1_ecdsa_recover")?; - let rs = match secp256k1::Signature::parse_slice(&sig[0..64]) { - Ok(rs) => rs, - _ => return Ok(1), - }; - - let recovery_id = if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8; - let v = match secp256k1::RecoveryId::parse(recovery_id) { - Ok(v) => v, - _ => return Ok(2), - }; - - let mut msg = [0u8; 32]; - context.read_memory_into(msg_data, &mut msg[..]) - .map_err(|_| "Invalid attempt to get message in ext_secp256k1_ecdsa_recover")?; - - let pubkey = match secp256k1::recover(&secp256k1::Message::parse(&msg), &rs, &v) { - Ok(pk) => pk, - _ => return Ok(3), - }; - - context.write_memory(pubkey_data, &pubkey.serialize()[1..65]) - .map_err(|_| "Invalid attempt to set pubkey in ext_secp256k1_ecdsa_recover")?; + match secp256k1_recover(context, msg_data, sig_data)? { + RecoverResult::Invalid(c) => Ok(c), + RecoverResult::Valid(pubkey) => { + context.write_memory(pubkey_data, &pubkey.serialize()[1..65]) + .map_err(|_| "Invalid attempt to set pubkey in ext_secp256k1_ecdsa_recover")?; + Ok(0) + } + } + } - Ok(0) + ext_secp256k1_ecdsa_recover_compressed( + msg_data: Pointer<u8>, + sig_data: Pointer<u8>, + pubkey_data: Pointer<u8>, + ) -> u32 { + match secp256k1_recover(context, msg_data, sig_data)? { + RecoverResult::Invalid(c) => Ok(c), + RecoverResult::Valid(pubkey) => { + context.write_memory(pubkey_data, &pubkey.serialize_compressed()[..]) + .map_err(|_| "Invalid attempt to set pubkey in ext_secp256k1_ecdsa_recover")?; + Ok(0) + } + } } ext_is_validator() -> u32 { diff --git a/substrate/core/keyring/src/ed25519.rs b/substrate/core/keyring/src/ed25519.rs index 56bdb1ce8c0..c1a357fc0e4 100644 --- a/substrate/core/keyring/src/ed25519.rs +++ b/substrate/core/keyring/src/ed25519.rs @@ -20,6 +20,7 @@ use std::{collections::HashMap, ops::Deref}; use lazy_static::lazy_static; use primitives::{ed25519::{Pair, Public, Signature}, Pair as PairT, Public as PublicT, H256}; pub use primitives::ed25519; +use sr_primitives::AccountId32; /// Set of test accounts. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum_macros::Display, strum_macros::EnumIter)] @@ -39,6 +40,10 @@ impl Keyring { Self::iter().find(|&k| &Public::from(k) == who) } + pub fn from_account_id(who: &AccountId32) -> Option<Keyring> { + Self::iter().find(|&k| &k.to_account_id() == who) + } + pub fn from_raw_public(who: [u8; 32]) -> Option<Keyring> { Self::from_public(&Public::from_raw(who)) } @@ -59,6 +64,10 @@ impl Keyring { Public::from(self).to_raw_vec() } + pub fn to_account_id(self) -> AccountId32 { + self.to_raw_public().into() + } + pub fn sign(self, msg: &[u8]) -> Signature { Pair::from(self).sign(msg) } @@ -119,6 +128,12 @@ impl From<Keyring> for Public { } } +impl From<Keyring> for AccountId32 { + fn from(k: Keyring) -> Self { + k.to_account_id() + } +} + impl From<Keyring> for Pair { fn from(k: Keyring) -> Self { k.pair() diff --git a/substrate/core/keyring/src/sr25519.rs b/substrate/core/keyring/src/sr25519.rs index bb3aaa6b51d..b37b2bdf9b2 100644 --- a/substrate/core/keyring/src/sr25519.rs +++ b/substrate/core/keyring/src/sr25519.rs @@ -21,6 +21,7 @@ use std::ops::Deref; use lazy_static::lazy_static; use primitives::{sr25519::{Pair, Public, Signature}, Pair as PairT, Public as PublicT, H256}; pub use primitives::sr25519; +use sr_primitives::AccountId32; /// Set of test accounts. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum_macros::Display, strum_macros::EnumIter)] @@ -40,6 +41,10 @@ impl Keyring { Self::iter().find(|&k| &Public::from(k) == who) } + pub fn from_account_id(who: &AccountId32) -> Option<Keyring> { + Self::iter().find(|&k| &k.to_account_id() == who) + } + pub fn from_raw_public(who: [u8; 32]) -> Option<Keyring> { Self::from_public(&Public::from_raw(who)) } @@ -60,6 +65,10 @@ impl Keyring { Public::from(self).to_raw_vec() } + pub fn to_account_id(self) -> AccountId32 { + self.to_raw_public().into() + } + pub fn sign(self, msg: &[u8]) -> Signature { Pair::from(self).sign(msg) } @@ -114,6 +123,12 @@ lazy_static! { }; } +impl From<Keyring> for AccountId32 { + fn from(k: Keyring) -> Self { + k.to_account_id() + } +} + impl From<Keyring> for Public { fn from(k: Keyring) -> Self { (*PUBLIC_KEYS).get(&k).unwrap().clone() diff --git a/substrate/core/primitives/Cargo.toml b/substrate/core/primitives/Cargo.toml index 557ce55b883..bc8106de3ab 100644 --- a/substrate/core/primitives/Cargo.toml +++ b/substrate/core/primitives/Cargo.toml @@ -31,6 +31,8 @@ num-traits = { version = "0.2.8", default-features = false } zeroize = "0.10.1" lazy_static = { version = "1.4.0", optional = true } parking_lot = { version = "0.9.0", optional = true } +libsecp256k1 = { version = "0.3.0", optional = true } +tiny-keccak = { version = "1.5.0", optional = true } substrate-debug-derive = { version = "2.0.0", path = "./debug-derive" } externalities = { package = "substrate-externalities", path = "../externalities", optional = true } primitives-storage = { package = "substrate-primitives-storage", path = "storage", default-features = false } @@ -82,6 +84,8 @@ std = [ "schnorrkel", "regex", "num-traits/std", + "libsecp256k1", + "tiny-keccak", "substrate-debug-derive/std", "externalities", "primitives-storage/std", diff --git a/substrate/core/primitives/src/crypto.rs b/substrate/core/primitives/src/crypto.rs index 588f3bc6e27..b7c70e62247 100644 --- a/substrate/core/primitives/src/crypto.rs +++ b/substrate/core/primitives/src/crypto.rs @@ -255,7 +255,7 @@ pub enum PublicError { /// Key that can be encoded to/from SS58. #[cfg(feature = "std")] -pub trait Ss58Codec: Sized { +pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default { /// Some if the string is a properly encoded SS58Check address. fn from_ss58check(s: &str) -> Result<Self, PublicError> { Self::from_ss58check_with_version(s) @@ -269,7 +269,23 @@ pub trait Ss58Codec: Sized { }) } /// Some if the string is a properly encoded SS58Check address. - fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError>; + fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { + let mut res = Self::default(); + let len = res.as_mut().len(); + let d = s.from_base58().map_err(|_| PublicError::BadBase58)?; // failure here would be invalid encoding. + if d.len() != len + 3 { + // Invalid length. + return Err(PublicError::BadLength); + } + let ver = d[0].try_into().map_err(|_: ()| PublicError::UnknownVersion)?; + + if d[len + 1..len + 3] != ss58hash(&d[0..len + 1]).as_bytes()[0..2] { + // Invalid checksum. + return Err(PublicError::InvalidChecksum); + } + res.as_mut().copy_from_slice(&d[1..len + 1]); + Ok((res, ver)) + } /// Some if the string is a properly encoded SS58Check address, optionally with /// a derivation path following. fn from_string(s: &str) -> Result<Self, PublicError> { @@ -285,7 +301,13 @@ pub trait Ss58Codec: Sized { } /// Return the ss58-check string for this key. - fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String; + fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String { + let mut v = vec![version.into()]; + v.extend(self.as_ref()); + let r = ss58hash(&v); + v.extend(&r.as_bytes()[0..2]); + v.to_base58() + } /// Return the ss58-check string for this key. fn to_ss58check(&self) -> String { self.to_ss58check_with_version(*DEFAULT_VERSION.lock()) } /// Some if the string is a properly encoded SS58Check address, optionally with @@ -408,44 +430,28 @@ pub fn set_default_ss58_version(version: Ss58AddressFormat) { } #[cfg(feature = "std")] -impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T { - fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { - let mut res = T::default(); - let len = res.as_mut().len(); - let d = s.from_base58().map_err(|_| PublicError::BadBase58)?; // failure here would be invalid encoding. - if d.len() != len + 3 { - // Invalid length. - return Err(PublicError::BadLength); - } - let ver = d[0].try_into().map_err(|_: ()| PublicError::UnknownVersion)?; - - if d[len+1..len+3] != ss58hash(&d[0..len+1]).as_bytes()[0..2] { - // Invalid checksum. - return Err(PublicError::InvalidChecksum); - } - res.as_mut().copy_from_slice(&d[1..len+1]); - Ok((res, ver)) - } - - fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String { - let mut v = vec![version.into()]; - v.extend(self.as_ref()); - let r = ss58hash(&v); - v.extend(&r.as_bytes()[0..2]); - v.to_base58() - } - +impl<T: Sized + AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T { 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"); let cap = re.captures(s).ok_or(PublicError::InvalidFormat)?; let re_junction = Regex::new(r"/(/?[^/]+)") .expect("constructed from known-good static value; qed"); - let addr = Self::from_ss58check( - cap.name("ss58") - .map(|r| r.as_str()) - .unwrap_or(DEV_ADDRESS) - )?; + let s = cap.name("ss58") + .map(|r| r.as_str()) + .unwrap_or(DEV_ADDRESS); + let addr = if s.starts_with("0x") { + let d = hex::decode(&s[2..]).map_err(|_| PublicError::InvalidFormat)?; + let mut r = Self::default(); + if d.len() == r.as_ref().len() { + r.as_mut().copy_from_slice(&d); + r + } else { + Err(PublicError::BadLength)? + } + } else { + Self::from_ss58check(s)? + }; if cap["path"].is_empty() { Ok(addr) } else { @@ -457,7 +463,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T { } fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), 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"); let cap = re.captures(s).ok_or(PublicError::InvalidFormat)?; let re_junction = Regex::new(r"/(/?[^/]+)") @@ -495,6 +501,103 @@ pub trait Public: AsRef<[u8]> + AsMut<[u8]> + Default + Derive + CryptoType + Pa fn as_slice(&self) -> &[u8] { self.as_ref() } } +/// An opaque 32-byte cryptographic identifier. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Default, Encode, Decode)] +pub struct AccountId32([u8; 32]); + +impl UncheckedFrom<crate::hash::H256> for AccountId32 { + fn unchecked_from(h: crate::hash::H256) -> Self { + AccountId32(h.into()) + } +} + +#[cfg(feature = "std")] +impl Ss58Codec for AccountId32 {} + +impl AsRef<[u8]> for AccountId32 { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for AccountId32 { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl AsRef<[u8; 32]> for AccountId32 { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl AsMut<[u8; 32]> for AccountId32 { + fn as_mut(&mut self) -> &mut [u8; 32] { + &mut self.0 + } +} + +impl From<[u8; 32]> for AccountId32 { + fn from(x: [u8; 32]) -> AccountId32 { + AccountId32(x) + } +} + +impl<'a> rstd::convert::TryFrom<&'a [u8]> for AccountId32 { + type Error = (); + fn try_from(x: &'a [u8]) -> Result<AccountId32, ()> { + if x.len() == 32 { + let mut r = AccountId32::default(); + r.0.copy_from_slice(x); + Ok(r) + } else { + Err(()) + } + } +} + +impl From<AccountId32> for [u8; 32] { + fn from(x: AccountId32) -> [u8; 32] { + x.0 + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for AccountId32 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } +} + +impl rstd::fmt::Debug for AccountId32 { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut rstd::fmt::Formatter) -> rstd::fmt::Result { + let s = self.to_ss58check(); + write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.0), &s[0..8]) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut rstd::fmt::Formatter) -> rstd::fmt::Result { + Ok(()) + } +} + +#[cfg(feature = "std")] +impl serde::Serialize for AccountId32 { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { + serializer.serialize_str(&self.to_ss58check()) + } +} + +#[cfg(feature = "std")] +impl<'de> serde::Deserialize<'de> for AccountId32 { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { + Ss58Codec::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } +} + #[cfg(feature = "std")] pub use self::dummy::*; @@ -544,17 +647,10 @@ mod dummy { Ok(Default::default()) } fn derive< - Iter: Iterator<Item=DeriveJunction> - >(&self, _: Iter) -> Result<Self, Self::DeriveError> { Ok(Self) } + Iter: Iterator<Item=DeriveJunction>, + >(&self, _: Iter, _: Option<Dummy>) -> Result<(Self, Option<Dummy>), Self::DeriveError> { Ok((Self, None)) } fn from_seed(_: &Self::Seed) -> Self { Self } fn from_seed_slice(_: &[u8]) -> Result<Self, SecretStringError> { Ok(Self) } - fn from_standard_components< - I: Iterator<Item=DeriveJunction> - >( - _: &str, - _: Option<&str>, - _: I - ) -> Result<Self, SecretStringError> { Ok(Self) } fn sign(&self, _: &[u8]) -> Self::Signature { Self } fn verify<M: AsRef<[u8]>>(_: &Self::Signature, _: M, _: &Self::Public) -> bool { true } fn verify_weak<P: AsRef<[u8]>, M: AsRef<[u8]>>(_: &[u8], _: M, _: P) -> bool { true } @@ -604,7 +700,10 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { fn from_phrase(phrase: &str, password: Option<&str>) -> Result<(Self, Self::Seed), SecretStringError>; /// Derive a child key from a series of given junctions. - fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, path: Iter) -> Result<Self, Self::DeriveError>; + fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, + path: Iter, + seed: Option<Self::Seed>, + ) -> Result<(Self, Option<Self::Seed>), Self::DeriveError>; /// Generate new key pair from the provided `seed`. /// @@ -619,11 +718,6 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// by an attacker then they can also derive your key. fn from_seed_slice(seed: &[u8]) -> Result<Self, SecretStringError>; - /// Construct a key from a phrase, password and path. - fn from_standard_components< - I: Iterator<Item=DeriveJunction> - >(phrase: &str, password: Option<&str>, path: I) -> Result<Self, SecretStringError>; - /// Sign a message. fn sign(&self, message: &[u8]) -> Self::Signature; @@ -636,7 +730,9 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// Get the public key. fn public(&self) -> Self::Public; - /// Interprets the string `s` in order to generate a key Pair. + /// Interprets the string `s` in order to generate a key Pair. Returns both the pair and an optional seed, in the + /// case that the pair can be expressed as a direct derivation from a seed (some cases, such as Sr25519 derivations + /// with path components, cannot). /// /// This takes a helper function to do the key generation from a phrase, password and /// junction iterator. @@ -662,31 +758,40 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// be equivalent to no password at all. /// /// `None` is returned if no matches are found. - fn from_string(s: &str, password_override: Option<&str>) -> Result<Self, SecretStringError> { - let hex_seed = if s.starts_with("0x") { - &s[2..] - } else { - s - }; - - if let Ok(d) = hex::decode(hex_seed) { - if let Ok(r) = Self::from_seed_slice(&d) { - return Ok(r) - } - } - - let re = Regex::new(r"^(?P<phrase>\w+( \w+)*)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$") + fn from_string_with_seed(s: &str, password_override: Option<&str>) -> Result<(Self, Option<Self::Seed>), SecretStringError> { + let re = Regex::new(r"^(?P<phrase>[\d\w ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$") .expect("constructed from known-good static value; qed"); let cap = re.captures(s).ok_or(SecretStringError::InvalidFormat)?; + let re_junction = Regex::new(r"/(/?[^/]+)") .expect("constructed from known-good static value; qed"); let path = re_junction.captures_iter(&cap["path"]) .map(|f| DeriveJunction::from(&f[1])); - Self::from_standard_components( - cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE), - password_override.or_else(|| cap.name("password").map(|m| m.as_str())), - path, - ) + + let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE); + let password = password_override.or_else(|| cap.name("password").map(|m| m.as_str())); + + let (root, seed) = if phrase.starts_with("0x") { + hex::decode(&phrase[2..]).ok() + .and_then(|seed_vec| { + let mut seed = Self::Seed::default(); + if seed.as_ref().len() == seed_vec.len() { + seed.as_mut().copy_from_slice(&seed_vec); + Some((Self::from_seed(&seed), seed)) + } else { + None + } + }) + .ok_or(SecretStringError::InvalidSeed)? + } else { + Self::from_phrase(phrase, password) + .map_err(|_| SecretStringError::InvalidPhrase)? + }; + root.derive(path, Some(seed)).map_err(|_| SecretStringError::InvalidPath) + } + + fn from_string(s: &str, password_override: Option<&str>) -> Result<Self, SecretStringError> { + Self::from_string_with_seed(s, password_override).map(|x| x.0) } /// Return a vec filled with raw data. @@ -846,13 +951,13 @@ mod tests { } impl Pair for TestPair { type Public = TestPublic; - type Seed = [u8; 0]; + type Seed = [u8; 8]; type Signature = [u8; 0]; type DeriveError = (); - fn generate() -> (Self, <Self as Pair>::Seed) { (TestPair::Generated, []) } + fn generate() -> (Self, <Self as Pair>::Seed) { (TestPair::Generated, [0u8; 8]) } fn generate_with_phrase(_password: Option<&str>) -> (Self, String, <Self as Pair>::Seed) { - (TestPair::GeneratedWithPhrase, "".into(), []) + (TestPair::GeneratedWithPhrase, "".into(), [0u8; 8]) } fn from_phrase(phrase: &str, password: Option<&str>) -> Result<(Self, <Self as Pair>::Seed), SecretStringError> @@ -860,14 +965,20 @@ mod tests { Ok((TestPair::GeneratedFromPhrase { phrase: phrase.to_owned(), password: password.map(Into::into) - }, [])) + }, [0u8; 8])) } - fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, _path: Iter) - -> Result<Self, Self::DeriveError> + fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, path_iter: Iter, _: Option<[u8; 8]>) + -> Result<(Self, Option<[u8; 8]>), Self::DeriveError> { - Err(()) + Ok((match self.clone() { + TestPair::Standard {phrase, password, path} => + TestPair::Standard { phrase, password, path: path.into_iter().chain(path_iter).collect() }, + TestPair::GeneratedFromPhrase {phrase, password} => + TestPair::Standard { phrase, password, path: path_iter.collect() }, + x => if path_iter.count() == 0 { x } else { return Err(()) }, + }, None)) } - fn from_seed(_seed: &<TestPair as Pair>::Seed) -> Self { TestPair::Seed(vec![]) } + fn from_seed(_seed: &<TestPair as Pair>::Seed) -> Self { TestPair::Seed(_seed.as_ref().to_owned()) } fn sign(&self, _message: &[u8]) -> Self::Signature { [] } fn verify<M: AsRef<[u8]>>(_: &Self::Signature, _: M, _: &Self::Public) -> bool { true } fn verify_weak<P: AsRef<[u8]>, M: AsRef<[u8]>>( @@ -876,17 +987,6 @@ mod tests { _pubkey: P ) -> bool { true } fn public(&self) -> Self::Public { TestPublic } - fn from_standard_components<I: Iterator<Item=DeriveJunction>>( - phrase: &str, - password: Option<&str>, - path: I - ) -> Result<Self, SecretStringError> { - Ok(TestPair::Standard { - phrase: phrase.to_owned(), - password: password.map(ToOwned::to_owned), - path: path.collect() - }) - } fn from_seed_slice(seed: &[u8]) -> Result<Self, SecretStringError> { @@ -903,10 +1003,6 @@ mod tests { TestPair::from_string("0x0123456789abcdef", None), Ok(TestPair::Seed(hex!["0123456789abcdef"][..].to_owned())) ); - assert_eq!( - TestPair::from_string("0123456789abcdef", None), - Ok(TestPair::Seed(hex!["0123456789abcdef"][..].to_owned())) - ); } #[test] diff --git a/substrate/core/primitives/src/ecdsa.rs b/substrate/core/primitives/src/ecdsa.rs new file mode 100644 index 00000000000..abff460c910 --- /dev/null +++ b/substrate/core/primitives/src/ecdsa.rs @@ -0,0 +1,608 @@ +// Copyright 2017-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 <http://www.gnu.org/licenses/>. + +// tag::description[] +//! Simple ECDSA API. +// end::description[] + +use rstd::cmp::Ordering; +use codec::{Encode, Decode}; + +#[cfg(feature = "std")] +use std::convert::{TryFrom, TryInto}; +#[cfg(feature = "std")] +use substrate_bip39::seed_from_entropy; +#[cfg(feature = "std")] +use bip39::{Mnemonic, Language, MnemonicType}; +#[cfg(feature = "std")] +use crate::{hashing::blake2_256, crypto::{Pair as TraitPair, DeriveJunction, SecretStringError, Ss58Codec}}; +#[cfg(feature = "std")] +use serde::{de, Serializer, Serialize, Deserializer, Deserialize}; +use crate::crypto::{Public as TraitPublic, UncheckedFrom, CryptoType, Derive}; +#[cfg(feature = "std")] +use secp256k1::{PublicKey, SecretKey}; + +/// A secret seed (which is bytewise essentially equivalent to a SecretKey). +/// +/// We need it as a different type because `Seed` is expected to be AsRef<[u8]>. +#[cfg(feature = "std")] +type Seed = [u8; 32]; + +/// The ECDSA 33-byte compressed public key. +#[derive(Clone, Encode, Decode)] +pub struct Public(pub [u8; 33]); + +impl PartialOrd for Public { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for Public { + fn cmp(&self, other: &Self) -> Ordering { + self.0[..].cmp(&other.0[..]) + } +} + +impl PartialEq for Public { + fn eq(&self, other: &Self) -> bool { + &self.0[..] == &other.0[..] + } +} + +impl Eq for Public {} + +impl Default for Public { + fn default() -> Self { + Public([0u8; 33]) + } +} + +/// A key pair. +#[cfg(feature = "std")] +#[derive(Clone)] +pub struct Pair { + public: PublicKey, + secret: SecretKey, +} + +impl AsRef<[u8; 33]> for Public { + fn as_ref(&self) -> &[u8; 33] { + &self.0 + } +} + +impl AsRef<[u8]> for Public { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for Public { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +impl rstd::convert::TryFrom<&[u8]> for Public { + type Error = (); + + fn try_from(data: &[u8]) -> Result<Self, Self::Error> { + if data.len() == 33 { + let mut inner = [0u8; 33]; + inner.copy_from_slice(data); + Ok(Public(inner)) + } else { + Err(()) + } + } +} + +impl From<Public> for [u8; 33] { + fn from(x: Public) -> Self { + x.0 + } +} + +#[cfg(feature = "std")] +impl From<Pair> for Public { + fn from(x: Pair) -> Self { + x.public() + } +} + +impl UncheckedFrom<[u8; 33]> for Public { + fn unchecked_from(x: [u8; 33]) -> Self { + Public(x) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for Public { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for Public { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let s = self.to_ss58check(); + write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&&self.0[..]), &s[0..8]) + } +} + +#[cfg(feature = "std")] +impl Serialize for Public { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer { + serializer.serialize_str(&self.to_ss58check()) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for Public { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> { + Public::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } +} + +#[cfg(feature = "std")] +impl std::hash::Hash for Public { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.0.hash(state); + } +} + +/// A signature (a 512-bit value). +#[derive(Encode, Decode)] +pub struct Signature([u8; 65]); + +impl rstd::convert::TryFrom<&[u8]> for Signature { + type Error = (); + + fn try_from(data: &[u8]) -> Result<Self, Self::Error> { + if data.len() == 65 { + let mut inner = [0u8; 65]; + inner.copy_from_slice(data); + Ok(Signature(inner)) + } else { + Err(()) + } + } +} + +impl Clone for Signature { + fn clone(&self) -> Self { + let mut r = [0u8; 65]; + r.copy_from_slice(&self.0[..]); + Signature(r) + } +} + +impl Default for Signature { + fn default() -> Self { + Signature([0u8; 65]) + } +} + +impl PartialEq for Signature { + fn eq(&self, b: &Self) -> bool { + self.0[..] == b.0[..] + } +} + +impl Eq for Signature {} + +impl From<Signature> for [u8; 65] { + fn from(v: Signature) -> [u8; 65] { + v.0 + } +} + +impl AsRef<[u8; 65]> for Signature { + fn as_ref(&self) -> &[u8; 65] { + &self.0 + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for Signature { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", crate::hexdisplay::HexDisplay::from(&self.0)) + } +} + +#[cfg(feature = "std")] +impl std::hash::Hash for Signature { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + std::hash::Hash::hash(&self.0[..], state); + } +} + +impl Signature { + /// A new instance from the given 65-byte `data`. + /// + /// NOTE: No checking goes on to ensure this is a real signature. Only use it if + /// you are certain that the array actually is a signature. GIGO! + pub fn from_raw(data: [u8; 65]) -> Signature { + Signature(data) + } + + /// Recover the public key from this signature and a message. + #[cfg(feature = "std")] + pub fn recover<M: AsRef<[u8]>>(&self, message: M) -> Option<Public> { + let message = secp256k1::Message::parse(&blake2_256(message.as_ref())); + let sig: (_, _) = self.try_into().ok()?; + secp256k1::recover(&message, &sig.0, &sig.1).ok() + .map(|recovered| Public(recovered.serialize_compressed())) + } +} + +#[cfg(feature = "std")] +impl From<(secp256k1::Signature, secp256k1::RecoveryId)> for Signature { + fn from(x: (secp256k1::Signature, secp256k1::RecoveryId)) -> Signature { + let mut r = Self::default(); + r.0[0..64].copy_from_slice(&x.0.serialize()[..]); + r.0[64] = x.1.serialize(); + r + } +} + +#[cfg(feature = "std")] +impl<'a> TryFrom<&'a Signature> for (secp256k1::Signature, secp256k1::RecoveryId) { + type Error = (); + fn try_from(x: &'a Signature) -> Result<(secp256k1::Signature, secp256k1::RecoveryId), Self::Error> { + Ok(( + secp256k1::Signature::parse_slice(&x.0[0..64]).expect("hardcoded to 64 bytes; qed"), + secp256k1::RecoveryId::parse(x.0[64]).map_err(|_| ())?, + )) + } +} + +/// An error type for SS58 decoding. +#[cfg(feature = "std")] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum PublicError { + /// Bad alphabet. + BadBase58, + /// Bad length. + BadLength, + /// Unknown version. + UnknownVersion, + /// Invalid checksum. + InvalidChecksum, +} + +impl Public { + /// A new instance from the given 33-byte `data`. + /// + /// NOTE: No checking goes on to ensure this is a real public key. Only use it if + /// you are certain that the array actually is a pubkey. GIGO! + pub fn from_raw(data: [u8; 33]) -> Self { + Public(data) + } + + /// Return a slice filled with raw data. + pub fn as_array_ref(&self) -> &[u8; 33] { + self.as_ref() + } +} + +impl TraitPublic for Public { + /// A new instance from the given slice that should be 33 bytes long. + /// + /// NOTE: No checking goes on to ensure this is a real public key. Only use it if + /// you are certain that the array actually is a pubkey. GIGO! + fn from_slice(data: &[u8]) -> Self { + let mut r = [0u8; 33]; + r.copy_from_slice(data); + Public(r) + } +} + +impl Derive for Public {} + +/// Derive a single hard junction. +#[cfg(feature = "std")] +fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { + ("Secp256k1HDKD", secret_seed, cc).using_encoded(|data| { + let mut res = [0u8; 32]; + res.copy_from_slice(blake2_rfc::blake2b::blake2b(32, &[], data).as_bytes()); + res + }) +} + +/// An error when deriving a key. +#[cfg(feature = "std")] +pub enum DeriveError { + /// A soft key was found in the path (and is unsupported). + SoftKeyInPath, +} + +#[cfg(feature = "std")] +impl TraitPair for Pair { + type Public = Public; + type Seed = Seed; + type Signature = Signature; + type DeriveError = DeriveError; + + /// Generate new secure (random) key pair and provide the recovery phrase. + /// + /// You can recover the same key later with `from_phrase`. + fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { + let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); + let phrase = mnemonic.phrase(); + let (pair, seed) = Self::from_phrase(phrase, password) + .expect("All phrases generated by Mnemonic are valid; qed"); + ( + pair, + phrase.to_owned(), + seed, + ) + } + + /// Generate key pair from given recovery phrase and password. + fn from_phrase(phrase: &str, password: Option<&str>) -> Result<(Pair, Seed), SecretStringError> { + let big_seed = seed_from_entropy( + Mnemonic::from_phrase(phrase, Language::English) + .map_err(|_| SecretStringError::InvalidPhrase)?.entropy(), + password.unwrap_or(""), + ).map_err(|_| SecretStringError::InvalidSeed)?; + let mut seed = Seed::default(); + seed.copy_from_slice(&big_seed[0..32]); + Self::from_seed_slice(&big_seed[0..32]).map(|x| (x, seed)) + } + + /// Make a new key pair from secret seed material. + /// + /// You should never need to use this; generate(), generate_with_phrase + fn from_seed(seed: &Seed) -> Pair { + Self::from_seed_slice(&seed[..]).expect("seed has valid length; qed") + } + + /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it + /// will return `None`. + /// + /// You should never need to use this; generate(), generate_with_phrase + fn from_seed_slice(seed_slice: &[u8]) -> Result<Pair, SecretStringError> { + let secret = SecretKey::parse_slice(seed_slice) + .map_err(|_| SecretStringError::InvalidSeedLength)?; + let public = PublicKey::from_secret_key(&secret); + Ok(Pair{ secret, public }) + } + + /// Derive a child key from a series of given junctions. + fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, + path: Iter, + _seed: Option<Seed> + ) -> Result<(Pair, Option<Seed>), DeriveError> { + let mut acc = self.secret.serialize(); + for j in path { + match j { + DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), + DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc), + } + } + Ok((Self::from_seed(&acc), Some(acc))) + } + + /// Get the public key. + fn public(&self) -> Public { + Public(self.public.serialize_compressed()) + } + + /// Sign a message. + fn sign(&self, message: &[u8]) -> Signature { + let message = secp256k1::Message::parse(&blake2_256(message)); + secp256k1::sign(&message, &self.secret).into() + } + + /// Verify a signature on a message. Returns true if the signature is good. + fn verify<M: AsRef<[u8]>>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { + let message = secp256k1::Message::parse(&blake2_256(message.as_ref())); + let sig: (_, _) = match sig.try_into() { Ok(x) => x, _ => return false }; + match secp256k1::recover(&message, &sig.0, &sig.1) { + Ok(actual) => &pubkey.0[..] == &actual.serialize_compressed()[..], + _ => false, + } + } + + /// Verify a signature on a message. Returns true if the signature is good. + /// + /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct + /// size. Use it only if you're coming from byte buffers and need the speed. + fn verify_weak<P: AsRef<[u8]>, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { + let message = secp256k1::Message::parse(&blake2_256(message.as_ref())); + if sig.len() != 65 { return false } + let ri = match secp256k1::RecoveryId::parse(sig[64]) { Ok(x) => x, _ => return false }; + let sig = match secp256k1::Signature::parse_slice(&sig[0..64]) { Ok(x) => x, _ => return false }; + match secp256k1::recover(&message, &sig, &ri) { + Ok(actual) => pubkey.as_ref() == &actual.serialize_compressed()[..], + _ => false, + } + } + + /// Return a vec filled with raw data. + fn to_raw_vec(&self) -> Vec<u8> { + self.seed().to_vec() + } +} + +#[cfg(feature = "std")] +impl Pair { + /// Get the seed for this key. + pub fn seed(&self) -> Seed { + self.secret.serialize() + } + + /// Exactly as `from_string` except that if no matches are found then, the the first 32 + /// characters are taken (padded with spaces as necessary) and used as the MiniSecretKey. + pub fn from_legacy_string(s: &str, password_override: Option<&str>) -> Pair { + Self::from_string(s, password_override).unwrap_or_else(|_| { + let mut padded_seed: Seed = [' ' as u8; 32]; + let len = s.len().min(32); + padded_seed[..len].copy_from_slice(&s.as_bytes()[..len]); + Self::from_seed(&padded_seed) + }) + } +} + +impl CryptoType for Public { + #[cfg(feature="std")] + type Pair = Pair; +} + +impl CryptoType for Signature { + #[cfg(feature="std")] + type Pair = Pair; +} + +#[cfg(feature = "std")] +impl CryptoType for Pair { + type Pair = Pair; +} + +#[cfg(test)] +mod test { + use super::*; + use hex_literal::hex; + use crate::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] + fn seed_and_derive_should_work() { + let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + let pair = Pair::from_seed(&seed); + assert_eq!(pair.seed(), seed); + let path = vec![DeriveJunction::Hard([0u8; 32])]; + let derived = pair.derive(path.into_iter(), None).ok().unwrap(); + assert_eq!( + derived.0.seed(), + hex!("b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61") + ); + } + + #[test] + fn test_vector_should_work() { + let pair = Pair::from_seed( + &hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") + ); + let public = pair.public(); + assert_eq!(public, Public::from_raw( + hex!("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91") + )); + let message = b""; + let signature = hex!("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); + let signature = Signature::from_raw(signature); + assert!(&pair.sign(&message[..]) == &signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn test_vector_by_string_should_work() { + let pair = Pair::from_string( + "0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + None + ).unwrap(); + let public = pair.public(); + assert_eq!(public, Public::from_raw( + hex!("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91") + )); + let message = b""; + let signature = hex!("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00"); + let signature = Signature::from_raw(signature); + assert!(&pair.sign(&message[..]) == &signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn generated_pair_should_work() { + let (pair, _) = Pair::generate(); + let public = pair.public(); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, b"Something else", &public)); + } + + #[test] + fn seeded_pair_should_work() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + assert_eq!(public, Public::from_raw( + hex!("035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9") + )); + let message = hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000"); + let signature = pair.sign(&message[..]); + println!("Correct signature: {:?}", signature); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, "Other message", &public)); + } + + #[test] + fn generate_with_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(None); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn generate_with_password_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn password_does_something() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_ne!(pair1.public(), pair2.public()); + } + + #[test] + fn ss58check_roundtrip_works() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let s = public.to_ss58check(); + println!("Correct: {}", s); + let cmp = Public::from_ss58check(&s).unwrap(); + assert_eq!(cmp, public); + } +} diff --git a/substrate/core/primitives/src/ed25519.rs b/substrate/core/primitives/src/ed25519.rs index ff3b21e160d..8c3aa9f89db 100644 --- a/substrate/core/primitives/src/ed25519.rs +++ b/substrate/core/primitives/src/ed25519.rs @@ -406,7 +406,10 @@ impl TraitPair for Pair { } /// Derive a child key from a series of given junctions. - fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, path: Iter) -> Result<Pair, DeriveError> { + fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, + path: Iter, + _seed: Option<Seed>, + ) -> Result<(Pair, Option<Seed>), DeriveError> { let mut acc = self.0.secret.to_bytes(); for j in path { match j { @@ -414,18 +417,7 @@ impl TraitPair for Pair { DeriveJunction::Hard(cc) => acc = derive_hard_junction(&acc, &cc), } } - Ok(Self::from_seed(&acc)) - } - - /// Generate a key from the phrase, password and derivation path. - fn from_standard_components<I: Iterator<Item=DeriveJunction>>( - phrase: &str, - password: Option<&str>, - path: I - ) -> Result<Pair, SecretStringError> { - Self::from_phrase(phrase, password)?.0 - .derive(path) - .map_err(|_| SecretStringError::InvalidPath) + Ok((Self::from_seed(&acc), Some(acc))) } /// Get the public key. @@ -528,7 +520,7 @@ mod test { let pair = Pair::from_seed(&seed); assert_eq!(pair.seed(), &seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; - let derived = pair.derive(path.into_iter()).ok().unwrap(); + let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; assert_eq!( derived.seed(), &hex!("ede3354e133f9c8e337ddd6ee5415ed4b4ffe5fc7d21e933f4930a3730e5b21c") diff --git a/substrate/core/primitives/src/hexdisplay.rs b/substrate/core/primitives/src/hexdisplay.rs index 6765ce517ea..2c8533e25b5 100644 --- a/substrate/core/primitives/src/hexdisplay.rs +++ b/substrate/core/primitives/src/hexdisplay.rs @@ -80,7 +80,7 @@ macro_rules! impl_non_endians { impl_non_endians!([u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], [u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], - [u8; 48], [u8; 56], [u8; 64], [u8; 80], [u8; 96], [u8; 112], [u8; 128]); + [u8; 48], [u8; 56], [u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128]); /// Format into ASCII + # + hex, suitable for storage key preimages. pub fn ascii_format(asciish: &[u8]) -> String { diff --git a/substrate/core/primitives/src/lib.rs b/substrate/core/primitives/src/lib.rs index 48d3d998fa8..d1b42766a08 100644 --- a/substrate/core/primitives/src/lib.rs +++ b/substrate/core/primitives/src/lib.rs @@ -59,6 +59,7 @@ pub mod u32_trait; pub mod ed25519; pub mod sr25519; +pub mod ecdsa; pub mod hash; mod hasher; pub mod offchain; diff --git a/substrate/core/primitives/src/sr25519.rs b/substrate/core/primitives/src/sr25519.rs index f2160b88b74..bb319b82218 100644 --- a/substrate/core/primitives/src/sr25519.rs +++ b/substrate/core/primitives/src/sr25519.rs @@ -388,8 +388,8 @@ impl AsRef<schnorrkel::Keypair> for Pair { /// Derive a single hard junction. #[cfg(feature = "std")] -fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> SecretKey { - secret.hard_derive_mini_secret_key(Some(ChainCode(cc.clone())), b"").0.expand(ExpansionMode::Ed25519) +fn derive_hard_junction(secret: &SecretKey, cc: &[u8; CHAIN_CODE_LENGTH]) -> MiniSecretKey { + secret.hard_derive_mini_secret_key(Some(ChainCode(cc.clone())), b"").0 } /// The raw secret seed, which can be used to recreate the `Pair`. @@ -444,17 +444,6 @@ impl TraitPair for Pair { } } - /// Generate a key from the phrase, password and derivation path. - fn from_standard_components<I: Iterator<Item=DeriveJunction>>( - phrase: &str, - password: Option<&str>, - path: I - ) -> Result<Pair, SecretStringError> { - Self::from_phrase(phrase, password)?.0 - .derive(path) - .map_err(|_| SecretStringError::InvalidPath) - } - fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); let phrase = mnemonic.phrase(); @@ -473,13 +462,27 @@ impl TraitPair for Pair { .map(|m| Self::from_entropy(m.entropy(), password)) } - fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, path: Iter) -> Result<Pair, Self::DeriveError> { + fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, + path: Iter, + seed: Option<Seed>, + ) -> Result<(Pair, Option<Seed>), Self::DeriveError> { + let seed = if let Some(s) = seed { + if let Ok(msk) = MiniSecretKey::from_bytes(&s) { + if msk.expand(ExpansionMode::Ed25519) == self.0.secret { + Some(msk) + } else { None } + } else { None } + } else { None }; let init = self.0.secret.clone(); - let result = path.fold(init, |acc, j| match j { - DeriveJunction::Soft(cc) => acc.derived_key_simple(ChainCode(cc), &[]).0, - DeriveJunction::Hard(cc) => derive_hard_junction(&acc, &cc), + let (result, seed) = path.fold((init, seed), |(acc, acc_seed), j| match (j, acc_seed) { + (DeriveJunction::Soft(cc), _) => + (acc.derived_key_simple(ChainCode(cc), &[]).0, None), + (DeriveJunction::Hard(cc), maybe_seed) => { + let seed = derive_hard_junction(&acc, &cc); + (seed.expand(ExpansionMode::Ed25519), maybe_seed.map(|_| seed)) + } }); - Ok(Self(result.into())) + Ok((Self(result.into()), seed.map(|s| MiniSecretKey::to_bytes(&s)))) } fn sign(&self, message: &[u8]) -> Signature { @@ -621,9 +624,9 @@ mod test { let pair = Pair::from_seed(&hex!( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" )); - let derive_1 = pair.derive(Some(DeriveJunction::soft(1)).into_iter()).unwrap(); - let derive_1b = pair.derive(Some(DeriveJunction::soft(1)).into_iter()).unwrap(); - let derive_2 = pair.derive(Some(DeriveJunction::soft(2)).into_iter()).unwrap(); + let derive_1 = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0; + let derive_1b = pair.derive(Some(DeriveJunction::soft(1)).into_iter(), None).unwrap().0; + let derive_2 = pair.derive(Some(DeriveJunction::soft(2)).into_iter(), None).unwrap().0; assert_eq!(derive_1.public(), derive_1b.public()); assert_ne!(derive_1.public(), derive_2.public()); } @@ -633,9 +636,9 @@ mod test { let pair = Pair::from_seed(&hex!( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" )); - let derive_1 = pair.derive(Some(DeriveJunction::hard(1)).into_iter()).unwrap(); - let derive_1b = pair.derive(Some(DeriveJunction::hard(1)).into_iter()).unwrap(); - let derive_2 = pair.derive(Some(DeriveJunction::hard(2)).into_iter()).unwrap(); + let derive_1 = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0; + let derive_1b = pair.derive(Some(DeriveJunction::hard(1)).into_iter(), None).unwrap().0; + let derive_2 = pair.derive(Some(DeriveJunction::hard(2)).into_iter(), None).unwrap().0; assert_eq!(derive_1.public(), derive_1b.public()); assert_ne!(derive_1.public(), derive_2.public()); } @@ -646,7 +649,7 @@ mod test { "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" )); let path = Some(DeriveJunction::soft(1)); - let pair_1 = pair.derive(path.clone().into_iter()).unwrap(); + let pair_1 = pair.derive(path.clone().into_iter(), None).unwrap().0; let public_1 = pair.public().derive(path.into_iter()).unwrap(); assert_eq!(pair_1.public(), public_1); } diff --git a/substrate/core/sr-io/src/lib.rs b/substrate/core/sr-io/src/lib.rs index b0274286dc2..fe5e50b3eda 100644 --- a/substrate/core/sr-io/src/lib.rs +++ b/substrate/core/sr-io/src/lib.rs @@ -220,15 +220,20 @@ export_api! { /// Verify and recover a SECP256k1 ECDSA signature. /// - `sig` is passed in RSV format. V should be either 0/1 or 27/28. - /// - returns `Err` if the signature is bad, otherwise the 64-byte pubkey (doesn't include the 0x04 prefix). + /// - returns `Err` if the signature is bad, otherwise the 64-byte raw pubkey (doesn't include the 0x04 prefix). fn secp256k1_ecdsa_recover(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 64], EcdsaVerifyError>; + + /// Verify and recover a SECP256k1 ECDSA signature. + /// - `sig` is passed in RSV format. V should be either 0/1 or 27/28. + /// - returns `Err` if the signature is bad, otherwise the 33-byte compressed pubkey. + fn secp256k1_ecdsa_recover_compressed(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 33], EcdsaVerifyError>; } } export_api! { pub(crate) trait HashingApi { /// Conduct a 256-bit Keccak hash. - fn keccak_256(data: &[u8]) -> [u8; 32] ; + fn keccak_256(data: &[u8]) -> [u8; 32]; /// Conduct a 128-bit Blake2 hash. fn blake2_128(data: &[u8]) -> [u8; 16]; diff --git a/substrate/core/sr-io/with_std.rs b/substrate/core/sr-io/with_std.rs index de40115cbe7..fdd32124c12 100644 --- a/substrate/core/sr-io/with_std.rs +++ b/substrate/core/sr-io/with_std.rs @@ -300,6 +300,16 @@ impl CryptoApi for () { res.copy_from_slice(&pubkey.serialize()[1..65]); Ok(res) } + + fn secp256k1_ecdsa_recover_compressed(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 33], EcdsaVerifyError> { + let rs = secp256k1::Signature::parse_slice(&sig[0..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let v = secp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] } as u8) + .map_err(|_| EcdsaVerifyError::BadV)?; + let pubkey = secp256k1::recover(&secp256k1::Message::parse(msg), &rs, &v) + .map_err(|_| EcdsaVerifyError::BadSignature)?; + Ok(pubkey.serialize_compressed()) + } } impl HashingApi for () { diff --git a/substrate/core/sr-io/without_std.rs b/substrate/core/sr-io/without_std.rs index 789098185e0..c3f7d62031b 100644 --- a/substrate/core/sr-io/without_std.rs +++ b/substrate/core/sr-io/without_std.rs @@ -394,12 +394,23 @@ pub mod ext { ) -> u32; /// Note: ext_secp256k1_ecdsa_recover returns 0 if the signature is correct, nonzero otherwise. + /// + /// pubkey_data must point to 64 bytes. fn ext_secp256k1_ecdsa_recover( msg_data: *const u8, sig_data: *const u8, pubkey_data: *mut u8, ) -> u32; + /// Note: ext_secp256k1_ecdsa_recover_compressed returns 0 if the signature is correct, nonzero otherwise. + /// + /// pubkey_data must point to 33 bytes. + fn ext_secp256k1_ecdsa_recover_compressed( + msg_data: *const u8, + sig_data: *const u8, + pubkey_data: *mut u8, + ) -> u32; + //================================ // Offchain-worker Context //================================ @@ -971,6 +982,19 @@ impl CryptoApi for () { _ => unreachable!("`ext_secp256k1_ecdsa_recover` only returns 0, 1, 2 or 3; qed"), } } + + fn secp256k1_ecdsa_recover_compressed(sig: &[u8; 65], msg: &[u8; 32]) -> Result<[u8; 33], EcdsaVerifyError> { + let mut pubkey = [0u8; 33]; + match unsafe { + ext_secp256k1_ecdsa_recover_compressed.get()(msg.as_ptr(), sig.as_ptr(), pubkey.as_mut_ptr()) + } { + 0 => Ok(pubkey), + 1 => Err(EcdsaVerifyError::BadRS), + 2 => Err(EcdsaVerifyError::BadV), + 3 => Err(EcdsaVerifyError::BadSignature), + _ => unreachable!("`ext_secp256k1_ecdsa_recover_compressed` only returns 0, 1, 2 or 3; qed"), + } + } } impl OffchainApi for () { diff --git a/substrate/core/sr-primitives/src/generic/unchecked_extrinsic.rs b/substrate/core/sr-primitives/src/generic/unchecked_extrinsic.rs index d50314f33bf..4af8d9a95b9 100644 --- a/substrate/core/sr-primitives/src/generic/unchecked_extrinsic.rs +++ b/substrate/core/sr-primitives/src/generic/unchecked_extrinsic.rs @@ -21,7 +21,7 @@ use rstd::fmt; use runtime_io::blake2_256; use codec::{Decode, Encode, EncodeLike, Input, Error}; use crate::{ - traits::{self, Member, MaybeDisplay, SignedExtension, Checkable, Extrinsic}, + traits::{self, Member, MaybeDisplay, SignedExtension, Checkable, Extrinsic, IdentifyAccount}, generic::CheckedExtrinsic, transaction_validity::{TransactionValidityError, InvalidTransaction}, }; @@ -98,7 +98,8 @@ for where Address: Member + MaybeDisplay, Call: Encode + Member, - Signature: Member + traits::Verify<Signer=AccountId>, + Signature: Member + traits::Verify, + <Signature as traits::Verify>::Signer: IdentifyAccount<AccountId=AccountId>, Extra: SignedExtension<AccountId=AccountId>, AccountId: Member + MaybeDisplay, Lookup: traits::Lookup<Source=Address, Target=AccountId>, @@ -284,17 +285,26 @@ mod tests { use super::*; use runtime_io::blake2_256; use crate::codec::{Encode, Decode}; - use crate::traits::{SignedExtension, IdentityLookup}; + use crate::traits::{SignedExtension, IdentifyAccount, IdentityLookup}; use serde::{Serialize, Deserialize}; type TestContext = IdentityLookup<u64>; + #[derive(Eq, PartialEq, Clone, Copy, Debug, Serialize, Deserialize, Encode, Decode)] + pub struct TestSigner(pub u64); + impl From<u64> for TestSigner { fn from(x: u64) -> Self { Self(x) } } + impl From<TestSigner> for u64 { fn from(x: TestSigner) -> Self { x.0 } } + impl IdentifyAccount for TestSigner { + type AccountId = u64; + fn into_account(self) -> u64 { self.into() } + } + #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Encode, Decode)] struct TestSig(u64, Vec<u8>); impl traits::Verify for TestSig { - type Signer = u64; - fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool { - *signer == self.0 && msg.get() == &self.1[..] + type Signer = TestSigner; + fn verify<L: traits::Lazy<[u8]>>(&self, mut msg: L, signer: &u64) -> bool { + signer == &self.0 && msg.get() == &self.1[..] } } diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index ea295fbea61..84f7bbcff64 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -42,7 +42,7 @@ pub use runtime_io::{StorageOverlay, ChildrenStorageOverlay}; use rstd::prelude::*; use rstd::convert::TryFrom; -use primitives::{crypto, ed25519, sr25519, hash::{H256, H512}}; +use primitives::{crypto, ed25519, sr25519, ecdsa, hash::{H256, H512}}; use codec::{Encode, Decode}; #[cfg(feature = "std")] @@ -59,7 +59,7 @@ pub mod weights; pub use generic::{DigestItem, Digest}; /// Re-export this since it's part of the API of this crate. -pub use primitives::{TypeId, crypto::{key_types, KeyTypeId, CryptoType}}; +pub use primitives::{TypeId, crypto::{key_types, KeyTypeId, CryptoType, AccountId32}}; pub use app_crypto::RuntimeAppPublic; /// Re-export `RuntimeDebug`, to avoid dependency clutter. @@ -117,6 +117,7 @@ macro_rules! create_runtime_str { #[cfg(feature = "std")] pub use serde::{Serialize, Deserialize, de::DeserializeOwned}; +use crate::traits::IdentifyAccount; /// Complex storage builder stuff. #[cfg(feature = "std")] @@ -175,6 +176,8 @@ pub enum MultiSignature { Ed25519(ed25519::Signature), /// An Sr25519 signature. Sr25519(sr25519::Signature), + /// An ECDSA/SECP256k1 signature. + Ecdsa(ecdsa::Signature), } impl From<ed25519::Signature> for MultiSignature { @@ -189,6 +192,12 @@ impl From<sr25519::Signature> for MultiSignature { } } +impl From<ecdsa::Signature> for MultiSignature { + fn from(x: ecdsa::Signature) -> Self { + MultiSignature::Ecdsa(x) + } +} + impl Default for MultiSignature { fn default() -> Self { MultiSignature::Ed25519(Default::default()) @@ -203,6 +212,8 @@ pub enum MultiSigner { Ed25519(ed25519::Public), /// An Sr25519 identity. Sr25519(sr25519::Public), + /// An SECP256k1/ECDSA identity (actually, the Blake2 hash of the pub key). + Ecdsa(ecdsa::Public), } impl Default for MultiSigner { @@ -224,6 +235,18 @@ impl AsRef<[u8]> for MultiSigner { match *self { MultiSigner::Ed25519(ref who) => who.as_ref(), MultiSigner::Sr25519(ref who) => who.as_ref(), + MultiSigner::Ecdsa(ref who) => who.as_ref(), + } + } +} + +impl traits::IdentifyAccount for MultiSigner { + type AccountId = AccountId32; + fn into_account(self) -> AccountId32 { + match self { + MultiSigner::Ed25519(who) => <[u8; 32]>::from(who).into(), + MultiSigner::Sr25519(who) => <[u8; 32]>::from(who).into(), + MultiSigner::Ecdsa(who) => runtime_io::blake2_256(who.as_ref()).into(), } } } @@ -240,23 +263,37 @@ impl From<sr25519::Public> for MultiSigner { } } - #[cfg(feature = "std")] +impl From<ecdsa::Public> for MultiSigner { + fn from(x: ecdsa::Public) -> Self { + MultiSigner::Ecdsa(x) + } +} + +#[cfg(feature = "std")] impl std::fmt::Display for MultiSigner { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { MultiSigner::Ed25519(ref who) => write!(fmt, "ed25519: {}", who), MultiSigner::Sr25519(ref who) => write!(fmt, "sr25519: {}", who), + MultiSigner::Ecdsa(ref who) => write!(fmt, "ecdsa: {}", who), } } } impl Verify for MultiSignature { type Signer = MultiSigner; - fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::Signer) -> bool { + fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId32) -> bool { + use primitives::crypto::Public; match (self, signer) { - (MultiSignature::Ed25519(ref sig), &MultiSigner::Ed25519(ref who)) => sig.verify(msg, who), - (MultiSignature::Sr25519(ref sig), &MultiSigner::Sr25519(ref who)) => sig.verify(msg, who), - _ => false, + (MultiSignature::Ed25519(ref sig), who) => sig.verify(msg, &ed25519::Public::from_slice(who.as_ref())), + (MultiSignature::Sr25519(ref sig), who) => sig.verify(msg, &sr25519::Public::from_slice(who.as_ref())), + (MultiSignature::Ecdsa(ref sig), who) => { + let m = runtime_io::blake2_256(msg.get()); + match runtime_io::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { + Ok(pubkey) => &runtime_io::blake2_256(pubkey.as_ref()) == <dyn AsRef<[u8; 32]>>::as_ref(who), + _ => false, + } + } } } } @@ -269,12 +306,13 @@ pub struct AnySignature(H512); impl Verify for AnySignature { type Signer = sr25519::Public; fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &sr25519::Public) -> bool { + use primitives::crypto::Public; + let msg = msg.get(); sr25519::Signature::try_from(self.0.as_fixed_bytes().as_ref()) - .map(|s| runtime_io::sr25519_verify(&s, msg.get(), &signer)) + .map(|s| s.verify(msg, signer)) .unwrap_or(false) || ed25519::Signature::try_from(self.0.as_fixed_bytes().as_ref()) - .and_then(|s| ed25519::Public::try_from(signer.0.as_ref()).map(|p| (s, p))) - .map(|(s, p)| runtime_io::ed25519_verify(&s, msg.get(), &p)) + .map(|s| s.verify(msg, &ed25519::Public::from_slice(signer.as_ref()))) .unwrap_or(false) } } @@ -398,7 +436,7 @@ impl From<&'static str> for DispatchError { /// Verify a signature on an encoded value in a lazy manner. This can be /// an optimization if the signature scheme has an "unsigned" escape hash. -pub fn verify_encoded_lazy<V: Verify, T: codec::Encode>(sig: &V, item: &T, signer: &V::Signer) -> bool { +pub fn verify_encoded_lazy<V: Verify, T: codec::Encode>(sig: &V, item: &T, signer: &<V::Signer as IdentifyAccount>::AccountId) -> bool { // The `Lazy<T>` trait expresses something like `X: FnMut<Output = for<'a> &'a T>`. // unfortunately this is a lifetime relationship that can't // be expressed without generic associated types, better unification of HRTBs in type position, diff --git a/substrate/core/sr-primitives/src/traits.rs b/substrate/core/sr-primitives/src/traits.rs index 0497f094d26..4586a84511f 100644 --- a/substrate/core/sr-primitives/src/traits.rs +++ b/substrate/core/sr-primitives/src/traits.rs @@ -50,46 +50,84 @@ impl<'a> Lazy<[u8]> for &'a [u8] { fn get(&mut self) -> &[u8] { &**self } } +/// Some type that is able to be collapsed into an account ID. It is not possible to recreate the original value from +/// the account ID. +pub trait IdentifyAccount { + /// The account ID that this can be transformed into. + type AccountId; + /// Transform into an account. + fn into_account(self) -> Self::AccountId; +} + +impl IdentifyAccount for primitives::ed25519::Public { + type AccountId = Self; + fn into_account(self) -> Self { self } +} + +impl IdentifyAccount for primitives::sr25519::Public { + type AccountId = Self; + fn into_account(self) -> Self { self } +} + +impl IdentifyAccount for primitives::ecdsa::Public { + type AccountId = Self; + fn into_account(self) -> Self { self } +} + /// Means of signature verification. pub trait Verify { /// Type of the signer. - type Signer; + type Signer: IdentifyAccount; /// Verify a signature. Return `true` if signature is valid for the value. - fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::Signer) -> bool; + fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &<Self::Signer as IdentifyAccount>::AccountId) -> bool; } impl Verify for primitives::ed25519::Signature { type Signer = primitives::ed25519::Public; - fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool { + fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &primitives::ed25519::Public) -> bool { runtime_io::ed25519_verify(self, msg.get(), signer) } } impl Verify for primitives::sr25519::Signature { type Signer = primitives::sr25519::Public; - fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &Self::Signer) -> bool { + fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &primitives::sr25519::Public) -> bool { runtime_io::sr25519_verify(self, msg.get(), signer) } } +impl Verify for primitives::ecdsa::Signature { + type Signer = primitives::ecdsa::Public; + fn verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &primitives::ecdsa::Public) -> bool { + match runtime_io::secp256k1_ecdsa_recover_compressed(self.as_ref(), &runtime_io::blake2_256(msg.get())) { + Ok(pubkey) => <dyn AsRef<[u8]>>::as_ref(signer) == &pubkey[..], + _ => false, + } + } +} + /// Means of signature verification of an application key. pub trait AppVerify { /// Type of the signer. - type Signer; + type AccountId; /// Verify a signature. Return `true` if signature is valid for the value. - fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::Signer) -> bool; + fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::AccountId) -> bool; } impl< S: Verify<Signer=<<T as AppKey>::Public as app_crypto::AppPublic>::Generic> + From<T>, T: app_crypto::Wraps<Inner=S> + app_crypto::AppKey + app_crypto::AppSignature + AsRef<S> + AsMut<S> + From<S>, -> AppVerify for T { - type Signer = <T as AppKey>::Public; - fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &Self::Signer) -> bool { +> AppVerify for T where + <S as Verify>::Signer: IdentifyAccount<AccountId = <S as Verify>::Signer>, + <<T as AppKey>::Public as app_crypto::AppPublic>::Generic: + IdentifyAccount<AccountId = <<T as AppKey>::Public as app_crypto::AppPublic>::Generic>, +{ + type AccountId = <T as AppKey>::Public; + fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &<T as AppKey>::Public) -> bool { use app_crypto::IsWrappedBy; let inner: &S = self.as_ref(); - let inner_pubkey = <Self::Signer as app_crypto::AppPublic>::Generic::from_ref(&signer); + let inner_pubkey = <<T as AppKey>::Public as app_crypto::AppPublic>::Generic::from_ref(&signer); Verify::verify(inner, msg, inner_pubkey) } } @@ -1179,6 +1217,21 @@ mod tests { use super::AccountIdConversion; use crate::codec::{Encode, Decode, Input}; + mod t { + use primitives::crypto::KeyTypeId; + use app_crypto::{app_crypto, sr25519}; + app_crypto!(sr25519, KeyTypeId(*b"test")); + } + + #[test] + fn app_verify_works() { + use t::*; + use super::AppVerify; + + let s = Signature::default(); + let _ = s.verify(&[0u8; 100][..], &Public::default()); + } + #[derive(Encode, Decode, Default, PartialEq, Debug)] struct U32Value(u32); impl super::TypeId for U32Value { diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index bc9f1e615fe..07f6946baf1 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -17,7 +17,7 @@ //! Substrate chain configurations. use chain_spec::ChainSpecExtension; -use primitives::{Pair, Public, crypto::UncheckedInto}; +use primitives::{Pair, Public, crypto::UncheckedInto, sr25519}; use serde::{Serialize, Deserialize}; use node_runtime::{ AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig, @@ -32,10 +32,13 @@ use substrate_telemetry::TelemetryEndpoints; use grandpa_primitives::{AuthorityId as GrandpaId}; use babe_primitives::{AuthorityId as BabeId}; use im_online::sr25519::{AuthorityId as ImOnlineId}; -use sr_primitives::Perbill; +use sr_primitives::{traits::Verify, Perbill}; -pub use node_primitives::{AccountId, Balance}; +pub use node_primitives::{AccountId, Balance, Signature}; pub use node_runtime::GenesisConfig; +use sr_primitives::traits::IdentifyAccount; + +type AccountPublic = <Signature as Verify>::Signer; const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; @@ -72,9 +75,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig { let initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId)> = vec![( // 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy - hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].unchecked_into(), + hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].into(), // 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq - hex!["781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"].unchecked_into(), + hex!["781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"].into(), // 5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC hex!["9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332"].unchecked_into(), // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 @@ -83,9 +86,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(), ),( // 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2 - hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].unchecked_into(), + hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].into(), // 5Gc4vr42hH1uDZc93Nayk5G7i687bAQdHHc9unLuyeawHipF - hex!["c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"].unchecked_into(), + hex!["c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"].into(), // 5EockCXN6YkiNCDjpqqnbcqd4ad35nU4RmA1ikM4YeRN4WcE hex!["7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f"].unchecked_into(), // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ @@ -94,9 +97,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(), ),( // 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp - hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].unchecked_into(), + hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].into(), // 5FeD54vGVNpFX3PndHPXJ2MDakc462vBCD5mgtWRnWYCpZU9 - hex!["9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"].unchecked_into(), + hex!["9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"].into(), // 5E1jLYfLdUQKrFrtqoKgFrRvxM3oQPMbf6DfcsrugZZ5Bn8d hex!["5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440"].unchecked_into(), // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH @@ -105,9 +108,9 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(), ),( // 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9 - hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].unchecked_into(), + hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].into(), // 5EPQdAQ39WQNLCRjWsCk5jErsCitHiY5ZmjfWzzbXDoAoYbn - hex!["66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"].unchecked_into(), + hex!["66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"].into(), // 5DMa31Hd5u1dwoRKgC4uvqyrdK45RHv3CpwvpUC1EzuwDit4 hex!["3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef"].unchecked_into(), // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x @@ -120,7 +123,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig { let root_key: AccountId = hex![ // 5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo "9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809" - ].unchecked_into(); + ].into(); let endowed_accounts: Vec<AccountId> = vec![root_key.clone()]; @@ -154,12 +157,18 @@ pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Pu .public() } +/// Helper function to generate an account ID from seed +pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where + AccountPublic: From<<TPublic::Pair as Pair>::Public> +{ + AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account() +} /// Helper function to generate stash, controller and session key from seed pub fn get_authority_keys_from_seed(seed: &str) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId) { ( - get_from_seed::<AccountId>(&format!("{}//stash", seed)), - get_from_seed::<AccountId>(seed), + get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)), + get_account_id_from_seed::<sr25519::Public>(seed), get_from_seed::<GrandpaId>(seed), get_from_seed::<BabeId>(seed), get_from_seed::<ImOnlineId>(seed), @@ -175,18 +184,18 @@ pub fn testnet_genesis( ) -> GenesisConfig { let endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(|| { vec![ - get_from_seed::<AccountId>("Alice"), - get_from_seed::<AccountId>("Bob"), - get_from_seed::<AccountId>("Charlie"), - get_from_seed::<AccountId>("Dave"), - get_from_seed::<AccountId>("Eve"), - get_from_seed::<AccountId>("Ferdie"), - get_from_seed::<AccountId>("Alice//stash"), - get_from_seed::<AccountId>("Bob//stash"), - get_from_seed::<AccountId>("Charlie//stash"), - get_from_seed::<AccountId>("Dave//stash"), - get_from_seed::<AccountId>("Eve//stash"), - get_from_seed::<AccountId>("Ferdie//stash"), + get_account_id_from_seed::<sr25519::Public>("Alice"), + get_account_id_from_seed::<sr25519::Public>("Bob"), + get_account_id_from_seed::<sr25519::Public>("Charlie"), + get_account_id_from_seed::<sr25519::Public>("Dave"), + get_account_id_from_seed::<sr25519::Public>("Eve"), + get_account_id_from_seed::<sr25519::Public>("Ferdie"), + get_account_id_from_seed::<sr25519::Public>("Alice//stash"), + get_account_id_from_seed::<sr25519::Public>("Bob//stash"), + get_account_id_from_seed::<sr25519::Public>("Charlie//stash"), + get_account_id_from_seed::<sr25519::Public>("Dave//stash"), + get_account_id_from_seed::<sr25519::Public>("Eve//stash"), + get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"), ] }); @@ -272,7 +281,7 @@ fn development_config_genesis() -> GenesisConfig { vec![ get_authority_keys_from_seed("Alice"), ], - get_from_seed::<AccountId>("Alice"), + get_account_id_from_seed::<sr25519::Public>("Alice"), None, true, ) @@ -298,7 +307,7 @@ fn local_testnet_genesis() -> GenesisConfig { get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob"), ], - get_from_seed::<AccountId>("Alice"), + get_account_id_from_seed::<sr25519::Public>("Alice"), None, false, ) @@ -330,7 +339,7 @@ pub(crate) mod tests { vec![ get_authority_keys_from_seed("Alice"), ], - get_from_seed::<AccountId>("Alice"), + get_account_id_from_seed::<sr25519::Public>("Alice"), None, false, ) diff --git a/substrate/node/cli/src/factory_impl.rs b/substrate/node/cli/src/factory_impl.rs index 84c24f2af09..48fb7b237f1 100644 --- a/substrate/node/cli/src/factory_impl.rs +++ b/substrate/node/cli/src/factory_impl.rs @@ -25,16 +25,21 @@ use codec::{Encode, Decode}; use keyring::sr25519::Keyring; use node_runtime::{ Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall, ExistentialDeposit, - MinimumPeriod, + MinimumPeriod }; +use node_primitives::Signature; use primitives::{sr25519, crypto::Pair}; -use sr_primitives::{generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension}}; +use sr_primitives::{ + generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension, Verify, IdentifyAccount} +}; use transaction_factory::RuntimeAdapter; use transaction_factory::modes::Mode; use inherents::InherentData; use timestamp; use finality_tracker; +type AccountPublic = <Signature as Verify>::Signer; + pub struct FactoryState<N> { block_no: N, @@ -167,7 +172,7 @@ impl RuntimeAdapter for FactoryState<Number> { } fn master_account_id() -> Self::AccountId { - Keyring::Alice.pair().public() + Keyring::Alice.to_account_id() } fn master_account_secret() -> Self::Secret { @@ -177,7 +182,7 @@ impl RuntimeAdapter for FactoryState<Number> { /// Generates a random `AccountId` from `seed`. fn gen_random_account_id(seed: &Self::Number) -> Self::AccountId { let pair: sr25519::Pair = sr25519::Pair::from_seed(&gen_seed_bytes(*seed)); - pair.public().into() + AccountPublic::from(pair.public()).into_account() } /// Generates a random `Secret` from `seed`. diff --git a/substrate/node/cli/src/service.rs b/substrate/node/cli/src/service.rs index 0f4a098f3d0..03d31c43924 100644 --- a/substrate/node/cli/src/service.rs +++ b/substrate/node/cli/src/service.rs @@ -330,17 +330,15 @@ mod tests { use consensus_common::{ Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport, }; - use node_primitives::{Block, DigestItem}; - use node_runtime::{BalancesCall, Call, UncheckedExtrinsic}; + use node_primitives::{Block, DigestItem, Signature}; + use node_runtime::{BalancesCall, Call, UncheckedExtrinsic, Address}; use node_runtime::constants::{currency::CENTS, time::SLOT_DURATION}; use codec::{Encode, Decode}; - use primitives::{ - crypto::Pair as CryptoPair, - sr25519::Public as AddressPublic, H256, - }; + use primitives::{crypto::Pair as CryptoPair, H256}; use sr_primitives::{ generic::{BlockId, Era, Digest, SignedPayload}, traits::Block as BlockT, + traits::Verify, OpaqueExtrinsic, }; use timestamp; @@ -348,6 +346,9 @@ mod tests { use keyring::AccountKeyring; use substrate_service::{AbstractService, Roles}; use crate::service::new_full; + use sr_primitives::traits::IdentifyAccount; + + type AccountPublic = <Signature as Verify>::Signer; #[cfg(feature = "rhd")] fn test_sync() { @@ -518,8 +519,8 @@ mod tests { }, |service, _| { let amount = 5 * CENTS; - let to = AddressPublic::from_raw(bob.public().0); - let from = AddressPublic::from_raw(charlie.public().0); + let to: Address = AccountPublic::from(bob.public()).into_account().into(); + let from: Address = AccountPublic::from(charlie.public()).into_account().into(); let genesis_hash = service.client().block_hash(0).unwrap().unwrap(); let best_block_id = BlockId::number(service.client().info().chain.best_number); let version = service.client().runtime_version_at(&best_block_id).unwrap().spec_version; diff --git a/substrate/node/primitives/src/lib.rs b/substrate/node/primitives/src/lib.rs index 6b128db4f3d..b6a5ec05655 100644 --- a/substrate/node/primitives/src/lib.rs +++ b/substrate/node/primitives/src/lib.rs @@ -21,21 +21,20 @@ #![cfg_attr(not(feature = "std"), no_std)] use sr_primitives::{ - generic, traits::{Verify, BlakeTwo256}, OpaqueExtrinsic, AnySignature + generic, traits::{Verify, BlakeTwo256, IdentifyAccount}, OpaqueExtrinsic, MultiSignature }; /// An index to a block. pub type BlockNumber = u32; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = AnySignature; +pub type Signature = MultiSignature; /// Some way of identifying an account on the chain. We intentionally make it equivalent /// to the public key of our transaction signing scheme. -pub type AccountId = <Signature as Verify>::Signer; +pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId; -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... +/// The type for looking up accounts. We don't expect more than 4 billion of them. pub type AccountIndex = u32; /// Balance of an account. diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index a198877552d..d7008dd8932 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -42,7 +42,7 @@ use sr_primitives::curve::PiecewiseLinear; use sr_primitives::transaction_validity::TransactionValidity; use sr_primitives::weights::Weight; use sr_primitives::traits::{ - self, BlakeTwo256, Block as BlockT, NumberFor, StaticLookup, SaturatedConversion, + BlakeTwo256, Block as BlockT, NumberFor, StaticLookup, }; use version::RuntimeVersion; #[cfg(any(feature = "std", test))] @@ -83,8 +83,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: 182, - impl_version: 182, + spec_version: 183, + impl_version: 183, apis: RUNTIME_API_VERSIONS, }; @@ -455,34 +455,6 @@ impl finality_tracker::Trait for Runtime { type ReportLatency = ReportLatency; } -impl system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtime { - type Signature = Signature; - - fn create_transaction<F: system::offchain::Signer<AccountId, Self::Signature>>( - call: Call, - account: AccountId, - index: Index, - ) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> { - let period = 1 << 8; - let current_block = System::block_number().saturated_into::<u64>(); - let tip = 0; - let extra: SignedExtra = ( - system::CheckVersion::<Runtime>::new(), - system::CheckGenesis::<Runtime>::new(), - system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)), - system::CheckNonce::<Runtime>::from(index), - system::CheckWeight::<Runtime>::new(), - transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip), - Default::default(), - ); - let raw_payload = SignedPayload::new(call, extra).ok()?; - let signature = F::sign(account.clone(), &raw_payload)?; - let address = Indices::unlookup(account); - let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature, extra))) - } -} - construct_runtime!( pub enum Runtime where Block = Block, @@ -693,28 +665,3 @@ impl_runtime_apis! { } } } - -#[cfg(test)] -mod tests { - use super::*; - use sr_primitives::app_crypto::RuntimeAppPublic; - use system::offchain::SubmitSignedTransaction; - - fn is_submit_signed_transaction<T, Signer>(_arg: T) where - T: SubmitSignedTransaction< - Runtime, - Call, - Extrinsic=UncheckedExtrinsic, - CreateTransaction=Runtime, - Signer=Signer, - >, - Signer: RuntimeAppPublic + From<AccountId>, - Signer::Signature: Into<Signature>, - {} - - #[test] - fn validate_bounds() { - let x = SubmitTransaction::default(); - is_submit_signed_transaction(x); - } -} diff --git a/substrate/node/testing/src/keyring.rs b/substrate/node/testing/src/keyring.rs index 853ca0ef6d7..ca44a53880f 100644 --- a/substrate/node/testing/src/keyring.rs +++ b/substrate/node/testing/src/keyring.rs @@ -23,9 +23,7 @@ use sr_primitives::generic::Era; use codec::Encode; /// Alice's account id. -pub fn alice() -> AccountId { - AccountKeyring::Alice.into() -} +pub fn alice() -> AccountId { AccountKeyring::Alice.into() } /// Bob's account id. pub fn bob() -> AccountId { @@ -82,7 +80,7 @@ pub fn sign(xt: CheckedExtrinsic, version: u32, genesis_hash: [u8; 32]) -> Unche match xt.signed { Some((signed, extra)) => { let payload = (xt.function, extra.clone(), version, genesis_hash, genesis_hash); - let key = AccountKeyring::from_public(&signed).unwrap(); + let key = AccountKeyring::from_account_id(&signed).unwrap(); let signature = payload.using_encoded(|b| { if b.len() > 256 { key.sign(&sr_io::blake2_256(b)) diff --git a/substrate/srml/system/src/offchain.rs b/substrate/srml/system/src/offchain.rs index e234c74c089..468c88af39d 100644 --- a/substrate/srml/system/src/offchain.rs +++ b/substrate/srml/system/src/offchain.rs @@ -21,71 +21,24 @@ use sr_primitives::app_crypto::RuntimeAppPublic; use sr_primitives::traits::Extrinsic as ExtrinsicT; /// A trait responsible for signing a payload using given account. -pub trait Signer<Account, Signature> { +pub trait Signer<Public, Signature> { /// Sign any encodable payload with given account and produce a signature. /// /// Returns `Some` if signing succeeded and `None` in case the `account` couldn't be used. - fn sign<Payload: Encode>(account: Account, payload: &Payload) -> Option<Signature>; + fn sign<Payload: Encode>(public: Public, payload: &Payload) -> Option<Signature>; } -impl<Account, Signature, AppPublic> Signer<Account, Signature> for AppPublic where - AppPublic: RuntimeAppPublic + From<Account>, +impl<Public, Signature, AppPublic> Signer<Public, Signature> for AppPublic where + AppPublic: RuntimeAppPublic + From<Public>, AppPublic::Signature: Into<Signature>, { - fn sign<Payload: Encode>(account: Account, raw_payload: &Payload) -> Option<Signature> { + fn sign<Payload: Encode>(public: Public, raw_payload: &Payload) -> Option<Signature> { raw_payload.using_encoded(|payload| { - AppPublic::from(account).sign(&payload).map(Into::into) + AppPublic::from(public).sign(&payload).map(Into::into) }) } } -/// Creates runtime-specific signed transaction. -pub trait CreateTransaction<T: crate::Trait, Extrinsic: ExtrinsicT> { - type Signature; - - /// Attempt to create signed extrinsic data that encodes call from given account. - /// - /// Runtime implementation is free to construct the payload to sign and the signature - /// in any way it wants. - /// Returns `None` if signed extrinsic could not be created (either because signing failed - /// or because of any other runtime-specific reason). - fn create_transaction<F: Signer<T::AccountId, Self::Signature>>( - call: Extrinsic::Call, - account: T::AccountId, - nonce: T::Index, - ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; -} - -/// A trait to sign and submit transactions in offchain calls. -pub trait SubmitSignedTransaction<T: crate::Trait, Call> { - /// Unchecked extrinsic type. - type Extrinsic: ExtrinsicT<Call=Call> + codec::Encode; - - /// A runtime-specific type to produce signed data for the extrinsic. - type CreateTransaction: CreateTransaction<T, Self::Extrinsic>; - - /// A type used to sign transactions created using `CreateTransaction`. - type Signer: Signer< - T::AccountId, - <Self::CreateTransaction as CreateTransaction<T, Self::Extrinsic>>::Signature, - >; - - /// Sign given call and submit it to the transaction pool. - /// - /// Returns `Ok` if the transaction was submitted correctly - /// and `Err` if the key for given `id` was not found or the - /// transaction was rejected from the pool. - fn sign_and_submit(call: impl Into<Call>, id: T::AccountId) -> Result<(), ()> { - let call = call.into(); - let expected = <crate::Module<T>>::account_nonce(&id); - let (call, signature_data) = Self::CreateTransaction - ::create_transaction::<Self::Signer>(call, id, expected) - .ok_or(())?; - let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?; - runtime_io::submit_transaction(xt.encode()) - } -} - /// A trait to submit unsigned transactions in offchain calls. pub trait SubmitUnsignedTransaction<T: crate::Trait, Call> { /// Unchecked extrinsic type. @@ -114,18 +67,6 @@ impl<S, C, E> Default for TransactionSubmitter<S, C, E> { } } -/// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. -impl<T, E, S, C, Call> SubmitSignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where - T: crate::Trait, - C: CreateTransaction<T, E>, - S: Signer<T::AccountId, <C as CreateTransaction<T, E>>::Signature>, - E: ExtrinsicT<Call=Call> + codec::Encode, -{ - type Extrinsic = E; - type CreateTransaction = C; - type Signer = S; -} - /// A blanket impl to use the same submitter for usigned transactions as well. impl<T, E, S, C, Call> SubmitUnsignedTransaction<T, Call> for TransactionSubmitter<S, C, E> where T: crate::Trait, diff --git a/substrate/subkey/src/cli.yml b/substrate/subkey/src/cli.yml index aa6c6d4a574..5d8cd11b1e6 100644 --- a/substrate/subkey/src/cli.yml +++ b/substrate/subkey/src/cli.yml @@ -12,6 +12,11 @@ args: long: sr25519 help: Use Schnorr/Ristretto x25519/BIP39 cryptography takes_value: false + - secp256k1: + short: k + long: secp256k1 + help: Use SECP256k1/ECDSA/BIP39 cryptography + takes_value: false - password: short: p long: password diff --git a/substrate/subkey/src/main.rs b/substrate/subkey/src/main.rs index 2596513ab28..1b537580f57 100644 --- a/substrate/subkey/src/main.rs +++ b/substrate/subkey/src/main.rs @@ -22,15 +22,15 @@ use bip39::{Language, Mnemonic, MnemonicType}; use clap::{load_yaml, App, ArgMatches}; use codec::{Decode, Encode}; use hex_literal::hex; -use node_primitives::{Balance, Hash, Index}; +use node_primitives::{Balance, Hash, Index, AccountId, Signature}; use node_runtime::{BalancesCall, Call, Runtime, SignedPayload, UncheckedExtrinsic, VERSION}; use primitives::{ crypto::{set_default_ss58_version, Ss58AddressFormat, Ss58Codec}, - ed25519, sr25519, Pair, Public, H256, hexdisplay::HexDisplay, + ed25519, sr25519, ecdsa, Pair, Public, H256, hexdisplay::HexDisplay, }; -use sr_primitives::generic::Era; +use sr_primitives::{traits::{IdentifyAccount, Verify}, generic::Era}; use std::{ - convert::TryInto, + convert::{TryInto, TryFrom}, io::{stdin, Read}, str::FromStr, }; @@ -43,8 +43,10 @@ trait Crypto: Sized { fn pair_from_suri(suri: &str, password: Option<&str>) -> Self::Pair { Self::Pair::from_string(suri, password).expect("Invalid phrase") } - fn ss58_from_pair(pair: &Self::Pair) -> String { - pair.public().to_ss58check() + fn ss58_from_pair(pair: &Self::Pair) -> String where + <Self::Pair as Pair>::Public: PublicT, + { + pair.public().into_runtime().into_account().to_ss58check() } fn public_from_pair(pair: &Self::Pair) -> Self::Public { pair.public() @@ -58,28 +60,43 @@ trait Crypto: Sized { { if let Ok((pair, seed)) = Self::Pair::from_phrase(uri, password) { let public_key = Self::public_from_pair(&pair); - println!("Secret phrase `{}` is account:\n Secret seed: {}\n Public key (hex): {}\n Address (SS58): {}", + println!("Secret phrase `{}` is account:\n \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", uri, format_seed::<Self>(seed), - format_public_key::<Self>(public_key), + format_public_key::<Self>(public_key.clone()), + format_account_id::<Self>(public_key), Self::ss58_from_pair(&pair) ); - } else if let Ok(pair) = Self::Pair::from_string(uri, password) { + } else if let Ok((pair, seed)) = Self::Pair::from_string_with_seed(uri, password) { let public_key = Self::public_from_pair(&pair); - println!( - "Secret Key URI `{}` is account:\n Public key (hex): {}\n Address (SS58): {}", + println!("Secret Key URI `{}` is account:\n \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", uri, - format_public_key::<Self>(public_key), + if let Some(seed) = seed { format_seed::<Self>(seed) } else { "n/a".into() }, + format_public_key::<Self>(public_key.clone()), + format_account_id::<Self>(public_key), Self::ss58_from_pair(&pair) ); } else if let Ok((public_key, v)) = <Self::Pair as Pair>::Public::from_string_with_version(uri) { let v = network_override.unwrap_or(v); - println!("Public Key URI `{}` is account:\n Network ID/version: {}\n Public key (hex): {}\n Address (SS58): {}", + println!("Public Key URI `{}` is account:\n \ + Network ID/version: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", uri, String::from(v), format_public_key::<Self>(public_key.clone()), + format_account_id::<Self>(public_key.clone()), public_key.to_ss58check_with_version(v) ); } else { @@ -106,17 +123,37 @@ impl Crypto for Sr25519 { type Public = sr25519::Public; } +struct Ecdsa; + +impl Crypto for Ecdsa { + type Pair = ecdsa::Pair; + type Public = ecdsa::Public; +} + type SignatureOf<C> = <<C as Crypto>::Pair as Pair>::Signature; type PublicOf<C> = <<C as Crypto>::Pair as Pair>::Public; type SeedOf<C> = <<C as Crypto>::Pair as Pair>::Seed; +type AccountPublic = <Signature as Verify>::Signer; -trait SignatureT: AsRef<[u8]> + AsMut<[u8]> + Default {} -trait PublicT: Sized + AsRef<[u8]> + Ss58Codec {} +trait SignatureT: AsRef<[u8]> + AsMut<[u8]> + Default { + /// Converts the signature into a runtime account signature, if possible. If not possible, bombs out. + fn into_runtime(self) -> Signature { + panic!("This cryptography isn't supported for this runtime.") + } +} +trait PublicT: Sized + AsRef<[u8]> + Ss58Codec { + /// Converts the public key into a runtime account public key, if possible. If not possible, bombs out. + fn into_runtime(self) -> AccountPublic { + panic!("This cryptography isn't supported for this runtime.") + } +} -impl SignatureT for sr25519::Signature {} -impl SignatureT for ed25519::Signature {} -impl PublicT for sr25519::Public {} -impl PublicT for ed25519::Public {} +impl SignatureT for sr25519::Signature { fn into_runtime(self) -> Signature { self.into() } } +impl SignatureT for ed25519::Signature { fn into_runtime(self) -> Signature { self.into() } } +impl SignatureT for ecdsa::Signature { fn into_runtime(self) -> Signature { self.into() } } +impl PublicT for sr25519::Public { fn into_runtime(self) -> AccountPublic { self.into() } } +impl PublicT for ed25519::Public { fn into_runtime(self) -> AccountPublic { self.into() } } +impl PublicT for ecdsa::Public { fn into_runtime(self) -> AccountPublic { self.into() } } fn main() { let yaml = load_yaml!("cli.yml"); @@ -125,10 +162,12 @@ fn main() { .get_matches(); if matches.is_present("ed25519") { - execute::<Ed25519>(matches) - } else { - execute::<Sr25519>(matches) + return execute::<Ed25519>(matches) } + if matches.is_present("secp256k1") { + return execute::<Ecdsa>(matches) + } + return execute::<Sr25519>(matches) } fn execute<C: Crypto>(matches: ArgMatches) @@ -165,7 +204,7 @@ where ("verify", Some(matches)) => { let should_decode = matches.is_present("hex"); let message = read_message_from_stdin(should_decode); - let is_valid_signature = do_verify::<C>(matches, message, password); + let is_valid_signature = do_verify::<C>(matches, message); if is_valid_signature { println!("Signature verifies correctly."); } else { @@ -182,20 +221,20 @@ where C::print_from_uri(&formated_seed, None, maybe_network); } ("transfer", Some(matches)) => { - let signer = read_pair::<Sr25519>(matches.value_of("from"), password); + let signer = read_pair::<C>(matches.value_of("from"), password); let index = read_required_parameter::<Index>(matches, "index"); let genesis_hash = read_genesis_hash(matches); - let to = read_public_key::<Sr25519>(matches.value_of("to"), password); + let to: AccountId = read_account_id(matches.value_of("to")); let amount = read_required_parameter::<Balance>(matches, "amount"); let function = Call::Balances(BalancesCall::transfer(to.into(), amount)); - let extrinsic = create_extrinsic(function, index, signer, genesis_hash); + let extrinsic = create_extrinsic::<C>(function, index, signer, genesis_hash); print_extrinsic(extrinsic); } ("sign-transaction", Some(matches)) => { - let signer = read_pair::<Sr25519>(matches.value_of("suri"), password); + let signer = read_pair::<C>(matches.value_of("suri"), password); let index = read_required_parameter::<Index>(matches, "nonce"); let genesis_hash = read_genesis_hash(matches); @@ -205,7 +244,7 @@ where .and_then(|x| Decode::decode(&mut &x[..]).ok()) .unwrap(); - let extrinsic = create_extrinsic(function, index, signer, genesis_hash); + let extrinsic = create_extrinsic::<C>(function, index, signer, genesis_hash); print_extrinsic(extrinsic); } @@ -236,13 +275,13 @@ where format_signature::<C>(&signature) } -fn do_verify<C: Crypto>(matches: &ArgMatches, message: Vec<u8>, password: Option<&str>) -> bool +fn do_verify<C: Crypto>(matches: &ArgMatches, message: Vec<u8>) -> bool where SignatureOf<C>: SignatureT, PublicOf<C>: PublicT, { let signature = read_signature::<C>(matches); - let pubkey = read_public_key::<C>(matches.value_of("uri"), password); + let pubkey = read_public_key::<C>(matches.value_of("uri")); <<C as Crypto>::Pair as Pair>::verify(&signature, &message, &pubkey) } @@ -305,9 +344,7 @@ where signature } -fn read_public_key<C: Crypto>(matched_uri: Option<&str>, password: Option<&str>) -> PublicOf<C> -where - SignatureOf<C>: SignatureT, +fn read_public_key<C: Crypto>(matched_uri: Option<&str>) -> PublicOf<C> where PublicOf<C>: PublicT, { let uri = matched_uri.expect("parameter is required; thus it can't be None; qed"); @@ -319,13 +356,28 @@ where if let Ok(pubkey_vec) = hex::decode(uri) { <C as Crypto>::Public::from_slice(pubkey_vec.as_slice()) } else { - <C as Crypto>::Pair::from_string(uri, password) + <C as Crypto>::Public::from_string(uri) .ok() - .map(|p| p.public()) .expect("Invalid URI; expecting either a secret URI or a public URI.") } } +fn read_account_id(matched_uri: Option<&str>) -> AccountId { + let uri = matched_uri.expect("parameter is required; thus it can't be None; qed"); + let uri = if uri.starts_with("0x") { + &uri[2..] + } else { + uri + }; + if let Ok(data_vec) = hex::decode(uri) { + AccountId::try_from(data_vec.as_slice()) + .expect("Invalid hex length for account ID; should be 32 bytes") + } else { + AccountId::from_ss58check(uri).ok() + .expect("Invalid SS58-check address given for account ID.") + } +} + fn read_pair<C: Crypto>( matched_suri: Option<&str>, password: Option<&str>, @@ -350,12 +402,21 @@ fn format_public_key<C: Crypto>(public_key: PublicOf<C>) -> String { format!("0x{}", HexDisplay::from(&public_key.as_ref())) } -fn create_extrinsic( +fn format_account_id<C: Crypto>(public_key: PublicOf<C>) -> String where + PublicOf<C>: PublicT, +{ + format!("0x{}", HexDisplay::from(&public_key.into_runtime().into_account().as_ref())) +} + +fn create_extrinsic<C: Crypto>( function: Call, index: Index, - signer: <Sr25519 as Crypto>::Pair, + signer: C::Pair, genesis_hash: H256, -) -> UncheckedExtrinsic { +) -> UncheckedExtrinsic where + PublicOf<C>: PublicT, + SignatureOf<C>: SignatureT, +{ let extra = |i: Index, f: Balance| { ( system::CheckVersion::<Runtime>::new(), @@ -380,13 +441,14 @@ fn create_extrinsic( (), ), ); - let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); + let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime(); + let signer = signer.public().into_runtime(); let (function, extra, _) = raw_payload.deconstruct(); UncheckedExtrinsic::new_signed( function, - signer.public().into(), - signature.into(), + signer.into_account().into(), + signature, extra, ) } @@ -439,7 +501,7 @@ mod tests { let matches = App::from_yaml(yaml).get_matches_from(arg_vec); let matches = matches.subcommand().1.unwrap(); - assert!(do_verify::<CryptoType>(matches, message, password)); + assert!(do_verify::<CryptoType>(matches, message)); } #[test] diff --git a/substrate/subkey/src/vanity.rs b/substrate/subkey/src/vanity.rs index b612bc470f3..835001a0aa9 100644 --- a/substrate/subkey/src/vanity.rs +++ b/substrate/subkey/src/vanity.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see <http://www.gnu.org/licenses/>. -use super::Crypto; +use super::{PublicOf, PublicT, Crypto}; use primitives::Pair; use rand::{rngs::OsRng, RngCore}; @@ -62,7 +62,9 @@ fn calculate_score(_desired: &str, key: &str) -> usize { 0 } -pub(super) fn generate_key<C: Crypto>(desired: &str) -> Result<KeyPair<C>, &str> { +pub(super) fn generate_key<C: Crypto>(desired: &str) -> Result<KeyPair<C>, &str> where + PublicOf<C>: PublicT, +{ if desired.is_empty() { return Err("Pattern must not be empty"); } diff --git a/substrate/test-utils/chain-spec-builder/src/main.rs b/substrate/test-utils/chain-spec-builder/src/main.rs index 22c5253b06f..8fdd282345b 100644 --- a/substrate/test-utils/chain-spec-builder/src/main.rs +++ b/substrate/test-utils/chain-spec-builder/src/main.rs @@ -22,7 +22,7 @@ use structopt::StructOpt; use keystore::{Store as Keystore}; use node_cli::chain_spec::{self, AccountId}; -use primitives::{crypto::{Public, Ss58Codec}, traits::BareCryptoStore}; +use primitives::{sr25519, crypto::{Public, Ss58Codec}, traits::BareCryptoStore}; /// A utility to easily create a testnet chain spec definition with a given set /// of authorities and endowed accounts and/or generate random accounts. @@ -237,11 +237,11 @@ fn main() -> Result<(), String> { } let endowed_accounts = endowed_seeds.iter().map(|seed| { - chain_spec::get_from_seed::<AccountId>(seed) + chain_spec::get_account_id_from_seed::<sr25519::Public>(seed) .to_ss58check() }).collect(); - let sudo_account = chain_spec::get_from_seed::<AccountId>(&sudo_seed) + let sudo_account = chain_spec::get_account_id_from_seed::<sr25519::Public>(&sudo_seed) .to_ss58check(); (authority_seeds, endowed_accounts, sudo_account) -- GitLab