mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 01:11:10 +00:00
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
This commit is contained in:
Generated
+2
@@ -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)",
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")?;
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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[..]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+104
-42
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user