主页 > imtoken usdt钱包 > 区块链的第一个应用---比特币(密钥、地址、钱包)

区块链的第一个应用---比特币(密钥、地址、钱包)

imtoken usdt钱包 2023-11-08 05:08:57

比特币密钥原理_比特币 挖矿 原理_挖比特币的原理

比特币的所有权是通过数字密钥、比特币地址和数字签名建立的。 数字密钥实际上并不存储在网络上,而是由用户生成并存储在称为钱包的文件或简单数据库中。 存储在用户钱包中的数字密钥完全独立于比特币协议,可以由用户的钱包软件生成和管理,无需区块链或网络连接。 密钥使比特币的许多有趣特性成为可能,包括去中心化信任和控制、所有权认证以及基于加密证明的安全模型。

每笔比特币交易都需要将有效签名存储在区块链上。 只有有效的数字密钥才能产生有效的数字签名,因此拥有比特币密钥的副本可以让您控制帐户的比特币。 密钥成对出现,由私钥和公钥组成。 公钥就像银行的帐号,私钥就像控制帐户的 PIN 或支票上的签名。 比特币用户很少直接看到数字密钥。 通常,它们存储在钱包文件中,由比特币钱包软件管理。

在比特币交易的支付环节中,收款人的公钥由其数字指纹表示,称为比特币地址,就像支票上支付对象(即“收款人”)的姓名一样。 一般来说,比特币地址是由公钥生成并对应的。 但是,并非所有比特币地址都是公钥; 它们也可以代表其他支付对象,比如本章后面会提到的脚本。 这样,比特币地址抽象了收款人,使得交易的目的地更加灵活,就像支票一样:这种支付工具可以支付到个人账户、公司账户,用于账单支付或现金支付。 比特币地址是用户始终看到的密钥的唯一表示,他们只需要将比特币地址告诉其他人即可。

在本章中,我们将介绍存放密钥的钱包。 我们将看到密钥是如何生成、存储和管理的。 我们将回顾私钥和公钥、地址和脚本地址的各种编码格式。 最后,我们将介绍密钥的具体用途:生成签名、证明所有权以及创建比特币虚荣地址和纸钱包。

4.1.1 公钥密码学和加密货币

公钥密码术是在 1970 年代发明的。 它是计算机和信息安全的数学基础。

自从公钥密码学发明以来,一些合适的数学函数被提出,例如:素数幂和椭圆曲线乘法。 这些数学函数是不可逆的,也就是说,在一个方向上很容易计算,但在相反的方向上是不可逆的。 基于这些数学函数的密码学使得生成数字密钥和不可伪造的数字签名成为可能。 比特币使用椭圆曲线乘法作为其公钥加密的底层算法。

在比特币系统中,我们使用公钥加密来创建控制比特币获取的密钥对。 密钥对由私钥和从中派生的唯一公钥组成。 公钥用于接收比特币,而私钥用于在使用比特币支付时签署交易。

公钥和私钥之间的数学关系,使得私钥可用于为特定消息生成签名。 此签名可以在不泄露私钥的情况下验证公钥。

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

为了方便起见,大多数比特币钱包工具将私钥和公钥存储在一个密钥对中。 然而,公钥可以从私钥计算出来,所以也可以只存储私钥。

4.1.2 私钥和公钥

比特币钱包包含一系列密钥对,每个密钥对包括一个私钥和一个公钥。 私钥 (k) 是一个数字,通常是随机选择的。 有了私钥,我们就可以利用椭圆曲线乘法的单向加密函数生成一个公钥(K)。 使用公钥 (K),我们可以使用单向加密哈希函数生成比特币地址 (A)。 在本节中,我们将从生成私钥开始,介绍如何使用椭圆曲线运算从私钥生成公钥,最后从公钥生成比特币地址。 私钥、公钥和比特币地址的关系如下图所示。

比特币 挖矿 原理_挖比特币的原理_比特币密钥原理

4.1.3 私钥

私钥只不过是一个随机选择的数字。 对比特币地址中所有资金的控制取决于相应私钥的所有权和控制权。 在比特币交易中,私钥用于生成支付比特币所需的签名,以证明资金的所有权。 私钥必须时刻保密,因为如果泄露给第三方,受私钥保护的比特币也随之丢失。 私钥也必须备份,以防不慎丢失,因为一旦丢失,私钥就很难找回,它所保护的比特币也就永远丢失了。

比特币私钥只是一个数字。 可以用硬币、铅笔和纸随机生成你的私钥:抛一枚硬币256次,用纸笔记录正反面转换成0和1,就可以使用随机的256位二进制数作为比特币钱包的私钥。 这个私钥可以进一步生成一个公钥。

从随机数生成私钥

生成密钥的第一步也是最重要的一步是找到足够安全的熵源,即随机源。 生成比特币私钥本质上等同于“在 1 到 2256 之间选择一个数字”。 只要选择的结果不可预测或不可重复,选择数字的确切方法并不重要。 比特币软件使用操作系统底层的随机数生成器来生成 256 位的熵(随机性)。 通常,操作系统的随机数发生器是由人为随机源初始化的,也可能需要通过摇动鼠标几秒钟来初始化。 对于真正的偏执狂,使用掷骰子的方法并用铅笔和纸做笔记。

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

本书强烈建议读者不要使用自己的代码或使用编程语言内置的简单随机数生成器来获取随机数。 我们建议读者使用加密安全伪随机数生成器 (CSPRNG),其种子来自具有足够熵的源。 使用随机数生成器库时,请仔细阅读其文档以确保它在密码学上是安全的。 CSPRNG的正确实现是密钥安全的关键。

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

1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD

比特币私钥空间的大小是2256,这是一个非常大的数字。 以十进制计算,大约是 1077,而可见宇宙估计只包含 1080 个原子。

要使用 Bitcoin Core 客户端(参见第 3 章)生成新密钥,请使用 getnewaddress 命令。 为了安全起见,命令运行后只显示生成的公钥,不显示私钥。 如果想让bitcoind显示私钥,可以使用dumpprivkey命令。 dumpprivkey 命令将以 Base58 校验和编码格式显示私钥。 这种私钥格式称为钱包导入格式(WIF,Wallet Import Format)。 在“私钥格式”部分有详细解释。 下面给出了使用这两个命令生成和显示私钥的示例:

$ bitcoind getnewaddress
1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
$ bitcoind dumpprivkey 1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy
KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

dumpprivkey命令只是读取钱包中getnewaddress命令生成的私钥并显示出来。 bitcoind 不知道公钥中的私钥。 dumpprivkey 命令仅在密钥对存储在钱包中时才有效。

dumpprivkey 命令无法从公钥中得到对应的私钥,因为这是不可能的。 该命令只提取钱包中已有的私钥,即提取getnewaddress命令生成的私钥。

