Base58Check 编码
一种改进的 Base 58 二进制到文本的编码称为 Base58Check,用于编码比特币地址。
通俗的说,Base58Check 编码用于把比特币中的字节数组编码为易于人类手动输入的字符串。
源码剖析
Base58Check 的编码和解码均实现在源文件 ”base58.cpp“ 中,下面是编码部分的源码:
/** All alphanumeric characters except for "0", "I", "O", and "l" */
static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; // 10 + 26 * 2 - 4 = 58
...
std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) // Base58 编码
{
// Skip & count leading zeroes.
int zeroes = 0; // 1. 跳过并统计开头 0 的个数
while (pbegin != pend && *pbegin == 0) {
pbegin++;
zeroes++;
}
// Allocate enough space in big-endian base58 representation. // 2. 为大端 base58 表示,开辟足够的空间(34 或 35 bytes),向上取整
std::vector<unsigned char> b58((pend - pbegin) * 138 / 100 + 1); // log(256) / log(58), rounded up.
// Process the bytes. // 3. 转换为 58 进制
while (pbegin != pend) {
int carry = *pbegin;
// Apply "b58 = b58 * 256 + ch".
for (std::vector<unsigned char>::reverse_iterator it = b58.rbegin(); it != b58.rend(); it++) {
carry += 256 * (*it);
*it = carry % 58;
carry /= 58;
}
assert(carry == 0);
pbegin++;
}
// Skip leading zeroes in base58 result. // 4. 跳过开头的 0
std::vector<unsigned char>::iterator it = b58.begin();
while (it != b58.end() && *it == 0)
it++;
// Translate the result into a string.
std::string str;
str.reserve(zeroes + (b58.end() - it)); // 1 + 33 or 0 + 34 = 34
str.assign(zeroes, '1'); // 5. 在字符串前面的位置指派 zeroes 个字符 1
while (it != b58.end()) // 6. 查 Base58 字符表转换结果为字符串
str += pszBase58[*(it++)]; // append *it then ++it
return str; // 前缀为 0x00 不参与 Base58 运算,地址长度固定为 34 bytes 且前缀位 '1',其他不为 0x00 的前缀,均参与 Base58 运算,地址长度变换范围 33 - 35 bytes
}
std::string EncodeBase58(const std::vector<unsigned char>& vch)
{
return EncodeBase58(&vch[0], &vch[0] + vch.size());
}
...
std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn)
{
// add 4-byte hash check to the end // 添加哈希值的前 4 个字节作为校验和到尾部
std::vector<unsigned char> vch(vchIn);
uint256 hash = Hash(vch.begin(), vch.end());
vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
return EncodeBase58(vch);
}
...
std::string CBase58Data::ToString() const
{
std::vector<unsigned char> vch = vchVersion; // 添加版本号到首部
vch.insert(vch.end(), vchData.begin(), vchData.end());
return EncodeBase58Check(vch);
}
在 20 个字节的公钥哈希值首部添加 1 个字节的版本号,并在尾部添加哈希值的前 4 个字节作为校验和后,进行 Base58 编码:
- 跳过开头的 0 同时统计其个数。zeroes 的取值只有两种情况,当版本号即地址前缀取 “0x00” 时,zeroes 为 1,其它情况 zeroes 为 0。
- 开辟足够大的空间,34 或 35 个字节,为大端表示的 Base58 编码做准备。
- 进行 Base58 编码,把 256 进制转化为 58 进制,公式 “b58 = b58 * 256 + ch”。
carry += 256 * (*it); *it = carry % 58; carry /= 58;
- 再次跳过开头的 0,这次不统计。
- 在字符串填充字符 ‘1’,个数为 1. 步中跳过 0 的个数 zeroes。
- 根据 Base58 字符对照表把数字转换为对应字符,得到地址。