Bitcoin地址生成
简介
比特币的所有权是通过数字密钥、比特币地址和数字签名来确定的。数字密钥实际上并不存储在网络中,而是由用户生成之后,存储在一个叫做钱包的文件或简单的数据库中。用户钱包中的数字密钥完全独立于比特币协议,可由用户的钱包软件生成并管理,而无需参照区块链或访问互联网。密钥实现了比特币的许多有趣特性,包括去中心化信任和控制、所有权认证和基于密码学证明的安全模型。
大多数比特币交易都需要在区块链中存储一个有效的数字签名。该数字签名只能由密钥产生,因此拥有密钥副本就等于拥有了该帐户中比特币的控制权。用于支出资金的数字签名也称为见证(witness),这是密码学中的术语。比特币交易中的见证数据证明了资金的真正所有权。
密钥是成对出现的,由私钥和公钥所组成。公钥就像银行的帐号,而私钥就像PIN码或支票的签名。比特币的用户很少会直接看到数字密钥。一般情况下,它们存储在钱包文件内,由比特币钱包软件进行管理。
在比特币交易的支付环节,收款人的公钥由数字指纹表示,称为比特币地址,就像支票上收款人名称 (即“付给谁的账户”)。一般情况下,比特币地址由公钥生成并与之对应。然而,并非所有比特币地址都代表公钥; 也可以代表其他支付对象,譬如脚本,我们将在本章后面提及。这样一来,比特币地址就可以抽象成资金接收者,使得交易更灵活,就像纸质支票:可以支付到个人账户或公司账户,也可以支付账单和现金。比特币地址是密钥被用户能够看到的唯一形式,因为这个地址就是需要告诉别人的。
首先,我们将介绍密码学,并解释在比特币中使用的数学知识。然后我们将了解密钥的产生、存储和管理方式。我们将检查私钥和公钥、地址和脚本地址的各种编码格式。最后,我们将讲解密钥和地址的高级用途:比特币靓号地址,多重签名以及脚本地址和纸钱包。
公钥密码学和密码货币
公钥密码学发明于20世纪70年代,它是计算机和信息安全的数学基础。
公钥密码学被发明之后,一些合适的数学函数被发现,譬如:素数幂运算和椭圆曲线乘法。这些数学函数都是不可逆的, 就是说很容易向一个方向计算,但不可以向相反方向倒推。基于这些数学函数的密码学,使得生成数字密钥和不可伪造的数字签名成为可能。比特币正是使用椭圆曲线乘法作为其密码学的基础。
在比特币系统中,我们用公钥密码学技术创建一个密钥对,用于控制对比特币的访问。密钥对包括一个私钥,和由其衍生出的唯一的公钥。公钥用于接收比特币,而私钥用于支付时进行交易签名。
公钥和私钥之间的数学关系,使得私钥可用于生成特定消息的签名。公钥则可以在不显示私钥的情况下验证这些签名。
支付比特币时,比特币的当前所有者需要在交易中提交其公钥和签名(每次交易的签名都不同,但都由同一个私钥生成)。针对展示的公钥和签名,比特币网络中的所有人都可以验证该交易有效并予以接受,从而确认支付者对该交易中的比特币的所有权。
提示 大多数比特币钱包工为了方便会将私钥和公钥以密钥对的形式存储在一起。然而,公钥可以由私钥计算得到, 所以只存储私钥也是可以的。
私钥和公钥
一个比特币钱包中包含一系列的密钥对,每个密钥对包括一个私钥和一个公钥。私钥(k)是一个数字,通常是随机选出的。基于私钥,我们就可以使用椭圆曲线乘法这个单向密码函数产生一个公钥(K)。基于公钥(K),我们就可以使用一个单向密码哈希函数生成比特币地址(A)。在本节中,我们将从生成私钥开始,讲述如何使用椭圆曲线运算将私钥生成公钥,并最终由公钥生成比特币地址。私钥、公钥和比特币地址之间的关系如下图所示。
私钥、公钥和比特币地址之间的关系
为什么使用非对称密码技术(公钥/私钥)?
为什么在比特币中使用非对称密码技术?它不是用于对交易进行“加密”(保密)的。 相反,非对称密码技术的最有用特性是生成数字签名。可以将私钥用作交易的数字指纹来产生数字签名。 该签名只能由知晓私钥的人生成。 但是,任何访问公钥和交易指纹的人都可以验证签名。 这种非对称密码学的适用性使得任何人都可以验证每笔交易的每个签名,并且确保只有私钥的所有者可以生成有效的签名。
私钥
私钥就是一个随机选出的数字而已。拥有和控制了私钥,就相当于控制了该私钥对应的比特币地址中的所有资金。通过证明比特币交易中资金的所有权,私钥可以生成花费该笔资金的签名。私钥任何情况下都必须保密,因为一旦被泄露给第三方,相当于该私钥保护之下的比特币也拱手相让了。私钥还必须进行备份,以防意外丢失,因为私钥一旦丢失就无法恢复,其所保护的比特币也将永远丢失。
提示 比特币私钥只是一个数字。你可以用硬币、铅笔和纸来随机生成你的私钥:掷硬币256次,用纸和笔记录正反面并转换为0和1,随机得到的256位二进制数字可作为比特币钱包的私钥。该私钥可进一步生成公钥。
从一个随机数生成私钥 生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源。生成一个比特币私钥在本质上与“在1到2256之间选一个数字”无异。只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。比特币软件使用操作系统底层的随机数生成器来产生256位的熵(随机性)。通常情况下,操作系统随机数生成器由人工的随机源进行初始化,这就是为什么也可能需要不停晃动鼠标几秒钟。
更准确地说,私钥可以是1和n-1之间的任何数字,其中n是一个常数(n=1.158 * 1077,略小于2256),并被定义为由比特币所使用的椭圆曲线的阶(见下面的椭圆曲线密码学解释这一节)。要生成这样的一个私钥,我们随机选择一个256位的数字,并检查它是否小于n-1。从编程的角度来看,一般是通过在一个密码学安全的随机源中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。如果运算结果小于n,我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。
警告 不要自己写代码来生成随机数,也不要使用编程语言提供的简易随机数生成器来获得一个随机数。使用密码学安全的伪随机数生成器(CSPRNG),并且需要有一个熵源值足够的的种子。使用随机数发生器的程序库时,需仔细研读其文档,以确保它是密码学安全的。正确实施CSPRNG是密钥安全性的关键所在。
以下是一个随机生成的私钥(k),以十六进制格式表示(256位的二进制数,转变为十六进制是64位,每个十六进制数占4位):
1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD
提示 比特币私钥空间的大小是2256,这是一个非常大的数字。用十进制表示的话,大约是1077,而可见宇宙被估计只含有1080个原子。
要使用Bitcoin Core客户端生成一个新的密钥(参见第三章内容),可使用 getnewaddress 命令。出于安全考虑,命令运行后只显示生成的公钥,而不显示私钥。如果要bitcoind显示私钥,可以使用 dumpprivkey 命令。 dumpprivkey 命令会把私钥以 Base58校验和编码格式显示,这种私钥格式被称为钱包导入格式(WIF,Wallet Import Format),在“私钥的格式”一节有详细讲解。下面给出了使用这两个命令生成和显示私钥的例子:
$ bitcoin-cli getnewaddress
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
$ bitcoin-cli dumpprivkey 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
dumpprivkey 命令打开钱包提取由 getnewaddress 命令生成的私钥。除非密钥对都存储在钱包里,否则bitcoind并不能从公钥得知私钥。
提示 dumpprivkey命令无法从公钥生成私钥,因为这是不可能的。这个命令只是显示钱包中已有也就是由getnewaddress命令生成的私钥。
还可以使用Bitcoin Explorer命令行工具(请参阅附录中的[appdx_bx])使用命令seed,ec-new和ec-to-wif生成和显示私钥:
$ bx seed | bx ec-new | bx ec-to-wif
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
公钥
通过椭圆曲线乘法可以从私钥计算得到公钥,这是不可逆转的过程:K = k * G 。其中 k 是私钥,G 是被称为生成点的常数点,而 K 是所得公钥。其反向运算,被称为“寻找离散对数”——已知公钥K来求出私钥k——是非常困难的,就像去尝试所有可能的k值,即暴力搜索。在演示如何从私钥生成公钥之前,我们先稍微详细学习下椭圆曲线密码算法。
提示 椭圆曲线乘法是密码学家称之为“陷阱门”的一种函数:在一个方向(乘法)很容易计算,而在相反的方向(除法)是不可能计算出来的。私钥的所有者可以容易地创建公钥,然后与世界共享,知道没有人可以从公钥反转该函数计算出私钥。 这个数学技巧成为证明比特币资金所有权不可伪造和安全的数字签名的基础。
椭圆曲线密码学(Elliptic Curve Cryptography)解释
椭圆曲线密码学算法是一种基于离散对数问题的非对称或者公钥密码学算法,可以用对椭圆曲线上的点进行加法或乘法运算来表达。
下图是一个椭圆曲线的示例,类似于比特币所用的曲线。

