subspace_farmer/utils/
ss58.rs1use base58::FromBase58;
5use blake2::digest::typenum::U64;
6use blake2::digest::FixedOutput;
7use blake2::{Blake2b, Digest};
8use ss58_registry::Ss58AddressFormat;
9use subspace_core_primitives::PublicKey;
10use thiserror::Error;
11
12const PREFIX: &[u8] = b"SS58PRE";
13const CHECKSUM_LEN: usize = 2;
14
15#[derive(Debug, Error)]
17pub enum Ss58ParsingError {
18 #[error("Base 58 requirement is violated")]
20 BadBase58,
21 #[error("Length is bad")]
23 BadLength,
24 #[error("Invalid SS58 prefix byte")]
26 InvalidPrefix,
27 #[error("Disallowed SS58 Address Format for this datatype")]
29 FormatNotAllowed,
30 #[error("Invalid checksum")]
32 InvalidChecksum,
33}
34
35pub fn parse_ss58_reward_address(s: &str) -> Result<PublicKey, Ss58ParsingError> {
37 let data = s.from_base58().map_err(|_| Ss58ParsingError::BadBase58)?;
38 if data.len() < 2 {
39 return Err(Ss58ParsingError::BadLength);
40 }
41 let (prefix_len, ident) = match data[0] {
42 0..=63 => (1, data[0] as u16),
43 64..=127 => {
44 let lower = (data[0] << 2) | (data[1] >> 6);
50 let upper = data[1] & 0b00111111;
51 (2, (lower as u16) | ((upper as u16) << 8))
52 }
53 _ => return Err(Ss58ParsingError::InvalidPrefix),
54 };
55 if data.len() != prefix_len + PublicKey::SIZE + CHECKSUM_LEN {
56 return Err(Ss58ParsingError::BadLength);
57 }
58 let format: Ss58AddressFormat = ident.into();
59 if format.is_reserved() {
60 return Err(Ss58ParsingError::FormatNotAllowed);
61 }
62
63 let hash = ss58hash(&data[0..PublicKey::SIZE + prefix_len]);
64 let checksum = &hash[0..CHECKSUM_LEN];
65 if data[PublicKey::SIZE + prefix_len..PublicKey::SIZE + prefix_len + CHECKSUM_LEN] != *checksum
66 {
67 return Err(Ss58ParsingError::InvalidChecksum);
69 }
70
71 let bytes: [u8; PublicKey::SIZE] = data[prefix_len..][..PublicKey::SIZE]
72 .try_into()
73 .map_err(|_| Ss58ParsingError::BadLength)?;
74
75 Ok(PublicKey::from(bytes))
76}
77
78fn ss58hash(data: &[u8]) -> [u8; 64] {
79 let mut state = Blake2b::<U64>::new();
80 state.update(PREFIX);
81 state.update(data);
82 state.finalize_fixed().into()
83}
84
85#[cfg(test)]
86mod tests {
87 use super::parse_ss58_reward_address;
88
89 #[test]
90 fn basic() {
91 parse_ss58_reward_address("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY").unwrap();
93 }
94}