Mastering Bitcoin Chapter04 密钥和地址

在本章中,我们将介绍一些在比特币中用来控制资金的所有权的密码学,包括密钥,地址和钱包。

4.1简介

比特币的所有权是通过数字密钥、比特币地址和数字签名来确定的。

数字密钥由用户生成存储在一个叫做钱包的文件或简单的数据库中,完全独立于比特币协议。

只有有效的密钥才能产生有效的数字签名,因此拥有密钥副本就拥有了对该帐户的比特币的控制权。用于支出资金的数字签名也称为见证(witness)。

密钥是成对出现的,由一个私钥和一个公钥所组成。 ### 4.1.1 公钥加密和加密货币

自从公钥加密被发明之后,一些合适的数学函数被发现,譬如:素数幂和椭圆曲线乘法。这些数学函数都是不可逆的, 即不可以向相反方向倒推。基于这些数学函数的密码学,使得生成数字密钥和不可伪造的数字签名成为可能。比特币正是使用椭圆曲线乘法作为其公钥加密的基础。

在比特币系统中,我们用公钥加密创建一个密钥对,用于控制比特币的获取。密钥对包括一个私钥,和由其衍生出的唯 一的公钥。公钥用于接收比特币,而私钥用于比特币支付时的交易签名。

公钥和私钥之间的数学关系,使得私钥可用于生成特定消息的签名。此签名可以在不泄露私钥的同时对公钥进行验证。

支付比特币时,比特币的当前所有者需要在交易中提交其公钥和签名(每次交易的签名都不同,但均从同一个私钥生成)。比特币网络中的所有人都可以通过所提交的公钥和签名进行验证,并确认该交易是否有效,即确认支付者在该时刻对所交易的比特币拥有所有权。

提示 大多数比特币钱包工具为了方便会将私钥和公钥以密钥对的形式存储在一起。然而,公钥可以由私钥计算得到, 所以只存储私钥也是可以的。

4.1.2 私钥和公钥

一个比特币钱包中包含一系列的密钥对,每个密钥对包括一个私钥和一个公钥。私钥(k)是一个数字,通常是随机选出的。有了私钥,我们就可以使用椭圆曲线乘法这个单向加密函数产生一个公钥(K)。有了公钥(K),我们就可以使用一个单向加密哈希函数生成比特币地址(A)。私钥、公钥和比特币地址之间的关系如下图所示。

图4-1私钥、公钥和比特币地址之间的关系
图4-1私钥、公钥和比特币地址之间的关系

4.1.3 私钥

比特币私钥只是一个数字。生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源。生成一个比特币私钥在本质上与“在 1 到 2256 之间选一个数字”无异。只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。

私钥可以是 1 和 n-1 之间的任何数字,其中 n 是一个常数(n=1.158 * 1077,略小于 2256),并被定义为由比特币所使用的椭圆曲线的阶。要生成这样的一个私钥,我们随机选择一个256位的数字,并检查它是否小于 n-1。从编程的角度来看,一般是通过在一个密码学安全的随机源中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。如果运算结果小于n-1,我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。

以下是一个随机生成的私钥(k),以十六进制格式表示(256位的二进制数,以64位十六进制数显示,每个十六进制数占4位):

1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD

提示比特币私钥空间的大小是 2256,这是一个非常大的数字。用十进制表示的话,大约是 1077,而可见宇宙被估计只含有 1080 个原子。

要使用比特币核心客户端生成一个新的密钥,可使用 getnewaddress 命令。出于安全考虑,命令运行后只显示生成的公钥,而不显示私钥。如果要bitcoind显示私钥,可以使用 dumpprivkey 命令。 下面给出了使用这两个命令生成和显示私钥的例子:

1
2
3
$bitcoin-cli getnewaddress 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
$bitcoin-cli dumpprivkey 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

dumpprivkey 命令打开钱包提取由 getnewaddress 命令生成的私钥。除非密钥对都存储在钱包里,否则 bitcoind 并不能从公钥得知私钥。

