mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 20:31:13 +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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user