subspace_farmer/utils/
ss58.rs

1//! Modified version of SS58 parser extracted from Substrate in order to not pull the whole
2//! `sp-core` into farmer application
3
4use 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/// An error type for SS58 decoding.
16#[derive(Debug, Error)]
17pub enum Ss58ParsingError {
18    /// Base 58 requirement is violated
19    #[error("Base 58 requirement is violated")]
20    BadBase58,
21    /// Length is bad
22    #[error("Length is bad")]
23    BadLength,
24    /// Invalid SS58 prefix byte
25    #[error("Invalid SS58 prefix byte")]
26    InvalidPrefix,
27    /// Disallowed SS58 Address Format for this datatype
28    #[error("Disallowed SS58 Address Format for this datatype")]
29    FormatNotAllowed,
30    /// Invalid checksum
31    #[error("Invalid checksum")]
32    InvalidChecksum,
33}
34
35/// Some if the string is a properly encoded SS58Check address.
36pub 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            // weird bit manipulation owing to the combination of LE encoding and missing two
45            // bits from the left.
46            // d[0] d[1] are: 01aaaaaa bbcccccc
47            // they make the LE-encoded 16-bit value: aaaaaabb 00cccccc
48            // so the lower byte is formed of aaaaaabb and the higher byte is 00cccccc
49            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        // Invalid checksum.
68        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        // Alice
92        parse_ss58_reward_address("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY").unwrap();
93    }
94}