fedimint_core/
base32.rs

1use std::collections::BTreeMap;
2
3use anyhow::{Context, ensure};
4
5use crate::encoding::{Decodable, Encodable};
6use crate::module::registry::ModuleDecoderRegistry;
7
8/// Lowercase RFC 4648 Base32hex alphabet (32 characters).
9const RFC4648: [u8; 32] = *b"0123456789abcdefghijklmnopqrstuv";
10
11/// Prefix used for some of the user-facing Base32 encodings in Fedimint to
12/// allow easy identification
13pub const FEDIMINT_PREFIX: &str = "fedimint";
14
15/// Encodes the input bytes as Base32 (hex variant) using lowercase characters
16pub fn encode(input: &[u8]) -> String {
17    let mut output = Vec::with_capacity(((8 * input.len()) / 5) + 1);
18
19    let mut buffer = 0;
20    let mut bits = 0;
21
22    for byte in input {
23        buffer |= (*byte as usize) << bits;
24        bits += 8;
25
26        while bits >= 5 {
27            output.push(RFC4648[buffer & 0b11111]);
28
29            buffer >>= 5;
30            bits -= 5;
31        }
32    }
33
34    if bits > 0 {
35        output.push(RFC4648[buffer & 0b11111]);
36    }
37
38    String::from_utf8(output).unwrap()
39}
40
41/// Decodes a base 32 string back to raw bytes. Returns an error
42/// if any invalid character is encountered.
43pub fn decode(input: &str) -> anyhow::Result<Vec<u8>> {
44    let decode_table = RFC4648
45        .iter()
46        .enumerate()
47        .map(|(i, c)| (*c, i))
48        .collect::<BTreeMap<u8, usize>>();
49
50    let mut output = Vec::with_capacity(((5 * input.len()) / 8) + 1);
51
52    let mut buffer = 0;
53    let mut bits = 0;
54
55    for byte in input.as_bytes() {
56        let value = decode_table
57            .get(byte)
58            .copied()
59            .context("Invalid character encountered")?;
60
61        buffer |= value << bits;
62        bits += 5;
63
64        while bits >= 8 {
65            output.push((buffer & 0xFF) as u8);
66
67            buffer >>= 8;
68            bits -= 8;
69        }
70    }
71
72    Ok(output)
73}
74
75pub fn encode_prefixed<T: Encodable>(prefix: &str, encodable: &T) -> String {
76    encode_prefixed_bytes(prefix, &encodable.consensus_encode_to_vec())
77}
78
79pub fn encode_prefixed_bytes(prefix: &str, bytes: &[u8]) -> String {
80    format!("{prefix}{}", encode(bytes))
81}
82
83pub fn decode_prefixed<T: Decodable>(prefix: &str, s: &str) -> anyhow::Result<T> {
84    Ok(T::consensus_decode_whole(
85        &decode_prefixed_bytes(prefix, s)?,
86        &ModuleDecoderRegistry::default(),
87    )?)
88}
89
90pub fn decode_prefixed_bytes(prefix: &str, s: &str) -> anyhow::Result<Vec<u8>> {
91    let s = s.to_lowercase();
92    ensure!(s.starts_with(prefix), "Invalid Prefix");
93    decode(&s[prefix.len()..])
94}
95
96#[test]
97fn test_base_32_roundtrip() {
98    const TEST_PREFIX: &str = "test";
99    let data: [u8; 10] = [0x50, 0xAB, 0x3F, 0x77, 0x01, 0xCD, 0x55, 0xFE, 0x10, 0x99];
100
101    for n in 1..10 {
102        let bytes = data[0..n].to_vec();
103
104        assert_eq!(decode(&encode(&bytes)).unwrap(), bytes);
105
106        assert_eq!(
107            decode_prefixed::<Vec<u8>>(TEST_PREFIX, &encode_prefixed(TEST_PREFIX, &bytes)).unwrap(),
108            bytes
109        );
110
111        assert_eq!(
112            decode_prefixed::<Vec<u8>>(
113                TEST_PREFIX,
114                &encode_prefixed(TEST_PREFIX, &bytes).to_ascii_uppercase()
115            )
116            .unwrap(),
117            bytes
118        );
119    }
120}