您还可以使用命令行 sx 工具(参见“3.3.1 Libbitcoin 和 sx 工具”)通过 newkey 命令生成并显示私钥:

$ sx newkey
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

4.1.4 公钥

公钥可以通过椭圆曲线乘法从私钥计算出来,这是一个不可逆的过程:K = k * G 。 其中 k 是私钥,G 是称为生成点的常数点,K 是生成的公钥。 它的逆运算,被称为“求离散对数”——知道公钥K来求私钥k——是非常困难的,就像试图测试k的所有可能值一样,也就是蛮力搜索。 在展示如何从私钥生成公钥之前,让我们详细了解一下椭圆曲线密码学。

4.1.5 椭圆曲线密码学解释

椭圆曲线密码学是一种基于离散对数问题的非对称(或公钥)加密方法,可以用椭圆曲线上的加点或乘点来表示。

挖比特币的原理_比特币密钥原理_比特币 挖矿 原理

上图是一个椭圆曲线的例子,类似于比特币使用的曲线。

比特币使用特殊的椭圆曲线和一系列由 secp256k1 标准定义的数学常数。 该标准由美国国家标准技术研究院 (NIST) 制定。 secp256k1 曲线由以下函数定义,该函数生成椭圆曲线:

y2 = (x3 + 7)} 在 (Fp)

或者

y2 模 p = (x3 + 7) 模 p

上面的mod p(素数p的模)表示曲线在素数p的有限域内,也记为Fp,其中p = 2256 – 232 – 29 – 28 – 27 – 26 – 24 – 1,即是一个非常大的质数。

由于这条曲线是定义在素数阶的有限域内而不是实数范围内,所以它的函数图看起来就像是散落在二维空间中的散点图,因此很难画出图形。 然而,它背后的数学原理类似于实数范围内的椭圆曲线。 例如,下图以网格上一系列散点的形式显示了一个小得多的素数阶 17 有限域上的椭圆曲线。 secp256k1的比特币椭圆曲线可以想象成一个非常大的网格上一系列比较复杂的散点。

比特币 挖矿 原理_比特币密钥原理_挖比特币的原理

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

这是一个例子,这是secp256k1曲线上的点P,它的坐标是(x,y)。 您可以使用 Python 检查它:

P =(55066263022277343669578718895168534326250603453777594175500187360389116729240,32670510020758816978083085130507043184471273380659243275938904335757337482424)
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 有时被混淆地称为“指数”。

4.1.6 生成公钥

从随机生成的私钥k开始,我们将其与曲线上定义的生成点G相乘,得到曲线上的另一个点,即对应的公钥K。生成点是secp256k1标准的一部分,生成比特币密钥的点都是一样的:

K = 1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD * G

其中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的几何运算。

大多数比特币程序使用 OpenSSL 密码库进行椭圆曲线计算。 例如调用EC_POINT_mul()函数可以计算出公钥。

4.2 比特币地址

比特币地址是一串数字和字母,可以与任何想给你比特币的人共享。 从公钥(也是一串数字和字母)生成的比特币地址以数字“1”开头。 以下是比特币地址的示例:

1J7mdg5rbQyUHENYdx39WVWK7fsLpEoXZy

在交易中,比特币地址通常作为收款人出现。 如果把比特币交易比作一张支票,比特币地址就是收款人,也就是我们要在收款人一栏写的。 支票的收款人可以是银行账户、公司、机构,甚至是现金支票。 支票不是指定一个特定的账户,而是支付给一个共同的名字,使其成为一种相当灵活的支付工具。 同样,比特币地址的使用也使得比特币交易变得灵活。 比特币地址可以代表一对公钥和私钥的所有者,也可以代表其他东西,例如第 132 页“P2SH(Pay-to-Script-Hash)”部分中讨论的支付脚本。现在,让我们看一个从公钥生成比特币地址的简单例子。

比特币地址可以通过一种单向加密哈希算法从公钥中获得。 散列算法是一种单向函数,它接受任意长度的输入并生成指纹摘要。 加密哈希函数在比特币中被广泛使用:比特币地址、脚本地址和挖矿中的工作量证明算法。 用于从公钥生成比特币地址的算法是安全哈希算法 (SHA) 和 RACE Integrity Primitives Evaluation Message Digest (RIPEMD),特别是 SHA256 和 RIPEMD160。

以公钥K为输入,计算其SHA256哈希值,并根据结果计算RIPEMD160哈希值,得到一个长度为160位(20字节)的数:

A = RIPEMD160(SHA256(K))

式中,K为公钥,A为生成的比特币地址。

比特币地址与公钥不同。 比特币地址是通过单向哈希函数从公钥生成的。

通常,用户看到的比特币地址是通过“Base58Check”编码的(参见第72页“Base58和Base58Check编码”一节),这种编码使用58个字符(一种Base58数字系统)和一个校验码来提高可读性,避免歧义,有效防止地址转录和打字错误。 Base58Check 编码也用在比特币的其他地方,比如比特币地址、私钥、加密密钥和脚本哈希,以提高可读性和条目的正确性。 下一节我们会详细讲解Base58Check的编码机制和产生的结果。 下图描述了如何从公钥生成比特币地址。

比特币 挖矿 原理_比特币密钥原理_挖比特币的原理

4.2.1 Base58和Base58Check编码

为了更简洁方便地表示一长串数字,许多计算机系统使用由数字和字母组成的大于十进制的表示法。 例如,传统的十进制计数系统使用十位数字 0-9,而十六进制系统使用额外的六个字母 AF。 同一个数,它的十六进制表示会比十进制表示短。 更进一步,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编码格式对数据(数字)进行编码,首先我们需要为数据添加一个名为“version byte”的前缀,用于明确需要编码的数据类型。 例如比特币地址的前缀为0(十六进制为0x00),私钥编码时前缀为128(十六进制为0x80)。 表 4-1 列出了一些常见的版本前缀。

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

checksum = SHA256(SHA256(prefix+data))

在生成的 32 字节长哈希(两次哈希操作)中,我们只取前 4 个字节。 这 4 个字节用作校验码。 数据后将添加校验和。

结果由三部分组成:前缀、数据和校验和。 此结果使用前面描述的 Base58 字母表进行编码。 下图描述了Base58Check编码的过程。

挖比特币的原理_比特币密钥原理_比特币 挖矿 原理

Base58Check编码:一种Base58格式,版本化,经过验证的格式,可以清晰地编码比特币数据

