use crate::prelude::*;
use crate::{stringify::ParseError, Value};
pub fn parse_ss58(s: &mut &str) -> Option<Result<Value<()>, ParseError>> {
let bytes = parse_ss58_bytes(s)?;
Some(Ok(Value::from_bytes(bytes)))
}
fn parse_ss58_bytes(s: &mut &str) -> Option<Vec<u8>> {
const CHECKSUM_LEN: usize = 2;
let end_idx = s.find(|c: char| !c.is_ascii_alphanumeric()).unwrap_or(s.len());
let maybe_ss58 = &s[0..end_idx];
let rest = &s[end_idx..];
if maybe_ss58.is_empty() {
return None;
}
if maybe_ss58 == "true"
|| maybe_ss58 == "false"
|| maybe_ss58.chars().all(|c: char| c.is_ascii_digit())
{
return None;
}
if rest.trim_start().starts_with(['(', '{']) {
return None;
}
use base58::FromBase58;
let Ok(bytes) = maybe_ss58.from_base58() else { return None };
let prefix_len = match bytes.first() {
Some(0..=63) => 1,
Some(64..=127) => 2,
_ => return None,
};
if bytes.len() < prefix_len + CHECKSUM_LEN {
return None;
}
let checksum_start_idx = bytes.len() - CHECKSUM_LEN;
let hash = ss58hash(&bytes[0..checksum_start_idx]);
let checksum = &hash[0..CHECKSUM_LEN];
if &bytes[checksum_start_idx..] != checksum {
return None;
}
*s = rest;
Some(bytes[prefix_len..checksum_start_idx].to_vec())
}
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()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn can_parse_ss58_address() {
let expected = [
(
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY ",
"d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
" ",
),
(
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty-100",
"8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48",
"-100",
),
(
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,1,2,3",
"90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22",
",1,2,3",
),
(
"5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw }",
"e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e",
" }",
),
];
for (ss58, expected_hex, expected_remaining) in expected {
let cursor = &mut &*ss58;
let bytes = parse_ss58_bytes(cursor).expect("address should parse OK");
let expected = hex::decode(expected_hex).expect("hex should decode OK");
assert_eq!(bytes, expected);
assert_eq!(*cursor, expected_remaining);
}
}
#[test]
fn invalid_addresses_will_error() {
let invalids = [
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY { hi: 1 }",
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY \t\n (1)",
"Foo",
"",
];
for invalid in invalids {
assert!(parse_ss58_bytes(&mut &*invalid).is_none());
}
}
}