Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
106 changes: 74 additions & 32 deletions primitives/core/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,17 +239,35 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default {
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.
let prefix = if d.len() == len + 3 && d[0] < 64 {
1
} else if d.len() == len + 4 && d[0] >= 64 && d[0] < 128 {
2
} else {
// Invalid length/prefix combination.
return Err(PublicError::BadLength);
}
let ver = d[0].try_into().map_err(|_: ()| PublicError::UnknownVersion)?;
};
let identifier: u16 = match d[0] {
0..=63 => d[0] as u16,
// weird bit manipulation owing to the combination of LE encoding and missing two bits
// from the left.
// d[0] d[1] are: 01aaaaaa bbcccccc
// they make the LE-encoded 16-bit value: aaaaaabb 00cccccc
// so the lower byte is formed of aaaaaabb and the higher byte is 00cccccc
64..=127 => {
let lower = ((d[0] & 0b00111111) << 2) | (d[1] >> 6);
let upper = d[1] & 0b00111111;
(lower as u16) | ((upper as u16) << 8)
}
_ => Err(PublicError::UnknownVersion)?,
};
let ver = identifier.try_into().map_err(|_: ()| PublicError::UnknownVersion)?;

if d[len + 1..len + 3] != ss58hash(&d[0..len + 1]).as_bytes()[0..2] {
if d[len + prefix..len + prefix + 2] != ss58hash(&d[0..len + prefix]).as_bytes()[0..2] {
// Invalid checksum.
return Err(PublicError::InvalidChecksum);
}
res.as_mut().copy_from_slice(&d[1..len + 1]);
res.as_mut().copy_from_slice(&d[prefix..len + prefix]);
Ok((res, ver))
}
/// Some if the string is a properly encoded SS58Check address, optionally with
Expand All @@ -267,7 +285,20 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + Default {
/// Return the ss58-check string for this key.
#[cfg(feature = "std")]
fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String {
let mut v = vec![version.into()];
// We mask out the upper two bits of the ident - SS58 Prefix currently only supports 14-bits
let ident: u16 = u16::from(version) & 0b00111111_11111111;
let mut v = match ident {
0..=63 => vec![ident as u8],
64..=16_383 => {
// upper six bits of the lower byte(!)
let first = ((ident & 0b00000000_11111100) as u8) >> 2;
// lower two bits of the lower byte in the high pos,
// lower bits of the upper byte in the low pos
let second = ((ident >> 8) as u8) | ((ident & 0b00000000_00000011) as u8) << 6;
vec![first | 0b01000000, second]
}
_ => unreachable!("masked out the upper two bits; qed"),
};
v.extend(self.as_ref());
let r = ss58hash(&v);
v.extend(&r.as_bytes()[0..2]);
Expand Down Expand Up @@ -321,8 +352,8 @@ macro_rules! ss58_address_format {
#[derive(Copy, Clone, PartialEq, Eq, crate::RuntimeDebug)]
pub enum Ss58AddressFormat {
$(#[doc = $desc] $identifier),*,
/// Use a manually provided numeric value.
Custom(u8),
/// Use a manually provided numeric value as a standard identifier
Custom(u16),
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -363,31 +394,43 @@ macro_rules! ss58_address_format {
}
}

impl From<Ss58AddressFormat> for u8 {
fn from(x: Ss58AddressFormat) -> u8 {
impl TryFrom<Ss58AddressFormat> for u8 {
type Error = ();

fn try_from(x: Ss58AddressFormat) -> Result<u8, ()> {
let v: u16 = x.into();
if v < 256 {
Ok(v as u8)
} else {
Err(())
}
}
}

impl TryFrom<u8> for Ss58AddressFormat {
type Error = ();

fn try_from(x: u8) -> Result<Ss58AddressFormat, ()> {
Ss58AddressFormat::try_from(x as u16)
}
}

impl From<Ss58AddressFormat> for u16 {
fn from(x: Ss58AddressFormat) -> u16 {
match x {
$(Ss58AddressFormat::$identifier => $number),*,
Ss58AddressFormat::Custom(n) => n,
}
}
}

impl TryFrom<u8> for Ss58AddressFormat {
impl TryFrom<u16> for Ss58AddressFormat {
type Error = ();

fn try_from(x: u8) -> Result<Ss58AddressFormat, ()> {
fn try_from(x: u16) -> Result<Ss58AddressFormat, ()> {
match x {
$($number => Ok(Ss58AddressFormat::$identifier)),*,
_ => {
#[cfg(feature = "std")]
match Ss58AddressFormat::default() {
Ss58AddressFormat::Custom(n) if n == x => Ok(Ss58AddressFormat::Custom(x)),
_ => Err(()),
}

#[cfg(not(feature = "std"))]
Err(())
},
_ => Ok(Ss58AddressFormat::Custom(x)),
}
}
}
Expand All @@ -403,7 +446,7 @@ macro_rules! ss58_address_format {
fn try_from(x: &'a str) -> Result<Ss58AddressFormat, Self::Error> {
match x {
$($name => Ok(Ss58AddressFormat::$identifier)),*,
a => a.parse::<u8>().map(Ss58AddressFormat::Custom).map_err(|_| ParseError),
a => a.parse::<u16>().map(Ss58AddressFormat::Custom).map_err(|_| ParseError),
}
}
}
Expand Down Expand Up @@ -444,12 +487,12 @@ macro_rules! ss58_address_format {
ss58_address_format!(
PolkadotAccount =>
(0, "polkadot", "Polkadot Relay-chain, standard account (*25519).")
Reserved1 =>
(1, "reserved1", "Reserved for future use (1).")
BareSr25519 =>
(1, "sr25519", "Bare 32-bit Schnorr/Ristretto 25519 (S/R 25519) key.")
KusamaAccount =>
(2, "kusama", "Kusama Relay-chain, standard account (*25519).")
Reserved3 =>
(3, "reserved3", "Reserved for future use (3).")
BareEd25519 =>
(3, "ed25519", "Bare 32-bit Edwards Ed25519 key.")
KatalChainAccount =>
(4, "katalchain", "Katal Chain, standard account (*25519).")
PlasmAccount =>
Expand Down Expand Up @@ -501,7 +544,7 @@ ss58_address_format!(
SubsocialAccount =>
(28, "subsocial", "Subsocial network, standard account (*25519).")
DhiwayAccount =>
(29, "cord", "Dhiway CORD network, standard account (*25519).")
(29, "cord", "Dhiway CORD network, standard account (*25519).")
PhalaAccount =>
(30, "phala", "Phala Network, standard account (*25519).")
LitentryAccount =>
Expand All @@ -522,8 +565,8 @@ ss58_address_format!(
(41, "poli", "Polimec Chain mainnet, standard account (*25519).")
SubstrateAccount =>
(42, "substrate", "Any Substrate network, standard account (*25519).")
Reserved43 =>
(43, "reserved43", "Reserved for future use (43).")
BareSecp256k1 =>
(43, "secp256k1", "Bare ECDSA SECP256k1 key.")
ChainXAccount =>
(44, "chainx", "ChainX mainnet, standard account (*25519).")
UniartsAccount =>
Expand All @@ -532,7 +575,6 @@ ss58_address_format!(
(46, "reserved46", "Reserved for future use (46).")
Reserved47 =>
(47, "reserved47", "Reserved for future use (47).")
// Note: 48 and above are reserved.
);

/// Set the default "version" (actually, this is a bit of a misnomer and the version byte is
Expand Down
27 changes: 26 additions & 1 deletion primitives/core/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,24 @@ mod test {
assert_eq!(cmp, public);
}

#[test]
fn ss58check_full_roundtrip_works() {
use crate::crypto::Ss58AddressFormat;
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let format = Ss58AddressFormat::PolkadotAccount;
let s = public.to_ss58check_with_version(format);
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
assert_eq!(k, public);
assert_eq!(f, format);

let format = Ss58AddressFormat::Custom(64);
let s = public.to_ss58check_with_version(format);
let (k, f) = Public::from_ss58check_with_version(&s).unwrap();
assert_eq!(k, public);
assert_eq!(f, format);
}

#[test]
fn ss58check_custom_format_works() {
// We need to run this test in its own process to not interfere with other tests running in
Expand All @@ -685,10 +703,17 @@ mod test {
// temp save default format version
let default_format = Ss58AddressFormat::default();
// set current ss58 version is custom "200" `Ss58AddressFormat::Custom(200)`

let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let s = public.to_ss58check_with_version(Ss58AddressFormat::Custom(200));
println!("{}", s);

set_default_ss58_version(Ss58AddressFormat::Custom(200));
// custom addr encoded by version 200
let addr = "2X64kMNEWAW5KLZMSKcGKEc96MyuaRsRUku7vomuYxKgqjVCRj";
let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV";
Public::from_ss58check(&addr).unwrap();

set_default_ss58_version(default_format);
// set current ss58 version to default version
let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C";
Expand Down
21 changes: 6 additions & 15 deletions ss58-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
},
{
"prefix": 1,
"network": "reserved1",
"displayName": "This prefix is reserved.",
"network": null,
"displayName": "Bare 32-bit Schnorr/Ristretto (S/R 25519) public key.",
"symbols": null,
"decimals": null,
"standardAccount": null,
Expand All @@ -39,8 +39,8 @@
},
{
"prefix": 3,
"network": "reserved3",
"displayName": "This prefix is reserved.",
"network": null,
"displayName": "Bare 32-bit Ed25519 public key.",
"symbols": null,
"decimals": null,
"standardAccount": null,
Expand Down Expand Up @@ -390,8 +390,8 @@
},
{
"prefix": 43,
"network": "reserved43",
"displayName": "This prefix is reserved.",
"network": null,
"displayName": "Bare 32-bit ECDSA SECP-256k1 public key.",
"symbols": null,
"decimals": null,
"standardAccount": null,
Expand Down Expand Up @@ -432,15 +432,6 @@
"decimals": null,
"standardAccount": null,
"website": null
},
{
"prefix": 48,
"network": "reserved48",
"displayName": "All prefixes 48 and higher are reserved and cannot be allocated.",
"symbols": null,
"decimals": null,
"standardAccount": null,
"website": null
}
]
}