1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![cfg_attr(not(test), no_std)]
5
6#[allow(unused)]
7use std_shims::prelude::*;
8use std_shims::{vec::Vec, string::String};
9
10use monero_primitives::keccak256;
11
12#[cfg(test)]
13mod tests;
14
15const ALPHABET_LEN: u64 = 58;
16pub(crate) const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
17
18pub(crate) const BLOCK_LEN: usize = 8;
19const ENCODED_BLOCK_LEN: usize = 11;
20
21const CHECKSUM_LEN: usize = 4;
22
23pub(crate) fn encoded_len_for_bytes(bytes: usize) -> usize {
27 let bits = u64::try_from(bytes).expect("length exceeded 2**64") * 8;
28 let mut max = if bits == 64 { u64::MAX } else { (1 << bits) - 1 };
29
30 let mut i = 0;
31 while max != 0 {
32 max /= ALPHABET_LEN;
33 i += 1;
34 }
35 i
36}
37
38pub fn encode(bytes: &[u8]) -> String {
40 let mut res = String::with_capacity(bytes.len().div_ceil(BLOCK_LEN) * ENCODED_BLOCK_LEN);
41
42 for chunk in bytes.chunks(BLOCK_LEN) {
43 let mut fixed_len_chunk = [0; BLOCK_LEN];
45 fixed_len_chunk[(BLOCK_LEN - chunk.len()) ..].copy_from_slice(chunk);
46 let mut val = u64::from_be_bytes(fixed_len_chunk);
47
48 let mut chunk_str = [char::from(ALPHABET[0]); ENCODED_BLOCK_LEN];
50 let mut i = 0;
51 while val > 0 {
52 chunk_str[i] = ALPHABET[usize::try_from(val % ALPHABET_LEN)
53 .expect("ALPHABET_LEN exceeds usize despite being a usize")]
54 .into();
55 i += 1;
56 val /= ALPHABET_LEN;
57 }
58
59 for c in chunk_str.into_iter().take(encoded_len_for_bytes(chunk.len())).rev() {
61 res.push(c);
62 }
63 }
64
65 res
66}
67
68pub fn decode(data: &str) -> Option<Vec<u8>> {
70 let mut res = Vec::with_capacity((data.len() / ENCODED_BLOCK_LEN) * BLOCK_LEN);
71
72 for chunk in data.as_bytes().chunks(ENCODED_BLOCK_LEN) {
73 let mut sum = 0u64;
75 for this_char in chunk {
76 sum = sum.checked_mul(ALPHABET_LEN)?;
77 sum += u64::try_from(ALPHABET.iter().position(|a| a == this_char)?)
78 .expect("alphabet len exceeded 2**64");
79 }
80
81 let mut used_bytes = None;
83 for i in 1 ..= BLOCK_LEN {
84 if encoded_len_for_bytes(i) == chunk.len() {
85 used_bytes = Some(i);
86 break;
87 }
88 }
89 let used_bytes = used_bytes?;
90 res.extend(&sum.to_be_bytes()[(BLOCK_LEN - used_bytes) ..]);
92 }
93
94 Some(res)
95}
96
97pub fn encode_check(mut data: Vec<u8>) -> String {
99 let checksum = keccak256(&data);
100 data.extend(&checksum[.. CHECKSUM_LEN]);
101 encode(&data)
102}
103
104pub fn decode_check(data: &str) -> Option<Vec<u8>> {
106 let mut res = decode(data)?;
107 if res.len() < CHECKSUM_LEN {
108 None?;
109 }
110 let checksum_pos = res.len() - CHECKSUM_LEN;
111 if keccak256(&res[.. checksum_pos])[.. CHECKSUM_LEN] != res[checksum_pos ..] {
112 None?;
113 }
114 res.truncate(checksum_pos);
115 Some(res)
116}