Coverage Report

Created: 2024-05-16 12:16

/__w/smoldot/smoldot/repo/lib/src/identity/ss58.rs
Line
Count
Source (jump to first uncovered line)
1
// Smoldot
2
// Copyright (C) 2023  Pierre Krieger
3
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
9
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
15
// You should have received a copy of the GNU General Public License
16
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18
use alloc::{string::String, vec::Vec};
19
use core::fmt;
20
21
/// Decoded version of an SS58 address.
22
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
23
pub struct Decoded<P> {
24
    /// Identifier indicating which chain is concerned.
25
    ///
26
    /// The mapping between chains and this prefix can be found in this central registry:
27
    /// <https://github.com/paritytech/ss58-registry/blob/main/ss58-registry.json>.
28
    pub chain_prefix: ChainPrefix,
29
30
    /// Public key of the account.
31
    pub public_key: P,
32
}
33
34
/// Identifier indicating which chain is concerned.
35
///
36
/// The mapping between chains and this prefix can be found in this central registry:
37
/// <https://github.com/paritytech/ss58-registry/blob/main/ss58-registry.json>.
38
///
39
/// This prefix is a 14 bits unsigned integer.
40
//
41
// Implementation note: the `u16` is guaranteed to be only up to 14 bits long. The upper two bits
42
// are always 0.
43
#[derive(Copy, Clone, PartialEq, Eq)]
44
pub struct ChainPrefix(u16);
45
46
impl fmt::Debug for ChainPrefix {
47
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48
0
        fmt::Debug::fmt(&self.0, f)
49
0
    }
