1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// This file is part of Gear.
//
// Copyright (C) 2024 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! SS58 encoding implementation
//!
//! This library is extracted from [ss58 codec][ss58-codec] in `sp-core`, not
//! importing `sp-core` because it is super big (~300 dependencies).
//!
//! [ss58-codec]: https://paritytech.github.io/polkadot-sdk/master/sp_core/crypto/trait.Ss58Codec.html

#![no_std]

extern crate alloc;

use alloc::{string::String, vec, vec::Vec};
use anyhow::{anyhow, Result};
use blake2::{Blake2b512, Digest};
use core::sync::atomic::{AtomicU16, Ordering};

/// The SS58 prefix of vara network.
pub const VARA_SS58_PREFIX: u16 = 137;

/// The default ss58 version.
pub static DEFAULT_SS58_VERSION: AtomicU16 = AtomicU16::new(VARA_SS58_PREFIX);

/// SS58 prefix
const SS58_PREFIX: &[u8] = b"SS58PRE";

/// The checksum length used in ss58 encoding
const CHECKSUM_LENGTH: usize = 2;

/// Encode data to SS58 format.
pub fn encode(data: &[u8]) -> String {
    let ident: u16 = default_ss58_version() & 0b0011_1111_1111_1111;
    let mut v = match ident {
        0..=63 => vec![ident as u8],
        64..=16_383 => {
            // upper six bits of the lower byte(!)
            let first = ((ident & 0b0000_0000_1111_1100) as u8) >> 2;
            // lower two bits of the lower byte in the high pos,
            // lower bits of the upper byte in the low pos
            let second = ((ident >> 8) as u8) | ((ident & 0b0000_0000_0000_0011) as u8) << 6;
            vec![first | 0b01000000, second]
        }
        _ => unreachable!("masked out the upper two bits; qed"),
    };

    v.extend_from_slice(data);
    let r = blake2b_512(&v);
    v.extend(&r[0..CHECKSUM_LENGTH]);
    bs58::encode(v).into_string()
}

/// Decode data from SS58 format.
pub fn decode(encoded: &[u8], body_len: usize) -> Result<Vec<u8>> {
    let data = bs58::decode(encoded)
        .into_vec()
        .map_err(|e| anyhow!("Invalid ss58 data: {e}"))?;
    if data.len() < CHECKSUM_LENGTH {
        return Err(anyhow!("Invalid length of encoded ss58 data."));
    }

    let (prefix_len, _) = match data[0] {
        0..=63 => (1, data[0] as u16),
        64..=127 => {
            // weird bit manipulation owing to the combination of LE encoding and missing two
            // bits from the left.
            // d[0] d[1] are: 01aaaaaa bbcccccc
            // they make the LE-encoded 16-bit value: aaaaaabb 00cccccc
            // so the lower byte is formed of aaaaaabb and the higher byte is 00cccccc
            let lower = (data[0] << 2) | (data[1] >> 6);
            let upper = data[1] & 0b00111111;
            (2, (lower as u16) | ((upper as u16) << 8))
        }
        _ => return Err(anyhow!("Invalid prefix of encoded ss58 data.")),
    };

    if data.len() != prefix_len + body_len + CHECKSUM_LENGTH {
        return Err(anyhow!("Invalid length of encoded ss58 data."));
    }

    let hash = blake2b_512(&data[..prefix_len + body_len]);
    let checksum = &hash[0..CHECKSUM_LENGTH];
    if data[body_len + prefix_len..body_len + prefix_len + CHECKSUM_LENGTH] != *checksum {
        return Err(anyhow!("Invalid checksum of encoded ss58 data."));
    }

    Ok(data[prefix_len..body_len + prefix_len].to_vec())
}

/// re-encoding a ss58 address in the current [`default_ss58_version`].
pub fn recode(encoded: &str) -> Result<String> {
    Ok(self::encode(&self::decode(encoded.as_bytes(), 32)?))
}

/// Get the default ss58 version.
pub fn default_ss58_version() -> u16 {
    DEFAULT_SS58_VERSION.load(Ordering::Relaxed)
}

/// Set the default ss58 version.
pub fn set_default_ss58_version(version: u16) {
    DEFAULT_SS58_VERSION.store(version, Ordering::Relaxed);
}

/// blake2b_512 hash
fn blake2b_512(data: &[u8]) -> Vec<u8> {
    let mut ctx = Blake2b512::new();
    ctx.update(SS58_PREFIX);
    ctx.update(data);
    ctx.finalize().to_vec()
}