subxt_core/utils/
account_id.rs1use alloc::format;
10use alloc::string::String;
11use alloc::vec;
12use alloc::vec::Vec;
13use codec::{Decode, Encode};
14use serde::{Deserialize, Serialize};
15use thiserror::Error as DeriveError;
16
17#[derive(
21 Clone,
22 Eq,
23 PartialEq,
24 Ord,
25 PartialOrd,
26 Encode,
27 Decode,
28 Debug,
29 scale_encode::EncodeAsType,
30 scale_decode::DecodeAsType,
31 scale_info::TypeInfo,
32)]
33pub struct AccountId32(pub [u8; 32]);
34
35impl AsRef<[u8]> for AccountId32 {
36 fn as_ref(&self) -> &[u8] {
37 &self.0[..]
38 }
39}
40
41impl AsRef<[u8; 32]> for AccountId32 {
42 fn as_ref(&self) -> &[u8; 32] {
43 &self.0
44 }
45}
46
47impl From<[u8; 32]> for AccountId32 {
48 fn from(x: [u8; 32]) -> Self {
49 AccountId32(x)
50 }
51}
52
53impl AccountId32 {
54 fn to_ss58check(&self) -> String {
57 const SUBSTRATE_SS58_PREFIX: u8 = 42;
61 let mut v = vec![SUBSTRATE_SS58_PREFIX];
63 v.extend(self.0);
65 let r = ss58hash(&v);
67 v.extend(&r[0..2]);
68 use base58::ToBase58;
70 v.to_base58()
71 }
72
73 fn from_ss58check(s: &str) -> Result<Self, FromSs58Error> {
77 const CHECKSUM_LEN: usize = 2;
78 let body_len = 32;
79
80 use base58::FromBase58;
81 let data = s.from_base58().map_err(|_| FromSs58Error::BadBase58)?;
82 if data.len() < 2 {
83 return Err(FromSs58Error::BadLength);
84 }
85 let prefix_len = match data[0] {
86 0..=63 => 1,
87 64..=127 => 2,
88 _ => return Err(FromSs58Error::InvalidPrefix),
89 };
90 if data.len() != prefix_len + body_len + CHECKSUM_LEN {
91 return Err(FromSs58Error::BadLength);
92 }
93 let hash = ss58hash(&data[0..body_len + prefix_len]);
94 let checksum = &hash[0..CHECKSUM_LEN];
95 if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LEN] != *checksum {
96 return Err(FromSs58Error::InvalidChecksum);
98 }
99
100 let result = data[prefix_len..body_len + prefix_len]
101 .try_into()
102 .map_err(|_| FromSs58Error::BadLength)?;
103 Ok(AccountId32(result))
104 }
105}
106
107#[derive(Clone, Copy, Eq, PartialEq, Debug, DeriveError)]
109#[allow(missing_docs)]
110pub enum FromSs58Error {
111 #[error("Base 58 requirement is violated")]
112 BadBase58,
113 #[error("Length is bad")]
114 BadLength,
115 #[error("Invalid checksum")]
116 InvalidChecksum,
117 #[error("Invalid SS58 prefix byte.")]
118 InvalidPrefix,
119}
120
121fn ss58hash(data: &[u8]) -> Vec<u8> {
123 use blake2::{Blake2b512, Digest};
124 const PREFIX: &[u8] = b"SS58PRE";
125 let mut ctx = Blake2b512::new();
126 ctx.update(PREFIX);
127 ctx.update(data);
128 ctx.finalize().to_vec()
129}
130
131impl Serialize for AccountId32 {
132 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133 where
134 S: serde::Serializer,
135 {
136 serializer.serialize_str(&self.to_ss58check())
137 }
138}
139
140impl<'de> Deserialize<'de> for AccountId32 {
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: serde::Deserializer<'de>,
144 {
145 AccountId32::from_ss58check(&String::deserialize(deserializer)?)
146 .map_err(|e| serde::de::Error::custom(format!("{e:?}")))
147 }
148}
149
150impl core::fmt::Display for AccountId32 {
151 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
152 write!(f, "{}", self.to_ss58check())
153 }
154}
155
156impl core::str::FromStr for AccountId32 {
157 type Err = FromSs58Error;
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 AccountId32::from_ss58check(s)
160 }
161}
162
163#[cfg(test)]
164mod test {
165 use super::*;
166 use sp_core::{self, crypto::Ss58Codec};
167 use sp_keyring::AccountKeyring;
168
169 #[test]
170 fn ss58_is_compatible_with_substrate_impl() {
171 let keyrings = vec![
172 AccountKeyring::Alice,
173 AccountKeyring::Bob,
174 AccountKeyring::Charlie,
175 ];
176
177 for keyring in keyrings {
178 let substrate_account = keyring.to_account_id();
179 let local_account = AccountId32(substrate_account.clone().into());
180
181 let substrate_ss58 = substrate_account.to_ss58check();
183 assert_eq!(substrate_ss58, local_account.to_ss58check());
184
185 assert_eq!(
187 sp_core::crypto::AccountId32::from_ss58check(&substrate_ss58).unwrap(),
188 substrate_account
189 );
190 assert_eq!(
191 AccountId32::from_ss58check(&substrate_ss58).unwrap(),
192 local_account
193 );
194 }
195 }
196}