509 lines
15 KiB
C++
509 lines
15 KiB
C++
#pragma once
|
|
#ifndef BASECODEC_HPP
|
|
#define BASECODEC_HPP
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cctype>
|
|
#include <cstdint>
|
|
#include <array>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
|
|
//By ChatGPT: 1:1 converted from cpython library source code
|
|
//base58 from base58 library
|
|
namespace basecodec
|
|
{
|
|
|
|
// -------------------------
|
|
// Base16 (hex) encoding/decoding
|
|
// Converted from Python base16 implementation:
|
|
// def b16encode(s): return binascii.hexlify(s).upper()
|
|
// def b16decode(s, casefold=False): ... binascii.unhexlify(s)
|
|
// Python source around lines 290-320 in base64.py
|
|
// -------------------------
|
|
|
|
// Encode to hex, always succeeds
|
|
inline std::string b16encode(const std::vector<uint8_t>& data)
|
|
{
|
|
std::ostringstream oss;
|
|
oss << std::uppercase << std::hex;
|
|
for (auto byte : data)
|
|
oss << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
|
|
return oss.str();
|
|
}
|
|
|
|
// Decode from hex, returns success status; outputs bytes in 'out'
|
|
inline bool b16decode(const std::string& s, std::vector<uint8_t>& out, bool casefold = false)
|
|
{
|
|
std::string str = s;
|
|
if (casefold)
|
|
{
|
|
for (auto& c : str)
|
|
c = std::toupper(static_cast<unsigned char>(c));
|
|
}
|
|
if (str.size() % 2 != 0)
|
|
return false; // invalid length
|
|
out.clear();
|
|
out.reserve(str.size() / 2);
|
|
for (size_t i = 0; i < str.size(); i += 2)
|
|
{
|
|
auto val = [&] (char c) -> int
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return -1;
|
|
};
|
|
int high = val(str[i]);
|
|
int low = val(str[i + 1]);
|
|
if (high < 0 || low < 0)
|
|
{
|
|
out.clear();
|
|
return false; // non-hex digit
|
|
}
|
|
out.push_back(static_cast<uint8_t>((high << 4) | low));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// -------------------------
|
|
// Base85 encoding/decoding (Z85-compatible)
|
|
// Converted from Python base85 implementation:
|
|
// def b85encode(b, pad=False): ... _85encode(...)
|
|
// def b85decode(b): ...
|
|
// Python source around lines 550-650 in base64.py
|
|
// -------------------------
|
|
|
|
// Base85 alphabet as per Python _b85alphabet
|
|
inline const std::string& _b85alphabet()
|
|
{
|
|
static const std::string alphabet =
|
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
|
|
return alphabet;
|
|
}
|
|
|
|
// Precomputed table of 85x85 two-character combos
|
|
inline const std::vector<std::string>& _b85chars2()
|
|
{
|
|
static std::vector<std::string> table;
|
|
if (table.empty())
|
|
{
|
|
const auto& alph = _b85alphabet();
|
|
table.reserve(85 * 85);
|
|
for (char a : alph)
|
|
for (char b : alph)
|
|
table.emplace_back(std::string{a, b});
|
|
}
|
|
return table;
|
|
}
|
|
|
|
// Encode bytes to Z85 string, always succeeds
|
|
inline std::string b85encode(const std::vector<uint8_t>& data, bool pad = false)
|
|
{
|
|
const auto& alph = _b85alphabet();
|
|
const auto& table2 = _b85chars2();
|
|
size_t len = data.size();
|
|
size_t padding = (4 - (len % 4)) % 4;
|
|
std::vector<uint8_t> bytes = data;
|
|
if (padding)
|
|
bytes.insert(bytes.end(), padding, 0);
|
|
|
|
std::string result;
|
|
result.reserve((bytes.size() / 4) * 5);
|
|
for (size_t i = 0; i < bytes.size(); i += 4)
|
|
{
|
|
uint32_t acc =
|
|
(uint32_t(bytes[i]) << 24) |
|
|
(uint32_t(bytes[i + 1]) << 16) |
|
|
(uint32_t(bytes[i + 2]) << 8) |
|
|
uint32_t(bytes[i + 3]);
|
|
uint32_t idx1 = acc / 614125; // 85^3
|
|
uint32_t idx2 = (acc / 85) % 7225; // 85^2
|
|
uint32_t idx3 = acc % 85;
|
|
result += table2[idx1];
|
|
result += table2[idx2];
|
|
result.push_back(alph[idx3]);
|
|
}
|
|
if (padding && !pad)
|
|
result.resize(result.size() - padding);
|
|
return result;
|
|
}
|
|
|
|
// Decode Z85 string to bytes, returns success status; outputs bytes in 'out'
|
|
inline bool b85decode(const std::string& s, std::vector<uint8_t>& out)
|
|
{
|
|
const auto& alph = _b85alphabet();
|
|
static std::array<int, 256> dec;
|
|
static bool init = false;
|
|
if (!init)
|
|
{
|
|
dec.fill(-1);
|
|
for (size_t i = 0; i < alph.size(); ++i)
|
|
dec[static_cast<unsigned char>(alph[i])] = int(i);
|
|
init = true;
|
|
}
|
|
size_t len = s.size();
|
|
size_t padding = (5 - (len % 5)) % 5;
|
|
std::string str = s;
|
|
str.append(padding, alph[0]);
|
|
|
|
out.clear();
|
|
out.reserve((str.size() / 5) * 4);
|
|
for (size_t i = 0; i < str.size(); i += 5)
|
|
{
|
|
uint32_t acc = 0;
|
|
for (size_t j = 0; j < 5; ++j)
|
|
{
|
|
int v = dec[static_cast<unsigned char>(str[i + j])];
|
|
if (v < 0)
|
|
{
|
|
out.clear();
|
|
return false; // bad base85 char
|
|
}
|
|
acc = acc * 85 + uint32_t(v);
|
|
}
|
|
out.push_back(uint8_t((acc >> 24) & 0xFF));
|
|
out.push_back(uint8_t((acc >> 16) & 0xFF));
|
|
out.push_back(uint8_t((acc >> 8) & 0xFF));
|
|
out.push_back(uint8_t(acc & 0xFF));
|
|
}
|
|
if (padding)
|
|
out.resize(out.size() - padding);
|
|
return true;
|
|
}
|
|
|
|
// -------------------------
|
|
// Base58 encoding/decoding (bitcoin-compatible)
|
|
// Converted from Python base58 implementation (base58.py)
|
|
// -------------------------
|
|
|
|
inline const std::string& b58_alphabet()
|
|
{
|
|
static const std::string alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
return alphabet;
|
|
}
|
|
|
|
inline std::string b58encode(const std::vector<uint8_t>& data)
|
|
{
|
|
const auto& alph = b58_alphabet();
|
|
std::string result;
|
|
uint64_t num = 0;
|
|
for (uint8_t b : data) num = (num << 8) | b;
|
|
|
|
while (num > 0)
|
|
{
|
|
result.insert(result.begin(), alph[num % 58]);
|
|
num /= 58;
|
|
}
|
|
|
|
for (uint8_t b : data)
|
|
{
|
|
if (b == 0x00)
|
|
result.insert(result.begin(), alph[0]);
|
|
else
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline bool b58decode(const std::string& s, std::vector<uint8_t>& out)
|
|
{
|
|
const auto& alph = b58_alphabet();
|
|
std::unordered_map<char, int> index;
|
|
for (size_t i = 0; i < alph.size(); ++i)
|
|
index[alph[i]] = int(i);
|
|
|
|
uint64_t num = 0;
|
|
for (char c : s)
|
|
{
|
|
if (index.find(c) == index.end())
|
|
return false;
|
|
num = num * 58 + index[c];
|
|
}
|
|
|
|
std::vector<uint8_t> tmp;
|
|
while (num > 0)
|
|
{
|
|
tmp.insert(tmp.begin(), static_cast<uint8_t>(num & 0xFF));
|
|
num >>= 8;
|
|
}
|
|
|
|
for (char c : s)
|
|
{
|
|
if (c == alph[0])
|
|
tmp.insert(tmp.begin(), 0x00);
|
|
else
|
|
break;
|
|
}
|
|
out = tmp;
|
|
return true;
|
|
}
|
|
|
|
inline std::string encodeBase58(const std::string& input) noexcept
|
|
{
|
|
return b58encode(std::vector<uint8_t>(input.begin(), input.end()));
|
|
}
|
|
|
|
inline bool decodeBase58(const std::string& input, std::string& output)
|
|
{
|
|
std::vector<uint8_t> data;
|
|
if (!b58decode(input, data)) return false;
|
|
output.assign(data.begin(), data.end());
|
|
return true;
|
|
}
|
|
|
|
// -------------------------
|
|
// Convenience string-based interface
|
|
// -------------------------
|
|
|
|
// Encode std::string (raw bytes) to Base16 string
|
|
// Returns encoded string
|
|
inline std::string encodeBase16(const std::string& input) noexcept
|
|
{
|
|
std::vector<uint8_t> data(input.begin(), input.end());
|
|
return b16encode(data);
|
|
}
|
|
|
|
// Decode Base16 string to std::string (raw bytes)
|
|
// Returns success flag, output in 'output'
|
|
inline bool decodeBase16(const std::string& input, std::string& output, bool casefold = false)
|
|
{
|
|
std::vector<uint8_t> data;
|
|
if (!b16decode(input, data, casefold))
|
|
return false;
|
|
output.assign(data.begin(), data.end());
|
|
return true;
|
|
}
|
|
|
|
// Encode std::string (raw bytes) to Base85 string
|
|
// Returns encoded string
|
|
inline std::string encodeBase85(const std::string& input, bool pad = false) noexcept
|
|
{
|
|
std::vector<uint8_t> data(input.begin(), input.end());
|
|
return b85encode(data, pad);
|
|
}
|
|
|
|
// Decode Base85 string to std::string (raw bytes)
|
|
// Returns success flag, output in 'output'
|
|
inline bool decodeBase85(const std::string& input, std::string& output)
|
|
{
|
|
std::vector<uint8_t> data;
|
|
if (!b85decode(input, data))
|
|
return false;
|
|
output.assign(data.begin(), data.end());
|
|
return true;
|
|
}
|
|
|
|
// -------------------------
|
|
// Base62 encoding/decoding (Python source: base62.py)
|
|
// -------------------------
|
|
|
|
inline const std::string& b62_charset_default()
|
|
{
|
|
static const std::string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
return charset;
|
|
}
|
|
|
|
inline int b62_char_value(char ch, const std::string& charset)
|
|
{
|
|
auto pos = charset.find(ch);
|
|
if (pos == std::string::npos)
|
|
return -1;
|
|
return static_cast<int>(pos);
|
|
}
|
|
|
|
inline std::string b62encode(uint64_t num, const std::string& charset = b62_charset_default())
|
|
{
|
|
// From encode() function
|
|
std::string result;
|
|
if (num == 0)
|
|
return "0";
|
|
while (num > 0)
|
|
{
|
|
result.insert(result.begin(), charset[num % 62]);
|
|
num /= 62;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline std::string encodeBase62(const std::string& input) noexcept
|
|
{
|
|
// From encodebytes() function
|
|
const std::string& charset = b62_charset_default();
|
|
std::vector<uint8_t> barray(input.begin(), input.end());
|
|
|
|
int leading_zeros = 0;
|
|
for (auto b : barray)
|
|
{
|
|
if (b != 0)
|
|
break;
|
|
leading_zeros++;
|
|
}
|
|
|
|
int n = static_cast<int>(leading_zeros / (charset.size() - 1));
|
|
int r = static_cast<int>(leading_zeros % (charset.size() - 1));
|
|
std::string zero_padding(n, '0');
|
|
zero_padding += std::string(n, charset.back());
|
|
if (r)
|
|
zero_padding += "0" + std::string(1, charset[r]);
|
|
|
|
if (leading_zeros == static_cast<int>(barray.size()))
|
|
return zero_padding;
|
|
|
|
uint64_t value = 0;
|
|
for (uint8_t b : barray)
|
|
value = (value << 8) | b;
|
|
|
|
return zero_padding + b62encode(value, charset);
|
|
}
|
|
|
|
inline uint64_t b62decode(const std::string& s, const std::string& charset = b62_charset_default())
|
|
{
|
|
// From decode() function
|
|
uint64_t value = 0;
|
|
for (char ch : s)
|
|
{
|
|
int v = b62_char_value(ch, charset);
|
|
if (v < 0)
|
|
return 0; // indicates failure
|
|
value = value * 62 + v;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
inline bool decodeBase62(const std::string& input, std::string& output)
|
|
{
|
|
// From decodebytes() function
|
|
const std::string& charset = b62_charset_default();
|
|
size_t i = 0;
|
|
std::vector<uint8_t> result;
|
|
|
|
while (i + 1 < input.size() && input[i] == '0')
|
|
{
|
|
int count = b62_char_value(input[i + 1], charset);
|
|
if (count < 0)
|
|
return false;
|
|
result.insert(result.end(), count, 0x00);
|
|
i += 2;
|
|
}
|
|
|
|
if (i >= input.size())
|
|
{
|
|
output.assign(result.begin(), result.end());
|
|
return true;
|
|
}
|
|
|
|
uint64_t decoded = b62decode(input.substr(i), charset);
|
|
std::vector<uint8_t> temp;
|
|
while (decoded > 0)
|
|
{
|
|
temp.push_back(decoded & 0xFF);
|
|
decoded >>= 8;
|
|
}
|
|
std::reverse(temp.begin(), temp.end());
|
|
result.insert(result.end(), temp.begin(), temp.end());
|
|
|
|
output.assign(result.begin(), result.end());
|
|
return true;
|
|
}
|
|
|
|
// -------------------------
|
|
// Base91 encoding/decoding (Python source: encode/decode functions)
|
|
// -------------------------
|
|
|
|
inline const std::string& b91_alphabet()
|
|
{
|
|
static const std::string alphabet =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[\\]^_`{|}~\"";
|
|
return alphabet;
|
|
}
|
|
|
|
inline bool encodeBase91(const std::string& input, std::string& output) noexcept
|
|
{
|
|
// From encode(bindata)
|
|
const std::string& alphabet = b91_alphabet();
|
|
uint32_t b = 0;
|
|
int n = 0;
|
|
output.clear();
|
|
|
|
for (uint8_t byte : input)
|
|
{
|
|
b |= static_cast<uint32_t>(byte) << n;
|
|
n += 8;
|
|
if (n > 13)
|
|
{
|
|
uint32_t v = b & 8191;
|
|
if (v > 88)
|
|
{
|
|
b >>= 13;
|
|
n -= 13;
|
|
}
|
|
else
|
|
{
|
|
v = b & 16383;
|
|
b >>= 14;
|
|
n -= 14;
|
|
}
|
|
output += alphabet[v % 91];
|
|
output += alphabet[v / 91];
|
|
}
|
|
}
|
|
if (n)
|
|
{
|
|
output += alphabet[b % 91];
|
|
if (n > 7 || b > 90)
|
|
output += alphabet[b / 91];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool decodeBase91(const std::string& input, std::string& output) noexcept
|
|
{
|
|
// From decode(encoded_str)
|
|
const std::string& alphabet = b91_alphabet();
|
|
std::unordered_map<char, int> decode_table;
|
|
for (size_t i = 0; i < alphabet.size(); ++i)
|
|
decode_table[alphabet[i]] = static_cast<int>(i);
|
|
|
|
int v = -1;
|
|
uint32_t b = 0;
|
|
int n = 0;
|
|
std::vector<uint8_t> result;
|
|
|
|
for (char c : input)
|
|
{
|
|
if (decode_table.find(c) == decode_table.end())
|
|
continue;
|
|
int val = decode_table[c];
|
|
if (v < 0)
|
|
v = val;
|
|
else
|
|
{
|
|
v += val * 91;
|
|
b |= v << n;
|
|
n += (v & 8191) > 88 ? 13 : 14;
|
|
while (n >= 8)
|
|
{
|
|
result.push_back(b & 255);
|
|
b >>= 8;
|
|
n -= 8;
|
|
}
|
|
v = -1;
|
|
}
|
|
}
|
|
if (v != -1)
|
|
result.push_back((b | (v << n)) & 255);
|
|
|
|
output.assign(result.begin(), result.end());
|
|
return true;
|
|
}
|
|
|
|
} // namespace basecodec
|
|
|
|
#endif // BASECODEC_HPP
|