darkfi/util/encoding/
base32.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (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 Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19//! Base32 encoding as specified by RFC4648
20//! Optional padding is the `=` character.
21// Taken from https://github.com/andreasots/base32
22use core::cmp::min;
23
24/// Standard Base32 alphabet.
25const ENCODE_STD: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
26
27/// Encode a byte slice with the given base32 alphabet into a base32 string.
28pub fn encode(padding: bool, data: &[u8]) -> String {
29    let mut ret = Vec::with_capacity(data.len().div_ceil(4) * 5);
30
31    for chunk in data.chunks(5) {
32        let buf = {
33            let mut buf = [0u8; 5];
34            for (i, &b) in chunk.iter().enumerate() {
35                buf[i] = b;
36            }
37            buf
38        };
39
40        ret.push(ENCODE_STD[((buf[0] & 0xf8) >> 3) as usize]);
41        ret.push(ENCODE_STD[(((buf[0] & 0x07) << 2) | ((buf[1] & 0xc0) >> 6)) as usize]);
42        ret.push(ENCODE_STD[((buf[1] & 0x3e) >> 1) as usize]);
43        ret.push(ENCODE_STD[(((buf[1] & 0x01) << 4) | ((buf[2] & 0xf0) >> 4)) as usize]);
44        ret.push(ENCODE_STD[(((buf[2] & 0x0f) << 1) | (buf[3] >> 7)) as usize]);
45        ret.push(ENCODE_STD[((buf[3] & 0x7c) >> 2) as usize]);
46        ret.push(ENCODE_STD[(((buf[3] & 0x03) << 3) | ((buf[4] & 0xe0) >> 5)) as usize]);
47        ret.push(ENCODE_STD[(buf[4] & 0x1f) as usize]);
48    }
49
50    if !data.len().is_multiple_of(5) {
51        let len = ret.len();
52        let num_extra = 8 - (data.len() % 5 * 8).div_ceil(5);
53        if padding {
54            for i in 1..num_extra + 1 {
55                ret[len - i] = b'=';
56            }
57        } else {
58            ret.truncate(len - num_extra);
59        }
60    }
61
62    String::from_utf8(ret).unwrap()
63}
64
65const STD_INV_ALPHABET: [i8; 43] = [
66    -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, 0, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,
67    9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
68];
69
70/// Tries to decode a base32 string into a byte vector. Returns `None` if
71/// something fails.
72pub fn decode(data: &str) -> Option<Vec<u8>> {
73    if !data.is_ascii() {
74        return None
75    }
76
77    let data = data.as_bytes();
78    let mut unpadded_data_len = data.len();
79
80    for i in 1..min(6, data.len()) + 1 {
81        if data[data.len() - i] != b'=' {
82            break
83        }
84        unpadded_data_len -= 1;
85    }
86
87    let output_length = unpadded_data_len * 5 / 8;
88    let mut ret = Vec::with_capacity(output_length.div_ceil(5) * 5);
89
90    for chunk in data.chunks(8) {
91        let buf = {
92            let mut buf = [0u8; 8];
93            for (i, &c) in chunk.iter().enumerate() {
94                match STD_INV_ALPHABET.get(c.to_ascii_uppercase().wrapping_sub(b'0') as usize) {
95                    Some(&-1) | None => return None,
96                    Some(&value) => buf[i] = value as u8,
97                };
98            }
99
100            buf
101        };
102
103        ret.push((buf[0] << 3) | (buf[1] >> 2));
104        ret.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
105        ret.push((buf[3] << 4) | (buf[4] >> 1));
106        ret.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
107        ret.push((buf[6] << 5) | buf[7]);
108    }
109
110    ret.truncate(output_length);
111    Some(ret)
112}
113
114#[cfg(test)]
115mod tests {
116    #[test]
117    fn base32_encoding_decoding() {
118        let s = b"b32Test"; // This should pad with 4 =
119        let encoded = super::encode(true, &s[..]);
120        assert_eq!(&encoded, "MIZTEVDFON2A====");
121        assert_eq!(super::decode(&encoded).unwrap(), s);
122
123        let s = b"b32Testoor"; // This shouldn't pad
124        let encoded = super::encode(true, &s[..]);
125        assert_eq!(&encoded, "MIZTEVDFON2G633S");
126        assert_eq!(super::decode(&encoded).unwrap(), s);
127    }
128}