在比特币中,大部分需要展示给用户的数据都是使用Base58Check编码的,可以实现数据压缩、可读性和错误校验。 Base58Check编码中的版本前缀易于识别数据的格式,编码后的数据头包含明确的属性。 这些属性使用户可以轻松指定要编码的数据类型以及如何使用它。 例如,我们可以看到它们的区别。 Base58Check 编码的比特币地址以 1 开头,而 Base58Check 编码的私钥 WIF 以 5 开头。表 4-1 列出了一些版本前缀及其对应的 Base58 格式。

表 4-1 Base58Check 版本前缀及编码结果

Type Version Prefix (hex) Base58 Format Bitcoin Address0x001Pay-to-Script-Hash Address0x053Bitcoin Testnet Address0x6Fm or nPrivate Key WIF0x805, K or LBIP38 Encrypted Private Key0x01426PBIP32 扩展公钥0x0488B21

我们来回顾一下比特币地址生成的完整过程,从私钥,到公钥(椭圆曲线上的一个点),再到两次哈希的地址,最后生成一个Base58Check格式的比特币地址。 示例 4-2 中的 C++ 代码完整详细地显示了从私钥到 Base58Check 编码的比特币地址的步骤。 代码使用了“3.3 其他客户端、数据库和工具包”一节中介绍的libbitcoin库,实现了一些辅助功能。

示例 4-2 从私钥生成以 Base58Check 格式编码的比特币地址

#include 
int main() {
    // Private secret key.
    bc::ec_secret secret = bc::decode_hash(
        "038109007313a5807b2eccc082c8c3fbb988a973cacf1a7df9ce725c31b14776");
    // Get public key.
    bc::ec_point public_key = bc::secret_to_public_key(secret);
    std::cout << "Public key: " << bc::encode_hex(public_key) << std::endl;
    // Create Bitcoin address.
    // Normally you can use:
    //   bc::payment_address payaddr;
    //   bc::set_public_key(payaddr, public_key);
    //   const std::string address = payaddr.encoded();
    // Compute hash of public key for P2PKH address.
    const bc::short_hash hash = bc::bitcoin_short_hash(public_key);
    bc::data_chunk unencoded_address; // Reserve 25 bytes
    // [ version:1 ]
    // [ hash:20 ]
    //   [ checksum:4 ]
    unencoded_address.reserve(25);
    // Version byte, 0 is normal BTC address (P2PKH).     unencoded_address.push_back(0);
    // Hash data
    bc::extend_data(unencoded_address, hash);
    // Checksum is computed by hashing data, and adding 4 bytes from hash. bc::append_checksum(unencoded_address);
    // Finally we must encode the result in Bitcoin's base58 encoding assert(unencoded_address.size() == 25);
    const std::string address = bc::encode_base58(unencoded_address);
    std::cout << "Address: " << address << std::endl;
    return 0; 
}

如编译和运行地址代码中所示,由于代码使用预定义的私钥,因此每次运行都会产生相同的比特币地址。 如例4-3所示。

示例 4-3 编译并运行 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.2.2 密钥格式

公钥和私钥都可以有多种编码格式。 密钥以不同的格式编码后,虽然结果可能看起来不同,但密钥编码的数字并没有改变。 这些不同的编码格式主要是为了方便密钥的使用和识别而不会出错。

私钥格式

私钥可以用多种不同的格式表示,所有格式都对应于相同的 256 位数字。 表 4-2 显示了三种常见的私钥格式。

表 4-2 私钥表示法(编码格式)

类型版本说明 HexNone64 十六进制数字WIF5Base58Check 编码:版本前缀为128 的Base58 和32 位校验和WIF-compressedK 或以上LAs,编码前添加后缀0x01

表 4-3 显示了使用这三种格式生成的私钥。

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

格式私钥Hex1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDDWIF5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1JcnWIF-compressedKxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

这些符号都是表示相同数字、相同私钥的不同方式。 尽管编码后的字符串看起来不同,但不同的格式可以很容易地相互转换。

解码Base58Check编码为十六进制

sx 工具包(参见“3.3.1 Libbitcoin 和 sx 工具”)可用于编写操纵比特币密钥、地址和交易的 shell 脚本和命令行“管道”。 您还可以使用 sx 工具从命令行解码 Base58Check 格式。

我们使用的命令是base58check-decode:

$ sx base58check-decode 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd 128

结果是十六进制的密钥,后跟钱包导入格式 (WIF) 的版本前缀 128。

十六进制转Base58Check编码

要转换为 Base58Check 编码(与上一个命令相反),我们需要提供十六进制的私钥和钱包导入格式(WIF)的版本号前缀 128:

$sx base58check-encode 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd 128 
5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn

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

要将压缩格式的私钥编码为Base58Check(参见“压缩格式私钥”一节),我们需要在16进制格式的私钥末尾添加后缀01,然后使用与上述相同的方法:

K = 04F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A07CF33DA18BD734C600B96A72BBC4749D5141C90EC8AC328AE52DDFE2E505BDB

生成的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 xy:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

压缩公钥

引入压缩格式公钥以减少比特币交易的字节数,从而节省运行区块链数据库的节点上的磁盘空间。 大多数比特币交易都包含公钥,用于验证用户凭证和支付比特币。 每个公钥有 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值,我们需要知道y的值是在x轴之上还是之下,因为它们代表椭圆曲线上的不同点,即不同的公钥。 当我们用二元运算在素数p阶有限域上计算椭圆曲线时,y坐标可能是奇数也可能是偶数,对应前面提到的y值的正负号。 因此,为了区分y坐标的两种可能取值,我们在生成压缩格式公钥时,如果y为偶数则使用02作为前缀,如果y为奇数则使用03作为前缀。 这样就可以根据公钥中给定的x值正确推导出对应的y坐标,从而将公钥解压成椭圆曲线上的一个完整的点坐标。 下图说明了公钥压缩:

比特币 挖矿 原理_比特币密钥原理_挖比特币的原理

下面是上一节生成的公钥,采用264位(66位十六进制数字)压缩格式公钥格式,其中前缀03表示y坐标为奇数:

K = 03F028892BAD7ED57D2FB57BF33081D5CFCF6F9ED3D3D7F159C2E2FFF579DC341A

这个压缩后的公钥对应同一个私钥,也就是说是由同一个私钥生成的。 但是,压缩格式公钥和非压缩格式公钥之间存在很大差异。 更重要的是,如果我们使用双重哈希函数(RIPEMD160(SHA256(K)))将压缩格式公钥转换为比特币地址,得到的地址将与未压缩格式公钥生成的地址不同。 这个结果可能会令人困惑,因为一个私钥可以生成两种不同格式的公钥——压缩的和未压缩的——而这两种格式的公钥可以生成两个不同的比特币地址。 然而,这两个不同比特币地址的私钥是相同的。

