1#![no_std]
27
28#[cfg(feature = "alloc")]
29extern crate alloc;
30
31#[cfg(feature = "alloc")]
32use alloc::{
33 string::{String, ToString},
34 vec::Vec,
35};
36use blake2::{Blake2b512, Digest};
37use bs58::{
38 decode::{self, DecodeTarget},
39 encode::{self, EncodeTarget},
40};
41use core::{
42 array::TryFromSliceError,
43 fmt,
44 ops::{Deref, DerefMut, RangeInclusive},
45 str,
46 sync::atomic::{AtomicU16, Ordering},
47};
48
49const PREFIX: &[u8] = b"SS58PRE";
51
52const PREFIX_LEN_RANGE: RangeInclusive<usize> = 1..=2;
54const MIN_PREFIX_LEN: usize = *PREFIX_LEN_RANGE.start();
56const MAX_PREFIX_LEN: usize = *PREFIX_LEN_RANGE.end();
58
59const BODY_LEN: usize = 32;
61
62const CHECKSUM_LEN: usize = 2;
64
65const MIN_ADDRESS_LEN: usize = MIN_PREFIX_LEN + BODY_LEN + CHECKSUM_LEN;
67const MAX_ADDRESS_LEN: usize = MAX_PREFIX_LEN + BODY_LEN + CHECKSUM_LEN;
69const ADDRESS_LEN_RANGE: RangeInclusive<usize> = MIN_ADDRESS_LEN..=MAX_ADDRESS_LEN;
71
72const fn base58_max_encoded_len(len: usize) -> usize {
75 len + len.div_ceil(2)
77}
78
79const MAX_ADDRESS_LEN_BASE58: usize = base58_max_encoded_len(MAX_ADDRESS_LEN);
81
82pub const SUBSTRATE_SS58_PREFIX: u16 = 42;
84pub const VARA_SS58_PREFIX: u16 = 137;
86
87static DEFAULT_SS58_VERSION: AtomicU16 = AtomicU16::new(VARA_SS58_PREFIX);
89
90pub fn default_ss58_version() -> u16 {
92 DEFAULT_SS58_VERSION.load(Ordering::Relaxed)
93}
94
95pub fn set_default_ss58_version(version: u16) {
97 DEFAULT_SS58_VERSION.store(version, Ordering::Relaxed);
98}
99
100struct Buffer<const N: usize>([u8; N]);
101
102impl<const N: usize> Buffer<N> {
103 pub const fn new() -> Self {
104 Self([0; N])
105 }
106}
107
108impl<const N: usize> Deref for Buffer<N> {
109 type Target = [u8];
110
111 fn deref(&self) -> &Self::Target {
112 &self.0
113 }
114}
115
116impl<const N: usize> DerefMut for Buffer<N> {
117 fn deref_mut(&mut self) -> &mut Self::Target {
118 &mut self.0
119 }
120}
121
122impl DecodeTarget for Buffer<MAX_ADDRESS_LEN> {
123 fn decode_with(
124 &mut self,
125 _max_len: usize,
126 f: impl for<'a> FnOnce(&'a mut [u8]) -> decode::Result<usize>,
127 ) -> decode::Result<usize> {
128 let len = f(&mut self[..])?;
129 Ok(len)
130 }
131}
132
133impl EncodeTarget for Buffer<MAX_ADDRESS_LEN_BASE58> {
134 fn encode_with(
135 &mut self,
136 _max_len: usize,
137 f: impl for<'a> FnOnce(&'a mut [u8]) -> encode::Result<usize>,
138 ) -> encode::Result<usize> {
139 let len = f(&mut self[..])?;
140 Ok(len)
141 }
142}
143
144#[derive(Debug, PartialEq, Eq)]
146pub enum Error {
147 Base58Encode,
148 BadBase58,
149 BadLength,
150 InvalidPrefix,
151 InvalidChecksum,
152 #[cfg(feature = "alloc")]
153 InvalidSliceLength,
154}
155
156impl fmt::Display for Error {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 Self::Base58Encode => writeln!(f, "Base 58 encoding failed"),
160 Self::BadBase58 => writeln!(f, "Base 58 requirement is violated"),
161 Self::BadLength => writeln!(f, "Length is bad"),
162 Self::InvalidPrefix => writeln!(f, "Invalid SS58 prefix byte"),
163 Self::InvalidChecksum => writeln!(f, "Invalid checksum"),
164 #[cfg(feature = "alloc")]
165 Self::InvalidSliceLength => writeln!(f, "Slice should be 32 length"),
166 }
167 }
168}
169
170impl core::error::Error for Error {}
171
172pub struct Ss58Address {
174 len: usize,
175 buf: Buffer<MAX_ADDRESS_LEN_BASE58>,
176}
177
178impl Ss58Address {
179 pub fn as_str(&self) -> &str {
181 unsafe { str::from_utf8_unchecked(&self.buf).get_unchecked(..self.len) }
182 }
183}
184
185impl fmt::Display for Ss58Address {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.write_str(self.as_str())
188 }
189}
190
191impl fmt::Debug for Ss58Address {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 fmt::Display::fmt(self, f)
194 }
195}
196
197pub struct RawSs58Address([u8; BODY_LEN]);
199
200impl From<RawSs58Address> for [u8; BODY_LEN] {
201 fn from(address: RawSs58Address) -> Self {
202 address.0
203 }
204}
205
206impl From<[u8; BODY_LEN]> for RawSs58Address {
207 fn from(array: [u8; BODY_LEN]) -> Self {
208 Self(array)
209 }
210}
211
212impl TryFrom<&[u8]> for RawSs58Address {
213 type Error = TryFromSliceError;
214
215 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
216 <[u8; BODY_LEN]>::try_from(slice).map(Self)
217 }
218}
219
220impl fmt::Display for RawSs58Address {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 let mut buf = [0; BODY_LEN * 2];
223 let _ = hex::encode_to_slice(self.0, &mut buf);
224 f.write_str("0x")?;
225 f.write_str(unsafe { str::from_utf8_unchecked(&buf) })
226 }
227}
228
229impl fmt::Debug for RawSs58Address {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 fmt::Display::fmt(self, f)
232 }
233}
234
235impl RawSs58Address {
236 pub fn from_ss58check(s: &str) -> Result<Self, Error> {
238 Self::from_ss58check_with_prefix(s).map(|(address, _)| address)
239 }
240
241 pub fn from_ss58check_with_prefix(s: &str) -> Result<(Self, u16), Error> {
243 let mut data = Buffer::<MAX_ADDRESS_LEN>::new();
244 let data_len = bs58::decode(s)
245 .onto(&mut data)
246 .map_err(|_| Error::BadBase58)?;
247
248 if !ADDRESS_LEN_RANGE.contains(&data_len) {
249 return Err(Error::BadLength);
250 }
251
252 let (prefix_len, prefix) = match data[0] {
253 0..=63 => (1, data[0] as u16),
254 64..=127 => {
255 let lower = (data[0] << 2) | (data[1] >> 6);
261 let upper = data[1] & 0b00111111;
262 (2, (lower as u16) | ((upper as u16) << 8))
263 }
264 _ => return Err(Error::InvalidPrefix),
265 };
266
267 if data_len != prefix_len + BODY_LEN + CHECKSUM_LEN {
268 return Err(Error::BadLength);
269 }
270
271 let (address_data, address_checksum) = data.split_at(prefix_len + BODY_LEN);
272
273 let hash = ss58hash(address_data);
274 let checksum = &hash[..CHECKSUM_LEN];
275
276 if &address_checksum[..CHECKSUM_LEN] != checksum {
277 return Err(Error::InvalidChecksum);
278 }
279
280 match <[u8; BODY_LEN]>::try_from(&address_data[prefix_len..]) {
281 Ok(array) => Ok((Self(array), prefix)),
282 Err(_) => Err(Error::BadLength),
283 }
284 }
285}
286
287impl RawSs58Address {
288 pub fn to_ss58check(&self) -> Result<Ss58Address, Error> {
290 self.to_ss58check_with_prefix(default_ss58_version())
291 }
292
293 pub fn to_ss58check_with_prefix(&self, prefix: u16) -> Result<Ss58Address, Error> {
295 let mut buffer = Buffer::<MAX_ADDRESS_LEN>::new();
296
297 let ident = prefix & 0b0011_1111_1111_1111;
299 let (prefix_len, address_len) = match ident {
300 0..=63 => {
301 buffer[0] = ident as u8;
302 (MIN_PREFIX_LEN, MIN_ADDRESS_LEN)
303 }
304 64..=16_383 => {
305 let first = ((ident & 0b0000_0000_1111_1100) as u8) >> 2;
307 let second = ((ident >> 8) as u8) | (((ident & 0b0000_0000_0000_0011) as u8) << 6);
310
311 buffer[0] = first | 0b01000000;
312 buffer[1] = second;
313 (MAX_PREFIX_LEN, MAX_ADDRESS_LEN)
314 }
315 _ => unreachable!("masked out the upper two bits; qed"),
316 };
317
318 let (address_data, address_checksum) = buffer.split_at_mut(prefix_len + BODY_LEN);
319
320 address_data[prefix_len..].copy_from_slice(&self.0);
321 let hash = ss58hash(address_data);
322 address_checksum[..CHECKSUM_LEN].copy_from_slice(&hash[..CHECKSUM_LEN]);
323
324 let mut buf = Buffer::<MAX_ADDRESS_LEN_BASE58>::new();
325 let len = bs58::encode(&buffer[..address_len])
326 .onto(&mut buf)
327 .map_err(|_| Error::Base58Encode)?;
328
329 Ok(Ss58Address { len, buf })
330 }
331}
332
333fn ss58hash(data: &[u8]) -> [u8; 64] {
334 let mut ctx = Blake2b512::new();
335 ctx.update(PREFIX);
336 ctx.update(data);
337 ctx.finalize().into()
338}
339
340#[cfg(feature = "alloc")]
342pub fn encode(data: &[u8]) -> Result<String, Error> {
343 let raw_address = RawSs58Address::try_from(data).map_err(|_| Error::InvalidSliceLength)?;
344 let address = raw_address.to_ss58check()?;
345 Ok(address.to_string())
346}
347
348#[cfg(feature = "alloc")]
350pub fn decode(encoded: &str) -> Result<Vec<u8>, Error> {
351 let raw_address: [u8; BODY_LEN] = RawSs58Address::from_ss58check(encoded)?.into();
352 Ok(raw_address.to_vec())
353}
354
355#[cfg(feature = "alloc")]
357pub fn recode(encoded: &str) -> Result<String, Error> {
358 self::encode(&self::decode(encoded)?)
359}