椭圆曲线的示例
比特币使用了secp256k1标准所定义的一种特殊的椭圆曲线和一系列数学常数。该标准由美国国家标准与技术研究院 (NIST)建立。secp256k1曲线由下述函数定义,该函数可产生一条椭圆曲线:

上述mod p(素数p取模)表明该曲线是在素数阶p的有限域内,也写作Fp,其中p = 2256 – 232 – 29 – 28 – 27 – 26 – 24 – 1, 这是个非常大的素数。
因为这条曲线被定义在一个素数阶的有限域内,而不是定义在实数范围,它的函数图像看起来像二维的离散的点,因此很难可视化。不过,该公式与实数的椭圆曲线数学公式是相似的。为了举例,下图显示了在一个小了很多的素数阶17的有限域内的椭圆曲线,其形式为网格上的一系列散点。而secp256k1的比特币椭圆曲线可以被想象成一个更大的网格上一系列更为复杂的散点。

椭圆曲线密码学F(p)上的椭圆曲线,其中p = 17
比如,下面是 secp256k1 曲线上的点P,其坐标为(x,y)。
`P = (55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)`
下面的例1显示了如何使用Python对其检验:
例1:使用pyhton确认这个点在椭圆曲线上
Python 3.4.0 (default, Mar 30 2014, 19:23:13)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> p = 115792089237316195423570985008687907853269984665640564039457584007908834671663
>>> x = 55066263022277343669578718895168534326250603453777594175500187360389116729240
>>> y = 32670510020758816978083085130507043184471273380659243275938904335757337482424
>>> (x ** 3 + 7 - y**2) % p
0
在椭圆曲线数学中,有一个点被称为“无穷远点”,这大致相当于0的作用。计算机中,它有时表示为X = Y = 0(虽然这不满足椭圆曲线方程,但这是一个可以单独检查的例子)。
还有一个 + 运算符,被称为“加法”,就像小学数学中的实数相加。给定椭圆曲线上的两个点P1和P2,则椭圆曲线上必定有第三点 P3 = P1 + P2。
从几何学上说,该第三点P3可以在P1和P2之间画一条线来计算出来的。这条直线恰好与椭圆曲线相交于另外一个地方。此点记为 P3'= (x,y)。然后,基于x轴的对称点就是 P3=(x,-y)。
下面是几个解释为何需要“无穷远点”的特殊例子。
若 P1和 P2是同一点,那么P1和P2之间的连线延长就会与曲线相切于p1处。该切线将会与曲线相交于一个新的点。该切线的斜率可用微积分求得。尽管我们只局限在曲线上两个整数坐标的那个点,但这个机制是没问题的。
在某些情况下(即,如果P1和P2具有相同的x值,不同的y值),则切线会完全垂直,在这种情况下,P3 = “无穷远点”。
若P1就是“无穷远点”,那么其和 P1 + P2= P2。类似地,当P2是无穷远点,则P1+ P2 = P1。这表明无穷远点类似于0的角色。
事实证明,在这里 + 运算符遵守结合律,即(A+B)+C = A+(B+C)。这就是说我们可以书写成 A + B + C,不加括号也可以,而不至于混淆。
至此,我们已经定义了椭圆加法,我们可以用标准方法对加法进行扩展,来定义乘法。给定椭圆曲线上的点P,如果k是整数,则 kP = P + P + P + …+ P(k次)。注意,在这种情况下k有时被混淆了称为“指数”。
生成公钥
以一个随机生成的私钥k为起点,将其乘以曲线上一个预定的点,叫做生成点G得到曲线上的另一点,这就是相应的公钥 K。生成点是secp256k1标准的一部分,比特币密钥的生成点都是相同的:
其中k是私钥,G是生成点,在该曲线上所得的点K是公钥。因为所有比特币用户的生成点是相同的,一个私钥k乘以G将得到相同的公钥K。k和K之间的关系是固定的,但只能单向运算,即从k得到K。这就是可以把比特币地址(K的衍生) 与任何人共享而不会泄露私钥(k)的原因。
提示 因为其中的数学运算是单向的,所以私钥可以转换为公钥,但公钥不能转换回私钥。
实现了椭圆曲线乘法,我们用之前产生的私钥k和与生成点G相乘得到公钥K:
K = 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD * G
公钥K 被定义为一个点 K = (x, y):
K = (x, y)
其中,
x = F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
y = 07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB
为了可视化展示整数与点的乘积,我们使用实数范围的简化的椭圆曲线。请记住,其中的数学原理是相同的。我们的目标是找到生成点G的倍数kG。也就是将G相加k次。在椭圆曲线中,点的相加等于该点的切线与曲线相交的那个点,该点基于x轴的对称点。
下图显示了在曲线上得到 G、2G、4G 的几何做法。