公钥的压缩格式逐渐成为各种比特币客户端的默认格式,可以大大减少交易所需的字节数,同时也减少了存储区块链所需的磁盘空间。 然而,并非所有客户端都支持压缩公钥,因此支持压缩公钥的新客户端必须考虑如何处理来自不支持压缩公钥的旧客户端的事务。 当一个钱包应用程序导入另一个钱包应用程序的私钥时,这一点变得尤为重要,因为新钱包需要扫描区块链并找到与这些导入密钥相关的所有交易。 比特币钱包应该扫描哪个比特币地址? 新客户端不知道使用哪个公钥:因为无论是压缩公钥生成的比特币地址,还是未压缩公钥生成的地址,都是合法的比特币地址,都可以是正确签名的私钥,但是它们是完全不同的比特币地址。

为了解决这个问题,较新的比特币客户端在从钱包导出私钥时将使用不同的钱包导入格式。 这种新的钱包导入格式可以用来表示私钥已经被用来生成压缩公钥,生成的比特币地址也是基于压缩公钥的。 该方案可以解决导入的私钥是来自旧钱包还是新钱包的问题,​​也可以解决公钥生成的比特币地址是来自压缩格式公钥还是非压缩格式的问题公钥。 最后,新钱包在扫描区块链时,可以通过对应的比特币地址,找到该比特币地址所在区块链中发生的交易。 我们将在下一节详细解释这种机制是如何工作的。

压缩格式私钥

事实上,“压缩格式私钥”是一个误导性的名称,因为使用WIF压缩格式导出私钥时,不仅没有压缩,而且还比“未压缩格式”私钥长一个字节。 多出的字节是私钥后缀为01,表示私钥来自较新的钱包,只能用来生成压缩公钥。 私钥是未压缩的,不能压缩。 “压缩私钥”实际上只是指“用于生成压缩格式公钥的私钥”,而“未压缩格式私钥”用于表示“用于生成未压缩格式公钥的私钥”。 为了避免更多的误解,应该只说导出格式是“WIF压缩格式”或“WIF”,而不是说私钥是“压缩”的。

请注意,这些格式不能互换使用。 在实施压缩公钥的较新钱包中,私钥只能并且将始终以 WIF 压缩格式(以 K 或 L 为前缀)导出。 对于未实现压缩公钥的旧钱包,私钥将仅以 WIF 格式(前缀为 5)导出。 这样做的目的是给导入这些私钥的钱包一个信号:是使用压缩格式的公钥和比特币地址扫描区块链,还是使用非压缩格式的公钥和比特币地址。

如果比特币钱包采用压缩公钥,它将在所有交易中使用该压缩公钥。 钱包中的私钥将用于生成压缩公钥,然后用于生成交易中的比特币地址。 当从实现压缩公钥格式的比特币钱包导出私钥时,钱包导入格式 (WIF) 将被修改为 WIF 压缩格式,这将在私钥上附加一个字节大小的后缀 01。 . 最终的 Base58Check 编码私钥称为 WIF(“压缩”)私钥,以字母“K”或“L”开头。 而以“5”开头的是从旧钱包以 WIF(非压缩)格式导出的私钥。

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

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

格式私钥Hex1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDDWIF5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1JcnHex-compressed1E99423A4ED27608A15A2616A2B0E9E52CED330AC530EDCC32C8FFC6A526AEDD01WIF-compressedKxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ

“压缩格式的私钥”用词不当! 私钥未压缩。 WIF 压缩格式的私钥仅用于表示只能由压缩公钥和对应的比特币地址生成。 相反,“WIF压缩”编码的私钥多了一个字节,因为这种私钥多了一个后缀“01”。 该后缀用于区分“未压缩格式”私钥和“压缩格式”私钥。

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

最全面的比特币 Python 库是 Vitalik Buterin 的 pybitcointools。 在示例 4-4 中,我们使用 pybitcointools 库(导入为“bitcoin”)以不同格式生成和显示密钥和比特币地址。

示例 4-4 使用 pybitcointools 库生成和格式化密钥和比特币地址

import bitcoin
# Generate a random private key
valid_private_key = False while not valid_private_key:
    private_key = bitcoin.random_key()
    decoded_private_key = bitcoin.decode_privkey(private_key, 'hex')
    valid_private_key =  0 < decoded_private_key < bitcoin.N
print "Private Key (hex) is: ", private_key
print "Private Key (decimal) is: ", decoded_private_key
# Convert private key to WIF format
wif_encoded_private_key = bitcoin.encode_privkey(decoded_private_key, 'wif')
print "Private Key (WIF) is: ", wif_encoded_private_key
# Add suffix "01" to indicate a compressed private key
compressed_private_key = private_key + '01'
print "Private Key Compressed (hex) is: ", compressed_private_key
# Generate a WIF format from the compressed private key (WIF-compressed)
wif_compressed_private_key = bitcoin.encode_privkey(
    bitcoin.decode_privkey(compressed_private_key, 'hex'), 'wif')
print "Private Key (WIF-Compressed) is: ", wif_compressed_private_key
# Multiply the EC generator point G with the private key to get a public key point
public_key = bitcoin.base10_multiply(bitcoin.G, decoded_private_key) print "Public Key (x,y) coordinates is:", public_key
# Encode as hex, prefix 04
hex_encoded_public_key = bitcoin.encode_pubkey(public_key,'hex') print "Public Key (hex) is:", hex_encoded_public_key
# Compress public key, adjust prefix depending on whether y is even or odd
(public_key_x, public_key_y) = public_key if (public_key_y % 2) == 0:
    compressed_prefix = '02' 
else:
    compressed_prefix = '03'
hex_compressed_public_key = compressed_prefix + bitcoin.encode(public_key_x, 16) print "Compressed Public Key (hex) is:", hex_compressed_public_key
# Generate bitcoin address from public key
print "Bitcoin Address (b58check) is:", bitcoin.pubkey_to_address(public_key)
# Generate compressed bitcoin address from compressed public key
print "Compressed Bitcoin Address (b58check) is:", \             bitcoin.pubkey_to_address(hex_compressed_public_key)

示例 4-5 显示了运行上述代码的结果。

示例 4-5 运行 key-to-address-ecc-example.py

$ python key-to-address-ecc-example.py
Private Key (hex) is:
 3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa6
Private Key (decimal) is:
 26563230048437957592232553826663696440606756685920117476832299673293013768870
Private Key (WIF) is:
 5JG9hT3beGTJuUAmCQEmNaxAuMacCTfXuw1R3FCXig23RQHMr4K
Private Key Compressed (hex) is:
 3aba4162c7251c891207b747840551a71939b0de081f85c4e44cf7c13e41daa601
Private Key (WIF-Compressed) is:
 KyBsPXxTuVD82av65KZkrGrWi5qLMah5SdNq6uftawDbgKa2wv6S
