Base58是如何工作的?

本文详细介绍了Base58编码方案,它在比特币地址和其他加密货币中被广泛使用,可以将原始二进制数据转换为更短且更易于理解的字符串。文章深入探讨了Base58的原理、编码和解码过程,并通过一个示例演示了其工作方式,以及这种编码方案在区块链技术中的应用。

Base58 是一种流行的编码方案,主要用于 比特币地址 和其他加密货币。它的目的是将 原始二进制数据(字节) 转换为更短且更易于理解的字符串。

为什么选择 Base58?

Base58 专门设计用于解决共享编码数据时的实际问题:

  • 紧凑:比十六进制编码更节省空间
  • 对人类友好:删除容易混淆的字符(0OIl)以减少转录错误
  • 广泛采用:比特币地址和许多其他加密货币系统的标准
  • 复制粘贴安全:没有可能被误读的模棱两可的字符

Base58 字母表(正好 58 个字符):

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

请注意缺少的内容:0(零)、O(大写 O)、I(大写 i)和 l(小写 L)- 惹麻烦的家伙!

高级概念

Base58 的魔力在于将你的数据视为数学:

  1. 考虑大数:将输入字节视为一个巨大的整数
  2. 分而治之:重复除以 58,收集余数
  3. 映射余数:每个余数 (0–57) 映射到一个 Base58 字符
  4. 反向解码:将字符转换回数值,重建大数

这就像在数字进制之间转换,但有一个转折——我们从 base-256(字节)转换为 base-58(我们的字母表)。

常量定义

在深入研究函数之前,让我们了解我们的基础:

#include <string>
const char * const ALPHABET =
    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
const char ALPHABET_MAP[128] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8, -1, -1, -1, -1, -1, -1,
    -1,  9, 10, 11, 12, 13, 14, 15, 16, -1, 17, 18, 19, 20, 21, -1,
    22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, -1, -1,
    -1, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, -1, 44, 45, 46,
    47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, -1, -1, -1, -1
};

这些是做什么用的:

  • ALPHABET:我们的 58 个字符的编码表
  • ALPHABET_MAP:闪电般快速的查找表(ASCII → Base58 索引)
  • 示例:'1' (ASCII 49) → 索引 0, 'H' (ASCII 72) → 索引 9
  • -1 表示“无效字符”

核心函数

base58encode

int base58encode(const std::string input, int len, unsigned char result[]) {
    unsigned char const* bytes = (unsigned const char*)(input.c_str());
    unsigned char digits[len * 137 / 100];  // Buffer size estimate
    int digitslen = 1;

    // Process each input byte
    // 处理每个输入字节
    for (int i = 0; i < len; i++) {
        unsigned int carry = (unsigned int) bytes[i];

        // Multiply existing digits by 256, add new byte
        // 将现有数字乘以 256,加上新的字节
        for (int j = 0; j < digitslen; j++) {
            carry += (unsigned int) (digits[j]) << 8;  // * 256
            digits[j] = (unsigned char) (carry % 58);
            carry /= 58;
        }

        // Handle overflow digits
        // 处理溢出数字
        while (carry > 0) {
            digits[digitslen++] = (unsigned char) (carry % 58);
            carry /= 58;
        }
    }
    int resultlen = 0;

    // Handle leading zero bytes → '1' characters
    // 处理前导零字节 → '1' 字符
    for (; resultlen < len && bytes[resultlen] == 0;)
        result[resultlen++] = '1';
    // Reverse and map digits to alphabet
    // 反转并将数字映射到字母表
    for (int i = 0; i < digitslen; i++)
        result[resultlen + i] = ALPHABET[digits[digitslen - 1 - i]];
    result[digitslen + resultlen] = 0;  // Null terminator
    // 空终止符
    return digitslen + resultlen;
}

它是如何工作的:

  1. 将输入视为大端数
  2. 对于每个字节,将当前结果乘以 256 并加上该字节
  3. 通过重复除以 58 转换为 base-58
  4. 将余数映射到 Base58 字符
  5. 特殊处理前导零(它们变成 '1')

base58decode

int base58decode(const std::string input, int len, unsigned char *result) {
    unsigned char const* str = (unsigned const char*)(input.c_str());
    result[0] = 0;
    int resultlen = 1;
    // Process each Base58 character
    // 处理每个 Base58 字符
    for (int i = 0; i < len; i++) {
        unsigned int carry = (unsigned int) ALPHABET_MAP[str[i]];

        // Multiply existing result by 58, add new digit
        // 将现有结果乘以 58,加上新的数字
        for (int j = 0; j < resultlen; j++) {
            carry += (unsigned int) (result[j]) * 58;
            result[j] = (unsigned char) (carry & 0xff);  // Keep low byte
            // 保留低字节
            carry >>= 8;  // Shift high bytes to carry
            // 将高字节移至进位
        }

        // Handle carry overflow
        // 处理进位溢出
        while (carry > 0) {
            result[resultlen++] = (unsigned int) (carry & 0xff);
            carry >>= 8;
        }
    }
    // Handle leading '1' characters → zero bytes
    // 处理前导 '1' 字符 → 零字节
    for (int i = 0; i < len && str[i] == '1'; i++)
        result[resultlen++] = 0;
    // Reverse result to correct byte order
    // 反转结果以更正字节顺序
    for (int i = resultlen - 1, z = (resultlen >> 1) + (resultlen & 1); i >= z; i--) {
        int k = result[i];
        result[i] = result[resultlen - i - 1];
        result[resultlen - i - 1] = k;
    }

    return resultlen;
}

