钱包 - 比特币开发指南

作者: terryc007 | 来源:发表于2018-03-08 14:48 被阅读197次

    钱包 - 比特币开发指南

    原文链接: https://bitcoin.org/en/developer-guide#transactions

    翻译: terryc007

    版本:1.0


    比特币开发指南

    1. 区块链

    2. 交易

    3.合约

    4.钱包

    5.支付处理

    6.工作模式

    7.P2P网络

    8.挖矿


    比特币钱包涉及到钱包程序或钱包文件。钱包程序创建公钥来收取satoshis,同时使用相对应的私钥花掉这些satoshis。钱包文件保存了私钥,以及为钱包程序保存了一些交易相关的信息(可选)。

    钱包程序和钱包文件在下面各个小节会讲到,本文档尝试说清楚我们讨论的到底是钱包程序了,还是钱包文件。

    钱包程序

    收取,花掉比特币是钱包程序最基本的功能 — 但是一个特定的钱包不必包含这两个功能。两个钱包程序能一起协调工作,一个负责分发公钥,用来收取比特币,另外一个程序负责签名交易,来花掉这些比特币。

    为从区块链上获取信息,广播交易,钱包程序需要跟比特币p2p网络交互。然而,那些分发公钥,签名交易部分的程序是不需要跟跟比特币P2P网络交互的。

    这样我们可以把钱包系统分成三个独立的部分: 一个公钥分发程序,一个签名程序,一个网络程序。在下面的小节,我们会讲述这几个部分。

    注意: 一般的,我谈到分发公钥时,在很多的情况,分发的是P2PKH或P2SH哈希,而不是公钥,实际上当他们控制的输出被花掉的时,才分发公钥。

    全服务钱包

    最简单的钱包就是包含三个功能:它生成私钥,然后基于它生成相应的公钥,在需要时,分发这些公钥,监控在这些公钥的输出,创建,签名交易(用于花掉输出),广播签名后的交易。

    在撰稿本文时,几乎所有主流的钱包被当做全服务钱包使用。

    全服务钱包的主要优势在于易用。用户可以用一个程序可以做所有的事情:收发比特币。

    全服务钱包一个最大的缺点就是它把私钥保存在联网的设备上。这种折中的设备很常见,加上联网,使得很容易把私钥从一个折中的设备传送到攻击者。

    为防止私钥被盗,很多钱包程序为用户提供了一个选项:对保存私钥的钱包文件进行加密。但私钥不被使用好,可以保护私钥,避免被盗。但是这无法防止攻击者有计划的捕捉到密钥,或者从内存读到解密后的密钥。

    签名钱包

    为提升安全,在一个更为安全的环境中,私钥可以在一个独立钱包程序里面生成,保存。这些签名钱包可以跟网络钱包(可以跟比特币P2P网络交互)一起协同工作。

    签名钱包程序通常使用确定性密钥构建方式来创建父私钥,父公钥。这些父钥可以创建子私钥,子公钥。

    当第一次运行签名钱包,它会创建一个父私钥,并把相应的父公钥传送到网络钱包。

    网络钱包使用父公钥来派生出子公钥,并按需分发子公钥,监控在这些公钥上的输出,创建花掉这些输出的非签名交易,然后发送非签名交易到签名钱包。

    在使用签名钱包时,用户常常有机会去审核这些非签名交易的详情(特别是输出详情)。

    在审核交易后(这步是可选的),签名钱包使用父私钥派生出一个相应的子私钥,同时签名这些非签名交易,并发送这些签名交易到网络钱包。

    然后网络钱包广播这些签名交易到P2P网络。

    下面的小节将会讲述最常用的签名钱包: 离线钱包,硬件钱包。

    离线钱包

    好几个全服务钱包程序也可以当做两个独立的钱包: 一个程序当做签名钱包(通常叫做:离线钱包),同时另外一个程序当作网络钱包(通常叫做:线上钱包 或者 观察钱包)

    离线钱包因它有意运行在一个不联网的设备上而得名,这会极大减少攻击。在这种情况,通过类似USB这样的媒介来转移数据,通常取决于用户自己。 用户的工作流程如下:

    1. (离线) 关闭设备上的所有网络连接,装上钱包软件。 在离线模式下,启动钱包软件,创建一个父私钥,父公钥,复制父公钥到可以插拔的媒介上。

    2. (在线)在另外一个设备上装上钱包,这钱包要连上互联网,同时从可插拔媒介,导入父公钥。跟你使用去钱包一样,通过分发公钥来收款。当准备花比特币时,准备好输出,并保存生成好的非签名交易到可插拔媒介。

    3. (离线)在离线钱包中,打开非签名交易,审核输出详情,确保要支付的数量,地址是正确无误的。这可以防止在线钱包中的恶意软件欺骗用户在支付给攻击者上的交易签名。审核后,签名交易并保存交易到可插拔媒介。

    4. (在线)在在线钱包中,打开签名交易。现在可以把它广播到P2P网络中。

    离线钱包主要的优点是:相比于全服务钱包,他们极大的提升了钱包的安全性。只要离线钱包没有丢失或者有缺陷,并且在签名前,用户审核所有交易,用户的satoshis是安全的,即使在线钱包泄露了。

    离线钱包的主要的缺点在于使用起来太繁琐。为了最大的安全,要求用户用一个设备专用于离线任务。每当需要发送比特币时,离线设备必须要启动,同时用户必须通过手动把数据在在线设备,离线设备之间来回复制。

    硬件钱包

    硬件钱包专门用来运行签名钱包。它们消除了签名钱包在目前操作系统上很多缺陷,它们允许钱包可以跟其他硬件安全的通信,因此用户没有必要手动的在设备之间传送数据。硬件钱包用户的使用流程如下:

    1. (硬件钱包) 创建父私钥,父公钥。把硬件钱包连接到一个网络钱包设备,这样它就可以获取到公钥。

    2. (网络钱包) 同使用全服务钱包一样,分发公钥来收款。当准备花掉satoshis时,填充好交易细节,并连接到硬件钱包,然后点击支付。网络钱包会自动把交易细节发送给硬件钱包。

    3. (硬件钱包)在硬件钱包屏幕上审核交易细节。一些硬件钱包可能会弹出一个密码或PIN输入框。硬件钱包签名交易,然后把它发送给网络钱包。

    4. (网络钱包)网络钱包从硬件钱包收到签名交易后,把它广播到P2P网络。

    相对于全服务钱包,硬件钱包的主要优点是极大的提升了安全,同时比起离线钱包又简单很多。

    硬件钱包主要的缺点是使用起来繁琐。即使比起离线钱包来没那么繁琐,但是用户仍然需要购买一个硬件钱包,并且每当需要给交易签名时,需随身携带。

    一个其他的缺点(希望是暂时的),在撰稿本文时,很少有主流的钱包程序支持硬件钱包 — 虽然很多主流的钱包已经发公告说他们有意向支持至少一种硬件钱包。

    分发钱包

    那些运行在很难实现安全的环境,比如web服务器,的钱包的功能可以设计成仅仅分发公钥(包括P2PKH, P2SH地址)。有两个通用的方式来设计这些极简的钱包。

    • 先预准备一些公钥或地址在一个数据库里面,然后使用数据库中的条目,按需分发公钥脚本或者地址。为避免重用,web服务器跟踪密钥使用情况,永远不要用完里面的公钥。可以通过父公钥很容易做到这点,这会在下面方面提到。

    • 使用父公钥生成子公钥。为避免重用,需要使用一个方法保证一个公钥不能被分发两次。这可以是一个数据库的条目对应一个密钥分发,或通过自增指针指向密钥的索引号。

    这两个方法没有增加太多额外的工作量,特别是在使用一个数据库来,通过把每个进来的支付跟独立的公钥绑定起来跟踪时,其工作量就更少。 具体情况支付处理章节。

    钱包文件

    比特币的核心其实就是一堆私钥集合。这些私钥以数字内容的形式存在文件里面,或甚至可以存在纸上。

    私钥格式

    私钥用来解锁在一个比特币地址上的satoshis。在比特币中,一个私钥的标准格式是一个简单的256-bit 数字,值的范围是: 0x01 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140, 所代表的值的范围在2256 - 1. 这范围由比特币中使用的secp256k1 ECDSA加密标准来控制。

    钱包导入格式(WIF)

    为使复制密钥少出错,可以使用钱包导入格式。WIF使用base58Check 来编码私钥,可以极大的降低复制引起的错误,这看起来很像比特币地址。

    1. 拿到一个私钥。

    2. 用于主网地址时,在私钥前面加上0x80,用于测试网地址时,在前面加入0xef。

    3. 如果跟压缩公钥(后续小节会讲到)使用,需要在后面加入0x01,如果跟非压缩公钥使用,就什么都不用加。

    4. 对扩展后的私钥进行SHA-256哈希。

    5. 对SHA-256哈希后结果再做一个SHA-256哈希。

    6. 拿掉第二次哈希后的哈希值的前4字节,这4个字节是校验和。

    7. 把这4字节校验和插入到扩展私钥倒数第5的位置。[?]

    8. 把结果从字节字符串,使用Base58Check转化成Base58编码字符串。

    使用Base58解码功能,很容易逆向操作这个过程,删掉那些填充。

    Mini私钥格式

    Mini私钥格式通过小于30个字符来编码私钥的一种方法,可以把密钥嵌入到非常小的物理空间上,比如一个物理的比特币,抗破坏的QR二维码。

    1. 第一个字母是: S

    2. 为判断一个mini私钥是否格式化良好,在私钥中加入了一个问题标志。

    3. 计算私钥的哈希值。如果哈希值第一个字节是00, 那它就是格式化良好的Mini私钥。这个密钥的限制起到校验和的作用。用户可以通过使用随机数字暴力处理,来生成一个格式化良好的mini私钥。

    4. 为了从完整的私钥派生,用户可以简单的获取一个源mini私钥的SHA256哈希。这是个单向处理:从派生的密钥去计算出mini私钥是很困难的。

    在很多的Mini私钥的实现中,禁止在mini私钥里使用字符 "1",因为它跟字符 “I” 很像。

    资源:Casascius 比特币地址工具: 一个创建,兑换这些密钥的工具.

    公钥格式

    在secp256k1中,比特币 ECDSA 公钥代表一条特定椭圆曲线(EC)上的一个点。在传统的非压缩格式,公钥包含一个标识字节,一个32字节x坐标,一个32字节y坐标。下面的一个简化版插图显示了一个在y2 = x3 + 7 椭圆曲线上的一个点。超过了连续数字域。[?]

    (Secp256k1实际通过一个大的素数对坐标进行取模,这会产生一个非连续整数域, 即使是使用同样的原理,也会导致一个非常不清晰的坐标。)

    在不改变任何基本原则,通用丢弃Y坐标,可以实现公钥大小减半效果。这是有可能的,因为两个在曲线上的点,共用一个X坐标,因此32字节Y坐标能够用一个bit微替代,用来表示这个点是在曲线上部分,还是在下部分。

    采用这样方法压缩,不会导致数据丢失 — 这仅需要一点点cpu来重建Y坐标,以及访问非压缩公钥。在secp256k1文档中,所描述的非压缩,压缩密钥,在广泛使用的OpenSSL库中,是默认支持的。

    因为他们很容易使用,因为比特币内核,默认会存储每个输出,加密公钥,以及其他所有比特币软件也如此,为存储这些公钥,这种方式几乎能为区块链减少一半空间需求。

    然而,比特币内核0.6之前的版本,使用的是非压缩密钥。这就带来一定的复杂,因为非压缩公钥的哈希跟压缩公钥的哈希的格式是不一样的,因此同一密钥会有两个不同的P2PKH,P2SH地址。这也就意味着密钥在签名脚本中,必须以正确的格式提交,这样它才能跟之前输出的公钥脚本匹配上。

    因为这个原因,比特币内核使用几个不断的标识字节来帮助程序识别怎么使用密钥:

    • 使用压缩公钥的密钥,在他们进行Base58编码前,需在其他后面加上0x01

    • 非压缩公钥以0x04开头,压缩公钥以0x03或0x02开头,至于是0x03,还是0x02,取决于曲线上点在Y轴位置是大于中点,还是小于中点。 这些前缀字节在官方的secp256k1文档中都有被用到。

    层级确定性密钥创建

    层级确定密钥创建和传输协议(HD 协议)极大的简化了钱包备份,减少在同一钱包中,多个程序重复通信,允许创建可独立操作的子账户,每个父账户可以监控,控制它的子账户,即便子账户泄露了,同时可以把账号分成可完全访问,限制访问部分,这样不可信用户或程序可以接收或者监控支付情况,但没有不能花里面的钱。

    HD协议利用ECDSA公钥的创建函数 point(), 它采用一个非常大的整数(私钥),把整数转化成曲线上的一个点(公钥)。

    point(private_key) == public_key

    因为point()内部工作方式,通过组合一个已有的(父)公钥,另外一个通过任何 整型(i) 数值生成的公钥,可以创建一个子公钥。 这个子公钥跟公钥一样,可以通过point()函数,在源(父)私钥上加上一个整型数i,然后父公钥跟 i 相加,再除以一个在比特币软件中的全局的常量,求余数, 来创建。

    point( (parent_private_key + i) % p ) == parent_public_key + point(i)

    这就意味着,两个或多个独立的程序,在不需要更多的通信的情况下,同意一串数字能从单个父密钥对创建一系列独一无二的子密钥对。更重要的是,为接受付款而分发这些公钥的程序的确在不需要访问私钥,就能是的公钥分发程序可以运行在可能不安全的平台上,比如公共的web服务器。

    子公钥也能通过重复子密钥派生操作,来创建他们自己的子公钥(孙公钥):

    point( (child_public_key + i ) % p) == child_public_key + point(i)

    是否要创建子公钥或者下一代公钥,一串可预测的整型数比起所有交易使用单个公钥来说更好。因为任何人如果他知道一个子公钥,他就可以找出所有由其父公钥生成的其他所有子公钥。相反,随机种子能够用来生成一串确定的整型数字,这样在没有种子的情况下,子公钥之间的关系是不可见的。

    HD协议使用单个根种子来创建一个层级的子,孙子,其他后代密钥。他们是一些无相关性的,具有确定性生成的整数。每个子密钥也可以从它的父密钥获得一个确定性生成的种子。叫做链码,所以一个链码泄露不会导致整个层级里的整数泄露,这就主链码就可以继续使用,即使,比如 一个基于web的公钥分发程序被黑掉。

    如上图所示,HD密钥派生需要4个输入:

    • 父私钥,父公钥,他们都是非压缩256位 ECDSA密钥。

    • 父链码 - 256位看似随机的数。

    • 索引号 - 程序指定的32位的整数

    如上图所示,单向加密哈希算法(HMAC-SHA512)把父链码,父公钥,索引号进行处理,生成一个确定的但看起来有点随机的数据。 看似随机的256位值右侧部分算出的哈希值被用来当做一个新的子链码。左侧部分算出的哈希值被当做一个整数,跟父私钥或父公钥组合一起,生成相应的子私钥或子公钥。

    child_private_key == (parent_private_key + lefthand_hash_output) % G 
    child_public_key == point( (parent_private_key + lefthand_hash_output) % G )
    child_public_key == point(child_private_key) == parent_public_key + point(lefthand_hahs_output)
    

    对于同一父密钥,指定不同的索引数字会创建不同的,无关联的子密钥。使用子链码对子密钥重复这个过程会创建无关的孙密钥。

    因为创建子密钥需要一个秘密,一个链码,他们两合起来叫做扩展密钥。一个扩展私钥跟它相应的扩展公钥有同一个链码。主私钥(跟私钥)和主链码是从随机数据派生而来,如下图所示:

    根种子通过128或256或512位随机数创建。为了能让使用特定设置的特定钱包程序派生每个密钥,用户只需要存储根种子中的128位即可。

    警告: 在撰写本文时,HD钱包程序不能达到完全兼容,因此对于一个特定的根种子,用户必须使用同一个HD钱包程序,并使用同样的HD相关设置。

    根种子通过哈希之后生成看似随机的512比特位数据,主密钥,主链码通过这512比特为数据来生成。主公钥使用point()从主私钥派生,主公钥和主链码一起,通过point()来生成主扩展公钥。主扩展密钥在功能上跟其他扩展密钥是一样的;它只是因为在在层级的最顶部而已。

    强化密钥

    强化扩展密钥解决了普通扩展密钥的一个潜在问题。如果一个攻击者获取一个普通父链码,一个父公钥,他可以暴力破解从它生成的所有子链码。如果攻击者也能获得一个子/孙子/其他后代的私钥,他就可以使用链码来生成所有从这私钥派生来的所有的扩展私钥。 如下面孙子,曾孙子插图中所示:


    更糟糕情况,攻击者能逆向普通私钥派生公式,并从父链码提取出一个字密钥,从而恢复父私钥,就如上图子代跟父代之间所显示的那样。 这就意味着攻击者拿到一个扩展公钥,以及它的一个后代密钥后,他就能恢复父私钥,以及它所以的后代密钥。

    因为这个原因,相比较于标准公钥而言,扩展公钥的链码部分需要更好的安全保护措施。并建议用户不要把非扩展私钥导出到可能不可信,不安全的环境。

    这可以通过一个折中的方案来解决,通过把普通密钥派生公式替换成强化密钥派生公式。

    在上节讲到的普通密钥派生公式,是通过把索引号,父链码,父公钥组合起来,来创建一个子链码,同时把一个整数跟父私钥组合起来,来创建一个子密钥。

    如上图所示,强化公式把索引号,父链码,父私钥组合起来创建一个用于生成子链码,子私钥的数据。这个公式使得在无需知道父密钥的情况下,创建子公钥成为可能。 换句话说,父扩展公钥不能创建强化子公钥

    因为这原因,强化扩展私钥比起普通的扩展私钥用处更少 — 然而,强化扩展公钥创建了一个防火前,使得多层级派密钥泄露不会发生。因为强化子扩展公钥不能自己生成孙子链码,那么使用泄露的父扩展公钥,泄露的孙子私钥是不能创建曾孙子扩展私钥的。

    HD协议使用一个索引号来指示是生成普通的,还是强化的密钥。范围在0x00 ~ 0x7fffffff(0 到 231 - 1)的索引号生成一个普通的密钥,范围在0x80000000 ~ 0xffffffff(0 到 231 - 1)的索引号生成一个强化的密钥。为方便描述,很多开发者使用撇符来区分强化密钥,因此第一个普通密钥(0x00)是 0, 第一个强化密钥(0x80000000)是 0'.

    (比特币开发者通常使用ASCII编码的撇号而不是UNICODE编码的撇号,因此我们也这样使用)。

    更为简洁的描述方式: 在/符号前面,加一个m 或者M前缀,来表示层级,以及密钥类型。 m表示私钥,M表示公钥。比如, m/0‘/0/122' 表示主私钥第一个强化子私钥的第一个普通私钥的第123个子私钥。下面的层级展示了撇会注释跟强化密钥防火墙:

    遵循BIP32 HD协议的钱包仅能创建主私钥的强化后代密钥,以防止子密钥泄露导致主密钥泄露。 因为主密钥没有普通的子密钥,在HD钱包中也没有用到主公钥。其他所有的密钥可以普通的子密钥,因此也可以使用相应的扩展公钥来替代普通的子公钥。

    HD协议中介绍了扩展公钥,扩展私钥序列化格式。更多详情,请看开发者参考中钱包章节或者 在BIP32了解HD协议详情。

    存储根种子

    在HD协议中,根种子是一些128位,258位,512位比特随机数据。必须要精确的保存好他们。为了更方便使用非数字方式保存他们,比如记忆,手写,BIP39 定义了一个方法。该方法可以通过由普通单词伪造出的句子(帮组记忆的)来生成512位根种子。 这句子通过128或256位的熵生成的,并且可以使用密码来保护这些组记词。

    单词个数跟消耗熵数量之间的关系如下:

    比特熵 单词数量
    128 12
    160 15
    192 18
    224 21
    256 24

    密码长度没有限制。它只是简单的加到组记词的后面,然后使用HMAC-SHA512对他们进行2048次哈希计算,获得一个看似随机的512比特未种子。因为任何输入到这个哈希函数的数据,都会生成一个看似随机的512比特位的种子,因为没有一个基本的方法证明用户输入了正确的密码,这样即使用户在强迫下,也有可能保护好种子。

    想要了解更多详情,请查看BIP39.

    松散密钥钱包

    松散密钥钱包也叫密钥串(JBOK), 这种形式的钱包源自于比特币内核客户端,目前已被遗弃不用。比特币内核客户端钱包会通过伪随机数生成器(PRNG)创建100个私钥/公钥对,以备后用。

    这些私钥储存在一个虚拟的“密钥池”,当之前的密钥被使用后,它会生成新的密钥对,这样确保密钥池维持有100个未被使用的密钥。(如果钱包被加密,新的密钥只有在被解锁后才可以生成新的密钥)

    考虑到不得不手动备份新生成的私钥,这会对保存密钥带来相对大的困难。如果新的密钥生成后,被用掉,但在备份前丢失了,那么里面的比特币就永远丢失了。很多老式的移动钱包则使用一个简单的格式,它们仅当用户需要的时候,才生成一个新的私钥。

    因为备份太繁琐,这类钱包正逐步被淘汰,也不鼓励大家使用。


    声明:

    文中带有[?]的地方,表示我对此翻译明显感觉不太对的,后续会不断修正。

    有些地方可能会翻译的不好,不地道,甚至错误,如果有发现,还请留言,指出,以便我好修正,谢谢!

    相关文章

      网友评论

        本文标题:钱包 - 比特币开发指南

        本文链接:https://www.haomeiwen.com/subject/lwlyfftx.html