Public Key (x,y) coordinates is:
 (41637322786646325214887832269588396900663353932545912953362782457239403430124L,
 16388935128781238405526710466724741593761085120864331449066658622400339362166L)
Public Key (hex) is:
 045c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec↵
243bcefdd4347074d44bd7356d6a53c495737dd96295e2a9374bf5f02ebfc176
Compressed Public Key (hex) is:
 025c0de3b9c8ab18dd04e3511243ec2952002dbfadc864b9628910169d9b9b00ec
Bitcoin Address (b58check) is:
 1thMirt546nngXqyPEz532S8fLwbozud8
Compressed Bitcoin Address (b58check) is:
 14cxpo3MBCYYWCgF74SWTdcmxipnGUsPw3

示例 4-6 是另一个示例,使用 Python ECDSA 库代替比特币库进行椭圆曲线计算。

示例 4-6 在比特币密钥中使用椭圆曲线算法的脚本

import ecdsa
import random
from ecdsa.util import string_to_number, number_to_string
# secp256k1, http://www.oid-info.com/get/1.3.132.0.10
_p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2FL
_r = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141L
_b = 0x0000000000000000000000000000000000000000000000000000000000000007L
_a = 0x0000000000000000000000000000000000000000000000000000000000000000L
_Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798L
_Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8L
curve_secp256k1 = ecdsa.ellipticcurve.CurveFp(_p, _a, _b)
generator_secp256k1 = ecdsa.ellipticcurve.Point(curve_secp256k1, _Gx, _Gy, _r)
oid_secp256k1 = (1, 3, 132, 0, 10)
SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1,
oid_secp256k1)
ec_order = _r
curve = curve_secp256k1
generator = generator_secp256k1
def random_secret():
    random_char = lambda: chr(random.randint(0, 255))
    convert_to_int = lambda array:     int("".join(array).encode("hex"), 16) 
    byte_array = [random_char() for i in range(32)]
    return convert_to_int(byte_array)
def get_point_pubkey(point): 
    if point.y() & 1:
        key = '03' + '4x' % point.x() 
    else:
        key = '02' + '4x' % point.x() 
    return key.decode('hex')
def get_point_pubkey_uncompressed(point): 
    key='04'+\
        '4x' % point.x() + \
        '4x' % point.y() 
    return key.decode('hex')
# Generate a new private key.
secret = random_secret() 
print "Secret: ", secret
# Get the public key point.
point = secret * generator 
print "EC point:", point
print "BTC public key:", get_point_pubkey(point).encode("hex")
# Given the point (x, y) we can create the object using:
point1 = ecdsa.ellipticcurve.Point(curve, point.x(), point.y(), ec_order) 
assert point1 == point

示例 4-7 显示了运行脚本的结果。

示例 4-7 安装 Python ECDSA 库并运行 ec_math.py 脚本

running the ec_math.py script
$ # Install Python PIP package manager
$ sudo apt-get install python-pip
$ # Install the Python ECDSA library
$ sudo pip install ecdsa
$ # Run the script
$ python ec-math.py
Secret:
38090835015954358862481132628887443905906204995912378278060168703580660294000
EC point:
(70048853531867179489857750497606966272382583471322935454624595540007269312627,
105262206478686743191060800263479589329920209527285803935736021686045542353380)
BTC public key: 029ade3effb0a67d5c8609850d797366af428f4a0d5194cb221d807770a1522873

4.4 比特币钱包

钱包是私钥的容器,通常通过有序文件或简单数据库实现。 另一种制作私钥的方法是确定性密钥生成。 这里可以使用原始私钥通过单向哈希函数生成每一个新的私钥,并将新生成的密钥按顺序连接起来。 只要你能重新创建这个序列,你只需要第一个私钥(称为种子,主私钥)来生成它们。 在本节中,我们将研究不同的私钥生成方法及其钱包结构。

比特币钱包只包含私钥,不包含比特币。 每个用户都有一个包含多个私钥的钱包。 钱包包含成对的私钥和公钥。 用户使用这些私钥签署交易,证明他们拥有交易的输出(其中的比特币)。 比特币以交易输出(通常表示为 vout 或 txout)的形式存储在区块链中。

4.4.1 非确定性(随机)钱包

在最早一批比特币客户端中,钱包只是随机生成的私钥集合。 这种钱包称为零型非确定性钱包。 例如,Bitcoin Core 客户端预先生成 100 个随机私钥,从一开始就生成足够的私钥,每个密钥只使用一次。 这类钱包有个昵称“Just a Bunch Of Keys”,简称JBOK。 此类钱包现在正被确定性钱包取代,因为它们难以管理、备份和导入。 随机密钥的缺点是,如果生成很多密钥,则必须保留所有密钥的副本。 这意味着必须定期备份钱包。 每个密钥都必须备份,否则钱包无法访问时,钱包控制的资金将丢失。 这种情况直接与避免地址重用的原则相冲突——每个比特币地址只能用于一次交易。 地址重用通过将多个交易与交易对手地址相关联来减少隐私。 Type 0 non-deterministic wallets 不是一个好的钱包选择,特别是如果你不想重复使用地址和创建太多私钥并想保存它们。 尽管 Bitcoin Core 客户端包含 Type 0 钱包,但 Bitcoin Core 开发人员不希望鼓励每个人都使用它们。 下图表示一个非确定性钱包,其中包含结构松散的随机密钥集合。

比特币密钥原理_挖比特币的原理_比特币 挖矿 原理

4.4.2 确定性(种子)钱包

确定性或“种子”钱包包含可以使用单向离散方程从公共种子生成的私钥。 种子是随机生成的数字。 这个数字还包含,例如,一个索引号或一个可以从中生成私钥的“链代码”(参见“4.4.4 分层确定性钱包(BIP0032/BIP0044)”部分)。 在确定性钱包中,种子足以检索已生成的所有私钥,因此在初始创建时进行简单备份就足够了。 种子足够钱包输入或输出。 这很容易让用户的私钥轻松地在钱包之间传输输入。

4.4.3 助记词汇

助记词表是一系列英文单词,表示(编码)用作确定性钱包对应的种子的随机数。 单词序列足以重新创建种子,并且从种子、钱包和所有私钥。 运行确定性钱包的钱包应用程序将在首次创建钱包时通过助记符向用户呈现 12 到 24 个单词的序列。 单词序列是钱包的备份。 它还可以用于为具有相同或兼容应用程序的钱包恢复和重新创建密钥。 助记码可以让用户更容易复制钱包,因为与随机数字序列相比,助记码更容易阅读和正确转录。

