scale_value/string_impls/custom_parsers/
ss58.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::prelude::*;
17use crate::{stringify::ParseError, Value};
18
19/// Attempt to parse an ss58 address into a [`Value<()>`] (or more specifically,
20/// an unnamed composite wrapped in a newtype which represents an AccountId32).
21///
22/// - Returns `None` if we can't parse the address.
23/// - Returns `Some(value)` if parsing was successful. In this case, the string
24///   reference given is wound forwards to consume what was parsed.
25pub fn parse_ss58(s: &mut &str) -> Option<Result<Value<()>, ParseError>> {
26    let bytes = parse_ss58_bytes(s)?;
27    Some(Ok(Value::from_bytes(bytes)))
28}
29
30fn parse_ss58_bytes(s: &mut &str) -> Option<Vec<u8>> {
31    const CHECKSUM_LEN: usize = 2;
32
33    // ss58 addresses are base58 encoded. Base58 is all alphanumeric chars
34    // minus a few that look potentially similar. So, gather alphanumeric chars
35    // first.
36    let end_idx = s.find(|c: char| !c.is_ascii_alphanumeric()).unwrap_or(s.len());
37    let maybe_ss58 = &s[0..end_idx];
38    let rest = &s[end_idx..];
39
40    if maybe_ss58.is_empty() {
41        return None;
42    }
43
44    // Break early on obvious non-addresses that we want to parse elsewise, ie true, false or numbers.
45    // This is mostly an optimisation but also eliminates some potential weird edge cases.
46    if maybe_ss58 == "true"
47        || maybe_ss58 == "false"
48        || maybe_ss58.chars().all(|c: char| c.is_ascii_digit())
49    {
50        return None;
51    }
52
53    // If what we are parsing is a variant ident, a `{` or `(` will follow
54    // (eg `Foo { hi: 1 }` or `Foo (1)`). In this case, don't try to parse
55    // as an ss58 address, since it would definitely be wrong to do so.
56    if rest.trim_start().starts_with(['(', '{']) {
57        return None;
58    }
59
60    // Attempt to base58-decode these chars.
61    use base58::FromBase58;
62    let Ok(bytes) = maybe_ss58.from_base58() else { return None };
63
64    // decode length of address prefix.
65    let prefix_len = match bytes.first() {
66        Some(0..=63) => 1,
67        Some(64..=127) => 2,
68        _ => return None,
69    };
70
71    if bytes.len() < prefix_len + CHECKSUM_LEN {
72        return None;
73    }
74
75    // The checksum is the last 2 bytes:
76    let checksum_start_idx = bytes.len() - CHECKSUM_LEN;
77
78    // Check that the checksum lines up with the rest of the address; if not,
79    // this isn't a valid address.
80    let hash = ss58hash(&bytes[0..checksum_start_idx]);
81    let checksum = &hash[0..CHECKSUM_LEN];
82    if &bytes[checksum_start_idx..] != checksum {
83        return None;
84    }
85
86    // Everything checks out; wind the string cursor forwards and
87    // return the bytes representing the address provided.
88    *s = rest;
89    Some(bytes[prefix_len..checksum_start_idx].to_vec())
90}
91
92fn ss58hash(data: &[u8]) -> Vec<u8> {
93    use blake2::{Blake2b512, Digest};
94    const PREFIX: &[u8] = b"SS58PRE";
95    let mut ctx = Blake2b512::new();
96    ctx.update(PREFIX);
97    ctx.update(data);
98    ctx.finalize().to_vec()
99}
100
101#[cfg(test)]
102mod test {
103    use super::*;
104
105    #[test]
106    fn can_parse_ss58_address() {
107        // hex keys obtained via `subkey`, so we're comparing our decoding against that.
108        // We simultaneously check that things after the address aren't consumed.
109        let expected = [
110            // Alice
111            (
112                "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY ",
113                "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
114                " ",
115            ),
116            // Bob
117            (
118                "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty-100",
119                "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48",
120                "-100",
121            ),
122            // Charlie
123            (
124                "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,1,2,3",
125                "90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22",
126                ",1,2,3",
127            ),
128            // Eve
129            (
130                "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw }",
131                "e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e",
132                " }",
133            ),
134        ];
135
136        for (ss58, expected_hex, expected_remaining) in expected {
137            let cursor = &mut &*ss58;
138            let bytes = parse_ss58_bytes(cursor).expect("address should parse OK");
139            let expected = hex::decode(expected_hex).expect("hex should decode OK");
140
141            assert_eq!(bytes, expected);
142            assert_eq!(*cursor, expected_remaining);
143        }
144    }
145
146    #[test]
147    fn invalid_addresses_will_error() {
148        let invalids = [
149            // An otherwise valid address in a variant "ident" position will not parse:
150            "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY { hi: 1 }",
151            "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY     \t\n (1)",
152            // Invalid addresses will return None:
153            "Foo",
154            "",
155        ];
156
157        for invalid in invalids {
158            assert!(parse_ss58_bytes(&mut &*invalid).is_none());
159        }
160    }
161}