它是如何工作的:

  1. 将每个 Base58 字符转换为其数值
  2. 对于每个数字,将当前结果乘以 58 并加上该数字
  3. 将结果保存在 base-256(字节数组)中
  4. 将前导 '1' 字符作为零字节处理
  5. 反转最终结果以更正字节序

逐步示例

让我们跟踪对字符串 "Hi" 的编码和解码,以了解算法的实际应用。

ASCII 值

'H' = 72 (decimal)
'i' = 105 (decimal)

输入字节:[72, 105]

编码过程

初始状态:

  • digits = [0]
  • digitslen = 1

步骤 1:处理字节 72 ('H')

  • carry = 72
  • j=0: carry = 0×256 + 72 = 72
  • digits[0] = 72 % 58 = 14
  • carry = 72 ÷ 58 = 1
  • 处理进位digits[1] = 1 % 58 = 1
  • 结果digits = [14, 1], digitslen = 2

步骤 2:处理字节 105 ('i')

  • carry = 105
  • j=0: carry = 14×256 + 105 = 3689
  • digits[0] = 3689 % 58 = 37
  • carry = 3689 ÷ 58 = 63
  • j=1: carry = 1×256 + 63 = 319
  • digits[1] = 319 % 58 = 29
  • carry = 319 ÷ 58 = 5
  • 处理进位digits[2] = 5 % 58 = 5
  • 结果digits = [37, 29, 5], digitslen = 3

步骤 3:转换为 Base58

  • 反转数字[5, 29, 37]
  • 映射到字母表
  • 5 → ALPHABET[5] = '6'
  • 29 → ALPHABET[29] = 'W'
  • 37 → ALPHABET[37] = 'e'

最终编码结果: "6We"

解码过程

"6We" 开始,让我们回到 "Hi"

来自 ALPHABET_MAP 的字符值:

'6' → 5
'W' → 29
'e' → 37

初始状态:

  • result = [0]
  • resultlen = 1

步骤 1:处理 '6' (value = 5)

  • carry = 5
  • j=0: carry = 5 + 0×58 = 5
  • result[0] = 5 & 0xff = 5
  • carry = 5 >> 8 = 0
  • 结果result = [5], resultlen = 1

步骤 2:处理 'W' (value = 29)

  • carry = 29
  • j=0: carry = 29 + 5×58 = 319
  • result[0] = 319 & 0xff = 63
  • carry = 319 >> 8 = 1
  • 处理进位result[1] = 1
  • 结果result = [63, 1], resultlen = 2

步骤 3:处理 'e' (value = 37)

  • carry = 37
  • j=0: carry = 37 + 63×58 = 3691
  • result[0] = 3691 & 0xff = 105
  • carry = 3691 >> 8 = 14
  • j=1: carry = 14 + 1×58 = 72
  • result[1] = 72 & 0xff = 72
  • carry = 72 >> 8 = 0
  • 结果result = [105, 72], resultlen = 2

步骤 4:反转结果

  • 之前:[105, 72]
  • 之后:[72, 105]

步骤 5:转换回 ASCII

72 → 'H'
105 → 'i'

最终解码结果: "Hi"

关键见解

数学之美

Base58 编码本质上是具有特殊处理的进制转换

  • 编码:从 base-256(字节)转换为 base-58(字符)
  • 解码:从 base-58(字符)转换回 base-256(字节)

特殊情况

  • 前导零0x00 字节变为 '1' 字符
  • 大数:所有算术运算都通过进位传播完成
  • 字节序:反转结果以保持正确的字节顺序

性能优化

  • ALPHABET_MAP:O(1) 字符到值的查找,而不是 O(58) 搜索
  • 缓冲区大小调整len * 137 / 100 为输出提供安全的上限。为什么是 137/100? 当从 base-256 转换为 base-58 时,输出最多可以比输入长约 37%。这来自数学关系:log₅₈(256) ≈ 1.37。因此,我们需要至少多 37% 的空间来安全地存储转换期间所有可能的数字。
  • 位运算:使用 & 0xff>> 8 进行高效的字节操作

现实世界的应用

Base58 编码在加密世界中无处不在:

  • 比特币地址:所有比特币地址都使用 Base58Check(Base58 + 校验和)
  • Solana 地址:公钥是 Base58 编码的
  • IPFS 哈希:内容标识符通常使用 Base58
  • Monero 地址:集成地址使用 Base58

对于任何从事区块链技术或加密货币系统工作的人来说,理解这种编码至关重要。

参考

如果你对 Solana 开发、机密计算、DePIN 网络或开源索引工具有兴趣,请关注:

  • 原文链接: blog.blockmagnates.com/h...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockmagnates
blockmagnates
The New Crypto Publication on The Block