- 4.2 比特币地址
- 4.2.1 Base58和Base58Check编码
- 4.2.2 密钥的格式
- 4.2.2.1私钥的格式
- 4.2.2.2 从Base58Check解码
- 4.2.2.3 将十六进制转换为Base58Check编码
- 4.2.2.4 将十六进制(压缩格式密钥)转换为Base58Check编码
- 4.2.2.5 公钥的格式
- 4.2.2.6 压缩格式公钥
- 4.2.2.7 压缩格式私钥
- 4.3 用C++实现密钥和地址
4.2 比特币地址
比特币地址是一个由数字和字母组成的字符串,可以展示给任何给你转账比特币的人。由公钥(一个同样由数字和字母组成的字符串)生成的比特币地址以数字“1”开头。下面是一个比特币地址的例子:
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
在交易中,比特币地址通常作为资金接受者地址。如果把比特币交易比作一张支票,比特币地址就是收益人,也就是写入“支付给谁”一栏的内容。一张支票的收款人可能是某个银行账户,也可能是某个公司、机构,甚至是现金支票。由于支票不需要指定一个特定的账户,而是用一个抽象的名字作为收款人,这就使它成为一种相当灵活的支付工具。与此类似,比特币交易使用类似的抽象:比特币地址,这就使比特币交易变得很灵活。比特币地址代表一对公钥和私钥的所有者,也可以代表其它东西,比如会在后面的“P2SH (Pay-to-Script-Hash)”一节讲到的付款脚本。现在,让我们来看一个简单的例子,比特币地址代表公钥,并由公钥生成。
比特币地址可由公钥经过单向加密哈希算法得到。哈希算法是一种单向函数,接收任意长度的输入产生指纹或哈希。加密哈希函数在比特币中被广泛使用 :比特币地址、脚本地址以及在挖矿中的工作量证明算法。由公钥生成比特币地址时使用的算法是Secure Hash Algorithm (SHA)和the RACE Integ rity Primitives Evaluation Message Digest (RIPEMD),具体来说是SHA256和RIPEMD160。
以公钥 K 为输入,计算其SHA256哈希值,并以此结果计算RIPEMD160 哈希值,得到一个长度为160位(20字节)的数字:
A = RIPEMD160(SHA256(K))
公式中,K是公钥,A是生成的比特币地址。
提示 比特币地址与公钥并不不同。比特币地址是公钥经过单向的哈希函数生成的。
通常用户见到的比特币地址是经过“Base58Check”编码的(参见下面的“Base58和Base58Check编码”一节),这种编码使用了58个字符(Base58数字系统)和校验码,提高了可读性、避免歧义并有效防止了在地址转录和输入中产生的错误。Base58Check编码也被用于比特币的其它地方,例如比特币地址、私钥、加密的密钥和脚本哈希中,用来提高可读性和录入的正确性。下一节中我们会详细解释Base58Check的编码和解码机制,以及它产生的结果。
下图描述了如何从公钥生成比特币地址。
图4-5从公钥生成比特币地址]
4.2.1 Base58和Base58Check编码
为了更简洁方便地表示长串的数字,使用更少的符号,许多计算机系统在表示大于十进制时,会使用数字和字母混合组成。例如,传统的十进制计数系统使用0-9十个数字,而十六进制系统使用了16个,增加了 A-F 六个字母来表示0-9额外的符号。一个同样的数字,它的十六进制表示就会比等值得十进制表示更短。更为简洁的是,Base64使用了26个小写字母、26个大写字母、10个数字以及两个符号(例 如“+”和“/”),用于在像电子邮件这样的文本媒介中传输二进制数据。Base64通常用于邮件中添加二进制附件。Base58 是一种基于文本的二进制编码格式,用在比特币和其它的加密货币中。这种编码格式提供了紧凑表示,易读性,错误检测预防这几方面彼此之间的平衡。Base58是Base64编码格式的子集,同样使用大小写字母和10个数字,但舍弃了一些容易读错和在特定字体中外观容易混淆的字符。具体地,Base58不含Base64中的0(数字0)、O(大写字母o)、l(小写字母 L)、I(大写字母i),以及“+”和“/”两个字符。简而言之,Base58就是由不包括(0,O,l,I)的大小写字母和数字组成。下面的例4-2是完整的Base58字母表。
例4-2 比特币的Base58字母表
123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
为了增加防止打印和转录错误的安全性,比特币常用的是Base58Check,它是一种内置错误校验代码的Base58编码格式。检验和是额外4个字节,被添加到正在编码的数据末端。校验和是从编码的数据的哈希值中得到的,所以可以用来检测并避免转录和输入中产生的错误。使用Base58check编码时,解码软件会计算数据的校验和并和编码中自带的校验和进行对比。二者不匹配则表明有错误产生,这个Base58Check的数据就是无效的。这就防止输错的比特币地址被钱包软件认为是有效的地址,造成资金的丢失。
为了将数据(数字)转换成Base58Check格式,首先我们要对数据添加一个称作“版本字节”的前缀,这个前缀用来识别编码的数据的类型。例如,比特币地址的前缀是0(十六进制是0x00),而编码私钥的前缀是128(十六进制是0x80)。 表4-1会列出一些常见版本的前缀。
接下来,我们计算“双哈希”校验和,意味着要对之前的结果(前缀和数据)运行两次SHA256哈希算法:
checksum = SHA256(SHA256(prefix+data))
在产生的长度为32个字节的哈希值(两次哈希运算)中,我们只取前4个字节。这4个字节就作为检验错误的代码或者校验和。将校验码添加到最后。
结果由三部分组成:前缀、数据和校验和。这个结果采用之前描述的Base58字母表编码。下图描述了Base58Check编码的过程。
图4-6 Base58Check编码:Base58、版本化和校验和格式,用于对比特币数据进行明确编码
在比特币中,大多数需要向用户展示的数据都使用Base58Check编码,因为它紧凑,易读而且有错误检验。 Base58Check编码中的版本前缀是用来创造易于辨别的格式Base58编码时,该格式在base58check编码的负载的开头包含特定字符。这些字符使人们很容易了解别被编码的数据的类型和使用方法。例如我们很容易看到,Base58Check编码的比特币地址是以1开头的,而Base58Check编码的私钥WIF是以5开头的。表4-1展示了一些版本前缀和他们对应的Base58格式。
表4-1 Base58Check版本前缀和编码后的结果
Type | Version prefix (hex) | Base58 result prefix |
---|---|---|
Bitcoin Address | 0x00 | 1 |
Pay-to-Script-Hash Address | 0x05 | 3 |
Bitcoin Testnet Address | 0x6F | m or n |
Private Key WIF | 0x80 | 5, K, or L |
BIP-38 Encrypted Private Key | 0x0142 | 6P |
BIP-32 Extended Public Key | 0x0488B21E | xpub |
4.2.2 密钥的格式
公钥和私钥都可以有多种格式。虽然看起来可能不同,但是所编码的是同样的数字。这些不同的编码格式主要是用来方便人们无误地阅读和抄写。
4.2.2.1私钥的格式
私钥的格式有许多,所有这些都对应于相同的256位的数字。表4-2展示了私钥的三种常见格式。不同的格式用在不同的场景。十六进制和原始的二进制格式用在软件的内部,很少展示给用户看。WIF格式用在钱包之间密钥的输入和输出,也用于代表私钥的二维码(条形码)。
表4-2 私钥形式(编码格式)
Type | Prefix | Description |
---|---|---|
Raw | None | 32 bytes |
Hex | None | 64 hexadecimal digits |
WIF | 5 | Base58Check encoding: Base58 with version prefix of 128- and 32-bit checksum |
WIF-compressed | K or L | As above, with added suffix 0x01 before encoding |
下表表4-3显示了三种格式的私钥。
表4-3 示例:相同的私钥,不同的格式
Format | Private key |
---|---|
Hex | 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd |
WIF | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn |
WIF-compressed | KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ |
这些格式都表示相同的数字,相同的私钥。虽然编码后的字符串看起来不同,但任何一种格式都能很容易转换为其他格式。请注意,“raw binary”未显示在表4-3 示例中,因为根据定义,此处显示的任何编码的格式,都不是raw binary数据。
我们使用Bitcoin Explorer中的wif-to-ec命令(请参阅[appdx_bx])来显示两个WIF键代表相同的私钥:
$ bx wif-to-ec 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
$ bx wif-to-ec KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
4.2.2.2 从Base58Check解码
Bitcoin Explorer命令(参见本书附录[appdx_bx])使我们很容易编写shell脚本和命令行“管道”,处理比特币密钥,地址和交易。 Bitcoin Explorer命令行可以解码Base58Check格式。
我们使用base58check-decode命令解码未压缩的密钥:
$ bx base58check-decode 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
wrapper
{
checksum 4286807748
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
version 128
}
结果包含密钥有效内容(payload),WIF版本前缀128和校验和。
请注意,压缩密钥的“有效内容”附加了后缀01,表示派生的公钥要被压缩:
$ bx base58check-decode KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
wrapper
{
checksum 2339607926
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01
version 128
}
4.2.2.3 将十六进制转换为Base58Check编码
要转换成Base58Check(与上一个命令相反),使用Bitcoin Explorer的base58check-encode命令(请参阅本书附录[appdx_bx]),需要十六进制私钥,后面跟WIF版本前缀128:
bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd --version 128
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
4.2.2.4 将十六进制(压缩格式密钥)转换为Base58Check编码
要将压缩格式的私钥(参见“压缩格式私钥”一节)编码为Base58Check,需要在十六进制私钥的后面添加后缀01,然后使用跟上面一样的方法:
$ bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 --version 128
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
生成的WIF压缩格式的私钥以字母“K”开头,表明被编码的私钥中有一个后缀“01”,且该私钥只能被用于生成压缩格式的公钥(参见“压缩格式公钥”一节)。
4.2.2.5 公钥的格式
公钥也可以用多种不同格式来表示,通常分为非压缩格式或压缩格式公钥这两种形式。
我们从前文可知,公钥是在椭圆曲线上的一个点,由一对坐标(x,y)组成。公钥通常表示为前缀04紧接着两个256位的数字。其中一个256位数字是公钥的x坐标,另一个256位数字是y坐标。前缀04是是非压缩格式公钥, 压缩格式公钥是以02或者03开头。
下面是由前文中的私钥所生成的公钥,其坐标x和y如下:
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
下面是同样的公钥以520位的数字(130个十六进制数字)来表达。这个520位的数字以前缀04开头,紧接着是x坐标及y坐标,格式为:04 x y:
K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE 52DDFE2E505BDB
4.2.2.6 压缩格式公钥
比特币引入压缩格式公钥是为了减少交易的大小,从而节省运行区块链数据库的节点磁盘空间。大部分比特币交易包含了公钥,用于验证用户的凭证和支付比特币。每个公钥有520位(包括前缀,x坐标,y坐标)。如果每个区块有数百个交易,每天有成千上万的交易发生,区块链里就会被写入大量的数据。
正如“4.1.4 公钥”一节所述,一个公钥是一个椭圆曲线上的点(x, y)。而椭圆曲线实际是一个数学方程,曲线上的点实际是该方程的一个解。因此,如果我们知道了公钥的x坐标,就可以通过解方程y2 mod p = (x3 + 7) mod p得到y坐标。这可以让我们只存储公钥的x坐标,略去y坐标,从而将公钥的大小和存储空间减少了256位。这样每笔交易需要的字节数减少了近一半,随着时间推移,就能保存更多的交易数据。
未压缩格式公钥使用04作为前缀,而压缩格式公钥是以02或03作为前缀。为什么会有两个前缀:因为椭圆曲线加密的公式的左边是y2 ,也就是说y的解是来自于一个平方根,可能是正值也可能是负值。更形象地说,y坐标可能在x坐标轴的上面或者下面。就像图4-2的椭圆曲线图中可以看出,曲线是对称的,就像是x轴的镜像。因此,如果我们略去y坐标,就必须储存y的符号(正号或者负号)。换句话说,需要知道在x轴的上方还是下方,因为上方下方代表椭圆曲线上不同的点,即不同的公钥。当我们在素数p阶的有限域上使用二进制算术计算椭圆曲线的时候,y坐标可能是偶数或者奇数,分别对应前面所讲的y值的正/负符号。因此,为了区分y坐标的两种可能值,在生成压缩格式公钥时,如果y是偶数,则使用02作为前缀;如果y是奇数,则使用03作为前缀。这样就可以让软件能够根据x坐标,正确推导出对应的y坐标,从而将公钥解压缩为在椭圆曲线上点的完整坐标。下图阐释了公钥压缩:
图4-7 公钥压缩
下面是前述章节所生成的公钥,使用了264比特(66个十六进制数字)的压缩格式公钥格式,其中前缀03表示y坐标是一个奇数:
K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
这个压缩格式公钥对应着同样的一个私钥,意味它是由同样的私钥所生成。但是压缩格式公钥和非压缩格式公钥看起来不同。更重要的是,如果我们使用双哈希函数(RIPEMD160(SHA256(K)))将压缩格式公钥转化成比特币地址,得到的地址将会不同于由非压缩格式公钥产生的地址。这种结果会让人迷惑,一个私钥生成了两种不同格式的公钥——压缩格式和非压缩格式,这两种格式的公钥又生成了两个不同的比特币地址。但是,这两个不同的比特币地址的私钥是一样的。
压缩格式公钥渐渐成为了各种比特币客户端的默认格式,它可以大大减少交易所需的字节数,同时也让存储区块链所需的磁盘空间变小。然而,并非所有的客户端都支持压缩格式公钥,于是那些较新的支持压缩格式公钥的客户端就不得不考虑如何处理那些来自较老的不支持压缩格式公钥的客户端的交易。当一个钱包应用导入另一个钱包应用的私钥的时候就会变得尤其重要,因为新钱包需要扫描区块链并找到所有与这些被导入密钥相关的交易。比特币钱包应该扫描哪个比特币地址呢?到底是压缩的公钥产生的比特币地址,还是通过非压缩的公钥产生的地址?两个都是有效的比特币地址,都可以被私钥签名,但是他们是不同的比特币地址。
为了解决这个问题,当私钥从钱包中被导出时,WIF表示私钥时,在较新的比特币钱包里被处理的方式就会有所不同,表明该私钥已经被用来生成压缩的公钥和压缩的比特币地址。这让导入钱包可以区分私钥来自于老钱包还是新钱包,使用分别对应于压缩格式公钥还是非压缩格式公钥的比特币地址,搜索区块链中对应的交易。我们将在下一节详细解释这种机制是如何工作的。
4.2.2.7 压缩格式私钥
实际上“压缩格式私钥”是一种名称上的误导,因为当私钥使用WIF压缩格式导出时,不但没有压缩,反而比“非压缩格式”私钥长出一个字节。这个多出来的一个字节是私钥被加了后缀01,用以表明该私钥是来自于一个较新的钱包,只能被用来生成压缩公钥。私钥并没有压缩的,也不能被压缩。“压缩私钥”实际上表示“只能生成压缩公钥的私钥”,而“非压缩私钥”用来表明“只能生成非压缩公钥的私钥”。为避免更多误解,应该只可以说导出格式 是“WIF压缩格式”或者“WIF”,而不能说这个私钥是“压缩”的。
表4-4展示了同样的私钥的WIF和WIF压缩格式编码。表4示例:相同的密钥,不同的格式
Format | Private key |
---|---|
Hex | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD |
WIF | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn |
Hex-compressed | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01 |
WIF-compressed | xFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ |
请注意,十六进制压缩私钥在末尾有一个额外的字节(十六进制为01)。 虽然Base58编码版本前缀对于WIF和WIF压缩格式都是相同的(0x80),但在数字末尾添加一个字节会导致Base58编码的第一个字符从5变为K或 L,Base58这一点有点类似十进制数字100和数字99之间的差别。100比99多一位数字,它的前缀是1,不是9。当长度变化,会影响前缀。与此类似,在Base58中,数字长度增加一个字节,前缀5就会改变为K或L。
要注意的是,这些格式并不是可互换使用的。在实现了压缩格式公钥的较新的钱包中,私钥只能且永远被导出为WIF压缩格式(以K或L为前缀)。对于较老的没有实现压缩格式公钥的钱包,私钥只能被导出为WIF格式(以5为前缀)。这样做的目的就是为了给导入这些私钥的钱包一个信号:是否必须在区块链中搜索压缩或非压缩公钥和地址。
如果一个比特币钱包实现了压缩格式公钥,那么将会用在所有交易中。钱包中的私钥将会被用来在曲线上生成公钥点,就会被压缩。压缩格式公钥被用来生成交易中的比特币地址。当从一个实现了压缩格式公钥的新的比特币钱包导出私钥时,钱包导入格式(WIF)将会被修改为WIF压缩格式,该格式将会在私钥的后面附加一个字节的后缀01。最终的Base58Check编码格式的私钥被称作WIF(“压缩”)私钥,以字母“K”或“L”开头。而以“5”开头的是从较老的钱包中以WIF(非压缩)格式导出的私钥。
提示 “压缩格式私钥”是一个不当用词!私钥是不可压缩的。WIF压缩格式的私钥只是用来表明他们只能被生成压缩公钥和对应的比特币地址。更矛盾的是,“WIF压缩”编码的私钥还多出一个字节,因为这种私钥多了后缀“01”。该后缀是用来区分“非压缩格式”和“压缩格式”。
4.3 用C++实现密钥和地址
我们回顾比特币地址产生的完整过程,从私钥、到公钥(椭圆曲线上某个点)、再到双重哈希地址,到最终的 Base58Check编码。例4-3的C++代码完整详细的展示了从私钥到Base58Check编码后的比特币地址的步骤。代码中使用“3.3 其他客户端、资料库、工具包 ”一节中介绍的libbitcoin库中的助手函数。
例4-3.从私钥中创建Base58Check编码的比特币地址
link:code/addr.cpp[]
上述代码使用预定义的私钥在每次运行时产生相同的比特币地址,如下例所示
例4-4 编译并运行addr代码
Compile the addr.cpp code
$ g++ -o addr addr.cpp $(pkg-config --cflags --libs libbitcoin)
Run the addr executable
$ ./addr
Public key: 0202a406624211f2abbdc68da3df929f938c3399dd79fac1b51b0e4ad1d26a47aa
Address: 1PRTTaJesdNovgne6Ehcdu1fpEdX7913CK
提示 例4-4中的代码从压缩公钥(参见上面的“压缩公钥”一节)生成了一个比特币地址(1PRTT…)。如果使用了未压缩公钥,就会生成另外一个地址(14K1y…)。