use scale::{
Decode,
Encode,
};
use serde::{
Deserialize,
Serialize,
};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)]
pub struct AccountId32(pub [u8; 32]);
impl scale_info::TypeInfo for AccountId32 {
type Identity = Self;
fn type_info() -> scale_info::Type {
scale_info::Type::builder()
.path(::scale_info::Path::new("AccountId32", "sp_core::crypto"))
.composite(
scale_info::build::Fields::unnamed()
.field(|f| f.ty::<[u8; 32]>().type_name("[u8; 32]").docs(&[])),
)
}
}
impl AsRef<[u8]> for AccountId32 {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsRef<[u8; 32]> for AccountId32 {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<[u8; 32]> for AccountId32 {
fn from(x: [u8; 32]) -> Self {
AccountId32(x)
}
}
impl<'a> TryFrom<&'a [u8]> for AccountId32 {
type Error = ();
fn try_from(x: &'a [u8]) -> Result<AccountId32, ()> {
if x.len() == 32 {
let mut data = [0; 32];
data.copy_from_slice(x);
Ok(AccountId32(data))
} else {
Err(())
}
}
}
impl AccountId32 {
pub fn to_ss58check(&self) -> String {
const SUBSTRATE_SS58_PREFIX: u8 = 42;
let mut v = vec![SUBSTRATE_SS58_PREFIX];
v.extend(self.0);
let r = ss58hash(&v);
v.extend(&r[0..2]);
use base58::ToBase58;
v.to_base58()
}
fn from_ss58check(s: &str) -> Result<Self, FromSs58Error> {
const CHECKSUM_LEN: usize = 2;
let body_len = 32;
use base58::FromBase58;
let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?;
if data.len() < 2 {
return Err(FromSs58Error::BadLength)
}
let prefix_len = match data[0] {
0..=63 => 1,
64..=127 => 2,
_ => return Err(FromSs58Error::InvalidPrefix),
};
if data.len() != prefix_len + body_len + CHECKSUM_LEN {
return Err(FromSs58Error::BadLength)
}
let hash = ss58hash(&data[0..body_len + prefix_len]);
let checksum = &hash[0..CHECKSUM_LEN];
if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum
{
return Err(FromSs58Error::InvalidChecksum)
}
let result = data[prefix_len..body_len + prefix_len]
.try_into()
.map_err(|_| FromSs58Error::BadLength)?;
Ok(AccountId32(result))
}
}
#[derive(thiserror::Error, Clone, Copy, Eq, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum FromSs58Error {
#[error("Base 58 requirement is violated")]
BadBase58,
#[error("Length is bad")]
BadLength,
#[error("Invalid checksum")]
InvalidChecksum,
#[error("Invalid SS58 prefix byte.")]
InvalidPrefix,
}
fn ss58hash(data: &[u8]) -> Vec<u8> {
use blake2::{
Blake2b512,
Digest,
};
const PREFIX: &[u8] = b"SS58PRE";
let mut ctx = Blake2b512::new();
ctx.update(PREFIX);
ctx.update(data);
ctx.finalize().to_vec()
}
impl Serialize for AccountId32 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_ss58check())
}
}
impl<'de> Deserialize<'de> for AccountId32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
AccountId32::from_ss58check(&String::deserialize(deserializer)?)
.map_err(|e| serde::de::Error::custom(format!("{e:?}")))
}
}
impl std::fmt::Display for AccountId32 {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_ss58check())
}
}
impl std::str::FromStr for AccountId32 {
type Err = FromSs58Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
AccountId32::from_ss58check(s)
}
}
#[cfg(test)]
mod test {
use super::*;
use sp_core::crypto::Ss58Codec;
use sp_keyring::AccountKeyring;
#[test]
fn ss58_is_compatible_with_substrate_impl() {
let keyrings = vec![
AccountKeyring::Alice,
AccountKeyring::Bob,
AccountKeyring::Charlie,
];
for keyring in keyrings {
let substrate_account = keyring.to_account_id();
let local_account = AccountId32(substrate_account.clone().into());
let substrate_ss58 = substrate_account.to_ss58check();
assert_eq!(substrate_ss58, local_account.to_ss58check());
assert_eq!(
sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(),
substrate_account
);
assert_eq!(
AccountId32::from_ss58check(&substrate_ss58).unwrap(),
local_account
);
}
}
}