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