4.5 高级密钥和地址

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

4.5.1 加密私钥(BIP0038)

私钥必须保密。私钥的保密性需求的真实情况是,在实践中相当难以实现,因为该需求与同样重要的可用性安全目标相矛盾。当你为了避免私钥丢失而存储备份时,会发现维护私钥私密性相当困难。通过密码加密保存私钥的钱包可能要安全一点,但那个钱包也需要备份。有时,例如用户因为要升级或重装钱包软件,而需要把密钥从一个钱包转移到另一个。私钥备份也可能需要存储在纸张上(参见“纸钱包”一节)或者外部存储介质里,比如U盘。但如果一旦备份文件失窃或丢失呢?这些相互矛盾的安全目标推进了便携、方便、可以被众多不同钱包和比特币客户端理解的加密私钥标准BIP-38的出台(BIP-38详细可参见附录部分)。

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

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

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

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

表4-5 BIP-38加密私钥例子

Private Key (WIF) 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn
Passphrase MyTestPassphrase
Encrypted Key (BIP-38) 6PRTHL6mWa48xSopbU1cKrVjpKbBZxcLRRCdctLJ3z5yxE87MobKoXdTsJ

4.5.2 P2SH (Pay-to-Script Hash)支付脚本哈希和多重签名地址

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

以数字3开头的比特币地址是P2SH地址,有时被错误的称谓多重签名或多重签名地址。它们指定比特币交易中受益人为脚本哈希,而不是公钥的所有者。这个特性在2012年1月由BIP-16(参见附录appdxbitcoinimpproposals)引进,正是因为BIP-16提供了向地址本身添加功能的机会而被广泛地采纳。不同于P2PKH(pay-to-public-key-hash,支付公钥哈希),交易发送资金到传统1开头的比特币地址,资金被发送到3开头的地址时,要求的不仅仅是公钥的哈希值和私钥签名来证明所有权。这些要求是在创建地址时在脚本中指定的,对这个地址的所有输入都将使用相同的要求。

P2SH地址从交易脚本中创建,后者定义谁能花费这个交易输出(参见“P2SH(Pay-to-Script-Hash)”一节)。对P2SH地址编码使用的是与创建比特币地址同样的双重哈希函数,只是应用在脚本而不是公钥:

  1. script hash = RIPEMD160(SHA256(script))

得到的”脚本哈希”使用Base58Check编码,版本前缀为5,最后生成3开头的编码地址。一个P2SH地址例子是 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM。可以使用比特币浏览器命令script encode、sha256、ripemd160和base58check encode(参见附录appdx_bx])导出,举例如下:

  1. $ echo \
  2. 'DUP HASH160 [89abcdefabbaabbaabbaabbaabbaabbaabbaabba] EQUALVERIFY CHECKSIG' > script
  3. $ bx script-encode < script | bx sha256 | bx ripemd160 \
  4. | bx base58check-encode --version 5
  5. 3F6i6kwkevjR7AsAd4te2YB2zZyASEm1HM

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

4.5.2.1 多重签名地址和P2SH

目前,P2SH最常见的实现是多重签名地址脚本。顾名思义,底层脚本需要多个签名来证明所有权,才能花费资金。比特币多重签名功能的设计要求是总共N个密钥中需要M个签名(也称为“阈值”),被称为M-N多重签名,其中M是等于或小于N。例如,第一章中提到的咖啡店主Bob使用的多重签名地址,需要1-2签名,一个是属于他的密钥,另一个是他妻子的密钥,其中任何一方都可以签名花费这个地址锁定的输出。这类似于传统的银行中的一个“联合账户”,其中夫妻任何一方都可以单独签单消费。或就像Bob雇佣的网页设计师Gopesh, 可能需要的是一个2-3的多签名地址,可以确保至少两个合伙人签名了交易才可以继续支付。

我们将会在第五章交易中探讨如何构造P2SH(和多重签名)地址花费资金的交易。

4.5.3 靓号地址

靓号地址也是有效的比特币地址,只不过增加了可读性。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33就是有效的地址,开头的4个字符Base-58包含了字母love。靓号地址需要生成和测试几十亿个候选私钥,直到一个符合模式要求的比特币地址。虽然有一些优化过的靓号生成算法,但是这些方法必然涉及到随机选择一个私钥,生成公钥,再生成比特币地址,并检查是否与所要的靓号模式相匹配,需要重复数十亿次,直到找到一个匹配。

