背景图

Bitcoin中Base58Check编码

Base58二进制到文本编码被称为Base58Check,用于编码比特币地址。

更一般地说,Base58Check编码用于将比特币中的字节数组编码为人类可分类的字符串。

背景

最初的比特币客户端源代码解释了base58编码背后的原因:

base58.h:

1
2
3
4
5
6
// 为什么base-58而不是标准的base-64编码?
// - 不希望0OIl字符在某些字体和字体中看起来相同
//可以用来创建视觉上相同的账号。
// - 具有非字母数字字符的字符串并不像帐号那么容易被接受。
// - 如果没有标点符号,电子邮件通常不会换行。
// - 如果全部是字母数字,Doubleclicking会将整个数字选为一个单词。

Base58Check的功能

Base58Check具有以下功能:

  • 一个任意大小的payload(这个payload其实就是公钥的HASH160的双次hash值)。
  • 由容易区分的大写和小写字母组成的一组58个字母数字符号(0OIl不使用)(主要的原因还是因为可能会产生歧义)
  • 一个字节的version/application信息。对于这个字节比特币地址使用0x00(未来的可能使用0x05)。
  • 四个字节(32位)基于SHA256的错误校验码。此校验码可用于自动检测并可能更正印刷错误。
  • 保留数据中前导零的额外步骤。(这里应该更深入地进行分析才是,但是现在真不太明白这里面的意思。)

创建一个Base58Check字符串

Base58Check字符串是从version/application字节和payload创建的,如下所示。

  1. 获取version字节和payload字节,并将它们连接在一起(按字节)。
  2. 取SHA256的前四个字节(SHA256(步骤1的结果))(也应该是前面提到的那四个字节的事情
  3. 将步骤1的结果和步骤2的结果连在一起(按字节顺序)。
  4. 处理步骤3的结果 - 一系列字节 - 作为单个大端序号,使用正常的数学步骤(bignumber division)和下面描述的base-58字母表转换为base-58。结果应该被标准化为没有任何前导的base-58零(字符’1’)。(这里注意前导0对应的Base58Check是1)
  5. 在base58中值为零的前导字符’1’被保留用于表示整个前导零字节,就像它处于前导位置时一样,没有值作为base-58符号。必要时可以有一个或多个前导’1’来表示一个或多个前导零字节。计算第3步结果的前导零字节数(对于旧的比特币地址,至少有一个用于版本/应用程序字节;对于新地址,将永远不会有)。每个前导零字节在最终结果中应由其自己的字符’1’表示。
  6. 将步骤5中的1与步骤4 的结果连接起来。这是Base58Check的结果。

在描述比特币地址技术背景的页面上提供了一个更详细的例子。

编码比特币地址

比特币地址是使用以下任一项的散列的Base58Check编码实现的:

  • Pay-to-script-hash(p2sh):有效载荷是:其中redeemScript是钱包知道如何消费的脚本; 版本(这些地址以数字’3’开头)RIPEMD160(SHA256(redeemScript))0x05
  • 支付到PUBKEY散列(p2pkh):有效载荷是其中ECDSA_publicKey是钱包知道的私有密钥的公共密钥; 版本(这些地址以数字’1’开头)RIPEMD160(SHA256(ECDSA_publicKey))0x00

在这两种情况下得到的散列总是恰好为20个字节。这些是大端(最重要的字节在前)。(注意那些限制前导0x00字节的数字编码实现,或者预先增加额外的0x00字节来表示符号 - 你的代码必须正确处理这些情况,否则你可能会生成可以发送到但看不到的有效地址 -导致硬币的永久损失。)

编码一个私钥

Base58Check编码也用于编码钱包导入格式中的ECDSA私钥。除了0x80用于version/application字节,并且有效载荷是32字节而不是20(比特币中的私钥是单个32字节无符号的大端整数)之外,它与比特币地址完全相同。对于与未压缩的公钥相关的私钥,这种编码总是会产生一个以’5’开头的51个字符的字符串,或者更具体地说’5H’,’5J’或’5K’。

Base58符号图表

比特币中使用的Base58符号图特定于比特币项目,并不打算与比特币之外使用的任何其他Base58实现(排除的字符为:0,O,I和l)相同。

字符 字符 字符 字符
0 1 1 2 2 3 3 4
4 5 5 6 6 7 7 8
8 9 9 A 10 B 11 C
12 D 13 E 14 F 15 G
16 H 17 J 18 K 19 L
20 M 21 N 22 P 23 Q
24 R 25 S 26 T 27 U
28 V 29 W 30 X 31 Y
32 Z 33 a 34 b 35 c
36 d 37 e 38 f 39 g
40 h 41 i 42 j 43 k
44 m 45 n 46 o 47 p
48 q 49 r 50 s 51 t
52 u 53 v 54 w 55 x
56 y 57 z

编码address_byte_string的算法(由1-byte_version + hash_or_other_data + 4-byte_check_code组成)是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
code_string = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
x = convert_bytes_to_big_integer(hash_result)

output_string = ""

while(x > 0)
{
(x, remainder) = divide(x, 58)
output_string.append(code_string[remainder])
}

repeat(number_of_leading_zero_bytes_in_hash)
{
output_string.append(code_string[0]);
}

output_string.reverse();

版本字节

以下是一些常见的版本字节:

小数版本 领导的象征 使用
0 1 Bitcoin pubkey hash
5 3 Bitcoin script hash
21 4 Bitcoin (compact) public key (proposed)
52 M or N Namecoin pubkey hash
128 5 Private key
111 m or n Bitcoin testnet pubkey hash
196 2 Bitcoin testnet script hash

地址前缀列表是一个完整的列表。

也可以看看

在线Base58解码器,编码器和验证器

源代码

“Satoshi”C ++ codebase(解码和编码,不需要外部库)
libbase58 C代码(解码和编码,不需要外部库)
Base58在Perl中解码,编码和验证

参考和引用

Base58Check encoding

总结

关于比特币地址知识,在精通比特币中有详细地介绍,这里我们不做更多的介绍,这篇文章是我翻译的结果。

在之前我们分析过椭圆曲线的知识,对于确定性钱包的知识我们也做了相关地介绍,还有就是分层确定性钱包的知识。

另外Base58Check还有一个极大的好处就是可以校验地址是不是正确的,因为Base58生成的时候是加入了校验码。我觉得比特币团队做的真的是很棒不是吗?详情可以参考:为什么以太坊地址中没有校验值?

对于比特币地址也要特别注意一下,地址类型也就是version/appication字段其实是自己加入的,和ecdsa本身并没有任何关系。校验码也是自己加上去的而已。我们可以看一下下面的图片.

有一段时间我在分析java的ECKey的代码发现java底层的椭圆曲线算法的实现,这里我就不做更多的说明了。关键是我们如何实现分层确定性钱包,这个很简单也很复杂,其实就是找私钥G,至于这个G怎么找,我们可以查看精通比特币里面的说明,这里面只是提到了HMAC-SHA512。看来这里面隐藏了很多的细节因素,我们有时间需要研究一下,HMAC-SHA512的知识了。

0%