mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 10:31:04 +00:00
SS58 versioning (Network IDs) (#3147)
* Introduce network IDs for SS58 * Fix * Allow numeric overrides. * Improve docs * String rather than str * Comment out code that will become valid after other PR * Fix
This commit is contained in:
Generated
+2
@@ -4631,8 +4631,10 @@ dependencies = [
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex-literal 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"impl-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-codec 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"primitive-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -28,6 +28,8 @@ hex = { version = "0.3", optional = true }
|
||||
regex = { version = "1.1", optional = true }
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
zeroize = { version = "0.9.2", default-features = false }
|
||||
lazy_static = { version = "1.3", optional = true }
|
||||
parking_lot = { version = "0.8", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
substrate-serializer = { path = "../serializer" }
|
||||
@@ -47,6 +49,8 @@ bench = false
|
||||
default = ["std"]
|
||||
std = [
|
||||
"wasmi",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"primitive-types/std",
|
||||
"primitive-types/serde",
|
||||
"primitive-types/byteorder",
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
//! Cryptographic utilities.
|
||||
// end::description[]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
#[cfg(feature = "std")]
|
||||
use parking_lot::Mutex;
|
||||
#[cfg(feature = "std")]
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
#[cfg(feature = "std")]
|
||||
@@ -243,12 +247,36 @@ pub enum PublicError {
|
||||
#[cfg(feature = "std")]
|
||||
pub trait Ss58Codec: Sized {
|
||||
/// Some if the string is a properly encoded SS58Check address.
|
||||
fn from_ss58check(s: &str) -> Result<Self, PublicError>;
|
||||
fn from_ss58check(s: &str) -> Result<Self, PublicError> {
|
||||
Self::from_ss58check_with_version(s)
|
||||
.and_then(|(r, v)| match v {
|
||||
Ss58AddressFormat::SubstrateAccountDirect => Ok(r),
|
||||
v if v == *DEFAULT_VERSION.lock() => Ok(r),
|
||||
_ => Err(PublicError::UnknownVersion),
|
||||
})
|
||||
}
|
||||
/// Some if the string is a properly encoded SS58Check address.
|
||||
fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError>;
|
||||
/// Some if the string is a properly encoded SS58Check address, optionally with
|
||||
/// a derivation path following.
|
||||
fn from_string(s: &str) -> Result<Self, PublicError> { Self::from_ss58check(s) }
|
||||
fn from_string(s: &str) -> Result<Self, PublicError> {
|
||||
Self::from_string_with_version(s)
|
||||
.and_then(|(r, v)| match v {
|
||||
Ss58AddressFormat::SubstrateAccountDirect => Ok(r),
|
||||
v if v == *DEFAULT_VERSION.lock() => Ok(r),
|
||||
_ => Err(PublicError::UnknownVersion),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the ss58-check string for this key.
|
||||
fn to_ss58check(&self) -> String;
|
||||
fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String;
|
||||
/// 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
|
||||
/// a derivation path following.
|
||||
fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> {
|
||||
Self::from_ss58check_with_version(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
@@ -273,9 +301,92 @@ fn ss58hash(data: &[u8]) -> blake2_rfc::blake2b::Blake2bResult {
|
||||
context.finalize()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEFAULT_VERSION: Mutex<Ss58AddressFormat>
|
||||
= Mutex::new(Ss58AddressFormat::SubstrateAccountDirect);
|
||||
}
|
||||
|
||||
/// A known address (sub)format/network ID for SS58.
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Ss58AddressFormat {
|
||||
/// Any Substrate network, direct checksum, standard account (*25519).
|
||||
SubstrateAccountDirect,
|
||||
/// Polkadot Relay-chain, direct checksum, standard account (*25519).
|
||||
PolkadotAccountDirect,
|
||||
/// Kusama Relay-chain, direct checksum, standard account (*25519).
|
||||
KusamaAccountDirect,
|
||||
/// Use a manually provided numeric value.
|
||||
Custom(u8),
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl From<Ss58AddressFormat> for u8 {
|
||||
fn from(x: Ss58AddressFormat) -> u8 {
|
||||
match x {
|
||||
Ss58AddressFormat::SubstrateAccountDirect => 42,
|
||||
Ss58AddressFormat::PolkadotAccountDirect => 0,
|
||||
Ss58AddressFormat::KusamaAccountDirect => 2,
|
||||
Ss58AddressFormat::Custom(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl TryFrom<u8> for Ss58AddressFormat {
|
||||
type Error = ();
|
||||
fn try_from(x: u8) -> Result<Ss58AddressFormat, ()> {
|
||||
match x {
|
||||
42 => Ok(Ss58AddressFormat::SubstrateAccountDirect),
|
||||
0 => Ok(Ss58AddressFormat::PolkadotAccountDirect),
|
||||
2 => Ok(Ss58AddressFormat::KusamaAccountDirect),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<'a> TryFrom<&'a str> for Ss58AddressFormat {
|
||||
type Error = ();
|
||||
fn try_from(x: &'a str) -> Result<Ss58AddressFormat, ()> {
|
||||
match x {
|
||||
"substrate" => Ok(Ss58AddressFormat::SubstrateAccountDirect),
|
||||
"polkadot" => Ok(Ss58AddressFormat::PolkadotAccountDirect),
|
||||
"kusama" => Ok(Ss58AddressFormat::KusamaAccountDirect),
|
||||
a => a.parse::<u8>().map(Ss58AddressFormat::Custom).map_err(|_| ()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl From<Ss58AddressFormat> for String {
|
||||
fn from(x: Ss58AddressFormat) -> String {
|
||||
match x {
|
||||
Ss58AddressFormat::SubstrateAccountDirect => "substrate".into(),
|
||||
Ss58AddressFormat::PolkadotAccountDirect => "polkadot".into(),
|
||||
Ss58AddressFormat::KusamaAccountDirect => "kusama".into(),
|
||||
Ss58AddressFormat::Custom(x) => x.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the default "version" (actually, this is a bit of a misnomer and the version byte is
|
||||
/// typically used not just to encode format/version but also network identity) that is used for
|
||||
/// encoding and decoding SS58 addresses. If an unknown version is provided then it fails.
|
||||
///
|
||||
/// Current known "versions" are:
|
||||
/// - 0 direct (payload) checksum for 32-byte *25519 Polkadot addresses.
|
||||
/// - 2 direct (payload) checksum for 32-byte *25519 Polkadot Milestone 'K' addresses.
|
||||
/// - 42 direct (payload) checksum for 32-byte *25519 addresses on any Substrate-based network.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn set_default_ss58_version(version: Ss58AddressFormat) {
|
||||
*DEFAULT_VERSION.lock() = version
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T {
|
||||
fn from_ss58check(s: &str) -> Result<Self, PublicError> {
|
||||
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.
|
||||
@@ -283,21 +394,18 @@ impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T {
|
||||
// Invalid length.
|
||||
return Err(PublicError::BadLength);
|
||||
}
|
||||
if d[0] != 42 {
|
||||
// Invalid version.
|
||||
return Err(PublicError::UnknownVersion);
|
||||
}
|
||||
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)
|
||||
Ok((res, ver))
|
||||
}
|
||||
|
||||
fn to_ss58check(&self) -> String {
|
||||
let mut v = vec![42u8];
|
||||
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]);
|
||||
@@ -324,6 +432,28 @@ impl<T: AsMut<[u8]> + AsRef<[u8]> + Default + Derive> Ss58Codec for T {
|
||||
.ok_or(PublicError::InvalidPath)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> {
|
||||
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, v) = Self::from_ss58check_with_version(
|
||||
cap.name("ss58")
|
||||
.map(|r| r.as_str())
|
||||
.unwrap_or(DEV_ADDRESS)
|
||||
)?;
|
||||
if cap["path"].is_empty() {
|
||||
Ok((addr, v))
|
||||
} else {
|
||||
let path = re_junction.captures_iter(&cap["path"])
|
||||
.map(|f| DeriveJunction::from(&f[1]));
|
||||
addr.derive(path)
|
||||
.ok_or(PublicError::InvalidPath)
|
||||
.map(|a| (a, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait suitable for typical cryptographic PKI key public type.
|
||||
|
||||
@@ -18,6 +18,12 @@ args:
|
||||
takes_value: true
|
||||
required: false
|
||||
help: The password for the key
|
||||
- network:
|
||||
short: n
|
||||
long: network
|
||||
takes_value: true
|
||||
required: false
|
||||
help: Specify a network. One of substrate (default), polkadot and kusama.
|
||||
subcommands:
|
||||
- generate:
|
||||
about: Generate a random account
|
||||
|
||||
@@ -18,15 +18,18 @@
|
||||
#[cfg(feature = "bench")]
|
||||
extern crate test;
|
||||
|
||||
use std::{str::FromStr, io::{stdin, Read}};
|
||||
use std::{str::FromStr, io::{stdin, Read}, convert::TryInto};
|
||||
use hex_literal::hex;
|
||||
use clap::load_yaml;
|
||||
use bip39::{Mnemonic, Language, MnemonicType};
|
||||
use substrate_primitives::{ed25519, sr25519, hexdisplay::HexDisplay, Pair, Public, crypto::Ss58Codec, blake2_256};
|
||||
use substrate_primitives::{
|
||||
ed25519, sr25519, hexdisplay::HexDisplay, Pair, Public,
|
||||
crypto::{Ss58Codec, set_default_ss58_version, Ss58AddressFormat}, blake2_256
|
||||
};
|
||||
use parity_codec::{Encode, Decode, Compact};
|
||||
use sr_primitives::generic::Era;
|
||||
use node_primitives::{Balance, Index, Hash};
|
||||
use node_runtime::{Call, UncheckedExtrinsic, BalancesCall};
|
||||
use node_runtime::{Call, UncheckedExtrinsic, /*CheckNonce, TakeFees, */BalancesCall};
|
||||
|
||||
mod vanity;
|
||||
|
||||
@@ -52,11 +55,12 @@ trait Crypto {
|
||||
HexDisplay::from(&Self::public_from_pair(&pair)),
|
||||
Self::ss58_from_pair(&pair)
|
||||
);
|
||||
} else if let Ok(public) = <Self::Pair as Pair>::Public::from_string(uri) {
|
||||
println!("Public Key URI `{}` is account:\n Public key (hex): 0x{}\n Address (SS58): {}",
|
||||
} else if let Ok((public, v)) = <Self::Pair as Pair>::Public::from_string_with_version(uri) {
|
||||
println!("Public Key URI `{}` is account:\n Network ID/version: {}\n Public key (hex): 0x{}\n Address (SS58): {}",
|
||||
uri,
|
||||
String::from(Ss58AddressFormat::from(v)),
|
||||
HexDisplay::from(&public.as_ref()),
|
||||
public.to_ss58check()
|
||||
public.to_ss58check_with_version(v)
|
||||
);
|
||||
} else {
|
||||
println!("Invalid phrase/URI given");
|
||||
@@ -87,6 +91,12 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
<<C as Crypto>::Pair as Pair>::Public: Sized + AsRef<[u8]> + Ss58Codec + AsRef<<<C as Crypto>::Pair as Pair>::Public>,
|
||||
{
|
||||
let password = matches.value_of("password");
|
||||
let maybe_network = matches.value_of("network");
|
||||
if let Some(network) = maybe_network {
|
||||
let v = network.try_into()
|
||||
.expect("Invalid network name: must be polkadot/substrate/kusama");
|
||||
set_default_ss58_version(v);
|
||||
}
|
||||
match matches.subcommand() {
|
||||
("generate", Some(matches)) => {
|
||||
// create a new randomly generated mnemonic phrase
|
||||
@@ -120,7 +130,7 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
let sig = pair.sign(&message);
|
||||
println!("{}", hex::encode(&sig));
|
||||
}
|
||||
("transfer", Some(matches)) => {
|
||||
/*("transfer", Some(matches)) => {
|
||||
let signer = matches.value_of("from")
|
||||
.expect("parameter is required; thus it can't be None; qed");
|
||||
let signer = Sr25519::pair_from_suri(signer, password);
|
||||
@@ -147,7 +157,7 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
"elm" => hex!["10c08714a10c7da78f40a60f6f732cf0dba97acfb5e2035445b032386157d5c3"].into(),
|
||||
"alex" => hex!["dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b"].into(),
|
||||
h => hex::decode(h).ok().and_then(|x| Decode::decode(&mut &x[..]))
|
||||
.expect("Invalid genesis hash or unrecognised chain identifier"),
|
||||
.expect("Invalid genesis hash or unrecognised chain identifier"),
|
||||
};
|
||||
|
||||
println!("Using a genesis hash of {}", HexDisplay::from(&genesis_hash.as_ref()));
|
||||
@@ -161,11 +171,11 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
signer.sign(payload)
|
||||
});
|
||||
let extrinsic = UncheckedExtrinsic::new_signed(
|
||||
index,
|
||||
raw_payload.1,
|
||||
signer.public().into(),
|
||||
signature.into(),
|
||||
era,
|
||||
(CheckNonce(index), TakeFees(0)),
|
||||
);
|
||||
println!("0x{}", hex::encode(&extrinsic.encode()));
|
||||
}
|
||||
@@ -202,15 +212,15 @@ fn execute<C: Crypto>(matches: clap::ArgMatches) where
|
||||
);
|
||||
|
||||
let extrinsic = UncheckedExtrinsic::new_signed(
|
||||
index,
|
||||
raw_payload.1,
|
||||
signer.public().into(),
|
||||
signature.into(),
|
||||
era,
|
||||
(CheckNonce(index), TakeFees(0)),
|
||||
);
|
||||
|
||||
println!("0x{}", hex::encode(&extrinsic.encode()));
|
||||
}
|
||||
}*/
|
||||
("verify", Some(matches)) => {
|
||||
let sig_data = matches.value_of("sig")
|
||||
.expect("signature parameter is required; thus it can't be None; qed");
|
||||
|
||||
Reference in New Issue
Block a user