monero_address/
base58check.rs

1use std_shims::{vec::Vec, string::String};
2
3use monero_primitives::keccak256;
4
5const ALPHABET_LEN: u64 = 58;
6const ALPHABET: &[u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
7
8pub(crate) const BLOCK_LEN: usize = 8;
9const ENCODED_BLOCK_LEN: usize = 11;
10
11const CHECKSUM_LEN: usize = 4;
12
13// The maximum possible length of an encoding of this many bytes
14//
15// This is used for determining padding/how many bytes an encoding actually uses
16pub(crate) fn encoded_len_for_bytes(bytes: usize) -> usize {
17  let bits = u64::try_from(bytes).expect("length exceeded 2**64") * 8;
18  let mut max = if bits == 64 { u64::MAX } else { (1 << bits) - 1 };
19
20  let mut i = 0;
21  while max != 0 {
22    max /= ALPHABET_LEN;
23    i += 1;
24  }
25  i
26}
27
28// Encode an arbitrary-length stream of data
29pub(crate) fn encode(bytes: &[u8]) -> String {
30  let mut res = String::with_capacity(bytes.len().div_ceil(BLOCK_LEN) * ENCODED_BLOCK_LEN);
31
32  for chunk in bytes.chunks(BLOCK_LEN) {
33    // Convert to a u64
34    let mut fixed_len_chunk = [0; BLOCK_LEN];
35    fixed_len_chunk[(BLOCK_LEN - chunk.len()) ..].copy_from_slice(chunk);
36    let mut val = u64::from_be_bytes(fixed_len_chunk);
37
38    // Convert to the base58 encoding
39    let mut chunk_str = [char::from(ALPHABET[0]); ENCODED_BLOCK_LEN];
40    let mut i = 0;
41    while val > 0 {
42      chunk_str[i] = ALPHABET[usize::try_from(val % ALPHABET_LEN)
43        .expect("ALPHABET_LEN exceeds usize despite being a usize")]
44      .into();
45      i += 1;
46      val /= ALPHABET_LEN;
47    }
48
49    // Only take used bytes, and since we put the LSBs in the first byte, reverse the byte order
50    for c in chunk_str.into_iter().take(encoded_len_for_bytes(chunk.len())).rev() {
51      res.push(c);
52    }
53  }
54
55  res
56}
57
58// Decode an arbitrary-length stream of data
59pub(crate) fn decode(data: &str) -> Option<Vec<u8>> {
60  let mut res = Vec::with_capacity((data.len() / ENCODED_BLOCK_LEN) * BLOCK_LEN);
61
62  for chunk in data.as_bytes().chunks(ENCODED_BLOCK_LEN) {
63    // Convert the chunk back to a u64
64    let mut sum = 0u64;
65    for this_char in chunk {
66      sum = sum.checked_mul(ALPHABET_LEN)?;
67      sum += u64::try_from(ALPHABET.iter().position(|a| a == this_char)?)
68        .expect("alphabet len exceeded 2**64");
69    }
70
71    // From the size of the encoding, determine the size of the bytes
72    let mut used_bytes = None;
73    for i in 1 ..= BLOCK_LEN {
74      if encoded_len_for_bytes(i) == chunk.len() {
75        used_bytes = Some(i);
76        break;
77      }
78    }
79    // Only push on the used bytes
80    res.extend(&sum.to_be_bytes()[(BLOCK_LEN - used_bytes.unwrap()) ..]);
81  }
82
83  Some(res)
84}
85
86// Encode an arbitrary-length stream of data, with a checksum
87pub(crate) fn encode_check(mut data: Vec<u8>) -> String {
88  let checksum = keccak256(&data);
89  data.extend(&checksum[.. CHECKSUM_LEN]);
90  encode(&data)
91}
92
93// Decode an arbitrary-length stream of data, with a checksum
94pub(crate) fn decode_check(data: &str) -> Option<Vec<u8>> {
95  if data.len() < CHECKSUM_LEN {
96    None?;
97  }
98
99  let mut res = decode(data)?;
100  let checksum_pos = res.len() - CHECKSUM_LEN;
101  if keccak256(&res[.. checksum_pos])[.. CHECKSUM_LEN] != res[checksum_pos ..] {
102    None?;
103  }
104  res.truncate(checksum_pos);
105  Some(res)
106}