一旦找到一个与所需模式匹配的靓号地址,那么这个靓号地址的私钥就和其他地址一样被所有者花费比特币。靓号地址不比其他地址具有更多或更少的安全性。它们依靠和其他地址相同的椭圆曲线加密算法(ECC)和SHA。找一个靓号开头的地址的私钥并不比找其他地址容易。

在第一章中,我们介绍了Eugenia,一位菲律宾的儿童慈善机构负责人。我们假设Eugenia组织了一场比特币募捐活动,并希望使用靓号比特币地址来宣传这个募捐活动。Eugenia将会创造一个以1Kids开头的靓号地址来推广儿童慈善募捐的活动。让我们看看这个靓号地址如何被创建,以及对Eugenia慈善募捐的安全性有什么意义。

4.5.3.1 生成靓号地址

应该意识到,比特币地址不过是由Base58字母表中的符号表示的一组数字。寻找像“1kids”开头的靓号就像从1Kids11111111111111111111111111111到1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz这个区间找一个地址。以“1kid”开头的地址区间大约有5829个地址(大约是1.4 * 1051))。表4-6显示了这些有“1kids”前缀的地址范围。

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

From 1Kids11111111111111111111111111111
1Kids11111111111111111111111111112
1Kids11111111111111111111111111113
To 1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz

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

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

Length Pattern Frequency Average search time
1 1Ki 1 in 58 keys < 1 milliseconds
2 1Ki 1 in 3,364 50 milliseconds
3 1Kid 1 in 195,000 < 2 seconds
4 1Kids 1 in 11 million 1 minute
5 1KidsC 1 in 656 million 1 hour
6 1KidsCh 1 in 38 billion 2 days
7 1KidsCha 1 in 2.2 trillion 3–4 months
8 1KidsChar 1 in 128 trillion 13–18 years
9 1KidsChari 1 in 7 quadrillion 800 years
10 1KidsCharit 1 in 400 quadrillion 46,000 years
11 1KidsCharity 1 in 23 quintillion 2.5 million years

正如你所见,Eugenia并不能很快创造以“1KidsCharity”开头的靓号地址,即使她有数千台电脑同时进行运算。每增加一个字符就会增加58倍的计算难度。超过七个字符的靓号通常需要专用的硬件才能被找到,譬如用户定制的多个GPU的台式机。通常是使用无法继续比特币挖矿的矿机,被赋予了寻找靓号地址的任务。用GPU搜索靓号的速度比通用的CPU要快很多个量级。

另一种寻找靓号地址的方法是外包给一个靓号矿池,如Vanity Pool。矿池提供一种服务,把有GPU硬件的人组织起来,通过为他人寻找靓号地址来获得比特币。Eugenia可以将搜索7位字符模式的靓号地址的工作外包出去,只需要很少的金额(本书写作时大概是0.01比特币或者5美元),几个小时内就可以得到结果,而不用自己运行CPU搜索上几个月。

生成一个靓号地址是一项通暴力破解的过程:尝试一个随机密钥,检查生成的地址是否和所需的模式相匹配,重复这个过程直到成功找到为止。例4-9是个靓号挖矿的例子,用C++程序写的来寻找靓号地址的程序。这个例子运用到了我们在“其他替代客户端、资料库、工具包”一节介绍过的libbitcoin库。

例4-9 靓号挖矿程序

link:code/vanity-miner.cpp[]

注释 下面的例4-10 使用std :: random_device。 根据实施情况,可能会映射到底层操作系统提供的CSRNG。 在类Unix的操作系统(如Linux)中,它来自/ dev/urandom。 这里使用的随机数字生成器只用于演示,并不适用于生产级别的比特币密钥,因为它没有足够的安全性。

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

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

```$ # Compile the code with g++$ g++ -o vanity-miner vanity-miner.cpp $(pkg-config —cflags —libs libbitcoin)$ # Run the example$ ./vanity-minerFound vanity address! 1KiDzkG4MxmovZryZRj8tK81oQRhbZ46YTSecret: 57cc268a05f83a23ac9d930bc8565bac4e277055f4794cbd1a39e5e71c038f3f$ # Run it again for a different result$ ./vanity-minerFound vanity address! 1Kidxr3wsmMzzouwXibKfwTYs5Pau8TUFnSecret: 7f65bbbbe6d8caae74a0c6a0d2d7b5c6663d71b60337299a1a2cf34c04b2a623