dumpprivkey 命令无法从公钥得到对应的私钥,因为这是不可能的。这个命令只是显示钱包中已有也就是由 getnewaddress 命令生成的私钥。

您还可以使用 Bitcoin Explorer 命令行工具(请参阅附录中的[appdx_bx])使用命令 seed,ec-new和 ec-to-wif 生成和显示私钥:

1
2
$ bx seed | bx ec-new | bx ec-to-wif
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

4.1.4 公钥

通过椭圆曲线乘法可以从私钥计算得到公钥,这是不可逆转的过程:K = k * G 。其中 k 是私钥,G是被称为生成点的常数点,而 K 是所得公钥。其反向运算,被称为“寻找离散对数”——已知公钥 K来求出私钥 k——是非常困难的,就像去试验所有可能的 k 值,即暴力搜索。

4.1.5 椭圆曲线密码学(Elliptic Curve Cryptography)解释

椭圆曲线加密法是一种基于离散对数问题的非对称加密法,可以用对椭圆曲线上的点进行加法或乘法运算来表达。 下图是一个椭圆曲线的示例,类似于比特币所用的曲线。

图4-2椭圆曲线的示例 比特币使用了 secP256k1 标准所定义的一种特殊的椭圆曲线和一系列数学常数。secP256k1 曲线由下述函数定义,该函数可产生一条椭圆曲线:

产生椭圆曲线的函数
产生椭圆曲线的函数

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

图4-3图为:椭圆曲线密码学F(p)上的椭圆曲线,其中p = 17
图4-3图为:椭圆曲线密码学F(p)上的椭圆曲线,其中p = 17

下面举一个例子,这是 secP256k1 曲线上的点 P,其坐标为(x,y)。可以使用 Python 对其检验:

P = (55066263022277343669578718895168534326250603453777594175500187360389116729240, 32670510020758816978083085130507043184471273380659243275938904335757337482424)

在椭圆曲线的数学原理中,有一个点被称为“无穷远点”。表示为 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。这就是把无穷远点类似于0的作用。 事实证明,在这里 + 运算符遵守结合律,即(A+B)+C = A+(B+C)。因此,我们已经定义了椭圆加法,我们可以对乘法用拓展加法的标准方法进行定义。给定椭圆曲线上的点P,如果 k 是整数,则 kP = P + P + P + …+ P(k 次)。注意,在这种情况下k有时被混淆而称为“指数”。

4.1.6 生成公钥

以一个随机生成的私钥 k 为起点,我们将其与曲线上预定的生成点 G 相乘以获得曲线上的另一点,也就是相应的公钥 K。生成点是 secP256k1 标准的一部分,比特币密钥的生成点都是相同的:

{K = k * G}

其中 k 是私钥,G 是生成点,在该曲线上所得的点 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 的几何操作。

图4-4曲线上 G、2G、4G 的几何操作
图4-4曲线上 G、2G、4G 的几何操作

提示大多数比特币程序使用 OpenSSL 加密库进行椭圆曲线计算。例如,调用 EC_POINT_mul() 函数,可计算得到公钥。

4.2 比特币地址

比特币地址是一个由数字和字母组成的字符串,可以与任何想给你比特币的人分享。由公钥(一个同样由数字和字母组 成的字符串)生成的比特币地址以数字“1”开头。下面是一个比特币地址的例子:

1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy

比特币地址可由公钥经过单向的加密哈希算法得到。哈希算法是一种单向函数,接收任意长度的输入产生指纹或哈希。加密哈希函数在比特币中被广泛使用 :比特币地址、脚本地址以及在挖矿中的工作量证明算法。由公钥生成比特币地址时使用的算法是 Secure Hash Algorithm (SHA)和 the RACE Integrity 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-5从公钥生成比特币地址

4.2.1 Base58 和 Base58Check 编码

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-1 比特币的Base58字母表

123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz

为了增加防止打印和转录错误的安全性,Base58Check 是一种常用在比特币中的 Base58 编码格式,比特币有内置的检查错误的编码。检验和是添加到正在编码的数据末端的额外4个字节。校验和是从编码的数据的哈希值中得到的,所以可以用来检测并避免转录和输入中产生的错误。使用Base58check 编码时,解码软件会计算数据的校验和并和编码中自带的校验和进行对比。二者不匹配则表明有错误产生,那么这个 Base58Check 的数据就是无效的。一个错误比特币地址就不会被钱包软件认为是有效的地址,否则这种错误会造成资金的丢失。

为了将数据(数字)转换成 Base58Check 格式,首先我们要对数据添加一个称作“版本字节”的前缀,这个前缀用来识别编码的数据的类 型。例如,比特币地址的前缀是 0(十六进制是0x00),而对私钥编码时前缀是 128(十六进制是0x80)。

接下来,我们计算“双哈希”校验和,意味着要对之前的结果(前缀和数据)运行两次 SHA256 哈希算法:

checksum = SHA256(SHA256(prefix+data))

在产生的长 32 个字节的哈希值(两次哈希运算)中,我们只取前 4 个字节。这 4 个字节就作为检验错误的代码或者校验和。校验码会添加到数据之后。

结果由三部分组成:前缀、数据和校验和。这个结果采用之前描述的Base58字母表编码。下图描述了Base58Check编 码的过程。

图4-6Base58Check编 码的过程
图4-6Base58Check编 码的过程

表4-1 Base58Check版本前缀和编码后的结果

我们回顾比特币地址产生的完整过程,从私钥、到公钥(椭圆曲线上某个点)、再到两次哈希的地址,到最终的 Base58Check编码。

4.2.2 密钥的格式

公钥和私钥的都可以有多种格式。一个密钥被不同的格式编码后,虽然结果看起来可能不同,但是密钥所编码数字并没有改变。这些不同的编码格式主要是用来方便人们无误地使用和识别密钥。

4.2.2.1 私钥的格式

私钥可以以许多不同的格式表示,所有这些都对应于相同的256位的数字。表4-2展示了私钥的三种常见格式。不同的格式用在不同的场景下。十六进制和原始的二进制格式用在软件的内部,很少展示给用户看。WIF格式用在钱包之间密钥的输入和输出,也用于代表私钥的二维码(条形码)。

表4-2展私钥的三种常见格式
表4-2展私钥的三种常见格式

表4-3 示例:同样的私钥,不同的格式

表4-3 示例:同样的私钥,不同的格式
表4-3 示例:同样的私钥,不同的格式

这些表示法都是用来表示相同的数字、相同的私钥的不同方法。虽然编码后的字符串看起来不同,但不同的格式彼此之 间可以很容易地相互转换。请注意,“raw binary”未显示在表4-3 示例中,根据定义此处显示的任何编码的格式,不是 raw binary 数据。

我们使用 Bitcoin Explorer 中的 wif-to-ec 命令(请参阅[appdx_bx])来显示两个 WIF 键代表相同的私钥:

1
2
3
4
$ bx wif-to-ec 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
$ bx wif-to-ec KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd

4.2.3 从Base58Check解码

Bitcoin Explorer 命令(参见[appdx_bx])使得编写shell脚本和命令行“管道”变得容易,这些方式可以处理比特币密钥,地址和交易。 您可以使用 Bitcoin Explorer 在命令行上解码 Base58Check 格式。

我们使用 base58check-decode 命令解码未压缩的密钥:

1
2
3
4
5
6
7
$ bx base58check-decode 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
wrapper
{
checksum 4286807748
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd
version 128
}

结果包含密钥作为有效载荷,WIF 版本前缀128和校验和。

请注意,压缩密钥的“有效负载”附加了后缀01,表示导出的公钥要压缩:

1
2
3
4
5
6
7
$ bx base58check-decode KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ
wrapper
{
checksum 2339607926
payload 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01
version 128
}

将十六进制转换为Base58Check编码

要转换成Base58Check(与上一个命令相反),我们使用Bitcoin Explorer的base58check-encode命令(请参阅[appdx_bx]),并提供十六进制私钥,其次是WIF版本前缀128:

1
2
3
$bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd --version 128