Unexecuted instantiation: _RNvXNtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB2_11ChainPrefixNtNtCsaYZPK01V26L_4core3fmt5Debug3fmt
Unexecuted instantiation: _RNvXNtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB2_11ChainPrefixNtNtCsaYZPK01V26L_4core3fmt5Debug3fmt
50
}
51
52
impl TryFrom<u16> for ChainPrefix {
53
    type Error = PrefixTooLargeError;
54
55
0
    fn try_from(prefix: u16) -> Result<Self, Self::Error> {
56
0
        if (prefix >> 14) == 0 {
57
0
            Ok(ChainPrefix(prefix))
58
        } else {
59
0
            Err(PrefixTooLargeError())
60
        }
61
0
    }
Unexecuted instantiation: _RNvXs_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB4_11ChainPrefixINtNtCsaYZPK01V26L_4core7convert7TryFromtE8try_from
Unexecuted instantiation: _RNvXs_NtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB4_11ChainPrefixINtNtCsaYZPK01V26L_4core7convert7TryFromtE8try_from
62
}
63
64
/// Integer is too large to be a valid prefix
65
0
#[derive(Debug, Clone, derive_more::Display)]
Unexecuted instantiation: _RNvXsf_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB5_19PrefixTooLargeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
Unexecuted instantiation: _RNvXsf_NtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB5_19PrefixTooLargeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
66
pub struct PrefixTooLargeError();
67
68
impl From<u8> for ChainPrefix {
69
0
    fn from(prefix: u8) -> ChainPrefix {
70
0
        ChainPrefix(u16::from(prefix))
71
0
    }
Unexecuted instantiation: _RNvXs0_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB5_11ChainPrefixINtNtCsaYZPK01V26L_4core7convert4FromhE4from
Unexecuted instantiation: _RNvXs0_NtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB5_11ChainPrefixINtNtCsaYZPK01V26L_4core7convert4FromhE4from
72
}
73
74
impl From<ChainPrefix> for u16 {
75
2
    fn from(prefix: ChainPrefix) -> u16 {
76
2
        prefix.0
77
2
    }
_RNvXs1_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58tINtNtCsaYZPK01V26L_4core7convert4FromNtB5_11ChainPrefixE4from
Line
Count
Source
75
2
    fn from(prefix: ChainPrefix) -> u16 {
76
2
        prefix.0
77
2
    }
Unexecuted instantiation: _RNvXs1_NtNtCseuYC0Zibziv_7smoldot8identity4ss58tINtNtCsaYZPK01V26L_4core7convert4FromNtB5_11ChainPrefixE4from
78
}
79
80
/// Turns a decoded SS58 address into a string.
81
2
pub fn encode(decoded: Decoded<impl AsRef<[u8]>>) -> String {
82
2
    let prefix = decoded.chain_prefix.0;
83
2
    let public_key = decoded.public_key.as_ref();
84
2
85
2
    let mut bytes = Vec::with_capacity(2 + public_key.len() + 2);
86
2
87
2
    if prefix < 64 {
88
1
        bytes.push(prefix as u8);
89
1
    } else {
90
1
        // This encoding is plain weird.
91
1
        bytes.push(((prefix & 0b0000_0000_1111_1100) as u8) >> 2 | 0b01000000);
92
1
        bytes.push(((prefix >> 8) as u8) | ((prefix & 0b0000_0000_0000_0011) as u8) << 6);
93
1
    }
94
95
2
    bytes.extend_from_slice(public_key);
96
2
97
2
    let checksum = calculate_checksum(&bytes);
98
2
    bytes.extend_from_slice(&checksum);
99
2
100
2
    bs58::encode(&bytes).into_string()
101
2
}
_RINvNtNtCsN16ciHI6Qf_7smoldot8identity4ss586encodeNtNvB2_6decode6AdjustEB6_
Line
Count
Source
81
2
pub fn encode(decoded: Decoded<impl AsRef<[u8]>>) -> String {
82
2
    let prefix = decoded.chain_prefix.0;
83
2
    let public_key = decoded.public_key.as_ref();
84
2
85
2
    let mut bytes = Vec::with_capacity(2 + public_key.len() + 2);
86
2
87
2
    if prefix < 64 {
88
1
        bytes.push(prefix as u8);
89
1
    } else {
90
1
        // This encoding is plain weird.
91
1
        bytes.push(((prefix & 0b0000_0000_1111_1100) as u8) >> 2 | 0b01000000);
92
1
        bytes.push(((prefix >> 8) as u8) | ((prefix & 0b0000_0000_0000_0011) as u8) << 6);
93
1
    }
94
95
2
    bytes.extend_from_slice(public_key);
96
2
97
2
    let checksum = calculate_checksum(&bytes);
98
2
    bytes.extend_from_slice(&checksum);
99
2
100
2
    bs58::encode(&bytes).into_string()
101
2
}
Unexecuted instantiation: _RINvNtNtCseuYC0Zibziv_7smoldot8identity4ss586encodepEB6_
102
103
/// Decodes an SS58 address from a string.
104
2
pub fn decode(encoded: &'_ str) -> Result<Decoded<impl AsRef<[u8]>>, DecodeError> {
105
2
    let mut bytes = bs58::decode(encoded)
106
2
        .into_vec()
107
2
        .map_err(|err| 
DecodeError::InvalidBs58(Bs58DecodeError(err))0
)
?0
;
Unexecuted instantiation: _RNCNvNtNtCsN16ciHI6Qf_7smoldot8identity4ss586decode0B7_
Unexecuted instantiation: _RNCNvNtNtCseuYC0Zibziv_7smoldot8identity4ss586decode0B7_
108
109
2
    if bytes.len() < 4 {
110
0
        return Err(DecodeError::TooShort);
111
2
    }
112
2
113
2
    // Verify the checksum.
114
2
    let expected_checksum = calculate_checksum(&bytes[..bytes.len() - 2]);
115
2
    if expected_checksum[..] != bytes[bytes.len() - 2..] {
116
0
        return Err(DecodeError::InvalidChecksum);
117
2
    }
118
2
    bytes.truncate(bytes.len() - 2);
119
120
    // Grab and remove the prefix.
121
2
    let (prefix_len, chain_prefix) = if bytes[0] < 64 {
122
1
        (1, ChainPrefix(u16::from(bytes[0])))
123
1
    } else if bytes[0] < 128 {
124
1
        let prefix = u16::from_be_bytes([bytes[1] & 0b00111111, (bytes[0] << 2) | (bytes[1] >> 6)]);
125
1
        (2, ChainPrefix(prefix))
126
    } else {
127
0
        return Err(DecodeError::InvalidPrefix);
128
    };
129
130
    // Rather than remove the prefix from the beginning of `bytes`, we adjust the `AsRef`
131
    // implementation to skip the prefix.
132
2
    let public_key = {
133
2
        struct Adjust(Vec<u8>, usize);
134
2
        impl AsRef<[u8]> for Adjust {
135
4
            fn as_ref(&self) -> &[u8] {
136
4
                &self.0[self.1..]
137
4
            }
_RNvXNvNtNtCsN16ciHI6Qf_7smoldot8identity4ss586decodeNtB2_6AdjustINtNtCsaYZPK01V26L_4core7convert5AsRefShE6as_ref
Line
Count
Source
135
4
            fn as_ref(&self) -> &[u8] {
136
4
                &self.0[self.1..]
137
4
            }
Unexecuted instantiation: _RNvXNvNtNtCseuYC0Zibziv_7smoldot8identity4ss586decodeNtB2_6AdjustINtNtCsaYZPK01V26L_4core7convert5AsRefShE6as_ref
138
2
        }
139
2
        Adjust(bytes, prefix_len)
140
2
    };
141
2
142
2
    Ok(Decoded {
143
2
        chain_prefix,
144
2
        public_key,
145
2
    })
146
2
}
_RNvNtNtCsN16ciHI6Qf_7smoldot8identity4ss586decode
Line
Count
Source
104
2
pub fn decode(encoded: &'_ str) -> Result<Decoded<impl AsRef<[u8]>>, DecodeError> {
105
2
    let mut bytes = bs58::decode(encoded)
106
2
        .into_vec()
107
2
        .map_err(|err| DecodeError::InvalidBs58(Bs58DecodeError(err)))
?0
;
108
109
2
    if bytes.len() < 4 {
110
0
        return Err(DecodeError::TooShort);
111
2
    }
112
2
113
2
    // Verify the checksum.
114
2
    let expected_checksum = calculate_checksum(&bytes[..bytes.len() - 2]);
115
2
    if expected_checksum[..] != bytes[bytes.len() - 2..] {
116
0
        return Err(DecodeError::InvalidChecksum);
117
2
    }
118
2
    bytes.truncate(bytes.len() - 2);
119
120
    // Grab and remove the prefix.
121
2
    let (prefix_len, chain_prefix) = if bytes[0] < 64 {
122
1
        (1, ChainPrefix(u16::from(bytes[0])))
123
1
    } else if bytes[0] < 128 {
124
1
        let prefix = u16::from_be_bytes([bytes[1] & 0b00111111, (bytes[0] << 2) | (bytes[1] >> 6)]);
125
1
        (2, ChainPrefix(prefix))
126
    } else {
127
0
        return Err(DecodeError::InvalidPrefix);
128
    };
129
130
    // Rather than remove the prefix from the beginning of `bytes`, we adjust the `AsRef`
131
    // implementation to skip the prefix.
132
2
    let public_key = {
133
2
        struct Adjust(Vec<u8>, usize);
134
2
        impl AsRef<[u8]> for Adjust {
135
2
            fn as_ref(&self) -> &[u8] {
136
2
                &self.0[self.1..]
137
2
            }
138
2
        }
139
2
        Adjust(bytes, prefix_len)
140
2
    };
141
2
142
2
    Ok(Decoded {
143
2
        chain_prefix,
144
2
        public_key,
145
2
    })
146
2
}
Unexecuted instantiation: _RNvNtNtCseuYC0Zibziv_7smoldot8identity4ss586decode
147
148
/// Error while decoding an SS58 address.
149
0
#[derive(Debug, Clone, derive_more::Display)]
Unexecuted instantiation: _RNvXsi_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB5_11DecodeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
Unexecuted instantiation: _RNvXsi_NtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB5_11DecodeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
150
pub enum DecodeError {
151
    /// SS58 is too short to possibly be valid.
152
    TooShort,
153
    /// Invalid SS58 prefix encoding.
154
    InvalidPrefix,
155
    /// Invalid BS58 format.
156
    #[display(fmt = "{_0}")]
157
    InvalidBs58(Bs58DecodeError),
158
    /// Calculated checksum doesn't match the one provided.
159
    InvalidChecksum,
160
}
161
162
/// Error when decoding Base58 encoding.
163
0
#[derive(Debug, Clone, derive_more::Display, derive_more::From)]
Unexecuted instantiation: _RNvXsl_NtNtCsN16ciHI6Qf_7smoldot8identity4ss58NtB5_15Bs58DecodeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
Unexecuted instantiation: _RNvXsl_NtNtCseuYC0Zibziv_7smoldot8identity4ss58NtB5_15Bs58DecodeErrorNtNtCsaYZPK01V26L_4core3fmt7Display3fmt
164
pub struct Bs58DecodeError(bs58::decode::Error);
165
166
4
fn calculate_checksum(data: &[u8]) -> [u8; 2] {
167
4
    let mut hasher = blake2_rfc::blake2b::Blake2b::new(64);
168
4
    hasher.update(b"SS58PRE");
169
4
    hasher.update(data);
170
4
171
4
    let hash = hasher.finalize();
172
4
    *<&[u8; 2]>::try_from(&hash.as_bytes()[..2]).unwrap_or_else(|_| 
unreachable!()0
)
Unexecuted instantiation: _RNCNvNtNtCsN16ciHI6Qf_7smoldot8identity4ss5818calculate_checksum0B7_
Unexecuted instantiation: _RNCNvNtNtCseuYC0Zibziv_7smoldot8identity4ss5818calculate_checksum0B7_
173
4
}
_RNvNtNtCsN16ciHI6Qf_7smoldot8identity4ss5818calculate_checksum
Line
Count
Source
166
4
fn calculate_checksum(data: &[u8]) -> [u8; 2] {
167
4
    let mut hasher = blake2_rfc::blake2b::Blake2b::new(64);
168
4
    hasher.update(b"SS58PRE");
169
4
    hasher.update(data);
170
4
171
4
    let hash = hasher.finalize();
172
4
    *<&[u8; 2]>::try_from(&hash.as_bytes()[..2]).unwrap_or_else(|_| unreachable!())
173
4
}
Unexecuted instantiation: _RNvNtNtCseuYC0Zibziv_7smoldot8identity4ss5818calculate_checksum
174
175
#[cfg(test)]
176
mod tests {
177
    #[test]
178
1
    fn alice_polkadot() {
179
1
        let encoded = "15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5";
180
1
181
1
        let decoded = super::decode(encoded).unwrap();
182
1
        assert_eq!(u16::from(decoded.chain_prefix), 0);
183
1
        assert_eq!(
184
1
            decoded.public_key.as_ref(),
185
1
            &[
186
1
                212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44,
187
1
                133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125
188
1
            ][..]
189
1
        );
190
191
1
        assert_eq!(super::encode(decoded), encoded);
192
1
    }
193
194
    #[test]
195
1
    fn sora_default_seed_phrase() {
196
1
        let encoded = "cnT6GtrVo7AYsRc2LgfTgW8Gu4gpZpxhaaKMm7zH8Ry14pJ8b";
197
1
198
1
        let decoded = super::decode(encoded).unwrap();
199
1
        assert_eq!(u16::from(decoded.chain_prefix), 69);
200
1
        assert_eq!(
201
1
            decoded.public_key.as_ref(),
202
1
            &[
203
1
                70, 235, 221, 239, 140, 217, 187, 22, 125, 195, 8, 120, 215, 17, 59, 126, 22, 142,
204
1
                111, 6, 70, 190, 255, 215, 125, 105, 211, 155, 173, 118, 180, 122
205
1
            ][..]
206
1
        );
207
208
1
        assert_eq!(super::encode(decoded), encoded);
209
1
    }
210
}