曲线上 G、2G、4G 的几何做法
提示 比特币使用【secp256k1 optimized C library】 进行椭圆曲线计算。
比特币地址
比特币地址是一个由数字和字母组成的字符串,可以展示给任何给你转账比特币的人。由公钥(一个同样由数字和字母组成的字符串)生成的比特币地址以数字“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的编码和解码机制,以及它产生的结果。
下图描述了如何从公钥生成比特币地址。

从公钥生成比特币地址]
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编码的过程。

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 |
密钥的格式
公钥和私钥都可以有多种格式。虽然看起来可能不同,但是所编码的是同样的数字。这些不同的编码格式主要是用来方便人们无误地阅读和抄写。
私钥的格式
私钥的格式有许多,所有这些都对应于相同的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
从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
}
将十六进制转换为Base58Check编码
要转换成Base58Check(与上一个命令相反),使用Bitcoin Explorer的base58check-encode命令(请参阅本书附录[appdx_bx]),需要十六进制私钥,后面跟WIF版本前缀128:
bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd --version 128
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
将十六进制(压缩格式密钥)转换为Base58Check编码
要将压缩格式的私钥(参见“压缩格式私钥”一节)编码为Base58Check,需要在十六进制私钥的后面添加后缀01,然后使用跟上面一样的方法:
$ bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 --version 128
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
生成的WIF压缩格式的私钥以字母“K”开头,表明被编码的私钥中有一个后缀“01”,且该私钥只能被用于生成压缩格式的公钥(参见“压缩格式公钥”一节)。
公钥的格式
公钥也可以用多种不同格式来表示,通常分为非压缩格式或压缩格式公钥这两种形式。
我们从前文可知,公钥是在椭圆曲线上的一个点,由一对坐标(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
压缩格式公钥
比特币引入压缩格式公钥是为了减少交易的大小,从而节省运行区块链数据库的节点磁盘空间。大部分比特币交易包含了公钥,用于验证用户的凭证和支付比特币。每个公钥有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坐标,从而将公钥解压缩为在椭圆曲线上点的完整坐标。下图阐释了公钥压缩:

公钥压缩
下面是前述章节所生成的公钥,使用了264比特(66个十六进制数字)的压缩格式公钥格式,其中前缀03表示y坐标是一个奇数:
K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A
这个压缩格式公钥对应着同样的一个私钥,意味它是由同样的私钥所生成。但是压缩格式公钥和非压缩格式公钥看起来不同。更重要的是,如果我们使用双哈希函数(RIPEMD160(SHA256(K)))将压缩格式公钥转化成比特币地址,得到的地址将会不同于由非压缩格式公钥产生的地址。这种结果会让人迷惑,一个私钥生成了两种不同格式的公钥——压缩格式和非压缩格式,这两种格式的公钥又生成了两个不同的比特币地址。但是,这两个不同的比特币地址的私钥是一样的。
压缩格式公钥渐渐成为了各种比特币客户端的默认格式,它可以大大减少交易所需的字节数,同时也让存储区块链所需的磁盘空间变小。然而,并非所有的客户端都支持压缩格式公钥,于是那些较新的支持压缩格式公钥的客户端就不得不考虑如何处理那些来自较老的不支持压缩格式公钥的客户端的交易。当一个钱包应用导入另一个钱包应用的私钥的时候就会变得尤其重要,因为新钱包需要扫描区块链并找到所有与这些被导入密钥相关的交易。比特币钱包应该扫描哪个比特币地址呢?到底是压缩的公钥产生的比特币地址,还是通过非压缩的公钥产生的地址?两个都是有效的比特币地址,都可以被私钥签名,但是他们是不同的比特币地址。
为了解决这个问题,当私钥从钱包中被导出时,WIF表示私钥时,在较新的比特币钱包里被处理的方式就会有所不同,表明该私钥已经被用来生成压缩的公钥和压缩的比特币地址。这让导入钱包可以区分私钥来自于老钱包还是新钱包,使用分别对应于压缩格式公钥还是非压缩格式公钥的比特币地址,搜索区块链中对应的交易。我们将在下一节详细解释这种机制是如何工作的。
压缩格式私钥
实际上“压缩格式私钥”是一种名称上的误导,因为当私钥使用WIF压缩格式导出时,不但没有压缩,反而比“非压缩格式”私钥长出一个字节。这个多出来的一个字节是私钥被加了后缀01,用以表明该私钥是来自于一个较新的钱包,只能被用来生成压缩公钥。私钥并没有压缩的,也不能被压缩。“压缩私钥”实际上表示“只能生成压缩公钥的私钥”,而“非压缩私钥”用来表明“只能生成非压缩公钥的私钥”。为避免更多误解,应该只可以说导出格式 是“WIF压缩格式”或者“WIF”,而不能说这个私钥是“压缩”的。
表4-4展示了同样的私钥的WIF和WIF压缩格式编码。 表4示例:相同的密钥,不同的格式
| Format | Private key |
|---|---|
| Hex | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD |
| WIF | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn |
| Hex-compressed | 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01 |
| WIF-compressed | KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ |
请注意,十六进制压缩私钥在末尾有一个额外的字节(十六进制为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”。该后缀是用来区分“非压缩格式”和“压缩格式”。
用Java实现密钥和比特币地址
首先,你需要在你的项目中引入bitcoinj库。如果你的项目是Maven项目,你可以在pom.xml文件中添加以下依赖:
<dependencies>
<dependency>
<groupId>com.google.code.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.15</version>
</dependency>
</dependencies>
然后,你可以使用以下Java代码来生成比特币私钥和地址:
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
public class BitcoinGenerator {
public static void main(String[] args) {
// 创建网络参数对象,这里我们使用的是主网(MainNet)
NetworkParameters params = MainNetParams.get();
// 生成一个新的ECKey(椭圆曲线密钥)
ECKey key = new ECKey();
// 获取私钥
byte[] privateKeyBytes = key.getPrivateKeyAsBytes();
System.out.println("Private Key (Hex): " + key.getPrivateKeyAsString());
// 获取公钥
byte[] publicKeyBytes = key.getPubKey();
System.out.println("Public Key (Hex): " + key.getPublicKeyAsString());
// 根据公钥生成比特币地址
String address = key.toAddress(params).toString();
System.out.println("Bitcoin Address: " + address);
}
}
这段代码首先创建了比特币网络参数对象,然后生成了一个新的ECKey,这是比特币密钥对的基础。接着,它获取并打印了私钥和公钥的十六进制表示。最后,它根据公钥生成了比特币地址,并将其打印出来。 内容来源