助记符在比特币改进提案 39(参见“附录 2 比特币改进协议 [bip0039]”)中定义,目前处于草案状态。 需要注意的是,BIP0039 是一个提案草案,而不是一个标准的解决方案。 具体来说,电子钱包和 BIP0039 使用不同的标准,对应不同的词汇集。 Trezor 钱包以及其他一些钱包使用 BIP0039,但 BIP0039 不兼容电子钱包的操作。

BIP0039定义助记词和种子创建过程如下:

1. 创建一个 128 到 256 位的随机序列(熵)。 2. 通过提出 SHA256 哈希的前几位数字,可以创建随机序列的校验和。 3. 将校验和添加到随机序列的末尾。 4. 将序列分解为不同的 11 位集合,并使用这些集合来对应一个预定义的 2048 个单词的字典。 5.生成12到24个字的助记词。

表4-5显示了熵数据的大小与助记词长度之间的关系。

表 4-5 助记码:熵和字段长度

熵(位)验证者(位)熵+验证者字段长度128413212160516515192619818224723121256826424

助记词代表 128 到 256 位数字。 这可以通过使用私钥拉伸函数 PBKDF2 来导出更长(512 位)的种子。 The resulting seed can be used to create a deterministic wallet and all keys derived from it.

Tables 4-6 and 4-7 show some examples of mnemonics and the seeds they generate.

Table 4-6 Mnemonics for 128-bit entropy and resulting seeds

负熵输入(128 bits)0c1e24e5917779d297e14d45f14e1a1a助记码(12 个单词)army van defense carry jealous true garbage claim echo media make crunch种子(512 bits) 3338a6d2ee71c7f28eb5b882159634cd46a898463e9d2d0980f8e80dfbba5b0fa0291e5fb88 8a599b44b93187be6ee3ab5fd3ead7dd646341b2cdb8d08d13bf

Table 4-7 Mnemonics for 256-bit entropy and resulting seeds

Negative entropy input (256 bits) 2041546864449caff939d32d574753fe684d3c947c3346713dd8423e74abcf8c mnemonic code (24 words) cake apple borrow silk endorse fitness top denial coil riot

stay wolf luggage oxygen faint major edit measure invite love trap field

Dilemma oblige seed (512 bits) 3972e432e99040f75ebe13a660110c3e29d131a2c808c7ee5f1631d0a977fcf473bee22

fce540af281bf7cdeade0dd2c1c795bd02f1e4049e205a0158906c343

4.4.4 Hierarchical Deterministic Wallets (BIP0032/BIP0044)

开发确定性钱包是为了更容易从单个“种子”生成许多密钥。 The most advanced shape from a deterministic wallet is the hierarchical deterministic wallet or HD wallet defined by the BIP0032 standard. Hierarchical deterministic wallets contain keys generated from data structures. 该主密钥可以生成一系列子密钥。 这些子密钥可以派生自孙子密钥,等等。 这个树形结构表如下图所示。

比特币 挖矿 原理_挖比特币的原理_比特币密钥原理

If you want to install and run a Bitcoin wallet, you need to build an HD wallet that complies with BIP0032 and BIP0044.

HD 钱包提供具有两个主要优点的随机(非确定性)密钥。 首先,树结构可以用来表达额外的组织意义。 例如,当一个特定的分叉子项用于接收交易收入而另一个分叉子项负责支付支出时。 在企业环境中可以使用不同的分支密钥,可以管理不同的分支部门、子公司、特定职能和会计类别。

A second benefit of HD wallets is that they allow users to create a sequence of public keys without having access to the corresponding private keys. 这可能允许 HD 钱包在不安全的服务器中使用,或者为每笔交易发布不同的公钥。 公钥不需要预先加载或导出,但是服务器中没有私钥可以用来支付。

Create HD wallet from seed

HD wallets are created from a single root seed, which is a random number of 128 to 256 bits. All certainty of the HD wallet is derived from this root seed. Any root seed compatible with HD wallets can also recreate the entire HD wallet. So simply transferring the root of the HD wallet allows the millions of keys contained in the HD wallet to be copied, stored, exported and imported. Root seeds are generally always expressed as a mnemonic word sequence, as described in the "4.4.3 Mnemonic vocabulary" section, mnemonic vocabulary can make it easier for people to transcribe and store.

The process of creating the master key and HD wallet master chain code is shown in the figure below.

The root seed is input into the HMAC-SHA512 algorithm to obtain a hash that can be used to create a master private key(m) and a master chain code. The master private key (m) can then be generated into the corresponding master public key (M) by using the same ordinary elliptic curve m*G procedure we saw earlier in this chapter. Chaincode can introduce entropy into the equation that creates child keys from the parent key.

Derivation of private subkey

Hierarchical deterministic wallets use the CKD (child key derivation) equation to derive child keys from the parent key.

The subkey derivation equation is based on a one-way hash equation. This equation combines:

• A parent private or public key (ECDSA uncompressed key) • A seed called chaincode (256 bits) • An index number (32 bits)

Chaincodes are used to introduce seemingly random data into this process such that the index cannot adequately derive other subkeys. So having a subkey doesn't make it discover its own similar subkeys, unless you already have the chaincode. The initial chaincode seed (at the root of the cryptographic tree) is constructed with random data, and subsequent chaincodes are derived from the respective parent chaincodes.

These three items are combined and hashed to generate subkeys, as follows.

The parent public key — the chaincode — and the index number are combined and hashed with the HMAC-SHA512 equation to produce a 512-bit hash. The resulting hash can be split into two parts. The 256-bit output of the right half of the hash can be used as a chain code for the child chain. The left half of the 256-bit hash and index code is loaded on the parent private key to derive the child private key. In Figure 4-11, we see this illustration—the index set is set to 0 to produce the 0th child key of the parent key (the first to pass through the index).

Figure 4-11 Extending the parent private key to create a child private key

Changing the index allows us to extend the parent key and create other subkeys in the sequence. Such as child 0, child 1, child 2 and so on. Each master key can contain 2 billion child keys.

Repeating this process to the next level of the password tree, each subkey can in turn become the parent key and continue to create its own subkeys until infinite generations.

Using a derived subkey

Subprivate keys cannot be distinguished from non-deterministic (random) keys.因为衍生方程是单向方程,所以子密钥不能被用来发现他们的母密钥。子密钥也不能用来发现他们的相同层级的姊妹密钥。如果你有第n个子密钥,你不能发现它前面的(第n-1)或者后面的子密钥(n+1)或者在同一顺序中的其他子密钥。只有母密钥以及链码才能得到所有的子密钥。没有子链码的话,子密钥也不能用来衍生出任何孙密钥。你需要同时有子密钥以及对应的链码才能创建一个新的分支来衍生出孙密钥。

那子私钥自己可被用做什么呢?它可以用来做公共钥匙和比特币地址。之后它就可以被用那个地址来签署交易和支付任何东西。