5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

将十六进制(压缩格式密钥)转换为Base58Check编码

要将压缩格式的私钥编码为Base58Check(参见“压缩格式私钥”一节),我们需在十六进制私钥的后面添加后缀01,然后使用跟上面一样的方法:

1
2
3
$bx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 --version 128

KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

生成的WIF压缩格式的私钥以字母“K”开头,用以表明被编码的私钥有一个后缀“01”,且该私钥只能被用于生成压缩格式 的公钥(参见“压缩格式公钥”一节)。

4.2.3.1 公钥的格式

公钥也可以用多种不同格式来表示,最重要的是它们分为非压缩格式或压缩格式公钥这两种形式。

我们从前文可知,公钥是在椭圆曲线上的一个点,由一对坐标(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.3.2 压缩格式公钥

引入压缩格式公钥是为了减少比特币交易的字节数,从而可以节省那些运行区块链数据库的节点磁盘空间。大部分比特币交易包含了公钥,用于验证用户的凭据和支付比特币。

正如我们在“4.1.4 公钥”一节所见,一个公钥是一个椭圆曲线上的点(x, y)。如果我们知道了公钥的 x坐标,就可以通过解方程得到 y 坐 标。这种方案可以让我们只存储公钥的 x 坐标,从而将公钥的大小和存储空间减少了 256比特。

未压缩格式公钥使用 04 作为前缀,而压缩格式公钥是以 02 或 03 作为前缀。需要这两种不同前缀的原因是:因为椭圆曲线加密的公式的左边是 y2 ,也就是说 y 的解是来自于一个平方根,可能是正值也可能是负值。它们代表椭圆曲线上不同的点,即不同的公钥。当我们在素数 p 阶的有限域上使用二进制算术计算椭圆曲线的时候,y 坐标可能是奇数或者偶数,分别对应前面所讲的 y 值的正负符号。因此,为了区分 y 坐标的两种可能值,我们在生成压缩格式公钥时,如果 y 是偶数,则使用 02 作为前缀;如果 y 是奇数,则使用 03 作为前缀。这样就可以根据公钥中给定的 x 值,正确推导出对应的y坐标,从而将公钥解压缩为在椭圆曲线上的完整的点坐标。下图阐释了公钥压缩:

图4-7公钥压缩
图4-7公钥压缩

下面是前述章节所生成的公钥,使用了 264 比特(66个十六进制数字)的压缩格式公钥格式,其中前缀 03 表示 y 坐标是一个奇数:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

一个私钥可以生成两种不同格式的公钥——压缩格式和非压缩格式,而这两种格式的公钥可以生成两个不同的比特币地址。但是,这两个不同的比特币地址的私钥是一样的。

4.2.3.3 压缩格式私钥

实际上“压缩格式私钥”是一种名称上的误导,因为当一个私钥被使用WIF压缩格式导出时,不但没有压缩,而且比“非压缩格式”私钥长出一个字节。这个多出来的一个字节是私钥被加了后缀01,用以表明该私钥是来自于一个较新的钱包, 只能被用来生成压缩的公钥。私钥是非压缩的,也不能被压缩。“压缩的私钥”实际上只是表示“用于生成压缩格式公钥的私钥”,而“非压缩格式私钥”用来表明“用于生成非压缩格式公钥的私钥”。为避免更多误解,应该只可以说导出格式 是“WIF压缩格式”或者“WIF”,而不能说这个私钥是“压缩”的。

表4示例:相同的密钥,不同的格式

表4-4展示了同样的私钥使用不同的WIF和WIF压缩格式编码。
表4-4展示了同样的私钥使用不同的WIF和WIF压缩格式编码。

请注意,十六进制压缩私钥格式在末尾有一个额外的字节(十六进制为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”。该后缀是用来区分“非压缩格式”私钥和“压缩格式”私钥。

4.3 用Python实现密钥和比特币地址

最全面的比特币Python库是 Vitalik Buterin写的 pybitcointools。

4.4 高级密钥和地址

在以下部分中,我们将看到高级形式的密钥和地址,诸如加密私钥、脚本和多重签名地址,靓号地址,和纸钱包。

4.4.1 加密私钥(BIP0038)

BIP0038提出了一个通用标准,使用一个口令加密私钥并使用Base58Check对加密的私钥进行编码,这样加密的私钥就可以安全地保存在备份介质里,安全地在钱包间传输,保持密钥在任何可能被暴露情况下的安全性。这个加密标准使用了AES,这个标准由NIST建立,并广泛应用于商业和军事应用的数据加密。

BIP0038加密方案是:输入一个比特币私钥,通常使用WIF编码过,base58chek字符串的前缀“5”。此外BIP0038加密方案需要一个长密码作为口令,通常由多个单词或一段复杂的数字字母字符串组成。BIP0038加密方案的结果是一个由 base58check编码过的加密私钥,前缀为6P。如果你看到一个6P开头的的密钥,这就意味着该密钥是被加密过,并需要一个口令来转换(解码)该密钥回到可被用在任何钱包WIF格式的私钥(前缀为5)。许多钱包APP现在能够识别 BIP0038加密过的私钥,会要求用户提供口令解码并导入密钥。第三方APP,诸如非常好用基于浏览器的Bit Address , 可以被用来解码BIP00038的密钥。

最通常使用BIP0038加密的密钥用例是纸钱包——一张纸张上备份私钥。只要用户选择了强口令,使用BIP0038加密的私钥的纸钱包就无比的安全,这也是一种很棒的比特币离线存储方式(也被称作“冷存储”)。

在bitaddress.org上测试表4-5中加密密钥,看看如何输入密码以得到加密密钥。

表4-5 BIP0038加密私钥例子

表4-5 BIP0038加密私钥例子
表4-5 BIP0038加密私钥例子

4.4.2 P2SH (Pay-to-Script Hash)和多重签名地址

正如我们所知,传统的比特币地址从数字1开头,来源于公钥,而公钥来源于私钥。虽然任何人都可以将比特币发送到 一个1开头的地址,但比特币只能在通过相应的私钥签名和公钥哈希值后才能消费。

以数字3开头的比特币地址是P2SH地址,有时被错误的称谓多重签名或多重签名地址。他们指定比特币交易中受益人为哈希的脚本,而不是公钥的所有者。这个特性在2012年1月由BIP0016引进,目前因为BIP0016提供了增加功能到地址本身的机会而被广泛的采纳。不同于P2PKH交易发送资金到传统1开头的比特币地址,资金被发送到3开头的地址时,需要的不仅仅是一个公钥的哈希值和一个私钥签名作为所有者证明。在创建地址的时候,这些要求会被指定在脚本中,所有对地址的输入都会被这些要求阻隔。

一个P2SH地址从交易脚本中创建,它定义谁能消耗这个交易输出(后面“P2SH(Pay-to-Script-Hash)”一节对此有 详细的介绍)。编码一个P2SH地址涉及使用一个在创建比特币地址用到过的双重哈希函数,并且只能应用在脚本而不是公钥:

script hash = RIPEMD160(SHA256(script))

产生的脚本哈希由Base58Check编码前缀为5的版本、编码后得到开头为3的编码地址。一个P2SH地址例子是 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM。可以使用Bitcoin Explorer命令脚本编码获得,比如sha256, ripemd160, and base58check-encode,举例如下:

1
2
3
$ echo dup hash160 [ 89abcdefabbaabbaabbaabbaabbaabbaabbaabba ] equalverify checksig > script

$ bx script-encode < script | bx sha256 | bx ripemd160 | bx base58check-encode --version 53F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM

提示P2SH 不一定是多重签名的交易。虽然P2SH地址通常都是代表多重签名,但也可能是编码其他类型的交易脚本。

4.4.2.1 多重签名地址和P2SH

目前,P2SH函数最常见的实现是多重签名地址脚本。顾名思义,底层脚本需要多个签名来证明所有权,此后才能消费资金。设计比特币多重签名特性是需要从总共N个密钥中需要M个签名(也被称为“阈值”),被称为M-N多签名,其中M≤N。

4.4.3 比特币靓号地址

靓号地址包含了人类可读信息的有效比特币地址。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33 就是包含了Base-58 字母love的。靓号地址需要生成并通过数十亿的候选私钥测试,直到一个私钥能生成具有所需图案的比特币地址。

一旦找到一个匹配所要图案的靓号地址,来自这个靓号地址的私钥可以和其他地址相同的方式被拥有者消费比特币。靓号地址不比其他地址具有更多或更少的安全性。它们依靠和其他地址相同的ECC和SHA。你无法比任何别的地址更容易的获得一个靓号图案开头的地址的私钥。

4.4.3.1 生成靓号地址

认识到比特币地址不过是由Base58字母代表的一个数字是非常重要的。搜索“1kids”开头的图案我们会发 现从1Kids11111111111111111111111111111到1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz的地址。这些以“1kid”开头的地址范围中大约有58的29次方地址(1.4 * 10^51))。表4-6显示了这些有“1kids”前缀的地址。

表4-6 “1Kids”靓号的范围

表4-6 “1Kids”靓号的范围
表4-6 “1Kids”靓号的范围

我们把“1Kids”这个前缀当作数字,我们可以看看比特币地址中这个前缀出现的频率。如果是一台普通性能的桌面电脑, 没有任何特殊的硬件,可以每秒搜索大约10万个密钥。

表4-12 靓号的出现的频率(1KidsCharity)以及生成所需时间
表4-12 靓号的出现的频率(1KidsCharity)以及生成所需时间

每增加一个字符就会增加58倍的计算难度。超过七个字符的图案通常需要专用的硬件才能被找出,譬如用户定制的具有多个图形处理单元(GPU)的台式机。那些通常是无法继续在比特币挖矿中盈利的钻机,被重新赋予了寻找靓号地址的任务。用GPU系统搜索靓号的速度比用通用CPU要快很多个量级。

生成一个靓号地址是一项通过蛮力的过程:尝试一个随机密钥,检查生成的地址是否和所需的图案相匹配,重复这个过程直到成功找到为止。

示例代码需要用C编译器链接libbitcoin库(此库需要提前装入该系统)进行编译。可以不带参数直接执行vanity-miner的可执行文件 (参见例4-10),它就会尝试找到以“1kid”开头的靓号地址。

例4-10编译并运行vanity-miner程序示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
\Compile the code with g++
$ g++ -o vanity-miner vanity-miner.cpp

$(pkg-config --cflags --libs libbitcoin)

\Run the example

$ ./vanity-miner

Found vanity address! 1KiDzkG4MxmovZryZRj8tK81oQRhbZ46YT
Secret: 57cc268a05f83a23ac9d930bc8565bac4e277055f4794cbd1a39e5e71c038f3f

\ Run it again for a different result

$ ./vanity-miner

Found vanity address! 1Kidxr3wsmMzzouwXibKfwTYs5Pau8TUFn
Secret: 7f65bbbbe6d8caae74a0c6a0d2d7b5c6663d71b60337299a1a2cf34c04b2a623

使用时间命令查看需要多久才能找到结果

1
2
3
4
5
6
7
$ time ./vanity-miner
Found vanity address! 1KidPWhKgGRQWD5PP5TAnGfDyfWp5yceXM
Secret: 2a802e7a53d8aa237cd059377b616d2bfcfa4b0140bc85fa008f2d3d4b225349

real 0m8.868s
user 0m8.828s
sys 0m0.035s

正如我们运行Unix命令所测出的运行时间所示,示例代码要花几秒钟来找出匹配“kid”三个字符模板的结果。你可以尝试在源代码中改变search这一搜索模板,看一看如果是四个字符或者五个字符的搜索模板需要花多久时间!

4.4.3.2 靓号地址安全性

靓号地址既可以增加、也可以削弱安全措施,它们着实是一把双刃剑。用于改善安全性时,一个独特的地址使对手难以使用他们自己的地址替代你的地址,以欺骗你的顾客支付他们的账单。不幸的是,靓号地址也可能使得任何人都能创建一个类似于随机地址的地址,甚至另一个靓号地址,从而欺骗你的客户。

使用单一固定地址(而不是每比捐款用一个独立的动态地址)的风险之一是小偷有可能会黑进你的网站,用他自己的地址取代你的地址,从而将捐赠转移给他自己。如果你在不同的地方公布了你的捐款地址,你的用户可以在付款之前用自己眼睛检查以确保这个地址跟在你的网站、邮件和传单上看到的地址是同一个。在随机地址 1j7mdg5rbqyuhenydx39wvwk7fslpeoxzy 的情况下,普通用户可能会只检查头几个字符“1j7mdg”,就认为地址匹配。使用靓号地址生成器,那些想通过替换类似地址来盗窃的人可以快速生成与前几个字符相匹配的地址,如表4-8所示。

表4-8 生成匹配某随机地址的多个靓号

表4-8 生成匹配某随机地址的多个靓号
表4-8 生成匹配某随机地址的多个靓号

那靓号地址会不会增加安全性?如果Eugenia生成1Kids33q44erFfpeXrmDSz7zEqG2FesZEN的靓号地址,用户可能看到靓号图案的字母和一些字符在上面,例如在地址部分中注明了1Kids33。这样就会迫使攻击者生成至少6个字母相匹 配的的靓号地址(比之前多2个字符),就要花费比Eugenia多3364倍的努力。本质上,Eugenia付出的努力(或者靓号池付出的)迫使攻击者不得不生成更长的靓号图案。如果Eugenia花钱请矿池生成8个字符的靓号地址,攻击者将会被逼迫到10字符的境地,那将是个人电脑,甚至昂贵自定义靓号挖掘机或靓号池也无法生成。对Eugenia来说可承担的起支出,对攻击者来说则变成了无法承担支出,特别是如果欺诈的潜在回报不足以支付生成靓号地址所需的费用。

4.4.4 纸钱包

纸钱包是打印在纸张上的比特币私钥。有时纸钱包为了方便起见也包括对应的比特币地址,但这并不是必要的,因为地址可以从私钥中导出。纸钱包是一个非常有效的建立备份或者线下存储比特币(即冷存储)的方式。作为备份机制,一个纸钱包可以提供安全性,以防在电脑硬盘损坏、失窃或意外删除的情况下造成密钥的的丢失。作为一个冷存储的机制,如果纸钱包密钥在线下生成并永久不在电脑系统中存储,他们在应对黑客攻击,键盘记录器,或其他在线电脑威胁更有安全性。

纸钱包有许多不同的形状,大小,和外观设计,但非常基本的原则是一个密钥和一个地址打印在纸张上。表4-14展现了纸钱包最基本的形式。

表4-9 比特币纸钱包的私钥和公钥的打印形式

表4-9 比特币纸钱包的私钥和公钥的打印形式
表4-9 比特币纸钱包的私钥和公钥的打印形式

通过使用工具,就可以很容易地生成纸钱包,譬如使用bitaddress.org网站上的客户端Javascript生成器。这个页面包含所有生成密钥和纸钱包所必须代码,甚至在完全失去网络连接的情况下,也可以生成密钥和纸钱包。若要使用它,先将HTML页面保存在本地磁盘或外部U盘。从Internet网络断开,从浏览器中打开文件。更方便的,使用一个原始操作系统启动电脑,比如一 个光盘启动的Linux系统。任何在脱机情况下使用这个工具所生成的密钥,都可以通过USB线在本地打印机上打印出 来,从而制造了密钥只存在纸张上而从未存储在在线系统上的纸钱包。将这些纸钱包放置在防火保险柜内,发送比特币到 对应的比特币地址上,从而实现了一个简单但非常有效的冷存储解决方案。图4-8展示了通过bitaddress.org 生成的纸钱包。

图4-8展示了通过bitaddress.org 生成的纸钱包
图4-8展示了通过bitaddress.org 生成的纸钱包

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×