子密钥、对应的公共钥匙以及比特币地址都不能从随机创造的密钥和地址中被区分出来。事实是它们所在的序列,在创造他们的HD钱包方程之外是不可见的。一旦被创造出来,它们就和“正常”钥匙一样运行了。

扩展密钥

正如我们之前看到的,密钥衍生方程可以被用来创造钥匙树上任何层级的子密钥。这只需要三个输入量:一个密钥,一个链码以及想要的子密钥的索引。密钥以及链码这两个重要的部分被结合之后,就叫做extended key。术语“extended key”也被认为是“可扩展的密钥”是因为这种密钥可以用来衍生子密钥。

扩展密钥可以简单地被储存并且表示为简单的将256位密钥与256位链码所并联的512位序列。有两种扩展密钥。扩展的私钥是私钥以及链码的结合。它可被用来衍生子私钥(子私钥可以衍生子公共密钥)公共钥匙以及链码组成扩展公共钥匙。这个钥匙可以用来扩展子公共钥匙,见“4.1.6 生成公钥”。

想象一个扩展密钥作为HD钱包中钥匙树结构的一个分支的根。你可以衍生出这个分支的剩下所有部分。扩展私人钥匙可以创建一个完整的分支而扩展公共钥匙只能够创造一个公共钥匙的分支。

一个扩展钥匙包括一个私钥(或者公共钥匙)以及一个链码。一个扩展密钥可以创造出子密钥并且能创造出在钥匙树结构中的整个分支。分享扩展钥匙就可以访问整个分支。

扩展密钥通过Base58Check来编码,从而能轻易地在不同的BIP0032-兼容钱包之间导入导出。扩展密钥编码用的Base58Check使用特殊的版本号,这导致在Base58编码字符中,出现前缀“xprv”和“xpub”。这种前缀可以让编码更易被识别。因为扩展密钥是512或者513位,所以它比我们之前所看到的Base58Check-encoded串更长一些。

这是一个在Base58Check中编码的扩展私钥的例子:

xprv9tyUQV64JT5qs3RSTJkXCWKMyUgoQp7F3hA1xzG6ZGu6u6Q9VMNjGr67Lctvy5P8oyaYAL9CAWrUE9i6GoNMKUga5biW6Hx4tws2six3b9c

这是在Base58Check中编码的对应的扩展公共钥匙:

xpub67xpozcx8pe95XVuZLHXZeG6XWXHpGq6Qv5cmNfi7cS5mtjJ2tgypeQbBs2UAR6KECeeMVKZBPLrtJunSDMstweyLXhRgPxdp14sk9tJPW9

公共子钥匙推导

正如之前提到的,分层确定性钱包的一个很有用的特点就是可以不通过私钥而直接从公共母钥匙派生出公共子钥匙的能力。这就给了我们两种去衍生子公共钥匙的方法:或者通过子私钥,再或者就是直接通过母公共钥匙。

因此,扩展的公共钥匙可以再HD钱包结构的分支中,被用来衍生所有的公钥(且只有公共钥匙)。

这种快捷方式可以用来创造非常保密的public-key-only配置。在配置中,服务器或者应用程序不管有没有私钥,都可以有扩展公共钥匙的副本。这种配置可以创造出无限数量的公共钥匙以及比特币地址。但是不可以花送到这个地址里的任何比特币。与此同时,在另一种更保险的服务器上,扩展私钥可以衍生出所有的对应的可签署交易以及花钱的私钥。

这种方案的一个常见的方案是安装一个扩展的公共钥匙在服务电商公共程序的网络服务器上。网络服务器可以使用这个公共钥匙衍生方程去给每一笔交易(比如客户的购物车)创造一个新的比特币地址。但为了避免被偷,网络服务商不会有任何私钥。没有HD钱包的话,唯一的方法就是在不同的安全服务器上创造成千上万个比特币地址,之后就提前上传到电商服务器上。这种方法比较繁琐而且要求持续的维护来确保电商服务器不“用光”公共钥匙。

这种解决方案的另一种常见的应用是冷藏或者硬件钱包。在这种情况下,扩展的私钥可以被储存在纸质钱包中或者硬件设备中(比如Trezor 硬件钱包)与此同时扩展公共钥匙可以在线保存。使用者可以根据意愿创造“接收”地址而私钥可以安全地在线下被保存。为了支付资金,使用者可以使用扩展的私钥离线签署比特币客户或者通过硬件钱包设备(比如Trezor)签署交易。图4-12阐述了扩展母公共钥匙来衍生子公共钥匙的传递机制。

图4-12 扩展母公共钥匙来创造一个子公共钥匙

硬化子密钥的衍生

从扩展公共钥匙衍生一个分支公共钥匙的能力是很重要的,但牵扯一些风险。 Access to the extended public key does not give access to the child private key. 然而,由于扩展公钥包含链码,如果子私钥已知或泄露,链码可用于派生所有其他子私钥。 一个简单的泄露私钥,连同父链代码,可以暴露所有子密钥。 Even worse, the child private key and the parent chaincode can be used to infer the parent private key.

为了应对这种风险,HD钱包使用一种叫做hardened derivation的替代衍生方程。这就“打破”了母公共钥匙以及子链码之间的关系。这个硬化衍生方程使用了母私钥去推到子链码,而不是母公共钥匙。这就在母/子顺序中创造了一道“防火墙”——有链码但并不能够用来推算子链码或者姊妹私钥。强化的衍生方程看起来几乎与一般的衍生的子私钥相同,不同的是是母私钥被用来输入散列方程中而不是母公共钥匙,如图4-13所示。

图4-13 子密钥的强化衍生;忽略了母公共密钥

当强化私钥衍生方程被使用时,得到的子私钥以及链码与使用一般衍生方程所得到的结果完全不同的。得到的密钥“分支”可以被用来生产不易被攻击的扩展公共钥匙,因为它所含的链码不能被用来开发或者暴露任何私钥。强化的衍生也因此被用来在上一层级,使用扩展公共钥匙的的密钥树中创造“间隙”。

简单地来说,如果你想要利用扩展公共钥匙的便捷来衍生公共钥匙的分支而不将你自己暴露在泄露扩展链码的风险下,你应该从强化母私钥,而不是一般的母私钥,来衍生公共钥匙。最好的方式是,为了避免了推到出主钥匙,主钥匙所衍生的第一层级的子钥匙最好使用强化衍生。

正常衍生和强化衍生的索引号码

用在衍生方程中的索引号码是32位的整数。为了区分密钥是从正常衍生方程中衍生出来还是从强化衍生方程中产出,这个索引号被分为两个范围。索引号在0和231–1(0x0 to 0x7FFFFFFF)之间的是只被用在常规衍生。索引号在231和232–1(0x80000000 to 0xFFFFFFFF)之间的只被用在强化衍生方程。因此,索引号小于231就意味着子密钥是常规的,而大于或者等于231的子密钥就是强化型的。

为了让索引号码更容易被阅读和展示,强化子密码的索引号码是从0开始展示的,但是右上角有一个小撇号。第一个常规子密钥因此被表述为0,但是第一个强化子密钥(索引号为0x80000000)就被表示为0'。第二个强化密钥依序有了索引号0x80000001,且被显示为1',以此类推。当你看到HD钱包索引号i'比特币密钥原理,这就意味着231+i。

HD钱包密钥识别符(路径)

HD钱包中的密钥是用“路径”命名的,且每个级别之间用斜杠(/)字符来表示(见表4-8)。由主私钥衍生出的私钥起始以“m”打头。因此,第一个母密钥生成的子私钥是m/0。第一个公共钥匙是M/0。第一个子密钥的子密钥就是m/0/1,以此类推。

密钥的“祖先”是从右向左读,直到你达到了衍生出的它的主密钥。举个例子,标识符m/x/y/z描述的是子密钥m/x/y的第z个子密钥。而子密钥m/x/y又是m/x的第y个子密钥。m/x又是m的第x个子密钥。

表4-8 HD钱包路径的例子

HD path密钥描述m/0从主私钥(m)衍生出的第一个(0)子密钥。m/0/0第一个私人子密钥(m/0)的子密钥。m/0'/0第一个子强化密钥first hardened child(m/0')的第一个常规子密钥。m/1/0第2个子密钥(m/1)的第一个常规子密钥M/23/17/0/0主密钥衍生出的第24个子密钥所衍生出的第17个子密钥的第一个子密钥所衍生出的第一个子密钥。

HD钱包树状结构的导航

HD钱包树状结构提供了极大的灵活性。每一个母扩展密钥有40已个子密钥:20亿个常规子密钥和20亿个强化子密钥。而每个子密钥又会有40亿个子密钥并且以此类推。只要你愿意,这个树结构可以无限类推到无穷代。但是,又由于有了这个灵活性,对无限的树状结构进行导航就变得异常困难。尤其是对于在不同的HD钱包之间进行转移交易,因为内部组织到内部分支以及亚分支的可能性是无穷的。

两个比特币改进建议(BIPs)提供了这个复杂问的解决办法——通过创建几个HD钱包树的提议标准。BIP0043提出使用第一个强化子索引作为特殊的标识符表示树状结构的“purpose”。基于BIP0043,HD钱包应该使用且只用第一层级的树的分支,而且有索引号码去识别结构并且有命名空间来定义剩余的树的目的地。举个例子,HD钱包只使用分支m/i'/是为了表明那个被索引号“i”定义的特殊为目地。

在BIP0043标准下,为了延长的那个特殊规范,BIP0044提议了多账户结构作为“purpose”。所有遵循BIP0044的HD钱包依据只使用树的第一个分支的要求而被定义:m/44'/。

BIP0044指定了包含5个预定义树状层级的结构:

m / purpose' / coin_type' / account' / change / address_index

第一层的目的地总是被设定为44'。第二层的“coin_type”特指密码货币硬币的种类并且允许多元货币HD钱包中的货币在第二个层级下有自己的亚树状结构。目前有三种货币被定义:Bitcoin is m/44'/0'、Bitcoin Testnet is m/44'/1',以及Litecoin is m/44'/2'。

树的第三层级是“account”,这可以允许使用者为了会计或者组织目的,而去再细分他们的钱包到独立的逻辑性亚账户。举个例子,一个HD钱包可能包含两个比特币“账户”:m/44'/0'/0' 和m/44'/0'/1'。每个账户都是它自己亚树的根。

第四层级就是“change”。每一个HD钱包有两个亚树,一个是用来接收地址一个是用来创造变更地址。注意无论先前的层级是否使用是否使用强化衍生,这一层级使用的都是常规衍生。这是为了允许这一层级的树可以在可供不安全环境下,输出扩展的公共钥匙。被HD钱包衍生的可用的地址是第四层级的子级,就是第五层级的树的“address_index”。比如,第三个层级的主账户收到比特币支付的地址就是M/44'/0'/0'/0/2。表4-9展示了更多的例子。

表4-9 BIP0044 HD 钱包结构的例子

HD 路径主要描述M/44'/0'/0'/0/2第三个收到公共钥匙的主比特币账户M/44'/0'/3'/1/14第十五改变地址公钥的第四个比特币账户m/44'/2'/0'/0/1为了签署交易的在莱特币主账户的第二个私钥

使用比特币浏览器实验比特币钱包

依据第3章介绍的使用比特币浏览管理器命令工具,你可以试着生产和延伸BIP0032确定性密钥以及将它们用不同的格式进行展示:

$ sx hd-seed > m # create a new master private key from a seed and store in file "m"
$ cat m # show the master extended private key
96 | Chapter 4: Keys, Addresses, Wallets
xprv9s21ZrQH143K38iQ9Y5p6qoB8C75TE71NfpyQPdfGvzghDt39DHPFpovvtWZaR- gY5uPwV7RpEgHs7cvdgfiSjLjjbuGKGcjRyU7RGGSS8Xa
$ cat m | sx hd-pub 0 # generate the M/0 extended public key xpub67xpozcx8pe95XVuZLHXZeG6XWXHpGq6Qv5cmNfi7cS5mtjJ2tgypeQbBs2UAR6KE- CeeMVKZBPLrtJunSDMstweyLXhRgPxdp14sk9tJPW9
$ cat m | sx hd-priv 0 # generate the m/0 extended private key xprv9tyUQV64JT5qs3RSTJkXCWKMyUgoQp7F3hA1xzG6ZGu6u6Q9VMNjGr67Lctvy5P8oyaYAL9CA- WrUE9i6GoNMKUga5biW6Hx4tws2six3b9c
$ cat m | sx hd-priv 0 | sx hd-to-wif # show the private key of m/0 as a WIF L1pbvV86crAGoDzqmgY85xURkz3c435Z9nirMt52UbnGjYMzKBUN
$ cat m | sx hd-pub 0 | sx hd-to-address # show the bitcoin address of M/0 1CHCnCjgMNb6digimckNQ6TBVcTWBAmPHK
$ cat m | sx hd-priv 0 | sx hd-priv 12 --hard | sx hd-priv 4 # generate m/ 0/12'/4 xprv9yL8ndfdPVeDWJenF18oiHguRUj8jHmVrqqD97YQHeTcR3LCeh53q5PXPkLsy2kRaqgwoS6YZ- BLatRZRyUeAkRPe1kLR1P6Mn7jUrXFquUt