第10章 比特币安全保护比特币是很具有挑战性的事,因为比特币不像银行账户余额那样体现抽象价值。比特币其实更像数字现金或黄金。你可能听过这样的说法,“现实持有, 败一胜九。”好吧,在比特币的世界里,这样的持有只能让你有一成胜率。而只有拥有解锁比特币的密钥才相当于持有现金或一块贵重金属。你可能会将密钥丢失, 会放错地方,会被盗或者不小心错支了数额。无论是哪种场景,用户都没有办法撤回,因为这就像是将现金丢在了车水马龙的大街上。 10.1 安全准则比特币的核心准则是去中心化,这一点对安全性具有重要意义。在中心化的模式下,例如传统的银行或支付网络,需要依赖于访问控制和审查制度将不良行为 者拒之门外。相比之下,比特币这样的去中心化系统则将责任和控制权都移交给了用户。由于网络的安全性是基于工作量证明而非访问控制,比特币网络可以对所有 人开放,也无需对比特币流量进行加密。 10.1.1 比特币系统安全开发对于比特币开发者而言最重要的是去中心化原则。大多数开发者对中心化的安全模型很熟悉,并可能试图将中心化的模型运用到借鉴比特币的应用中去去,这将给比特币带来灭顶之灾。 10.1.2 信任根源传统的安全体系基于一个称为信任根源的概念,它指的总体系统或应用程序中一个可信赖的安全核心。安全体系像一圈同心圆一样围绕着信任根源来进行开 发,像层层包裹的洋葱一样,信任从内至外依次延伸。每一层都构建于更可信的内层之上,通过访问控制,数字签名,加密和其他安全方式确保可信。随着软件系统 变得越来越复杂,它们更可能出现问题,安全更容易受到威胁。其结果是,软件系统变得越复杂,就越难维护安全性。信任根源的概念确保绝大多数的信任被置于系 统一个不是过于复杂的部分,因此该系统的这部分也相对坚固,而更复杂的软件则在它之上构建。这样的安全体系随着规模扩大而不断重复出现,首先信任根源建立 于单个系统的硬件内,然后将该信任根源通过操作系统扩展到更高级别的系统服务,最后逐次扩散到圈内多台服务器上。 10.2 用户最佳安全实践人类使用物理的安全控制已经有数千年之久。相比之下,我们的数字化安全经验的年纪还不满50岁。现代通用的操作系统并不是十分安全,亦不特别适合用 来存储数字货币。我们的电脑通过一直连接的互联网长时间暴露在外,它们运行着成千上万第三方软件组件,这些软件往往可以不受约束地访问用户的文件。你电脑 上安装的众多软件只要有一个恶意软件,就会威胁到你的文件,可窃取你钱包里的所有比特币。想要杜绝病毒和木马对电脑的威胁,用户要达到一定的计算机维护水 平,只有小部分人能做到。 10.2.1 比特币物理存储相比数字信息的安全,大多数用户对物理安全更加熟悉,一个非常有效保护比特币的方法是,将它们转换为物理形式。比特币密钥不过是串长数字而已。这意 味着它们可以以物理形式存储起来,如印在纸上或蚀刻成金属硬币上。这样保护密钥就变成了简单地保护印着比特币密钥的物理实体。一组打印在纸上的比特币密钥 被称为“纸钱包”,有许多可以用来创建它们的免费工具。我个人将大部分(99%以上)的比特币存储在纸钱包上,并用BIP0038加密,复制了多份并锁在 保险箱里。将比特币离线保存被称为冷存储,它是最有效的安全技术之一。冷存储系统是在一个离线系统(一个从来没有连接过互联网的系统)上生成密钥,并离线 存储到纸上或者U盘等电子媒介。 10.2.2 硬件钱包从长远来看,比特币安全将越来越多地以硬件防篡改钱包的形式出现。与智能手机或台式电脑不同,一个比特币硬件钱包只有一个目的,安全地存储比特币。 不像容易受害的常用软件那样,硬件钱包只提供了有限的接口,从而可以给非专业用户提供近乎万无一失的安全等级。我预期将看到硬件钱包成为比特币储存的主要 方式。要想看硬件钱包的实例,请查阅Trezor。 10.2.3 平衡风险虽然大多数用户都非常关注比特币防盗,其实还有一个更大的风险存在。数据文件丢失的情况时有发生。如果比特币的数据也在其中,损失将会让人痛苦不 堪。为了保护好比特币钱包,用户必须非常注意不要剑走偏锋,这样不至于会搞丢比特币。在2011年7月,一个著名的比特币认知教育项目损失了近7,000 枚比特币。为了防止被盗窃,其主人曾之前采取了一系列复杂的操作去加密备份。结果他们不慎丢失了加密的密钥,使得备份变得毫无价值,白白失去了一大笔财 富。如果你保护比特币的方式太过了,这好比于把钱藏在沙漠里,你可能不能再把它找回来了。 10.2.4 分散风险你会将你的全部家当换成现金放在钱包里随身携带么?大多数人会认为这非常不明智,但比特币用户经常会将所有的比特币放在一个钱包里。用户应该将风险 分散到不同类型的比特币钱包。审慎的用户应该只留一小部分(或许低于5%)的比特币在一个在线的或手机钱包,就像零用钱一样,其余的部分应该采用不同存储 机制分散开来,诸如电脑钱包和离线(冷存储)钱包。 10.2.5 多重签名管理当一个公司或个人持有大量比特币时,他们应该考虑采用多重签名的比特币地址。多重签名比特币地址需要多个签名才能支付,从而保证资金的安全。多重签 名的密钥应存储在多个不同的地方,并由不同的人掌控。打个比方,在企业环境中,密钥应该分别生成并由若干公司管理人员持有,以确保没有任何一个人可以独自 占有资金。多重签名的地址也可以提供冗余,例如一个人持有多个密钥,并将它们分别存储在不同的地方。 10.2.6 存活能力一个非常重要却又常常被忽视的安全性考虑是可用性,尤其是在密钥持有者丧失工作能力或死亡的情况下。比特币的用户被告知应该使用复杂的密码,并保证 他们的密钥安全且不为他人所知。不幸的是,这种做法使得在用户无法解锁时,用户的家人几乎无法将该财产恢复。事实上,比特币用户的家人可能完全不知道这笔 比特币资金的存在。 10.3 总结比特币是一项全新的,前所未有的,复杂的技术。随着时间的推移,我们将开发出更好的安全工具,而且更容易被非专业人士使用的做法。而现在,比特币用户可以使用许多这里所讨论的技巧,享受安全而无困扰的比特币生活。 ==目录== 简介 |
精通比特币 – 第9章 竞争币、竞争块链和应用程序
第9章 竞争币、竞争块链和应用程序比特币是20多年的分布式系统和货币研究的结果,是一项具有革命性的新技术:一种基于工作量证明的去中心化的一致性机制。这项比特币的核心发明引领了一场包括货币体系、金融服务、经济学、分布式系统、投票系统、联合监管和合同体系在内的创新浪潮。 9.1 竞争币和竞争块链的分类比特币是一个开源项目,其源代码也作为其他的一些软件项目的基础。由比特币衍生出来的最常见的形式,就是替代性去中心化货币,简称“竞争币”,这类货币使用跟比特币同样的创建块链的方式来实现自己的电子货币系统。 9.2 元币平台元币和元块链是在比特币之上实现的软件层,也可以认为是覆盖在比特币系统之上的平台/协议,或者是在一个币中币的实现。这些功能层拓展了核心比特币 协议,使得在比特币交易和比特币地址中编码附加信息成为可能。元币的第一个实现利用了大量的 hack 技巧把元数据添加到比特币块链中,比如使用比特币地址编码数据,或者利用空白的交易字段存放新协议层增加的这些元数据。自从交易脚本操作码问世之后,元币 得以直接将信息存放在块链之中。 9.2.1 染色币染色币是一种在少量比特币上存储信息的一种元协议。一个“被染色的”币,是一定数额的重新用于表达另一种资产的比特币。想象一下,在一张一美金的纸 币上盖上写有一行“这是Acme公司的一份股权的证明”的印章。现在这张一美金的纸币就有了两层意义,它既是流通的货币,同时又是一份股权证明。由于它作 为一份股权证明的价值更大一些,因此你肯定不大会用它来购买糖果吃了(而是保留着),这也让这张纸币不再具有货币的流通属性。染色币也是这个工作原理,通 过将一笔数额不大的具体比特币交易转化为某种证明来指征另外一笔财产。所谓“染色”也仅仅是一种隐喻,并非字面意思,而是指增加属性(比如给个颜色)的方 式。因此,染色币并没有颜色。
9.2.2 万事达币万事达币是另一个建立在比特币之上的协议,该协议支持多个平台对比特币系统的扩展。万事达币使用名为MST的代币来指导交易,但它并不是一种通货。 相反的,它服务于其他应用平台,比如用户货币,智能财产代币,去中心化的财产交易和合约系统等等。就像HTTP协议是TCP协议的应用层一 样,Mastercoin是比特币协议的应用层协议。 9.2.3 合约币合约币是另一个建立在比特币系统之上的协议层。合约币拥有用户货币、可交易代币、金融手段、去中心化财产交易和其他一些功能。合约币利用比特币脚本语言中的OP_RETURE操作符记录元信息来增加比特币交易的额外信息。合约币使用名为XCP的代币维持整个系统的运行。 9.3 竞争币/山寨币绝大多数的山寨币都来自比特币源代码的克隆,少数则没有使用比特币的任何源码,仅仅是借鉴了块链的模型后自己实现。竞争币或竞争块链(下一节会讲到)都是运行在自己块链上的独立的块链实现。之所以以命名区分,主要是因为竞争币主要用做货币,而竞争块链则不是。 9.3.1 评估竞争币的价值市面上这么多竞争币,该如何决定关注哪些呢?一些竞争币旨在成为广泛流通的主流货币,还有一些是实验室项目,仅仅是为了测试不同的特性和货币模型,更多的仅仅是那些发起者们创富的手段。我一般通过某款竞争币的决定性特性和市场规模来对其进行价值评估。 9.3.2 货币属性不同于比特币的竞争币:莱特币、狗狗币和Freicoin比特币本身所具有的一些货币属性令其成为总额固定并且不通货膨胀的货币。比如,比特币的总量为固定的2,100万枚,新币的生成速度随时间递减,块 生成速度为十分钟一块,这个频率也控制了整个比特币系统交易的确认速度和新币的生成。很多竞争币通过对这些货币属性的微调,来达到实现不同的货币政策的目 的。在这类竞争币中,值得一提的有以下几种。 莱特币莱特币是最早的一批竞争币中的一员,自2011年发布至今,已经成为继比特币之后的第二成功的电子货币。它的主要创新在于两点,一是使用了scrypt作为工作量证明算法(继承自前文提到的Tenebrix),二是更快的货币参数。 狗狗币狗狗币是基于莱特币的一款竞争币,于2013年12月发布。狗狗币之所以值得一提,主要是因为其飞快的出块速度和惊人的货币总量,其目的也是为了鼓 励用户交易和给小费等。狗狗币始于一个玩笑,在其2014年快速衰退之前,一经发布就风行于巨大而活跃的用户社区。下面是 狗狗币的一些特性: FreicoinFreicoin于2012年7月发布。它是一种滞留性通货,可以理解为存在钱包中的货币的利率为负数。为了鼓励用户消费和减少储 蓄,Freicoin拟定了一个4.5%的APR fee。Freicoin值得一提的原因是它的货币策略跟比特币的通货紧缩策略恰恰相反。作为货币,Freicoin并不是非常成功,但它是竞争币所能表 现的多样性货币策略的生动体现。 9.3.3 一致性机制创新:peercoin,Myriad,Blackcoin,vericoin 和 NXT比特币的一致性机制建立在基于SHA256算法的工作量证明之上。第一款引入scrypt算法作为一致性机制的竞争币是为了便于CPU挖矿,避免 ASIC矿机可能导致的算力集中化的问题。在那之后,对于一致性机制的创新一直很活跃。诸多竞争币陆续引进了包括scrypt,scrypt-N, Skein, Groestl, SHA3, X11, Blake 在内的算法来实现工作量证明的一致性机制。而在2013年,作为工作量证明的一种替代机制——权益证明的出现,成为现代竞争币的基础。 PeercoinPeercoin于2012年8月发布,是首款工作量证明和权益证明混用的竞争币。 MyriadMyriad于2014年2月发布,值得一提的是,它同时使用5种工作量证明算法(HA256d, Scrypt, Qubit, Skein, or Myriad-Groestl),根据参与矿工的情况动态选择。这是为了让整个Myriad系统不受集中化的ASIC矿机的影响,同时也加强了其抵御一致 性攻击的能力。 BlackcoinBlackcoin发布于2014年2月,使用的是权益证明的一致性机制。同时,它引入的可以根据受益自动切换到不同竞争币的“多矿池”机制也值得一提。 VeriCoinVeriCoin于2014年5月发布。它使用了权益证明机制,并辅以随着市场供需关系动态调整的利率。它也是首款可以直接在钱包中兑换比特币支付的竞争币。 NXTNXT(发音同Next)是一种“纯”权益证明的竞争币,它甚至不采用工作量证明的挖矿机制。NXT是一款完全自己实现的加密货币,并非衍生自比特 币或其他竞争币。NXT具有很多先进的功能,包括名字注册、去中心化资产交易、、集成的去中心化加密信息和权益委托。NXT的拥趸称NXT为新一代加密货 币或者或者加密货币2.0。 9.3.4 多目的挖矿创新:Primecoin, Curecoin, Gridcoin比特币的工作量证明机制只有一个目的:维护比特币系统的安全。跟维护一个传统货币系统比起来,挖矿的成本并不高。然而,某些批评者认为某些批评者认 为挖矿这一行为是一种浪费。新一代的加密货币试图解决这个争议。多目的挖矿算法就是为了解决工作量证明导致的“浪费”问题而出现的。多目的挖矿在为货币系 统的安全加入额外需求的同时,也为该系统的供需关系加入了额外的变量。 PrimecoinPrimecoin是在2013年7月发布的。它它的工作量证明算法可以搜索质数,计算孪生素数表。素数在科研领域有广泛的应用。 Primecoin的块链中包含其发现的质数,因此Primecoin的块链在用于维护公共交易账簿的同时,还会产生一份公开的科学发现(素数表)。 CurecoinCurecoin于2013年5月发布。通过Folding@Home项目,它将SHA256工作量证明算法和蛋白质褶皱结构的研究结合了起来。蛋白质褶皱研究需要对蛋白质进行生化反应的模拟,用于发现治愈疾病的新药,但这一过程需要大量的计算资源。 GridcoinGridcoin是2013年10月对外发布的。它结合了以scrypt为基础的工作量证明算法和参与BOINC计算项目的补贴机制。BOINC ——伯克利开发网络计算系统——是一项用于科学研究网格计算的开放协议。Gridcoin网络输出算力给BOINC这个计算平台,而不是自己直接用算力去 解决某一个具体的科学问题。 9.3.5 致力于匿名性的竞争币:CryptoNote, Bytecoin, Monero, Zerocash/Zerocoin, Darkcoin比特币一直被误解为匿名货币。事实上,将个人和比特币地址关联起来,是一件相对容易的事情。利用大数据分析可以很容易地得到某一比特币地址的消费习 惯。一些竞争币试图通过增强匿名性来解决这个问题。最初尝试的是Zerocoin,它是一种建立在比特币协议之上的元币协议,最早发布于2013 IEEE安全隐私讨论会上。截至本书完稿时,基于这个协议的Zerocash的竞争币系统还在开发当中。匿名性的另一种实现名为CryptoNote,初 见于2013年10月的一篇论文。CryptoNote是一种由多个竞争币一起实现的基础技术,稍后将重点讨论。除了上述两种实现之外,还有一些其他的独 立的匿名币,比如利用影子地址和交易混淆来达到匿名性目的的Darkcoin。 Zerocoin/ZerocashZerocoin是 2013 年由Johns Hopkins发表的电子货币匿名性的一种理论实现。截至本书完稿时,基于这一理论的Zerocash的竞争币系统还在开发当中。 CryptoNoteCryptoNote是一种提供了电子货币基础的匿名性的参考实现,于2013年10月发布。它可以被克隆继而衍生出其他实现,并且内建了一个周期 性的重置机制使其不能用作货币。很多竞争币是基于CryptoNote实现的。比如Bytecoin (BCN), Aeon (AEON), Boolberry (BBR), duckNote (DUCK), Fantomcoin (FCN), Monero (XMR), MonetaVerde (MCN), 和Quazarcoin(QCN)。值得指出的是,CryptoNote是一个没有借鉴比特币的完全独立的实现。 BytecoinBytecoin是CryptoNote的第一个实现,基于CryptoNote技术提供切实可行的匿名货币方案。Bytecoin于2012年发 布。这里要留意一下,在基于CryptoNote的Bytecoin发布之前,有一个名字同样为Bytecoin的电子货币,货币符号为BTE,而基础 CryptoNote的Bytecoin的货币符号为BCN。Bytecoin使用了基于Cryptonight的工作量证明机制,每个实例需要至少 2MB的RAM,这使得GPU和ASIC矿机无法在Bytecoin网络中运行。Bytecoin继承了CryptoNote的环签名、不可链接交易和块 链抗分析匿名性等机制。 MoneroMonero是CryptoNote的另一个实现。其货币曲线比Bytecoin稍显平缓,在系统运行的最开始四年发行 80% 的货币。它提供一些基于 CryptoNote 的匿名性特性。 DarkcoinDarkcoin在2014年1月发布。Darkcoin通过一个名为DarkSend的混淆协议来实现匿名货币。值得一提的是,Darkcoin 在工作量证明算法中使用了11轮不同的哈希函数(blake, bmw, groestl, jh, kECCak, skein, luffa, cubehash, shavite, simd, echo) 9.4 非货币型竞争区块链非货币型竞争币区块链是区块链设计模式的另类实现,并不主要作为货币使用。当然不少这种区块链的确含有货币,只不过它们的货币仅是一种象征,用于分配其他东西,比如一种资源或者一份合约。换句话说,货币并不是非货币型竞争币区块链的要点,仅仅是一种次要特征。 9.4.1 域名币域名币是比特币源代码的首个克隆产物,它是一种使用区块链的去中心化平台,用来注册和转让键-值对。域名币支持全球的域-名注册,类似因特网上的域 -名注册系统。目前域名币作为根域名.bit的替代性域名服务(DNS)使用。域名币也可以用来注册其他命名空间下的名称和键-值对,例如存储邮件地址、 密钥、SSL证书、文件签名、投票系统和股票凭证之类,以及许多其他应用。
这个例子将会把域名 www.mastering-bitcoin.bit 映射到1.2.3.4这个IP地址上,返回的哈希数则是交易ID,能够用于追踪此次注册。你可以运行name_list命令来查看自己名下注册了哪些域名:
每生成36,000个区块(大约200到250天),域名币上的注册就需要更新一次。不过name_update命令不 收取费用,因此续约域名是免费的。也有第三方提供商提供一个网页界面来帮助处理注册、自动续约及更新等事宜,当然,这要花费你少许费用。使用第三方提供商 的好处是你不需要运行一个域名币客户端了,坏处是你失去了对域名币提供去中心化的域名注册服务的自主控制。 9.4.2 BitmessageBitmessage是一个实现了去中心化安全消息服务的比特币竞争币区块链,其本质上是一个无服务器的加密电子邮件系统。Bitmessage可 以让用户通过一个Bitmessage地址来编写和发送消息。这些消息的运作方式与比特币交易大致相同,但区别在于消息是短暂瞬态的——如果超过两天还没 被传送至目的节点,消息将会丢失。发送方和接收方都是假名,除了一个bitmessage地址外,他们没有其他的身份标识。但发送方和接收方有严格的身份 验证,这意味着不会出现“欺骗”消息。Bitmessage都是经加密再发送给接收方,Bitmessage网络也因此可以抵御全面监视。除非网络偷听者 破坏了接收方的设备,否则他们无法截取邮件消息。 9.4.3 以太坊以太坊是一种图灵完备的平台,基于区块链账簿,用于合约的处理和执行。它不是比特币的一个克隆,而是完完全全独立的一种设计和实现。以太坊内置一种 叫做ether的货币,该货币是付合约执行之费用所必须的。以太坊区块链记录的东西叫做合约,所谓合约,就是一种低级二进制码,也是一种图灵完备语言。本 质上,合约其实是运行在以太坊系统中各个节点上的程序。这些程序可以存储数据、支付及收取、存储ether 以及执行无穷范围(因此才叫图灵完备)的计算行为,在系统中充当去中心化的自治软件代理。
9.5 加密货币的未来总体来看,加密货币的未来甚至比特币还要光明。这是因为,比特币引入了这样一种全新的形式,那就是去中心化的组织和共识,而且这种形式已经催生了大 量不可思议的创新。这些创新很有可能影响到社会中相当广泛的行业,从分布式系统科学到金融、经济、货币、中央银行以及企业管理,不一而足。在以前,很多人 类活动都需要一个中心化的机构或组织来实现权威或可信控制点的功能,现在,这些都可以去中心化了。区块链和共识系统的发明,还会显著降低大型系统在组织及 协调上的花销,同时也将消除权力攫取、腐败及管制俘获的可趁之机。 ==目录== 简介 |
精通比特币 – 第8章 区块链分叉、矿池、共识攻击
8.10 区块链的组装与选择比特币去中心化的共识机制的最后一步是将区块集合至有最大工作量证明的链中。一旦一个节点验证了一个新的区块,它将尝试将新的区块连接到到现存的区块链,将它们组装起来。 8.10.1 区块链分叉因为区块链是去中心化的数据结构,所以不同副本之间不能总是保持一致。区块有可能在不同时间到达不同节点,导致节点有不同的区块链视角。解决的办法是,每一个节点总是选择并尝试延长代表累计了最大工作量证明的区块链,也就是最长的或最大累计难度的链。节点通过将记录在每个区块中的难度加总起来,得到建立这个链所要付出的工作量证明的总量。只要所有的节点选择最长累计难度的区块链,整个比特币网络最终会收敛到一致的状态。分叉即在不同区块链间发生的临时差异,当更多的区块添加到了某个分叉中,这个问题便会迎刃而解。
当有两个候选区块同时想要延长最长区块链时,分叉事件就会发生。正常情况下,分叉发生在两名矿工在较短的时间内,各自都算得了工作量证明解的时候。两个矿工在各自的候选区块一发现解,便立即传播自己的“获胜”区块到网络中,先是传播给邻近的节点而后传播到整个网络。每个收到有效区块的节点都会将其并入并延长区块链。如果该节点在随后又收到了另一个候选区块,而这个区块又拥有同样父区块,那么节点会将这个区块连接到候选链上。其结果是,一些节点收到了一个候选区块,而另一些节点收到了另一个候选区块,这时两个不同版本的区块链就出现了。
假设有这样一种情况,一个在加拿大的矿工发现了“红色”区块的工作量证明解,在“蓝色”的父区块上延长了块链。几乎同一时刻,一个澳大利亚的矿工找到了“绿色”区块的解,也延长了“蓝色”区块。那么现在我们就有了两个区块:一个是源于加拿大的“红色”区块;另一个是源于澳大利亚的“绿色”。这两个区块都是有效的,均包含有效的工作量证明解并延长同一个父区块。这个两个区块可能包含了几乎相同的交易,只是在交易的排序上有些许不同。
从那时起,比特币网络中邻近(网络拓扑上的邻近,而非地理上的)加拿大的节点会首先收到“红色”区块,并建立一个最大累计难度的区块,“红色”区块为这个链的最后一个区块(蓝色-红色),同时忽略晚一些到达的“绿色”区块。相比之下,离澳大利亚更近的节点会判定“绿色”区块胜出,并以它为最后一个区块来延长区块链(蓝色-绿色),忽略晚几秒到达的“红色”区块。那些首先收到“红色”区块的节点,会即刻以这个区块为父区块来产生新的候选区块,并尝试寻找这个候选区块的工作量证明解。同样地,接受“绿色”区块的节点会以这个区块为链的顶点开始生成新块,延长这个链。
所有在上一轮选择“绿色”区块为胜出者的节点会直接将这条链延长一个区块。然而,那些选择“红色”区块为胜出者的节点现在会看到两个链:“蓝色-绿色-粉色”和“蓝色-红色”。如图8-6所示,这些节点会根据结果将“蓝色-绿色-粉色”这条链设置为主链,将“蓝色-红色”这条链设置为备用链。这些节点接纳了新的更长的链,被迫改变了原有对区块链的观点,这就叫做链的重新共识。因为“红”区块做为父区块已经不在最长链上,导致了他们的候选区块已经成为了“孤块”,所以现在任何原本想要在“蓝色-红色”链上延长区块链的矿工都会停下来。全网将“蓝色-绿色-粉色”这条链识别为主链,“粉色”区块为这条链的最后一个区块。全部矿工立刻将他们产生的候选区块的父区块切换为“粉色”,来延长“蓝色-绿色-粉色”这条链。
从理论上来说,两个区块的分叉是有可能的,这种情况发生在因先前分叉而相互对立起来的矿工,又几乎同时发现了两个不同区块的解。然而,这种情况发生的几率是很低的。单区块分叉每周都会发生,而双块分叉则非常罕见。 8.11 挖矿和算力竞赛比特币挖矿是一个极富竞争性的行业。自从比特币存在开始,每年比特币算力都成指数增长。一些年份的增长还体现出技术的变革,比如在2010年和2011年,很多矿工开始从使用CPU升级到使用GPU,进而使用FGPA(现场可编程门阵列)挖矿。在2013年,ASIC挖矿的引入,把SHA256算法直接固化在挖矿专用的硅芯片上,引起了算力的另一次巨大飞跃。一台采用这种芯片的矿机可以提供的算力,比2010年比特币网络的整体算力还要大。
随着比特币挖矿算力的爆炸性增长,与之匹配的难度也相应增长。图8-8中的相对难度值显示了当前难度与最小难度(第一个块的难度)的比例。
近两年,ASIC芯片变得更加密集,特征尺寸接近芯片制造业前沿的22纳米。挖矿的利润率驱动这个行业以比通用计算更快的速度发展。目前,ASIC制造商的目标是超越通用CPU芯片制造商,设计特征尺寸为16纳米的芯片。对比特币挖矿而言,已经没有更多飞跃的空间,因为这个行业已经触及了摩尔定律的最前沿。摩尔定律指出计算能力每18个月增加一倍。尽管如此,随着更高密度的芯片和数据中心的部署竞赛,网络算力继续保持同步的指数增长。现在的竞争已经不再是比较单一芯片的能力,而是一个矿场能塞进多少芯片,并处理好散热和供电问题。 8.11.1 随机值升位方案2012年以来,比特币挖矿发展出一个解决区块头基本结构限制的方案。在比特币的早期,矿工可以通过遍历随机数(Nonce)获得符合要求的hash来挖出一个块。难度增长后,矿工经常在尝试了40亿个值后仍然没有出块。然而,这很容易通过读取块的时间戳并计算经过的时间来解决。因为时间戳是区块头的一部分,它的变化可以让矿工用不同的随机值再次遍历。当挖矿硬件的速度达到了4GH/秒,这种方法变得越来越困难,因为随机数的取值在一秒内就被用尽了。当出现ASIC矿机并很快达到了TH/秒的hash速率后,挖矿软件为了找到有效的块,需要更多的空间来储存nonce值。可以把时间戳延后一点,但将来如果把它移动得太远,会导致区块变为无效。区块头需要一个新的“差异性”的信息来源。解决方案是使用coinbase交易作为额外的随机值来源,因为coinbase脚本可以储存2-100字节的数据,矿工们开始使用这个空间作为额外随机值的来源,允许他们去探索一个大得多的区块头值范围来找到有效的块。这个coinbase交易包含在merkle树中,这意味着任何coinbase脚本的变化将导致Merkle根的变化。8个字节的额外随机数,加上4个字节的“标准”随机数,允许矿工每秒尝试296(8后面跟28个零)种可能性而无需修改时间戳。如果未来矿工可以尝试所有的可能性,他们还可以通过修改时间戳来解决。同样,coinbase脚本中也有更多额外的空间可以为将来随机数的扩展做准备。 8.11.2 矿池在这个激烈竞争的环境中,个体矿工独立工作(也就是solo挖矿)没有一点机会。他们找到一个区块以抵消电力和硬件成本的可能性非常小,以至于可以称得上是赌博,就像是买彩票。就算是最快的消费型ASIC也不能和那些在巨大机房里拥有数万芯片并靠近水电站的商业矿场竞争。现在矿工们合作组成矿池,汇集数以千计参与者们的算力并分享奖励。通过参加矿池,矿工们得到整体回报的一小部分,但通常每天都能得到,因而减少了不确定性。 8.11.2.1 托管矿池大部分矿池是“托管的”,意思是有一个公司或者个人经营一个矿池服务器。矿池服务器的所有者叫矿池管理员,同时他从矿工的收入中收取一个百分比的费用。 8.11.2.2 P2P矿池托管矿池存在管理人作弊的可能,管理人可以利用矿池进行双重支付或使区块无效。(参见“8.12 共识攻击”) 此外,中心化的矿池服务器代表着单点故障。如果因为拒绝服务攻击服务器挂了或者被减慢,池中矿工就不能采矿。在2011年,为了解决由中心化造成的这些问题,提出和实施了一个新的矿池挖矿方法。P2Pool是一个点对点的矿池,没有中心管理人。 8.12 共识攻击比特币的共识机制指的是,被矿工(或矿池)试图使用自己的算力实行欺骗或破坏的难度很大,至少理论上是这样。就像我们前面讲的,比特币的共识机制依赖于这样一个前提,那就是绝大多数的矿工,出于自己利益最大化的考虑,都会通过诚实地挖矿来维持整个比特币系统。然而,当一个或者一群拥有了整个系统中大量算力的矿工出现之后,他们就可以通过攻击比特币的共识机制来达到破坏比特币网络的安全性和可靠性的目的。 ==目录== 简介 |
精通比特币 – 第8章 挖矿与共识
第8章 挖矿与共识8.1 简介挖矿是增加比特币货币供应的一个过程。挖矿同时还保护着比特币系统的安全,防止欺诈交易,避免“双重支付”,“双重支付”是指多次花费同一笔比特币。矿工们通过为比特币网络提供算力来换取获得比特币奖励的机会。 8.1.1 比特币经济学和货币创造通过创造出新区块,比特币以一个确定的但不断减慢的速率被铸造出来。大约每十分钟产生一个新区块,每一个新区块都伴随着一定数量从无到有的全新比特 币。每开采210,000个块,大约耗时4年,货币发行速率降低50%。在比特币运行的第一个四年中,每个区块创造出50个新比特币。
例8-2显示了这个脚本的运行结果。
总量有限并且发行速度递减创造了一种抗通胀的货币供应模式。法币可被中央银行无限制地印刷出来,而比特币永远不会因超额印发而出现通胀。
8.2 去中心化共识在上一章中我们了解了区块链。可以将区块链看作一本记录所有交易的公开总帐簿(列表),比特币网络中的每个参与者都把它看作一本所有权的权威记录。 8.3 交易的独立校验在第5章中,我们知道了钱包软件通过收集UTXO、提供正确的解锁脚本、构造支付给接收者的输出这一系列的方式来创建交易。产生的交易随后将被发送到比特币网络临近的节点,从而使得该交易能够在整个比特币网络中传播。 8.4 挖矿节点在比特币网络中,一些节点被称为专业节点矿工。第1章中,我们介绍了Jing,在中国上海的计算机工程专业学生,他就是一位矿工。Jing通过矿机 挖矿获得比特币,矿机是专门设计用于挖比特币的计算机硬件系统。Jing的这台专业挖矿设备连接着一个运行完整比特币节点的服务器。与Jing不同,一些 矿工是在没有完整节点的条件下进行挖矿,正如我们在“8.11.2 矿池”一节中所述的。与其他任一完整节点相同,Jing的节点在比特币网络中进行接收和传播未确认交易记录。然而,Jing的节点也能够在新区块中整合这些交易记录。 8.5 整合交易至区块验证交易后,比特币节点会将这些交易添加到自己的内存池中。内存池也称作交易池,用来暂存尚未被加入到区块的交易记录。与其他节点一样,Jing的节点会收集、验证并中继新的交易。而与其他节点不同的是,Jing的节点会把这些交易整合到一个候选区块中。 8.5.1 交易块龄,矿工费和优先级Jing的比特币节点需要为内存池中的每笔交易分配一个优先级,并选择较高优先级的交易记录来构建候选区块。交易的优先级是由交易输入所花费的 UTXO的“块龄”决定,交易输入值高、“块龄”大的交易比那些新的、输入值小的交易拥有更高的优先级。如果区块中有足够的空间,高优先级的交易行为将不 需要矿工费。
在这个等式中,交易输入的值是由比特币单位“聪”(100万分之1个比特币)来表示的。UTXO的“块龄”是自该UTXO被记录到区块链为止所经历过的区块数,即这个UTXO在区块链中的深度。交易记录的大小由字节来表示。
区块中用来存储交易的前50K字节是保留给较高优先级交易的。Jing的节点在填充这50K字节的时候,会优先考虑这些最高优先级的交易,不管它们是否包含了矿工费。这种机制使得高优先级交易即便是零矿工费,也可以优先被处理。
8.5.2 创币交易区块中的第一笔交易是笔特殊交易,称为创币交易或者coinbase交易。这个交易是由Jing的节点构造并用来奖励矿工们所做的贡献的。Jing 的节点会创建“向Jing的地址支付25.09094928个比特币”这样一个交易,把生成交易的奖励发送到自己的钱包。Jing挖出区块获得的奖励金额 是coinbase奖励(25个全新的比特币)和区块中全部交易矿工费的总和。如例8-4所示:
例8-4 创币交易
与常规交易不同,创币交易没有输入,不消耗UTXO。它只包含一个被称作coinbase的输入,仅仅用来创建新的比特 币。创币交易有一个输出,支付到这个矿工的比特币地址。创币交易的输出将这25.09094928个比特币发送到矿工的比特币地址,如本例所示的 1MxTkeEP2PmHSMze5tUZ1hAV3YTKu2Gh1N。 8.5.3 Coinbase奖励与矿工费为了构造创币交易,Jing的节点需要计算矿工费的总额,将这418个已添加到区块交易的输入和输出分别进行加总,然后用输入总额减去输出总额得到矿工费总额,公式如下:
在区块277,316中,矿工费的总额是0.09094925个比特币。
变量nSubsidy表示初始奖励额,值为 8.5.4 创币交易的结构经过计算,Jing的节点构造了一个创币交易,支付给自己25.09094928枚比特币。
表8-2 生成交易输入的结构
在创币交易中,“交易哈希”字段32个字节全部填充0,“交易输出索引”字段全部填充0xFF(十进制的255),这两个字段的值表示不引用UTXO。“解锁脚本”由coinbase数据代替,数据可以由矿工自定义。 8.5.5 Coinbase数据创币交易不包含“解锁脚本“(又称作
在例8-7中,我们使用GNU C++编译器编译源代码并运行得到的可执行文件
8.6 构造区块头为了构造区块头,挖矿节点需要填充六个字段,如表8-3中所示。
在区块277,316被挖出的时候,区块结构中用来表示版本号的字段值为2,长度为4字节,以小段格式编码值为0x20000000。接着,挖矿节 点需要填充“前区块哈希”,在本例中,这个值为Jing的节点从网络上接收到的区块277,315的区块头哈希值,它是区块277316候选区块的父区 块。区块277,315的区块头哈希值为:
为了向区块头填充merkle根字段,要将全部的交易组成一个merkle树。创币交易作为区块中的首个交易,后将余下 的418笔交易添至其后,这样区块中的交易一共有419笔。在164页,我们已经见到过“Merkle树”,树中必须有偶数个叶子节点,所以需要复制最后 一个交易作为第420个节点,每个节点是对应交易的哈希值。这些交易的哈希值逐层地、成对地组合,直到最终组合并成一个根节点。merkle数的根节点将 全部交易数据摘要为一个32字节长度的值,例8-3中merkel根的值如下:
挖矿节点会继续添加一个4字节的时间戳,以Unix纪元时间编码,即自1970年1月1日0点到当下总共流逝的秒数。本例中的1388185914对应的时间是2013年12月27日,星期五,UTC/GMT。 8.7 构建区块既然Jing的节点已经构建了一个候选区块,那么就轮到Jing的矿机对这个新区块进行“挖掘”,求解工作量证明算法以使这个区块有效。从本书中我们已经学习了比特币系统中不同地方用到的哈希加密函数。比特币挖矿过程使用的是SHA256哈希函数。 8.7.1 工作量证明算法哈希函数的输入数据的长度是任意的,将产生一个长度固定且绝不雷同的值,可将其视为输入的数字指纹。对于特定输入,哈希的结果每次都一样,任何实现 相同哈希函数的人都可以计算和验证。一个加密哈希函数的主要特征就是不同的输入几乎不可能出现相同的数字指纹。因此,相对于随机选择输入,有意地选择输入 去生成一个想要的哈希值几乎是不可能的。
在例8-8中,
执行这个脚本就能生成这些只是末尾数字不同的语句的哈希值。例8-10 中显示了我们只是增加了这个数字,却得到了非常不同的哈希值。
每个语句都生成了一个完全不同的哈希值。它们看起来是完全随机的,但你在任何计算机上用Python执行上面的脚本都能重现这些完全相同的哈希值。
你可以任意调整难度值(按二进制bit数来设定,即哈希值开头多少个bit必须是0)。然后执行代码,看看在你的计算机上求解需要多久。在例8-12中,你可以看到该程序在一个普通笔记本电脑上的执行情况。
你可以看出,随着难度位一位一位地增加,查找正确结果的时间会呈指数级增长。如果你考虑整个256bit数字空间,每次 要求多一个0,你就把哈希查找空间缩减了一半。在例8-12中,为寻找一个nonce使得哈希值开头的26位值为0,一共尝试了8千多万次。即使家用笔记 本每秒可以达270,000多次哈希计算,这个查找依然需要6分钟。 8.7.2 难度表示在例8-3中,我们在区块中看到难度目标,其被标为”难度位”或简称”bits”。在区块277,316中,它的值为 0x1903a30c。这个标记的值被存为系数/指数格式,前两位十六进制数字为幂,接下来得六位为系数。在这个区块里,0x19为幂,而 0x03a30c 为系数。
由此公式及难度位的值 0x1903a30c,可得:
按十进制计算为:
转化为十六进制后为:
也就是说高度为277,316的有效区块的头信息哈希值是小于这个目标值的。这个数字的二进制表示中前60位都是0。在 这个难度上,一个每秒可以处理1万亿个哈希计算的矿工(1 tera-hash per second 或 1 TH/sec)平均每8,496个区块才能找到一个正确结果,换句话说,平均每59天,才能为某一个区块找到正确的哈希值。 8.7.3 难度目标与难度调整如前所述,目标决定了难度,进而影响求解工作量证明算法所需要的时间。那么问题来了:为什么这个难度值是可调整的?由谁来调整?如何调整?
例8-13展示了比特币核心客户端中的难度调整代码。
参数Interval(2,016区块)和TergetTimespan(1,209,600秒及两周) 的定义在文件chainparams.cpp中。
值得注意的是目标难度与交易的数量和金额无关。这意味着哈希算力的强弱,即让比特币更安全的电力投入量,与交易的数量完全无关。换句话说,当比特币 的规模变得更大,使用它的人数更多时,即使哈希算力保持当前的水平,比特币的安全性也不会受到影响。哈希算力的增加表明更多的人为得到比特币回报而加入了 挖矿队伍。只要为了回报,公平正当地从事挖矿的矿工群体保持足够的哈希算力,”接管”攻击就不会得逞,让比特币的安全无虞。 8.8 成功构建区块前面已经看到,Jing的节点创建了一个候选区块,准备拿它来挖矿。Jing有几个安装了ASIC(专用集成电路)的矿机,上面有成千上万个集成电 路可以超高速地并行运行SHA256算法。这些定制的硬件通过USB连接到他的挖矿节点上。接下来,运行在Jing的桌面电脑上的挖矿节点将区块头信息传 送给这些硬件,让它们以每秒亿万次的速度进行nonce测试。
而这个值小于难度目标值:
Jing的挖矿节点立刻将这个区块发给它的所有相邻节点。这些节点在接收并验证这个新区块后,也会继续传播此区块。当这 个新区块在网络中扩散时,每个节点都会将它作为区块277,316加到自身节点的区块链副本中。当挖矿节点收到并验证了这个新区块后,它们会放弃之前对构 建这个相同高度区块的计算,并立即开始计算区块链中下一个区块的工作。 8.9 校验新区块比特币共识机制的第三步是通过网络中的每个节点独立校验每个新区块。当新区块在网络中传播时,每一个节点在将它转发到其节点之前,会进行一系列的测 试去验证它。这确保了只有有效的区块会在网络中传播。独立校验还确保了诚实的矿工生成的区块可以被纳入到区块链中,从而获得奖励。行为不诚实的矿工所产生 的区块将被拒绝,这不但使他们失去了奖励,而且也浪费了本来可以去寻找工作量证明解的机会,因而导致其电费亏损。 ==目录== 简介 |
精通比特币 – 第7章 区块链
第7章 区块链7.1 简介区块链是由包含交易信息的区块从后向前有序链接起来的数据结构。它可以被存储为flat file(一种包含没有相对关系记录的文件),或是存储在一个简单数据库中。比特币核心客户端使用Google的LevelDB数据库存储区块链元数据。区块被从后向前有序地链接在这个链条里,每个区块都指向前一个区块。区块链经常被视为一个垂直的栈,第一个区块作为栈底的首区块,随后每个区块都被放置在其他区块之上。用栈来形象化表示区块依次堆叠这一概念后,我们便可以使用一些术语,例如:“高度”来表示区块与首区块之间的距离;以及“顶部”或“顶端”来表示最新添加的区块。 7.2 区块结构区块是一种被包含在公开账簿(区块链)里的聚合了交易信息的容器数据结构。它由一个包含元数据的区块头和紧跟其后的构成区块主体的一长串交易组成。区块头是80字节,而平均每个交易至少是250字节,而且平均每个区块至少包含超过500个交易。因此,一个包含所有交易的完整区块比区块头的1000倍还要大。表7-1描述了一个区块结构。
7.3 区块头区块头由三组区块元数据组成。首先是一组引用父区块哈希值的数据,这组元数据用于将该区块与区块链中前一区块相连接。第二组元数据,即难度、时间戳和nonce,与挖矿竞争相关,详见第8章。第三组元数据是merkle树根(一种用来有效地总结区块中所有交易的数据结构)。表7-2描述了区块头的数据结构。
Nonce、难度目标和时间戳会用于挖矿过程,更多细节将在第8章讨论。 7.4 区块标识符:区块头哈希值和区块高度区块主标识符是它的加密哈希值,一个通过SHA256算法对区块头进行二次哈希计算而得到的数字指纹。产生的32字节哈希值被称为区块哈希值,但是更准确的名称是:区块头哈希值,因为只有区块头被用于计算。例如:000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f是第一个比特币区块的区块哈希值。区块哈希值可以唯一、明确地标识一个区块,并且任何节点通过简单地对区块头进行哈希计算都可以独立地获取该区块哈希值。
7.5 创世区块区块链里的第一个区块创建于2009年,被称为创世区块。它是区块链里面所有区块的共同祖先,这意味着你从任一区块,循链向后回溯,最终都将到达创世区块。
你可以在任何区块浏览网站搜索这个区块哈希值,如blockchain.info,你会发现一个用包含这个哈希值的链接来描述这一区块内容的页面:
创世区块包含一个隐藏的信息。在其Coinbase交易的输入中包含这样一句话“The Times 03/Jan/2009 Chancellor on brink of second bailout forbanks.”这句话是泰晤士报当天的头版文章标题,引用这句话,既是对该区块产生时间的说明,也可视为半开玩笑地提醒人们一个独立的货币制度的重要性,同时告诉人们随着比特币的发展,一场前所未有的世界性货币革命将要发生。该消息是由比特币的创立者中本聪嵌入创世区块中。 7.6 区块的连接比特币的完整节点保存了区块链从创世区块起的一个本地副本。随着新的区块的产生,该区块链的本地副本会不断地更新用于扩展这个链条。当一个节点从网络接收传入的区块时,它会验证这些区块,然后链接到现有的区块链上。为建立一个连接,一个节点将检查传入的区块头并寻找该区块的“父区块哈希值”。
对于这一新的区块,节点会在“父区块哈希值”字段里找出包含它的父区块的哈希值。这是节点已知的哈希值,也就是第277314块区块的哈希值。故这个区块是这个链条里的最后一个区块的子区块,因此现有的区块链得以扩展。节点将新的区块添加至链条的尾端,使区块链变长到一个新的高度277,315。图7-1显示了通过“父区块哈希值”字段进行连接三个区块的链。
7.7 Merkle 树区块链中的每个区块都包含了产生于该区块的所有交易,且以Merkle树表示。
通过串联相邻叶子节点的哈希值然后哈希之,这对叶子节点随后被归纳为父节点。 例如,为了创建父节点HAB,子节点A和子节点B的两个32字节的哈希值将被串联成64字节的字符串。随后将字符串进行两次哈希来产生父节点的哈希值:
继续类似的操作直到只剩下顶部的一个节点,即Merkle根。产生的32字节哈希值存储在区块头,同时归纳了四个交易的所有数据。
因为Merkle树是二叉树,所以它需要偶数个叶子节点。如果仅有奇数个交易需要归纳,那最后的交易就会被复制一份以构成偶数个叶子节点,这种偶数个叶子节点的树也被称为平衡树。如图7-3所示,C节点被复制了一份。
由四个交易构造Merkle树的方法同样适用于从任意交易数量构造Merkle树。在比特币中,在单个区块中有成百上千的交易是非常普遍的,这些交易都会采用同样的方法归纳起来,产生一个仅仅32字节的数据作为Merkle根。在图7-4中,你会看见一个从16个交易形成的树。需要注意的是,尽管图中的根看起来比所有叶子节点都大,但实际上它们都是32字节的相同大小。无论区块中有一个交易或者有十万个交易,Merkle根总会把所有交易归纳为32字节。
为了证明区块中存在某个特定的交易,一个节点只需要计算log2(N)个32字节的哈希值,形成一条从特定交易到树根的认证路径或者Merkle路径即可。随着交易数量的急剧增加,这样的计算量就显得异常重要,因为相对于交易数量的增长,以基底为2的交易数量的对数的增长会缓慢许多。这使得比特币节点能够高效地产生一条10或者12个哈希值(320-384字节)的路径,来证明了在一个巨量字节大小的区块中上千交易中的某笔交易的存在。
例7-1中的代码借用libbitcoin库中的一些辅助程序,演示了从叶子节点哈希至根创建整个Merkle树的过程。
例7-2展示了编译以及运行上述代码后的结果。
Merkle树的高效随着交易规模的增加而变得异常明显。表7-3展示了为了证明区块中存在某交易而所需转化为Merkle路径的数据量。
依表可得,当区块大小由16笔交易(4KB)急剧增加至65,535笔交易(16MB)时,为证明交易存在的Merkle路径长度增长极其缓慢,仅仅从128字节到512字节。有了Merkle树,一个节点能够仅下载区块头(80字节/区块),然后通过从一个满节点回溯一条小的Merkle路径就能认证一笔交易的存在,而不需要存储或者传输大量区块链中大多数内容,这些内容可能有几个G的大小。这种不需要维护一条完整的区块链的节点,又被称作简单支付验证(SPV)节点,它不需要下载整个区块而通过Merkle路径去验证交易的存在。 7.8 Merkle树和简单支付验证(SPV)Merkle树被SPV节点广泛使用。SPV节点不保存所有交易也不会下载整个区块,仅仅保存区块头。它们使用认证路径或者Merkle路径来验证交易存在于区块中,而不必下载区块中所有交易。 ==目录== 简介 |
精通比特币 – 第6章 比特币网络
第6章 比特币网络6.1 P2P网络架构比特币采用了基于国际互联网(Internet)的P2P(peer-to-peer)网络架构。P2P是指位于同一网络中的每台计算机都彼此对等,各个节点共同提供网络服务,不存在任何“特殊”节点。每个网络节点以“扁平(flat)”的拓扑结构相互连通。在P2P网络中不存在任何服务端(server)、中央化的服务、以及层级结构。P2P网络的节点之间交互运作、协同处理:每个节点在对外提供服务的同时也使用网络中其他节点所提供的服务。P2P网络也因此具有可靠性、去中心化,以及开放性。早期的国际互联网就是P2P网络架构的一个典型用例:IP网络中的各个节点完全平等。当今的互联网架构具有分层架构,但是IP协议仍然保留了扁平拓扑的结构。在比特币之外,规模最大也最成功的P2P技术应用是在文件分享领域:Napster是该领域的先锋,BitTorrent是其架构的最新演变。 6.2 节点类型及分工尽管比特币P2P网络中的各个节点相互对等,但是根据所提供的功能不同,各节点可能具有不同的分工。每个比特币节点都是路由、区块链数据库、挖矿、钱包服务的功能集合。一个全节点(full node)包括如图6-1所示的四个功能:
每个节点都参与全网络的路由功能,同时也可能包含其他功能。每个节点都参与验证并传播交易及区块信息,发现并维持与对等节点的连接。在图6-1所示的全节点用例中,名为“网络路由节点”的橙色圆圈即表示该路由功能。
6.3 扩展比特币网络运行比特币P2P协议的比特币主网络由大约7000-10000个运行着不同版本比特币核心客户端(Bitcoin Core)的监听节点、以及几百个运行着各类比特币P2P协议的应用(例如BitcoinJ、Libbitcoin、btcd等)的节点组成。比特币P2P网络中的一小部分节点也是挖矿节点,它们竞争挖矿、验证交易、并创建新的区块。许多连接到比特币网络的大型公司运行着基于Bitcoin核心客户端的全节点客户端,它们具有区块链的完整拷贝及网络节点,但不具备挖矿及钱包功能。这些节点是网络中的边缘路由器(edge routers),通过它们可以搭建其他服务,例如交易所、钱包、区块浏览器、商家支付处理(merchant payment processing)等。
6.4 网络发现当新的网络节点启动后,为了能够参与协同运作,它必须发现网络中的其他比特币节点。新的网络节点必须发现至少一个网络中存在的节点并建立连接。由于比特币网络的拓扑结构并不基于节点间的地理位置,因此各个节点之间的地理信息完全无关。在新节点连接时,可以随机选择网络中存在的比特币节点与之相连。
当建立一个或多个连接后,新节点将一条包含自身IP地址的addr消息发送给其相邻节点。相邻节点再将此条addr消息依次转发给它们各自的相邻节点,从而保证新节点信息被多个节点所接收、保证连接更稳定。另外,新接入的节点可以向它的相邻节点发送getaddr消息,要求它们返回其已知对等节点的IP地址列表。通过这种方式,节点可以找到需连接到的对等节点,并向网络发布它的消息以便其他节点查找。图6-5描述了这种地址发现协议。
节点必须连接到若干不同的对等节点才能在比特币网络中建立通向比特币网络的种类各异的路径(path)。由于节点可以随时加入和离开,通讯路径是不可靠的。因此,节点必须持续进行两项工作:在失去已有连接时发现新节点,并在其他节点启动时为其提供帮助。节点启动时只需要一个连接,因为第一个节点可以将它引荐给它的对等节点,而这些节点又会进一步提供引荐。一个节点,如果连接到大量的其他对等节点,这既没必要,也是对网络资源的浪费。在启动完成后,节点会记住它最近成功连接的对等节点;因此,当重新启动后它可以迅速与先前的对等节点网络重新建立连接。如果先前的网络的对等节点对连接请求无应答,该节点可以使用种子节点进行重启动。
用户可以通过提供 6.5 全节点全节点是指维持包含全部交易信息的完整区块链的节点。更加准确地说,这样的节点应当被称为完整区块链节点”。在比特币发展的早期,所有节点都是全节点;当前的比特币核心客户端也是完整区块链节点。但在过去的两年中出现了许多新型客户端,它们不需要维持完整的区块链,而是作为轻量级客户端运行。在下面的章节里我们会对这些轻量级客户端进行详细介绍。 6.6 交换“库存清单”一个全节点连接到对等节点之后,第一件要做的事情就是构建完整的区块链。如果该节点是一个全新节点,那么它就不包含任何区块链信息,它只知道一个区块——静态植入在客户端软件中的创世区块。新节点需要下载从0号区块(创世区块)开始的数十万区块的全部内容,才能跟网络同步、并重建全区块链。
6.7 简易支付验证 (SPV)节点并非所有的节点都有能力储存完整的区块链。许多比特币客户端被设计成运行在空间和功率受限的设备上,如智能电话、平板电脑、嵌入式系统等。对于这样的设备,通过简化的支付验证(SPV)的方式可以使它们在不必存储完整区块链的情况下进行工作。这种类型的客端被称为SPV客户端或轻量级客户端。随着比特币的使用热潮,SPV节点逐渐变成比特币节点(尤其是比特币钱包)所采用的最常见的形式。
SPV节点使用的是一条getheaders消息,而不是getblocks消息来获得区块头。发出响应的对等节点将用一条headers消息发送多达2000个区块头。这一过程和全节点获取所有区块的过程没什么区别。SPV节点还在与对等节点的连接上设置了过滤器,用以过滤从对等节点发来的未来区块和交易数据流。任何目标交易都是通过一条getdata的请求来读取的。对等节点生成一条包含交易信息的tx消息作为响应。区块头的同步过程如图6-7所示。
由于SPV节点需要读取特定交易从而选择性地验证交易,这样就又产生了隐私风险。与全区块链节点收集每一个区块内的全部交易所不同的是,SPV节点对特定数据的请求可能无意中透露了钱包里的地址信息。例如,监控网络的第三方可以跟踪某个SPV节点上的钱包所请求的全部交易信息,并且利用这些交易信息把比特币地址和钱包的用户关联起来,从而损害了用户的隐私。 6.8 Bloom过滤器Bloom过滤器是一个允许用户描述特定的关键词组合而不必精确表述的基于概率的过滤方法。它能让用户在有效搜索关键词的同时保护他们的隐私。在SPV节点里,这一方法被用来向对等节点发送交易信息查询请求,同时交易地址不会被暴露。
Bloom过滤器数组里的每一个数的初始值为零。关键词被加到Bloom过滤器中之前,会依次通过每一个哈希函数运算一次。该输入经第一个哈希函数运算后得到了一个在1和N之间的数,它在该数组(编号依次为1至N)中所对应的位被置为1,从而把哈希函数的输出记录下来。接着再进行下一个哈希函数的运算,把另外一位置为1;以此类推。当全部M个哈希函数都运算过之后,一共有M个位的值从0变成了1,这个关键词也被“记录”在了Bloom过滤器里。
增加第二个关键是就是简单地重复之前的步骤。关键词依次通过各哈希函数运算之后,相应的位变为1,Bloom过滤器则记录下该关键词。需要注意的是,当Bloom过滤器里的关键词增加时,它对应的某个哈希函数的输出值的位可能已经是1了,这种情况下,该位不会再次改变。也就是说,随着更多的关键词指向了重复的位,Bloom过滤器随着位1的增加而饱和,准确性也因此降低了。该过滤器之所以是基于概率的数据结构,就是因为关键词的增加会导致准确性的降低。准确性取决于关键字的数量以及数组大小(N)和哈希函数的多少(M)。更大的数组和更多的哈希函数会记录更多的关键词以提高准确性。而小的数组及有限的哈希函数只能记录有限的关键词从而降低准确性。
为测试某一关键词是否被记录在某个Bloom过滤器中,我们将该关键词逐一代入各哈希函数中运算,并将所得的结果与原数组进行对比。如果所有的结果对应的位都变为了1,则表示这个关键词有可能已被该过滤器记录。之所以这一结论并不确定,是因为这些字节1也有可能是其他关键词运算的重叠结果。简单来说,Bloom过滤器正匹配代表着“可能是”。
另一方面,如果我们代入关键词计算后的结果某位为0,说明该关键词并没有被记录在过滤器里。负匹配的结果不是可能,而是一定。也就是说,负匹配代表着“一定不是”。
BIP0037里已经对Bloom过滤器的实现有所描述。请参见附录2或访问GitHub。 6.9 Bloom过滤器和存货更新Bloom过滤器被用来过滤SPV节点从对等节点里收到的交易信息。SPV会建立一个只能和SPV节点钱包里的地址匹配的过滤器。随后,SPV节点会向对等节点发送一条包含需在该连接中使用的过滤器的filterload消息。当过滤器建好之后,对等节点将每个交易的输出值代入过滤器中验证。那些正匹配的交易会被传送回SPV节点。 6.10 交易池比特币网络中几乎每个节点都会维护一份未确认交易的临时列表,被称为内存池或交易池。节点们利用这个池来追踪记录那些被网络所知晓、但还未被区块链所包含的交易。例如,保存用户钱包的节点会利用这个交易池来记录那些网络已经接收但还未被确认的、属于该用户钱包的预支付信息。 6.11 警告消息警告消息并不经常使用,但在大多数节点上都有此功能。警告消息是比特币的“紧急广播系统”,比特币核心开发人员可以借此功能给所有比特币节点发送紧急文本消息。这一功能是为了让核心开发团队将比特币网络的严重问题通知所有的比特币用户,例如一个需要用户采取措施的的严重bug。警告系统迄今为止只被用过几次,最严重的一次是在2013年,一个关键的数据库缺陷导致比特币区块链中出现了一个多区块分叉。 ==目录== 简介 |
精通比特币 – 第5章 交易
第5章 交易5.1 简介比特币交易是比特币系统中最重要的部分。根据比特币系统的设计原理,系统中任何其他的部分都是为了确保比特币交易可以被生成、能在比特币网络中得以传播和通过验证,并最终添加入全球比特币交易总账簿(比特币区块链)。比特币交易的本质是数据结构,这些数据结构中含有比特币交易参与者价值转移的相关信息。比特币区块链是全球复式记账总账簿,每个比特币交易都是在比特币区块链上的一个公开记录。 5.2 比特币交易的生命周期一笔比特币交易的生命周期起始于它被创建的那一刻,也就是诞生(origination)。 随后,比特币交易会被一个或者多个签名加密,这些签名标志着对该交易指向的比特币资金的使用许可。接下来,比特币交易被广播到比特币网络中。在比特币网络中,每一个节点(比特币交易参与者)验证、并将交易在网络中进行广播,直到这笔交易被网络中大多数节点接收。最终,比特币交易被一个挖矿节点验证,并被添加到区块链上一个记录着许多比特币交易的区块中。 5.2.1 创建比特币交易将一笔比特币交易理解成纸质支票或许有助于加深我们对它的理解。与支票相似,一笔比特币交易其实是一个有着货币转移目的的工具,这个工具只有在交易被执行时才会在金融体系中体现,而且交易发起人并不一定是签署该笔交易的人。 5.2.2 将比特币交易传送至比特币网络首先,一笔交易需要传递至比特币网络,才能被传播,也才能加入区块链中。本质上,一笔比特币交易只是300到400字节的数据,而且它们必须被发送到成千上万个比特币节点中的任意一个。只要发送者能使用多于一个比特币节点来确保这笔交易被传播,那么发送者并不需要信任用来传播该笔交易的单一节点。相应地,这些节点不需要信任发送者,也不用建立发送者的“身份档案”。由于这笔交易是经过签名且不含任何机密信息、私钥或密码,因此它可被任何潜在的便利网络公开地传播。信用卡交易包含敏感信息,而且依赖加密网络连接完成信息传输,但比特币交易可在任意网络环境下被发送。只要这笔交易可以到达能将它广播到比特币网络的比特币节点,这笔交易是如何被传输至第一个节点的并不重要。 5.2.3 比特币交易在比特币网络中的传播一旦一笔比特币交易被发送到任意一个连接至比特币网络的节点,这笔交易将会被该节点验证。如果交易被验证有效,该节点将会将这笔交易传播到这个节点所连接的其他节点;同时,交易发起者会收到一条表示交易成功的返回信息。如果这笔交易被验证为无效,这个节点会拒绝接受这笔交易且同时返回给交易发起者一条表示交易被拒绝的信息。 5.3 交易结构一笔比特币交易是一个含有输入值和输出值的数据结构,该数据结构植入了将一笔资金从初始点(输入值)转移至目标地址(输出值)的代码信息。比特币交易的输入值和输出值与账号或者身份信息无关。你应该将它们理解成一种被特定秘密信息锁定的一定数量的比特币。只有拥有者或知晓这个秘密信息的人可以解锁。一笔比特币交易包含一些字段,如表5-1所示。
5.4 交易的输出和输入比特币交易的基本单位是未经使用的一个交易输出,简称UTXO。UTXO是不能再分割、被所有者锁住或记录于区块链中的并被整个网络识别成货币单位的一定量的比特币货币。比特币网络监测着以百万为单位的所有可用的(未花费的)UTXO。当一个用户接收比特币时,金额被当作UTXO记录到区块链里。这样,一个用户的比特币会被当作UTXO分散到数百个交易和数百个区块中。实际上,并不存在储存比特币地址或账户余额的地点,只有被所有者锁住的、分散的UTXO。“一个用户的比特币余额”,这个概念是一个通过比特币钱包应用创建的派生之物。比特币钱包通过扫描区块链并聚合所有属于该用户的UTXO来计算该用户的余额。
一个UTXO可以是一“聪”的任意倍。就像美元可以被分割成表示两位小数的“分”一样,比特币可以被分割成表示八位小数的“聪”。尽管UTXO可以是任意值,但只要它被创造出来了,就像不能被切成两半的硬币一样不可再分了。如果一个UTXO比一笔交易所需量大,它仍会被当作一个整体而消耗掉,但同时会在交易中生成零头。例如,你有20比特币的UTXO并且想支付1比特币,那么你的交易必须消耗掉整个20比特币的UTXO并且产生两个输出:一个是支付了1比特币给接收人,另一个是支付19比特币的找零到你的钱包。这样的话,大部分比特币交易都会产生找零。
5.4.1 交易输出每一笔比特币交易创造输出,输出都会被比特币账簿记录下来。除特例之外(见“5.7.4 数据输出(OP_RETURN操作符)”),几乎所有的输出都能创造一定数量的可用于支付的比特币,也就是UTXO。这些UTXO被整个网络识别,并且所有者可在未来的交易中使用它们。给某人发送比特币实际上是创造新的UTXO,注册到那个人的地址,并且能被他用于新的支付。
在例5-1中,我们使用blockchain.info应用程序接口来查找特定地址的UTXO。
# 它返回一个JSON对象,其中包括一个包含着UTXO的“unspent_outputs”列表,就像这样:#{ "unspent_outputs":[ #{ # tx_hash":"ebadfaa92f1fd29e2fe296eda702c48bd11ffd52313e986e99ddad9084062167",# "tx_index":51919767,# "tx_output_n": 1,# "script":"76a9148c7e252f8d64b0b6e313985915110fcfefcf4a2d88ac",# "value": 8000000,# "value_hex": "7a1200",# "confirmations":28691# },# ...#]} resp = requests.get('https://blockchain.info/unspent?active=%s' % address)utxo_set = json.loads(resp.text)["unspent_outputs"]for utxo in utxo_set: print "%s:%d - %ld Satoshis" % (utxo['tx_hash'], utxo['tx_output_n'], utxo['val ue'])
运行脚本,我们将会得到“交易ID,冒号,特定UTXO的索引号,以及这个UTXO包含的聪的数额”的列表。在例5-2中,锁定脚本被省略了。
支付条件(障碍)交易输出把用聪表示的一定数量的比特币,和特定的定义了支付输出所必须被满足的条件的障碍,或者叫锁定脚本,关联到了一起。在大多数情况下,锁定脚本会把输出锁在一个特定的比特币地址上,从而把一定数量的比特币的所有权转移到新的所有者上。当Alice在Bob的咖啡店为一杯咖啡付款时,Alice的交易创造了0.015比特币的输出,在咖啡店的比特币地址上成为一种障碍,或者说是被锁在了咖啡店的比特币地址上。那0.015比特币输出被记录到区块链中,并且成为UTXO的一部分,也就是作为可用余额出现在Bob的钱包里。当Bob选择使用这笔款项进行支付时,他的交易会释放障碍,通过提供一个包含Bob私钥的解锁脚本来解锁输出。 5.4.2 交易输入简单地说,交易输入是指向UTXO的指针。它们指向特定的UTXO,并被交易哈希和在区块链中记录UTXO的序列号作为参考。若想支付UTXO,一个交易的输入也需要包含一个解锁脚本,用来满足UTXO的支付条件。解锁脚本通常是一个签名,用来证明对于在锁定脚本中的比特币地址拥有所有权。
如果我们不使用参数运行select-utxo.py脚本,它会试图为一笔五千五百万聪(0.55比特币)的付款构造一组UTXO。如果你提供一个指定的付款额作为参数,脚本会选择UTXO来完成指定的付款额。在例5-4中,我们运行脚本来试着完成一笔0.5比特币,或者说是五千万聪的付款。
一旦UTXO被选中,钱包会为每个UTXO生成包含签名的解锁脚本,由此让它们变得可以通过满足锁定脚本的条件来被支付。钱包把这些UTXO作为参考,并且连同解锁脚本一起作为输入加到交易中。表5-3展示了交易输入的结构。
序列号是用来覆盖在交易锁定时间之前失效的交易,这是一项目前没有在比特币中用到的功能。大多数交易把这个值设置成最大的整数(0xFFFFFFFF)并且被比特币网络忽略。如果一次交易有非零的锁定时间,那么它至少需要有一个序列号比0xFFFFFFFF低的输入来激活锁定时间。 5.4.3 交易费大多数交易包含交易费,这是为了在网络安全方面给比特币矿工一种补偿。在第8章中,对于挖矿、费用和矿工得到的奖励,有更详细的讨论。这一节解释交易费是如何被包含在日常交易中的。大多数钱包自动计算并计入交易费。但是,如果你编程构造交易,或者使用命令行接口,你必须手动计算并计入这些费用。 5.4.4 把交易费加到交易中交易的数据结构没有交易费的字段。相反地,交易费通过所有输入的总和,以及所有输出的总和之间的差来表示。从所有输入中扣掉所有输出之后的多余的量会被矿工收集走。
让我们来看看在实际中它如何工作,重温一下Alice在咖啡店的交易。Alice想为咖啡支付0.015比特币。为了确保这笔交易能立即被处理,Alice想支付一笔交易费,比如说0.001。这意味着总交易成本会变成0.016。因此她的钱包需要凑齐0.016或更多的UTXO。如果需要,还要加上找零。我们假设他的钱包有一个0.2比特币的UTXO可用。他的钱包就会消耗掉这个UTXO,创造一个新的0.015的输出给Bob的咖啡店,另一个0.184比特币的输出作为找零回到Alice拥有的钱包,并留下未分配的0.001比特币内含在交易中。 5.5 交易链条和孤立交易正如我们之前所看到的那样,交易形成一条链,这条链的形式是一笔交易消耗了先前的交易(父交易)的输出,并为随后的交易(子交易)创造了输出。有的时候组成整个链条的所有交易依赖于他们自己——比如父交易、子交易和孙交易——而他们又被同时创造出来,来满足复杂交易的工作流程。这需要在一个交易的父交易被签名之前,有一个合法的子交易被签名。举个例子,这是CoinJoin交易使用的一项技术,这项技术可以让多方同时加入交易,从而保护他们的隐私。 5.6 比特币交易脚本和脚本语言比特币客户端通过执行一个用类Forth脚本语言编写的脚本验证比特币交易。锁定脚本被写入UTXO,同时它往往包含一个用同种脚本语言编写的签名。当一笔比特币交易被验证时,每一个输入值中的解锁脚本被与其对应的锁定脚本同时(互不干扰地)执行,从而查看这笔交易是否满足使用条件。
5.6.1 脚本创建(锁定与解锁)比特币的交易验证引擎依赖于两类脚本来验证比特币交易:一个锁定脚本和一个解锁脚本。
5.6.2 脚本语言比特币交易脚本语言,也成为脚本,是一种基于逆波兰表示法的基于堆栈的执行语言。如果这让您听起来似乎在胡言乱语,很有可能是您没学习过1960年的编程语言的缘故。脚本是一种非常简单的语言,这种语言被设计为能在有限的硬件上执行,这些硬件类似简单的嵌入式设备,如手持计算器。它仅需最少的处理即可,而且不能做许多现代编程语言可以做的事情。当涉及可编程的钱时,这是它的一个基于深思熟虑的安全特性。
在图5-2中,脚本“
5.6.3 图灵非完备性比特币脚本语言包含许多操作,但都故意限定为一种重要的方式——没有循环或者复杂流控制功能以外的其他条件的流控制。这样就保证了脚本语言的图灵非完备性,这意味着脚本的复杂性有限,交易可执行的次数也可预见。脚本并不是一种通用语言,施加的这些限制确保该语言不被用于创造无限循环或其它类型的逻辑炸弹,这样的炸弹可以植入在一笔交易中,通过引起拒绝服务的方式攻击比特币网络。受限制的语言能防止交易激活机制被人当作薄弱环节而加以利用。 5.6.4 非主权验证比特币交易脚本语言是无国家主权的,没有国家能凌驾于脚本之上,也没有国家会在脚本被执行后对其进行保存。所以需要执行脚本的所有信息都已包含在脚本中。可以预见的是,一个脚本能在任何系统上以相同的方式执行。如果您的系统对一个脚本进行验证,可以确信的是每一个比特币网络中的其他系统也将对其进行验证,这意味着一个有效的交易对每个人而言都是有效的,而且每一个人都明白这一点。这种对于结果的可预见性是比特币系统的一项重要良性特征。 5.7 标准交易在比特币最初几年的发展过程中,开发者对可以经由客户端进行操作的脚本类型设置了一些限制。这些限制被编译为一个Standard()函数,该函数定义了五种类型的标准交易。这些限制都是临时性的,当您阅读本书时或许已经更新。截至目前,五种标准交易脚本是仅有的被客户端和大多数运行客户端的矿工们所接受的脚本。虽然创设一个非标准交易(脚本类型非标准化)是有可能的,但前提是必须能找到一个不遵循标准而且能将该非标准交易纳入区块的矿工。 5.7.1 P2PKH(Pay-to-Public-Key-Hash)比特币网络上的大多数交易都是P2PKH交易,此类交易都含有一个锁定脚本,该脚本由公钥哈希实现阻止输出功能,公钥哈希即为广为人知的比特币地址。由P2PKH脚本锁定的输出可以通过键入公钥和由相应私钥创设的数字签名得以解锁。
只有当解锁版脚本与锁定版脚本的设定条件相匹配时,执行组合有效脚本时才会显示结果为真(Ture)。即只有当解锁脚本得到了咖啡馆的有效签名,交易执行结果才会被通过(结果为真),该有效签名是从与公钥哈希相匹配的咖啡馆的私钥中所获取的。
5.7.2 P2PK(Pay-to-Public-Key)与P2PKH相比,P2PK模式更为简单。与P2PKH模式含有公钥哈希的模式不同,在P2PK脚本模式中,公钥本身已经存储在锁定脚本中,而且代码长度也更短。P2PKH是由Satoshi创建的,主要目的一方面为使比特币地址更简短,另一方面也使之更方便使用。P2PK目前在Coinbase交易中最为常见,Coinbase交易由老的采矿软件产生,目前还没更新至P2PKH。 5.7.3 多重签名多重签名脚本设置了这样一个条件,假如记录在脚本中的公钥个数为N,则至少需提供其中的M个公钥才可以解锁。这也被称为M-N组合,其中,N是记录在脚本中的公钥总个数,M是使得多重签名生效的公钥数阀值(最少数目)。例如,对于一个2-3多重签名组合而言,存档公钥数为3个,至少同时使用其中2个或者2个以上的公钥时,才能生成激活交易的签名,通过验证后才可使用这笔资金。最初,标准多重签名脚本的最大存档公钥数被限定为15个,这意味着可采用1-1乃至15-15的任意多重签名组合,或者组合的组合来激活交易。15个存档公钥数的限制也许在本书出版时已有所增加,读者通过检索Standard()函数可以获得最新存档公钥数上限值的相关信息。
两个脚本组合将形成一个验证脚本: 5.7.4 数据输出(OP_RETURN操作符)比特币的分发和时间戳账户机制(也即区块链),其潜在运用将大大超越支付领域。许多开发者试图充分发挥交易脚本语言的安全性和可恢复性优势,将其运用于电子公证服务、证券认证和智能协议等领域。比特币脚本语言的早期运用主要包括在区块链上创造出交易输出。例如,为文件记录电子指纹,则任何人都可以通过该机制在特定的日期建立关于文档存在性的证明。 5.7.5 P2SH(Pay-to-Script-Hash)P2SH在2012年被作为一种新型、强大、且能大大简化复杂交易脚本的交易类型而引入。为进一步解释P2SH的必要性,让我们先看一个实际的例子。
表5-5 P2SH复杂脚本
正如你在表中所看到的,在P2SH中,出现了花费该笔支出(赎回脚本)条件的复杂脚本,而这在锁定脚本中并未出现。取而代之,在锁定脚本中,只出现了哈希,而赎回脚本则在稍后输出被支付时才作为解锁脚本的一部分而出现。
整个脚本都可由仅为20个字节的密码哈希所取代,首先采用SH256哈希算法,随后对其运用RIPEMD160算法。20字节的脚本为: 5.7.5.1 P2SH地址P2SH的另一重要特征是它能将脚本哈希编译为一个地址(其定义请见BIP0013)。P2SH地址是基于Base58编码的一个含有20个字节哈希的脚本,就像比特币地址是基于Base58编码的一个含有20个字节的公钥。由于P2SH地址采用5作为前缀,这导致基于Base58编码的地址以“3”开头。例如,Mohammed的脚本,基于Base58编码下的P2SH地址变为“39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw”。此时,Mohammed可以将该地址发送给他的客户,这些客户可以采用任何的比特币钱包实现简单支付,就像这是一个比特币地址一样。以“3”为前缀给予客户这是一种特殊类型的地址的暗示,该地址与一个脚本相对应而非与一个公钥相对应,但是它的效果与比特币地址支付别无二致。 5.7.5.2 P2SH的优点与直接使用复杂脚本以锁定输出的方式相比,P2SH具有以下特点: 5.7.5.3 赎回脚本和标准确认在0.9.2版比特币核心客户端之前,P2SH仅限于标准比特币交易脚本类型(即通过标准函数检验的脚本)。这也意味着使用该笔资金的交易中的赎回脚本只能是标准化的P2PK、P2PKH或者多重签名,而非 ==目录== 简介 |
精通比特 – 第4章,高级密钥和地址
4.5 高级密钥和地址在以下部分中,我们将看到高级形式的密钥和地址,诸如加密私钥、脚本和多重签名地址,靓号地址,和纸钱包。 4.5.1 加密私钥(BIP0038)私钥必须保密。私钥的机密性需求事实情况是,在实践中相当难以实现,因为该需求与同样重要的安全对象可用性相互矛盾。当你需要为了避免私钥丢失而存储备份时,会发现维护私钥私密性是一件相当困难的事情。通过密码加密内有私钥的钱包可能要安全一点,但那个钱包也需要备份。有时,例如用户因为要升级或重装钱包软件,而需要把密钥从一个钱包转移到另一个。私钥备份也可能需要存储在纸张上(参见“4.5.4 纸钱包”一节)或者外部存储介质里,比如U盘。但如果一旦备份文件失窃或丢失呢?这些矛盾的安全目标推进了便携、方便、可以被众多不同钱包和比特币客户端理解的加密私钥标准BIP0038的出台。
4.5.2 P2SH (Pay-to-Script Hash)和多重签名地址正如我们所知,传统的比特币地址从数字1开头,来源于公钥,而公钥来源于私钥。虽然任何人都可以将比特币发送到一个1开头的地址,但比特币只能在通过相应的私钥签名和公钥哈希值后才能消费。
脚本哈希的结果是由Base58Check编码前缀为5的版本、编码后得到开头为3的编码地址。一个P2SH地址例子是32M8ednmuyZ2zVbes4puqe44NZumgG92sM。
4.5.2.1 多重签名地址和P2SH目前,P2SH函数最常见的实现是用于多重签名地址脚本。顾名思义,底层脚本需要多个签名来证明所有权,此后才能消费资金。设计比特币多重签名特性是需要从总共N个密钥中需要M个签名(也被称为“阈值”),被称为M-的-N的多签名,其中M是等于或小于N。例如,第一章中提到的咖啡店主鲍勃使用多重签名地址需要1-2签名,一个是属于他的密钥和一个属于他同伴的密钥,以确保其中一方可以签署度过一个事务锁定输出到这个地址。这类似于传统的银行中的一个“联合账户”,其中任何一方配偶可以凭借单一签名消费。或Gopesh, Bob雇佣的网页设计师创立一个网站,可能为他的业务需要一个2-3的多签名地址,确保没有资金会被花费除非至少两个业务合作伙伴签署这笔交易。 4.5.3 比特币靓号地址靓号地址包含了可读信息的有效比特币地址。例如,1LoveBPzzD72PUXLzCkYAtGFYmK5vYNR33就是包含了Base-58字母love的。靓号地址需要生成并通过数十亿的候选私钥测试,直到一个私钥能生成具有所需图案的比特币地址。虽然有一些优化过的靓号生成算法,该方法必须涉及随机上选择一个私钥,生成公钥,再生成比特币地址,并检查是否与所要的靓号图案相匹配,重复数十亿次,直到找到一个匹配。 4.5.3.1 生成靓号地址我们必须认识到使用来自Base58字母表中简单符号来代表比特币地址是非常重要的。搜索“1kids”开头的图案我们会发现从1Kids11111111111111111111111111111到1Kidszzzzzzzzzzzzzzzzzzzzzzzzzzzzz的地址。这些以“1kid”开头的地址范围中大约有58的29次方地址。表4-11显示了这些有“1kids”前缀的地址。
我们把“1Kids”这个前缀当作数字,我们可以看看比特币地址中这个前缀出现的频率。如果是一台普通性能的桌面电脑,没有任何特殊的硬件,可以每秒发现大约10万个密钥。
正如你所见,Eugenia将不会很快地创建出以“1KidsCharity”开头的靓号地址,即使她有数千台的电脑同时进行运算。每增加一个字符就会增加58倍的计算难度。超过七个字符的搜索模式通常需要专用的硬件才能被找出,譬如用户定制的具有多图形处理单元(GPU)的桌面级设备。那些通常是无法继续在比特币挖矿中盈利的钻机,被重新赋予了寻找靓号地址的任务。用GPU系统搜索靓号的速度比用通用CPU要快很多个量级。
示例程序需要用C编译器链接libbitcoin库(此库需要提前装入该系统)进行编译。直接执行vanity-miner的可执行文件(不用参数,参见例4-9),它就会尝试碰撞以“1kid”开头的比特币地址。
正如我们运行Unix命令time所测出的运行时间所示,示例代码要花几秒钟来找出匹配“kid”三个字符模板的结果。读者们可以在源代码中改变search这一搜索模板,看一看如果是四个字符或者五个字符的搜索模板需要花多久时间! 4.5.3.2 靓号地址安全性靓号地址既可以增加、也可以削弱安全措施,它们着实是一把双刃剑。用于改善安全性时,一个独特的地址使对手难以使用他们自己的地址替代你的地址,以欺骗你的顾客支付他们的账单。不幸的是,靓号地址也可能使得任何人都能创建一个类似于随机地址的地址,甚至另一个靓号地址,从而欺骗你的客户。
那靓号地址会不会增加安全性?如果Eugenia生成1Kids33q44erFfpeXrmDSz7zEqG2FesZEN的靓号地址,用户可能看到靓号图案的字母和一些字符在上面,例如在地址部分中注明了1Kids33。这样就会迫使攻击者生成至少6个字母相匹配的的靓号地址(比之前多2个字符),就要花费比Eugenia多3364倍的靓号图案。本质上,Eugenia付出的努力(或者靓号池付出的)迫使攻击者不得不生成更长的靓号图案。如果Eugenia花钱请矿池生成8个字符的靓号地址,攻击者将会被逼迫到10字符的境地,那将是个人电脑,甚至昂贵自定义靓号挖掘机或靓号池也无法生成。对Eugenia来说可承担的起支出,对攻击者来说则变成了无法承担支出,特别是如果欺诈的回报不足以支付生成靓号地址所需的费用。 4.5.4 纸钱包纸钱包是打印在纸张上的比特币私钥。有时纸钱包为了方面起见也包括对应的比特币地址,但这并非是必要的。因为地址可以从私钥中导出。纸钱包是一个非常有效简历备份或者线下比特币存储方式,也是被称为“冷钱包”。作为备份机制,一个纸钱包可以提供安全性,以防在电脑硬盘损坏、失窃或意外删除的情况下造成密钥的的丢失。作为一个冷存储的机制,如果纸钱包密钥在线下生成并永久不在电脑系统中存储,他们在应对黑客攻击,键盘记录器,或其他在线电脑欺骗更有安全性。
通过使用工具,就可以很容易地生成纸钱包,譬如使用bitaddress.org网站上的客户端Javascript生成器。这个页面包含所有必要的代码,甚至在完全失去网络连接的情况下,也可以生成密钥和纸钱包。若要使用它,先将HTML页面保存在本地磁盘或外部U盘。从Internet网络断开,从浏览器中打开文件。更方便的,使用一个原始操作系统启动电脑,比如一个光盘启动的Linux系统。任何在脱机情况下使用这个工具所生成的密钥,都可以通过USB线在本地打印机上打印出来,从而制造了密钥只存在纸张上而从未存储在在线系统上的纸钱包。将这些纸钱包放置在防火容器内,发送比特币到对应的比特币地址上,从而实现了一个简单但非常有效的冷存储解决方案。图4-14展示了通过bitaddress.org 生成的纸钱包。
这个简单的纸钱包系统的不足之处是那些被打印下来的密钥容易被盗窃。一个能够获取接近这些纸币的小偷可以只需偷走纸币或者用把纸币上密钥拍摄下来,就能获得被这些密钥加密过的比特币的控制权。一个更复杂的纸钱包存储系统使用BIP0038加密的私钥。这些私钥被打印在纸钱包上被所有者记住的口令保护起来。没有口令,这些被加密过的密钥也是毫无用处的。但它们仍旧要比用口令保护的钱包级别要高,因为这些密钥从没有在线过,必须从物理上从保险箱或者其他物理安全存储中导出。图4-15展示了通过bitaddress.org 生成的加密纸钱包。
纸钱包有许多设计和大小,并有许多不同的特性。有些作为礼物送给他人,有季节性的主题,像圣诞节和新年主题。另外一些则是设计保存在银行金库或通过某种方式隐藏私钥的保险箱内,或者用不透明的刮刮贴,或者折叠和防篡改的铝箔胶粘密封。图4-16至图4-18展示了几个不同安全和备份功能的纸钱包的例子。
其他设计有密钥和地址的额外副本,类似于票根形式的可以拆卸存根,让你可以存储多个副本以防火灾、洪水或其他自然灾害。
==目录== 简介 |
精通比特币 – 第4章 密钥、地址、钱包
第4章 密钥、地址、钱包4.1 简介比特币的所有权是通过数字密钥、比特币地址和数字签名来确立的。数字密钥实际上并不是存储在网络中,而是由用户生成并存储在一个文件或简单的数据库 中,称为钱包。存储在用户钱包中的数字密钥完全独立于比特币协议,可由用户的钱包软件生成并管理,而无需区块链或网络连接。密钥实现了比特币的许多有趣特 性,包括去中心化信任和控制、所有权认证和基于密码学证明的安全模型。 4.1.1 公钥加密和加密货币公钥加密发明于20世纪70年代。它是计算机和信息安全的数学基础。
4.1.2 私钥和公钥一个比特币钱包中包含一系列的密钥对,每个密钥对包括一个私钥和一个公钥。私钥(k)是一个数字,通常是随机选出的。有了私钥,我们就可以使用椭圆 曲线乘法这个单向加密函数产生一个公钥(K)。有了公钥(K),我们就可以使用一个单向加密哈希函数生成比特币地址(A)。在本节中,我们将从生成私钥开 始,讲述如何使用椭圆曲线运算将私钥生成公钥,并最终由公钥生成比特币地址。私钥、公钥和比特币地址之间的关系如下图所示。 4.1.3 私钥私钥就是一个随机选出的数字而已。一个比特币地址中的所有资金的控制取决于相应私钥的所有权和控制权。在比特币交易中,私钥用于生成支付比特币所必 需的签名以证明资金的所有权。私钥必须始终保持机密,因为一旦被泄露给第三方,相当于该私钥保护之下的比特币也拱手相让了。私钥还必须进行备份,以防意外 丢失,因为私钥一旦丢失就难以复原,其所保护的比特币也将永远丢失。
从一个随机数生成私钥生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源。生成一个比特币私钥在本质上与“在1到2256之 间选一个数字”无异。只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。比特币软件使用操作系统底层的随机数生成器来产生256位 的熵(随机性)。通常情况下,操作系统随机数生成器由人工的随机源进行初始化,也可能需要通过几秒钟内不停晃动鼠标等方式进行初始化。对于真正的偏执狂, 可以使用掷骰子的方法,并用铅笔和纸记录。
以下是一个随机生成的私钥(k),以十六进制格式表示(256位的二进制数,以64位十六进制数显示,每个十六进制数占4位):
要使用比特币核心客户端生成一个新的密钥(参见第3章),可使用
你也可以使用命令行sx工具 (参见“3.3.1 Libbitcoin和sx Tools”)用newkey命令来生成并显示私钥:
4.1.4 公钥通过椭圆曲线乘法可以从私钥计算得到公钥,这是不可逆转的过程: 4.1.5 椭圆曲线密码学解释椭圆曲线加密法是一种基于离散对数问题的非对称(或公钥)加密法,可以用对椭圆曲线上的点进行加法或乘法运算来表达。
在椭圆曲线的数学原理中,有一个点被称为“无穷远点”,这大致对应于0在加法中的作用。计算机中,它有时表示为X = Y = 0(虽然这不满足椭圆曲线方程,但可作为特殊情况进行检验)。 还有一个 + 运算符,被称为“加法”,就像小学数学中的实数相加。给定椭圆曲线上的两个点P1和P2,则椭圆曲线上必定有第三点 P3 = P1 + P2。 4.1.6 生成公钥以一个随机生成的私钥k为起点,我们将其与曲线上已定义的 生成点G相乘以获得曲线上的另一点,也就是相应的公钥K。生成点是secp256k1标准的一部分,比特币密钥的生成点都是相同的:
其中k是私钥,G是生成点,在该曲线上所得的点K是公钥。因为所有比特币用户的生成点是相同的,一个私钥k乘以G将得到相同的公钥K。k和K之间的关系是固定的,但只能单向运算,即从k得到K。这就是可以把比特币地址(K的衍生)与任何人共享而不会泄露私钥(k)的原因。
为实现椭圆曲线乘法,我们以之前产生的私钥k和与生成点G相乘得到公钥K:
公钥K 被定义为一个点 K = (x, y):
为了展示整数点的乘法,我们将使用较为简单的实数范围的椭圆曲线。请记住,其中的数学原理是相同的。我们的目标是找到生成点G的倍数kG。也就是将G相加k次。在椭圆曲线中,点的相加等同于从该点画切线找到与曲线相交的另一点,然后映射到x轴。
4.2 比特币地址比特币地址是一个由数字和字母组成的字符串,可以与任何想给你比特币的人分享。由公钥(一个同样由数字和字母组成的字符串)生成的比特币地址以数字“1”开头。下面是一个比特币地址的例子:
在交易中,比特币地址通常以收款方出现。如果把比特币交易比作一张支票,比特币地址就是收款人,也就是我们要写入收款人 一栏的内容。一张支票的收款人可能是某个银行账户,也可能是某个公司、机构,甚至是现金支票。支票不需要指定一个特定的账户,而是用一个普通的名字作为收 款人,这使它成为一种相当灵活的支付工具。与此类似,比特币地址的使用也使比特币交易变得很灵活。比特币地址可以代表一对公钥和私钥的所有者,也可以代表 其它东西,比如会在132页的“P2SH (Pay-to-Script-Hash)”一节讲到的付款脚本。现在,让我们来看一个简单的例子,由公钥生成比特币地址。
公式中,K是公钥,A是生成的比特币地址。
通常用户见到的比特币地址是经过“Base58Check”编码的(参见72页“Base58和Base58Check编码”一节),这种编码使用 了58个字符(一种Base58数字系统)和校验码,提高了可读性、避免歧义并有效防止了在地址转录和输入中产生的错误。Base58Check编码也被 用于比特币的其它地方,例如比特币地址、私钥、加密的密钥和脚本哈希中,用来提高可读性和录入的正确性。下一节中我们会详细解释Base58Check的 编码机制,以及它产生的结果。下图描述了如何从公钥生成比特币地址。 4.2.1 Base58和Base58Check编码为了更简洁方便地表示长串的数字,许多计算机系统会使用一种以数字和字母组成的大于十进制的表示法。例如,传统的十进制计数系统使用0-9十个数 字,而十六进制系统使用了额外的 A-F 六个字母。一个同样的数字,它的十六进制表示就会比十进制表示更短。更进一步,Base64使用了26个小写字母、26个大写字母、10个数字以及两个符 号(例如“+”和“/”),用于在电子邮件这样的基于文本的媒介中传输二进制数据。Base64通常用于编码邮件中的附件。Base58是一种基于文本的 二进制编码格式,用在比特币和其它的加密货币中。这种编码格式不仅实现了数据压缩,保持了易读性,还具有错误诊断功能。Base58是Base64编码格 式的子集,同样使用大小写字母和10个数字,但舍弃了一些容易错读和在特定字体中容易混淆的字符。具体地,Base58不含Base64中的0(数字 0)、O(大写字母o)、l(小写字母L)、I(大写字母i),以及“+”和“/”两个字符。简而言之,Base58就是由不包括(0,O,l,I)的大 小写字母和数字组成。
Base58Check是一种常用在比特币中的Base58编码格式,增加了错误校验码来检查数据在转录中出现的错误。 校验码长4个字节,添加到需要编码的数据之后。校验码是从需要编码的数据的哈希值中得到的,所以可以用来检测并避免转录和输入中产生的错误。使用 Base58check编码格式时,编码软件会计算原始数据的校验码并和结果数据中自带的校验码进行对比。二者不匹配则表明有错误产生,那么这个 Base58Check格式的数据就是无效的。例如,一个错误比特币地址就不会被钱包认为是有效的地址,否则这种错误会造成资金的丢失。
在产生的长32个字节的哈希值(两次哈希运算)中,我们只取前4个字节。这4个字节就作为校验码。校验码会添加到数据之后。
在比特币中,大多数需要向用户展示的数据都使用Base58Check编码,可以实现数据压缩,易读而且有错误检验。Base58Check编码中 的版本前缀是数据的格式易于辨别,编码之后的数据头包含了明确的属性。这些属性使用户可以轻松明确被编码的数据的类型以及如何使用它们。例如我们可以看到 他们的不同,Base58Check编码的比特币地址是以1开头的,而Base58Check编码的私钥WIF是以5开头的。表4-1展示了一些版本前缀 和他们对应的Base58格式。
我们回顾比特币地址产生的完整过程,从私钥、到公钥(椭圆曲线上某个点)、再到两次哈希的地址,最终产生Base58Check格式的比特币地址。例4-2的C++代码完整详细的展示了从私钥到Base58Check编码后的比特币地址的步骤。代码中使用“3.3 其他客户端、资料库、工具包 ”一节中介绍的libbitcoin library来实现某些辅助功能。
正如编译并运行addr代码中展示的,由于代码使用预定义的私钥,所以每次运行都会产生相同的比特币地址。如例4-3所示。
4.2.2 密钥的格式公钥和私钥的都可以有多种编码格式。一个密钥被不同的格式编码后,虽然结果看起来可能不同,但是密钥所编码数字并没有改变。这些不同的编码格式主要是用来方便人们无误地使用和识别密钥。 私钥的格式私钥可以以许多不同的格式表示,所有这些都对应于相同的256位的数字。表4-2展示了私钥的三种常见格式。
表4-3展示了用这三种格式所生成的私钥。
这些表示法都是用来表示相同的数字、相同的私钥的不同方法。虽然编码后的字符串看起来不同,但不同的格式彼此之间可以很容易地相互转换。 将Base58Check编码解码为十六进制sx工具包(参见“3.3.1 Libbitcoin和sx Tools”)可用来编写一些操作比特币密钥、地址及交易的shell脚本和命令行“管道”。你也可以使用sx工具从命令行对Base58Check格式进行解码。
所得结果是十六进制的密钥,紧接着是钱包导入格式(Wallet Import Format,WIF)的版本前缀128。 将十六进制转换为Base58Check编码要转换成Base58Check编码(和之前的命令正好相反),我们需提供十六进制的私钥和钱包导入格式(Wallet Import Format,WIF)的版本号前缀128:
将十六进制(压缩格式密钥)转换为Base58Check编码要将压缩格式的私钥编码为Base58Check(参见“压缩格式私钥”一节),我们需在十六进制私钥的后面添加后缀01,然后使用跟上面一样的方法:
生成的WIF压缩格式的私钥以字母“K”开头,用以表明被编码的私钥有一个后缀“01”,且该私钥只能被用于生成压缩格式的公钥(参见“压缩格式公钥”一节)。 公钥的格式公钥也可以用多种不同格式来表示,最重要的是它们分为非压缩格式或压缩格式公钥这两种形式。
下面是同样的公钥以520比特的数字(130个十六进制数字)来表达。这个520比特的数字以前缀04开头,紧接着是x及y坐标,组成格式为04 x y:
压缩格式公钥引入压缩格式公钥是为了减少比特币交易的字节数,从而可以节省那些运行区块链数据库的节点磁盘空间。大部分比特币交易包含了公钥,用于验证用户的凭 据和支付比特币。每个公钥有520比特(包括前缀,x坐标,y坐标)。如果每个区块有数百个交易,每天有成千上万的交易发生,区块链里就会被写入大量的数 据。
这个压缩格式公钥对应着同样的一个私钥,这意味它是由同样的私钥所生成。但是压缩格式公钥和非压缩格式公钥差别很大。更 重要的是,如果我们使用双哈希函数(RIPEMD160(SHA256(K)))将压缩格式公钥转化成比特币地址,得到的地址将会不同于由非压缩格式公钥 产生的地址。这种结果会让人迷惑,因为一个私钥可以生成两种不同格式的公钥——压缩格式和非压缩格式,而这两种格式的公钥可以生成两个不同的比特币地址。 但是,这两个不同的比特币地址的私钥是一样的。 压缩格式私钥实际上“压缩格式私钥”是一种名称上的误导,因为当一个私钥被使用WIF压缩格式导出时,不但没有压缩,而且比“非压缩格式”私钥长出一个字节。这 个多出来的一个字节是私钥被加了后缀01,用以表明该私钥是来自于一个较新的钱包,只能被用来生成压缩的公钥。私钥是非压缩的,也不能被压缩。“压缩的私 钥”实际上只是表示“用于生成压缩格式公钥的私钥”,而“非压缩格式私钥”用来表明“用于生成非压缩格式公钥的私钥”。为避免更多误解,应该只可以说导出 格式是“WIF压缩格式”或者“WIF”,而不能说这个私钥是“压缩”的。
4.3 用Python实现密钥和比特币地址最全面的比特币Python库是 Vitalik Buterin写的 pybitcointools。在例4-4中,我们使用pybitcointools库(导入为“bitcoin”)来生成和显示不同格式的密钥和比特币地址。
例4-5显示了上段代码运行结果。
例4-6是另外一个示例,使用的是Python ECDSA库来做椭圆曲线计算而非使用bitcoin的库。
例4-7显示了运行脚本的结果。
4.4 比特币钱包钱包是私钥的容器,通常通过有序文件或者简单的数据库实现。另外一种制作私钥的途径是 确定性密钥生成。在这里你可以用原先的私钥,通过单向哈希函数来生成每一个新的私钥,并将新生成的密钥按顺序连接。只要你可以重新创建这个序列,你只需要 第一个私钥(称作种子、主私钥)来生成它们。在本节中,我们将会检查不同的私钥生成方法及其钱包结构。
4.4.1 非确定性(随机)钱包在最早的一批比特币客户端中,钱包只是随机生成的私钥集合。这种类型的钱包被称作零型非确定钱包。举个例子,比特币核心客户端预先生成100个随机 私钥,从最开始就生成足够多的私钥并且每把钥匙只使用一次。这种类型的钱包有一个昵称“Just a Bunch Of Keys(一堆私钥)”简称JBOK。这种钱包现在正在被确定性钱包替换,因为它们难以管理、备份以及导入。随机钥匙的缺点就是如果你生成很多,你必须保 存它们所有的副本。这就意味着这个钱包必须被经常性地备份。每一把钥匙都必须备份,否则一旦钱包不可访问时,钱包所控制的资金就付之东流。这种情况直接与 避免地址重复使用的原则相冲突——每个比特币地址只能用一次交易。地址通过关联多重交易和对方的地址重复使用会减少隐私。0型非确定性钱包并不是钱包的好 选择,尤其是当你不想重复使用地址而创造过多的私钥并且要保存它们。虽然比特币核心客户包含0型钱包,但比特币的核心开发者并不想鼓励大家使用。下图表示 包含有松散结构的随机钥匙的集合的非确定性钱包。 4.4.2 确定性(种子)钱包确定性,或者“种子”钱包包含通过使用单项离散方程而可从公共的种子生成的私钥。种子是随机生成的数字。这个数字也含有比如索引号码或者可生成私钥的“链码”(参见“4.4.4 分层确定性钱包(BIP0032/BIP0044)”一节)。在确定性钱包中,种子足够收回所有的已经产生的私钥,所以只用在初始创建时的一个简单备份就足以搞定。并且种子也足够让钱包输入或者输出。这就很容易允许使用者的私钥在钱包之间轻松转移输入。 4.4.3 助记码词汇助记码词汇是英文单词序列代表(编码)用作种子对应所确定性钱包的随机数。单词的序列足以重新创建种子,并且从种子那里重新创造钱包以及所有私钥。 在首次创建钱包时,带有助记码的,运行确定性钱包的钱包的应用程序将会向使用者展示一个12至24个词的顺序。单词的顺序就是钱包的备份。它也可以被用来 恢复以及重新创造应用程序相同或者兼容的钱包的钥匙。助记码代码可以让使用者复制钱包更容易一些,因为它们相比较随机数字顺序来说,可以很容易地被读出来 并且正确抄写。
助记码表示128至256位数。这可以通过使用私钥抻拉函数PBKDF2来导出更长的(512位)的种子。所得的种子可以用来创造一个确定性钱包以及其所派生的所有钥匙。
表4-7 256位熵的助记码以及所产生的种子
4.4.4 分层确定性钱包(BIP0032/BIP0044)确定性钱包被开发成更容易从单个“种子”中生成许多关键的钥匙。最高级的来自确定性钱包的形是通过BIP0032标准生成的 the hierarchical deterministic wallet or HD wallet defined。分层确定性钱包包含从数结构所生成的钥匙。这种母钥匙可以生成子钥匙的序列。这些子钥匙又可以衍生出孙钥匙,以此无穷类推。这个树结构表 如下图所示。 HD钱包提供了随机(不确定性)钥匙有两个主要的优势。第一,树状结构可以被用来表达额外的组织含义。比如当一个特定分支的子密钥被用来接收交易收 入并且有另一个分支的子密钥用来负责支付花费。不同分支的密钥都可以被用在企业环境中,这就可以支配不同的分支部门,子公司,具体功能以及会计类别。 从种子中创造HD钱包HD钱包从单个root seed中创建,为128到256位的随机数。HD钱包的所有的确定性都衍生自这个根种子。任何兼容HD钱包的根种子也可重新创造整个HD钱包。所以简单 的转移HD钱包的根中斯就让HD钱包中所包含的成千上百万的密钥被复制,储存导出以及导入。根种子一般总是被表示为a mnemonic word sequence,正如”4.4.3 助记码词汇“一节所表述的,助记码词汇可以让人们更容易地抄写和储存。 根种子输入到HMAC-SHA512算法中就可以得到一个可用来创造master private key(m) 和 a master chain code的哈希。主私钥(m)之后可以通过使用我们在本章先前看到的那个普通椭圆曲线 私有子密钥的衍生分层确定性钱包使用CKD(child key derivation)方程去从母密钥衍生出子密钥。 改变索引可以让我们延长母密钥以及创造序列中的其他子密钥。比如子0,子1,子2等等。每一个母密钥可以右20亿个子密钥。 使用衍生的子密钥子私钥不能从非确定性(随机)密钥中被区分出来。因为衍生方程是单向方程,所以子密钥不能被用来发现他们的母密钥。子密钥也不能用来发现他们的相同 层级的姊妹密钥。如果你有第n个子密钥,你不能发现它前面的(第n-1)或者后面的子密钥(n+1)或者在同一顺序中的其他子密钥。只有母密钥以及链码才 能得到所有的子密钥。没有子链码的话,子密钥也不能用来衍生出任何孙密钥。你需要同时有子密钥以及对应的链码才能创建一个新的分支来衍生出孙密钥。
扩展密钥正如我们之前看到的,密钥衍生方程可以被用来创造钥匙树上任何层级的子密钥。这只需要三个输入量:一个密钥,一个链码以及想要的子密钥的索引。密钥 以及链码这两个重要的部分被结合之后,就叫做extended key。术语“extended key”也被认为是“可扩展的密钥”是因为这种密钥可以用来衍生子密钥。
扩展密钥通过Base58Check来编码,从而能轻易地在不同的BIP0032-兼容钱包之间导入导出。扩展密钥编码用的Base58Check 使用特殊的版本号,这导致在Base58编码字符中,出现前缀“xprv”和“xpub”。这种前缀可以让编码更易被识别。因为扩展密钥是512或者 513位,所以它比我们之前所看到的Base58Check-encoded串更长一些。
这是在Base58Check中编码的对应的扩展公共钥匙:
公共子钥匙推导正如之前提到的,分层确定性钱包的一个很有用的特点就是可以不通过私钥而直接从公共母钥匙派生出公共子钥匙的能力。这就给了我们两种去衍生子公共钥匙的方法:或者通过子私钥,再或者就是直接通过母公共钥匙。 硬化子密钥的衍生从扩展公共钥匙衍生一个分支公共钥匙的能力是很重要的,但牵扯一些风险。访问扩展公共钥匙并不能得到访问子私人密钥的途径。但是,因为扩展公共钥匙 包含有链码,如果子私钥被知道或者被泄漏的话,链码就可以被用来衍生所有的其他子私钥。一个简单地泄露的私钥以及一个母链码,可以暴露所有的子密钥。更糟 糕的是,子私钥与母链码可以用来推断母私钥。 当强化私钥衍生方程被使用时,得到的子私钥以及链码与使用一般衍生方程所得到的结果完全不同的。得到的密钥“分支”可以被用来生产不易被攻击的扩展 公共钥匙,因为它所含的链码不能被用来开发或者暴露任何私钥。强化的衍生也因此被用来在上一层级,使用扩展公共钥匙的的密钥树中创造“间隙”。 正常衍生和强化衍生的索引号码用在衍生方程中的索引号码是32位的整数。为了区分密钥是从正常衍生方程中衍生出来还是从强化衍生方程中产出,这个索引号被分为两个范围。索引号在0和231–1(0x0 to 0x7FFFFFFF)之间的是只被用在常规衍生。索引号在231和232–1(0x80000000 to 0xFFFFFFFF)之间的只被用在强化衍生方程。因此,索引号小于231就意味着子密钥是常规的,而大于或者等于231的子密钥就是强化型的。 HD钱包密钥识别符(路径)HD钱包中的密钥是用“路径”命名的,且每个级别之间用斜杠(/)字符来表示(见表4-8)。由主私钥衍生出的私钥起始以“m”打头。因此,第一个母密钥生成的子私钥是m/0。第一个公共钥匙是M/0。第一个子密钥的子密钥就是m/0/1,以此类推。
HD钱包树状结构的导航HD钱包树状结构提供了极大的灵活性。每一个母扩展密钥有40已个子密钥:20亿个常规子密钥和20亿个强化子密钥。而每个子密钥又会有40亿个子 密钥并且以此类推。只要你愿意,这个树结构可以无限类推到无穷代。但是,又由于有了这个灵活性,对无限的树状结构进行导航就变得异常困难。尤其是对于在不 同的HD钱包之间进行转移交易,因为内部组织到内部分支以及亚分支的可能性是无穷的。
第一层的目的地总是被设定为44’。第二层的“coin_type”特指密码货币硬币的种类并且允许多元货币HD钱包中 的货币在第二个层级下有自己的亚树状结构。目前有三种货币被定义:Bitcoin is m/44’/0’、Bitcoin Testnet is m/44’/1’,以及Litecoin is m/44’/2’。
使用比特币浏览器实验比特币钱包依据第3章介绍的使用比特币浏览管理器命令工具,你可以试着生产和延伸BIP0032确定性密钥以及将它们用不同的格式进行展示:
==目录== 简介 本文原链接:http://zhibimo.com/read/wang-miao/mastering-bitcoin/Chapter04.html |
精通比特币 第3章 比特币客户端
第3章 比特币客户端
3.1 比特币核心:参考实现
你可以从bitcoin.org下载标准客户端,即比特币核心,也叫“中本聪客户端”(satoshi client)。它实现了比特币系统的所有方面,包括钱包、对整个交易账簿(区块链)完整拷贝的交易确认引擎,和点对点比特币网络中的一个完整网络节点。
在Bitcoin网站的选择钱包页面, 下载参考客户端比特币核心。根据操作系统不同,你将下载不同的可执行安装工具。对于Windows,它是一个ZIP归档文件或.exe格式的可执行文件。 对于Mac OS,它是一个.dmg格式的磁盘映像。Linux版本包括用于Ubuntu系统的PPA包,或是 tar.gz格式的压缩包。图3-1所示的Bitcoin.org页面上列出了一些推荐的比特币客户端。
图3-1 选择比特币客户端
3.1.1 第一次运行比特币核心
如果你下载了一个安装包,比如.exe、.dmg、或PPA,你可以和安装其它任何应用程序一样,在你的操作系统上安装它。对于Windows,运 行.exe文件,按照提示一步步操作。对于Mac OS,启动.dmg文件,然后将Bitcoin-QT 图标拖拽到你的应用程序目录就可以了。对于Ubuntu,在文件资源管理器中双击PPA文件,会打开程序包管理器来安装它。一旦完成了安装,在你的应用程 序列表中会有一个新的应用叫Bitcoin-QT。双击这个图标就可以启动比特币客户端了。
第一次运行比特币核心时,它会开始下载整个区块链,这个过程可能需要数天(见下图)。让它在后台运行,直到显示“已同步”,并且余额旁边不再显示“数据同步中”。
比特币核心拥有交易账簿(区块链)的一份完整拷贝,里面记录了自2009年比特币网络被发明以来发生在比特币网络上的每一笔交易。这个数据集有几个 GB(在2013年底大约是16GB),并且需要几天的时间完成增量形式的下载(从区块0顺次下载到最新区块)。在整个区块链数据集下载完成前,客户端将 不能处理任何交易或是更新账户余额。在下载数据集的过程中,客户端账户余额旁会显示“数据同步中”,底部会显示“正在同步”。请确保你有足够的磁盘空间、 带宽和时间来完成初始同步。
3.1.2 从源码编译比特币核心比特币核心
对于开发者,可以选择下载包含完整源代码的ZIP包,也可以从Github上克隆权威的源码仓库。在面GitHub上的比特币页面,在侧边栏选择下载ZIP。或者使用git命令行(git command line)在自己系统上创建源码的本地拷贝。在下面的例子中,我们将通过unix风格的命令行,在Linux或是Mac OS 上克隆源代码:
$ git clone https://github.com/bitcoin/bitcoin.git
Cloning into 'bitcoin'...
remote: Counting objects: 31864, done.
remote: Compressing objects: 100% (12007/12007), done.
remote: Total 31864 (delta 24480), reused 26530 (delta 19621)
Receiving objects: 100% (31864/31864), 18.47 MiB | 119 KiB/s, done.
Resolving deltas: 100% (24480/24480), done.
$
终端上的提示和输出结果可能会因版本有所不同。即使你的屏幕上输出的内容跟这里的例子有点不一样,请遵照代码中的文档,这些都是正常的。
在git clone操作完成后,在你本地的bitcoin目录就会有一份完整的源码拷贝。通过在命令提示行输入cd bitcoin切换到这个目录下:
$ cd bitcoin
默认情况下,本地拷贝将与最新的代码同步,这可能是bitcoin的一个不稳定或是 beta 版本。在编译这些代码之前,签出发布标签(realease tag)以选择某一特定版本(a specific version)。 这将通过一个关键的标签标记,让本地拷贝与代码仓库的特定快照同步。开发者用标签来标记代码的特定发行版本号(version numBTCer)。首先,要找到可用的标签,可以通过git tag命令:
$ git tag
v0.1.5
v0.1.6test1
v0.2.0
v0.2.10
v0.2.11
v0.2.12
[... many more tags ...]
v0.8.4rc2
v0.8.5
v0.8.6
v0.8.6rc1
v0.9.0rc1
列出的标签是bitcoin的所有发行版本。按照约定,带有rc后缀的是预发行版本,可以用来测试。没有后缀的稳定版本 可以直接在产品环境上运行。从上面的列表中,选择最新的发行版本,目前是v0.9.0rc1。为了让本地代码跟这个版本一致,我们需要用git checkout 命令:
$ git checkout v0.9.0rc1
Note: checking out 'v0.9.0rc1'.
HEAD is now at 15ec451... Merge pull request #3605
$
源代码包含文档,可以在多个文件夹中找到。在命令提示行输入more README.md可以在bitcoin目录下的README.md中查看主文档,用空格键可以翻页。在这一章,我们将构建命令行的比特币客户端,在 linux上称作bitcoind。在您的平台上,通过输入more doc/build-unix.md,可以阅读编译bitcoind命令行客户端的说明。Mac OSX和Windows平台的说明可以在doc目录下找到,分别是build-osx.md或是build-msw.md。
仔细阅读build文档第一部分中build的必备条件。这些是在你编译之前你的系统上必须具备的库文件。如果缺少这些必备条件,构建过程将会出现 错误并导致失败。如果因为缺少一个必备条件而发生这种情况,你可以先安装它,然后在你停下的地方重新构建。假设这些必备条件已经具备,你就可以开始构建过 程,通过authgen.sh脚本,生成一组构建脚本。
从v0.9版本开始,比特币核心的构建过程改用autogen/configure/make 体系。旧版本是用一个简单的Makefile,并且和下面的例子稍微有一点不同。请遵照你想要编译版本的说明文档。v0.9版本引入的 autogen/configure/make 构建体系可能用于未来所有版本的代码,是下面的例子中演示的系统
$ ./autogen.sh
configure.ac:12: installing `src/build-aux/config.guess'
configure.ac:12: installing `src/build-aux/config.sub'
configure.ac:37: installing `src/build-aux/install-sh'
configure.ac:37: installing `src/build-aux/missing'
src/Makefile.am: installing `src/build-aux/depcomp'
$
autogen.sh脚本创建了一系列的自动配置脚本,会询问你的系统以发现正确的设置,确保你已安装必要的库来编译源 码。这里面最重要的是configure脚本,它会提供许多不同的选项来定制构建过程。输入./configure –help 可以查看各种不同的选项:
$ ./configure --help
`configure' configures Bitcoin Core 0.9.0 to adapt to many kinds of systems.
Usage: ./configure [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE.
See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
-h, --help display this help and exit
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
[... many more options and variables are displayed below ...]
Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
[... more options ...]
Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations.
Report bugs to <info@bitcoin.org>.
$
通过使用–enable-FEATURE和–disable-FEATURE选项,configure脚本允许你启 用或是禁用某些功能,FEATURE需要被替换成功能名称,在上面的帮助输出中可以找到。在这一章,我们将用默认的功能来构建bitcoind客户端。这 里不会使用配置选项,但是你应该检查一遍,明白哪些可选的功能可以作为客户端的一部分。下一次,运行configure脚本就可以自动发现所有必要的库, 然后为我们的系统创建一个定制的构建脚本。
$ ./configure
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
[... many more system features are tested ...]
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating src/test/Makefile
config.status: creating src/qt/Makefile
config.status: creating src/qt/test/Makefile
config.status: creating share/setup.nsi
config.status: creating share/qt/Info.plist
config.status: creating qa/pull-tester/run-bitcoind-for-test.sh
config.status: creating qa/pull-tester/build-tests.sh
config.status: creating src/bitcoin-config.h
config.status: executing depfiles commands
$
如果一切顺利,configure命令将会以创建可定制的构建脚本结束。这些构建脚本允许我们编译bitcoind。如 果有缺失的库或是错误,configur命令将会以错误信息终止。如果出现了错误,可能是因为缺少库或是有不兼容的库。重新检查构建文档,确认你已经安装 缺失的必备条件。然后运行configure,看看错误是否消失了。下一步,你将编译源代码,这个过程可能需要1个小时完成。在编译的过程中,你应该过几 秒或是几分钟看一下输出结果。如果出现了问题,你会看到错误。如果中断了,编译的过程可以在任何时候恢复。输入make命令就可以开始编译了:
$ make
Making all in src
make[1]: Entering directory `/home/ubuntu/bitcoin/src'
make all-recursive
make[2]: Entering directory `/home/ubuntu/bitcoin/src'
Making all in .
make[3]: Entering directory `/home/ubuntu/bitcoin/src'
CXX addrman.o
CXX alert.o
CXX rpcserver.o
CXX bloom.o
CXX chainparams.o
[... many more compilation messages follow ...]
CXX test_bitcoin-wallet_tests.o
CXX test_bitcoin-rpc_wallet_tests.o
CXXLD test_bitcoin
make[4]: Leaving directory `/home/ubuntu/bitcoin/src/test'
make[3]: Leaving directory `/home/ubuntu/bitcoin/src/test'
make[2]: Leaving directory `/home/ubuntu/bitcoin/src'
make[1]: Leaving directory `/home/ubuntu/bitcoin/src'
make[1]: Entering directory `/home/ubuntu/bitcoin'
make[1]: Nothing to be done for `all-am'.
make[1]: Leaving directory `/home/ubuntu/bitcoin'
$
如果一切顺利,bitcoind现在已经编译完成。最后一步就是通过make命令,安装 bitcoind 可执行文件到你的系统路径下:
$ sudo make install
Making install in src
Making install in .
/bin/mkdir -p '/usr/local/bin'
/usr/bin/install -c bitcoind bitcoin-cli '/usr/local/bin'
Making install in test
make install-am
/bin/mkdir -p '/usr/local/bin'
/usr/bin/install -c test_bitcoin '/usr/local/bin'
$
你可以通过询问系统下面2个可执行文件的路径,来确认bitcoin是否安装成功。
$ which bitcoind
/usr/local/bin/bitcoind
$ which bitcoin-cli
/usr/local/bin/bitcoin-cli
bitcoind 默认的安装位置是/usr/local/bin。当你第一次运行bitcoind时,它会提醒你用一个安全密码给JSON-RPC接口创建一个配置文件。通过在终端输入bitcoind就可以运行bitcoind了:
$ bitcoind
Error: To use the "-server" option, you must set a rpcpassword in the configuration file:
/home/ubuntu/.bitcoin/bitcoin.conf
It is recommended you use the following random password:
rpcuser=bitcoinrpc
rpcpassword=2XA4DuKNCbtZXsBQRRNDEwEY2nM6M4H9Tx5dFjoAVVbK
(you do not need to remember this password)
The username and password MUST NOT be the same.
If the file does not exist, create it with owner-readable-only file permissions.
It is also recommended to set alertnotify so you are notified of problems;
for example: alertnotify=echo %s | mail -s "Bitcoin Alert" admin@foo.com
在你喜欢的编辑器中编辑配置文件并设置参数,将其中的密码替换成bitcoind推荐的强密码。不要使用出现在这里的密码。在.bitcoin目录下创建一个名为.bitcoin/bitcoin.conf 的文件,然后输入用户名和密码:
rpcuser=bitcoinrpc
rpcpassword=2XA4DuKNCbtZXsBQRRNDEwEY2nM6M4H9Tx5dFjoAVVbK
当你正在编辑配置文件的时候,你可能想要设置一些其他选项,例如txindex(见“交易数据库索引及txindex选项”)。通过输入bitcoind --help
,可以查看所有可用的选项列表。
现在可以运行比特币核心客户端。当你第一次运行的时候,它会下载所有的区块,重新构建比特币区块链。这是一个好几个GB的文件,可能需要大约2天的时间全部下载完。你可以通过SourceForge上的BitTorrent客户端下载区块链的部分拷贝来缩短区块链的初始化时间。
选项 -daemon 可以以后台模式运行 bitcoind。
$ bitcoind -daemon
Bitcoin version v0.9.0rc1-beta (2014-01-31 09:30:15 +0100)
Using OpenSSL version OpenSSL 1.0.1c 10 May 2012
Default data directory /home/bitcoin/.bitcoin
Using data directory /bitcoin/
Using at most 4 connections (1024 file descriptors available)
init message: Verifying wallet...
dbenv.open LogDir=/bitcoin/database ErrorFile=/bitcoin/db.log
Bound to [::]:8333
Bound to 0.0.0.0:8333
init message: Loading block index...
Opening LevelDB in /bitcoin/blocks/index
Opened LevelDB successfully
Opening LevelDB in /bitcoin/chainstate
Opened LevelDB successfully
[... more startup messages ...]
3.2 通过命令行使用比特币核心的JSON-RPC API接口
比特币核心客户端实现了JSON-RPC接口,这个接口也可以通过命令行帮助程序bitcoin-cli访问。命令行可以使用API进行编程,让我们有能力进行交互实验。开始前,调用help命令查看可用的比特币RPC命令列表:
$ bitcoin-cli help
addmultisigaddress nrequired ["key",...] ( "account" )
addnode "node" "add|remove|onetry"
backupwallet "destination"
createmultisig nrequired ["key",...]
createrawtransaction [{"txid":"id","vout":n},...] {"address":amount,...}
decoderawtransaction "hexstring"
decodescript "hex"
dumpprivkey "bitcoinaddress"
dumpwallet "filename"
getaccount "bitcoinaddress"
getaccountaddress "account"
getaddednodeinfo dns ( "node" )
getaddressesbyaccount "account"
getbalance ( "account" minconf )
getbestblockhash
getblock "hash" ( verbose )
getblockchaininfo
getblockcount
getblockhash index
getblocktemplate ( "jsonrequestobject" )
getconnectioncount
getdifficulty
getgenerate
gethashespersec
getinfo
getmininginfo
getnettotals
getnetworkhashps ( blocks height )
getnetworkinfo
getnewaddress ( "account" )
getpeerinfo
getrawchangeaddress
getrawmempool ( verbose )
getrawtransaction "txid" ( verbose )
getreceivedbyaccount "account" ( minconf )
getreceivedbyaddress "bitcoinaddress" ( minconf )
gettransaction "txid"
gettxout "txid" n ( includemempool )
gettxoutsetinfo
getunconfirmedbalance
getwalletinfo
getwork ( "data" )
help ( "command" )
importprivkey "bitcoinprivkey" ( "label" rescan )
importwallet "filename"
keypoolrefill ( newsize )
listaccounts ( minconf )
listaddressgroupings
listlockunspent
listreceivedbyaccount ( minconf includeempty )
listreceivedbyaddress ( minconf includeempty )
listsinceblock ( "blockhash" target-confirmations )
listtransactions ( "account" count from )
listunspent ( minconf maxconf ["address",...] )
lockunspent unlock [{"txid":"txid","vout":n},...]
move "fromaccount" "toaccount" amount ( minconf "comment" )
ping
sendfrom "fromaccount" "tobitcoinaddress" amount ( minconf "comment" "commentto"
)
sendmany "fromaccount" {"address":amount,...} ( minconf "comment" )
sendrawtransaction "hexstring" ( allowhighfees )
sendtoaddress "bitcoinaddress" amount ( "comment" "comment-to" )
setaccount "bitcoinaddress" "account"
setgenerate generate ( genproclimit )
settxfee amount
signmessage "bitcoinaddress" "message"
signrawtransaction "hexstring" ( [{"txid":"id","vout":n,"scriptPub-
Key":"hex","redeemScript":"hex"},...] ["privatekey1",...] sighashtype )
stop
submitblock "hexdata" ( "jsonparametersobject" )
validateaddress "bitcoinaddress"
verifychain ( checklevel numBTClocks )
verifymessage "bitcoinaddress" "signature" "message"
walletlock
walletpassphrase "passphrase" timeout
walletpassphrasechange "oldpassphrase" "newpassphrase"
3.2.1 获得比特币核心客户端状态的信息
命令:getinfo
比特币getinfo
RPC命令显示关于比特币网络节点、钱包、区块链数据库状态的基础信息。使用bitcoin-cli
运行它:
$ bitcoin-cli getinfo
{
"version" : 90000,
"protocolversion" : 70002,
"walletversion" : 60000,
"balance" : 0.00000000,
"blocks" : 286216,
"timeoffset" : -72,
"connections" : 4,
"proxy" : "",
"difficulty" : 2621404453.06461525,
"testnet" : false,
"keypoololdest" : 1374553827,
"keypoolsize" : 101,
"paytxfee" : 0.00000000,
"errors" : ""
}
数据以JSON格式显示,JSON是一种可以很容易被编程语言“消耗”,但同时对人类可读性也很高的格式。在这些数据 中,我们看到比特币软件客户端的版本编号(90000),协议编号(70002),钱包编号(60000)。我们看到钱包中的当前余额是0。我们看到当前 的区块高度,这可以告诉我们有多少区块对此客户端已知(286216)。我们同样看到关于比特币网络和关于此客户端的各种数据。我们将在其他章节中更具体 地探索这些设置。
bitcoind客户端需要花费可能超过一天的时间从其他比特币客户端下载区块以“赶上”当前区块链高度。你可以使用getinfo命令查看已知区块的数字以检查同步进度。
3.2.2 钱包设置及加密
命令:encryptwallet
、walletpassphrase
在你向前生成秘钥和其他命令之前,你应当先用密码加密钱包。对于本例,将使用encryptwallet
命令,密码为“foo”。很明显,在你自己操作的时候记得使用强且复杂的密码替换“foo”。
$ bitcoin-cli encryptwallet foo
wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet.
The keypool has been flushed, you need to make a new backup.
$
你可以再次使用getinfo
命令以验证钱包是否已经加密。这次你将发现有个叫做unlocked_until
的新条目。这是一个计数器,告诉你保持钱包处于解锁状态的解密密码将在内存中存储多久。最初计数器设置为0,意味着钱包是被锁定的:
$ bitcoin-cli getinfo
{
"version" : 90000,
#[... other information...]
"unlocked_until" : 0,
"errors" : ""
}
$
想解锁钱包,要使用walletpassphrase
命令。walletpassphrase
命令需要两个参数——密码,和多久钱包会再次被自动锁定的秒数数字(计时器):
$ bitcoin-cli walletpassphrase foo 360
$
你可以确认钱包是解锁状态然后通过再次运行getinfo
查看超过时限:
$ bitcoin-cli getinfo
{
"version" : 90000,
#[... other information ...]
"unlocked_until" : 1392580909,
"errors" : ""
}
3.2.3 钱包备份、纯文本导出及恢复
命令:backupwallet
、importwallet
、dumpwallet
下一步,我们将练习创建钱包的备份文件,然后从备份文件重新加载钱包。使用backupwallet
命令备份,提供文件名作为命令参数。这里我们将钱包备份为文件wallet.backup:
$ bitcoin-cli backupwallet wallet.backup
$
现在,为了重新加载备份文件,我们使用importwallet
命令。如果你的钱包处于锁定状态,你将需要先将钱包解锁(参考下一节的walletpassphrase
)以导入备份文件:
$ bitcoin-cli importwallet wallet.backup
$
dumpwallet
命令用来将钱包转储为人类可读的文本文件:
$ bitcoin-cli dumpwallet wallet.txt
$ more wallet.txt
# Wallet dump created by Bitcoin v0.9.0rc1-beta (2014-01-31 09:30:15 +0100)
# * Created on 2014-02- 8dT20:34:55Z
# * Best block at time of backup was 286234
(0000000000000000f74f0bc9d3c186267bc45c7b91c49a0386538ac24c0d3a44),
# mined on 2014-02- 8dT20:24:01Z
KzTg2wn6Z8s7ai5NA9MVX4vstHRsqP26QKJCzLg4JvFrp6mMaGB9 2013-07- 4dT04:30:27Z
change=1 # addr=16pJ6XkwSQv5ma5FSXMRPaXEYrENCEg47F
Kz3dVz7R6mUpXzdZy4gJEVZxXJwA15f198eVui4CUivXotzLBDKY 2013-07- 4dT04:30:27Z
change=1 # addr=17oJds8kaN8LP8kuAkWTco6ZM7BGXFC3gk
[... many more keys ...]
$
3.2.4 钱包地址及接收交易
命令:getnewaddress
、getreceivedbyaddress
、listtransactions
、getaddressesbyaccount
、getbalance
比特币参考客户端维护了一个地址池,地址池的大小可以用getinfo
命令keypoolsize
参数获取。这些地址是自动生成的,可以被用作公开接收地址或零钱地址。使用getnewaddress
命令可以获得其中的一个地址:
$ bitcoin-cli getnewaddress
1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL
现在我们可以使用这个地址从一个外部钱包(假设你在其他交易所、在线钱包或其他bitcoind钱包有一些比特币)向我们的bitcoind钱包发送一小笔比特币。在本例中,我们将向下面的地址中发送50mBTC(0.050比特币)。
我们可以询问bitcoind客户端此地址已经接收到的比特币数额,以及指定该数额要被加到余额中所需要的确认数。在本例中,我们指定只需要0个确认。在从另一个钱包发送比特币数秒之后,我们将在这个钱包看到反应。我们用getreceivedbyaddress
命令、这个地址及设置确认数为0:
$ bitcoin-cli getreceivedbyaddress 1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL 0
0.05000000
如果我们忽略这个命令后面的0,我们将只能在至少minconf
个确认之后才能看到数额,minconf
是想要某笔交易出现在余额中所设置的最少确认数。minconf
设置在bitcoind配置文件指定。由于这笔发送比特币的交易仅在数秒之前完成,它还没有被确认,因而我们将看到余额是0:
$ bitcoin-cli getreceivedbyaddress 1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL
0.00000000
整个钱包接收到的交易可以通过使用listtransactions
命令展示出来:
$ bitcoin-cli listtransactions
[
{
"account" : "",
"address":"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL",
"category" : "receive",
"amount" : 0.05000000,
"confirmations" : 0,
"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309ac
bae2c14ae3",
"time" : 1392660908,
"timereceived" : 1392660908
}
]
我们可以使用getaddressesbyaccount
命令列出整个钱包的所有地址:
$ bitcoin-cli getaddressesbyaccount ""
[
"1LQoTPYy1TyERbNV4zZbhEmgyfAipC6eqL",
"17vrg8uwMQUibkvS2ECRX4zpcVJ78iFaZS",
"1FvRHWhHBBZA8cGRRsGiAeqEzUmjJkJQWR",
"1NVJK3JsL41BF1KyxrUyJW5XHjunjfp2jz",
"14MZqqzCxjc99M5ipsQSRfieT7qPZcM7Df",
"1BhrGvtKFjTAhGdPGbrEwP3xvFjkJBuFCa",
"15nem8CX91XtQE8B1Hdv97jE8X44H3DQMT",
"1Q3q6taTsUiv3mMemEuQQJ9sGLEGaSjo81",
"1HoSiTg8sb16oE6SrmazQEwcGEv8obv9ns",
"13fE8BGhBvnoy68yZKuWJ2hheYKovSDjqM",
"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL",
"1KHUmVfCJteJ21LmRXHSpPoe23rXKifAb2",
"1LqJZz1D9yHxG4cLkdujnqG5jNNGmPeAMD"
]
最后,getbalance
命令将显示所有经过至少minconf
个确认的交易加和后的余额:
$ bitcoin-cli getbalance
0.05000000
如果交易还未被确认,getbalance返回的余额将为0。配置项“minconf”决定了交易在余额中体现的最少确认数。
3.2.5 探索及解码交易
命令:gettransaction
、getrawtransaction
、decoderawtransaction
我们将使用gettransaction
命令探索前面列出的入账交易。我们使用gettransaction
命令通过交易哈希值获取一笔交易,交易哈希值出现在前面的txid条目:
$ bitcoin-cli gettransaction
9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3
{
"amount" : 0.05000000,
"confirmations" : 0,
"txid":"9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"time" : 1392660908,
"timereceived" : 1392660908,
"details" : [
{
"account" : "",
"address":"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL",
"category" : "receive",
"amount" : 0.05000000
}
]
}
交易ID在交易确认之前并不权威。区块链中找不到交易哈希值并不意味着此笔交易没有进行。这被称作“交易延展性”,因为交易哈希值在区块确认之前是可以更改的。在确认之后txid是不变且权威的。
用gettransaction
命令显示的交易格式为简化格式。若要得到整个交易代码并且将之解码,我们将使用两个命令:getrawtransaction
和decoderawtransaction
。第一,getrawtransaction
把交易哈希值(txid)当做一个参数,并且把整个交易以一个“原始”的十六进制字符串的形式返回,而这也正是交易在比特币网络上存在的形式:
$ bitcoin-cli getrawtransaction 9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae30100000001d717279515f88e2f56ce4e8a31e2ae3e9f00ba1d0add648e80c480ea22e0c7d3000000008b483045022100a4ebbeec83225dedead659bbde7da3d026c8b8e12e61a2df0dd0758e227383b302203301768ef878007e9ef7c304f70ffaf1f2c975b192d34c5b9b2ac1bd193dfba2014104793ac8a58ea751f9710e39aad2e296cc14daa44fa59248be58ede65e4c4b884ac5b5b6dede05ba84727e34c8fd3ee1d6929d7a44b6e111d41cc79e05dbfe5ceaffffffff02404b4c00000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac1f312906000000001976a914107b7086b31518935c8d28703d66d09b3623134388ac00000000
要解码这个十六进制字符串,我们使用decoderawtransaction
命令。复制粘贴这个十六进制字符串作为decoderawtransaction
的第一个参数以将整个内容解读为JSON数据格式(由于格式原因,在下面例子中十六进制字符串被缩短):
$ bitcoin-cli decoderawtransaction 0100000001d717...388ac00000000
{
"txid":"9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid":"d3c7e022ea80c4808e64dd0a1dba009f3eaee2318a4ece562f8ef815952717d7",
"vout" : 0,
"scriptSig" : {
"asm" : "3045022100a4ebbeec83225dedead659bbde7da3d026c8b8e12e61a2df0dd0758e227383b302203301768ef878007e9ef7c304f70ffaf1f2c975b192d34c5b9b2ac1bd193dfba20104793ac8a58ea751f9710e39aad2e296cc14daa44fa59248be58ede65e4c4b↵
884ac5b5b6dede05ba84727e34c8fd3ee1d6929d7a44b6e111d41cc79e05dbfe5cea",
"hex": "483045022100a4ebbeec83225dedead659bbde7da3d026c8b8e12e61a2df0dd0758e227383b302203301768ef878007e9ef7c304f70ffaf1f2c975b192d34c5b9b2ac1bd193dfba2014104793ac8a58ea751f9710e39aad2e296cc14daa44fa59248be58ede65e4c4b884ac5b5b6dede05ba84727e34c8fd3ee1d6929d7a44b6e111d41cc79e05dbfe5cea"
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.05000000,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 07bdb518fa2e6089fd810235cf1100c9c13d1fd2 OP_EQUALVERIFY OP_CHECKSIG",
"hex" :"76a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL"
]
}
},
{
"value" : 1.03362847,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 107b7086b31518935c8d28703d66d09b36231343 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914107b7086b31518935c8d28703d66d09b3623134388ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"12W9goQ3P7Waw5JH8fRVs1e2rVAKoGnvoy"
]
}
}
]
}
交易解码展示这笔交易的所有成分,包括交易的输入及输出。在这个例子中,我们可以看到这笔给我们新地址存入50mBTC 的交易使用了一个输入并且产生两个输出。这笔交易的输入是前一笔确认交易的输出(展示位以d3c7开头的vin txid)。两个输出则是50mBTC存入额度及返回给发送者的找零。
我们可以使用相同命令(例如gettransaction
)通过检查由本次交易的txid索引的前一笔交易进一步探索区块链。通过从一笔交易跳到另外一笔交易,我们可以追溯一连串的交易,因为币值一定是从一个拥有者的地址传送到另一个拥有者的地址。
一旦我们接收到的交易以记录在区块中的方式被确认,gettransaction
命令将返回附加信息,显示包含交易的区块的哈希值(标识符)。
$ bitcoin-cli gettransaction 9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3
{
"amount" : 0.05000000,
"confirmations" : 1,
"blockhash" : "000000000000000051d2e759c63a26e247f185ecb7926ed7a6624bc31c2a717b",
"blockindex" : 18,
"blocktime" : 1392660808,
"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"time" : 1392660908,
"timereceived" : 1392660908,
"details" : [
{
"account" : "",
"address" : "1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL",
"category" : "receive",
"amount" : 0.05000000
}
]
}
这里,我们在区块哈希值(这笔交易所在区块的哈希值)条目看到新信息,以及值为18的区块索引(表明我们的交易为此区块的第18笔交易)。
交易数据库索引及txindex选项
比特币核心默认建立包含仅与用户钱包相关交易的数据库。若你想使用类似
gettransaction
的命令访问所有交易,你需要配置比特币核心去建立一个完整的交易索引,这个可以通过txindex选项实现。在比特币核心配置文件中将txindex
赋值为1(通常在安装目录的.bitcoin/bitcoin.conf中可以找到)。一旦你改变了此参数,你需要重启bitcoind,并等待其重建索引。
3.2.6 探索区块
命令:getblock
、getblockhash
既然我们知道我们的交易在哪个区块中,我们可以使用getblock
命令,并把区块哈希值作为参数来查询对应的区块:
$ bitcoin-cli getblock 000000000000000051d2e759c63a26e247f185ecb7926ed7a6624b↵ c31c2a717b true
{
"hash" : "000000000000000051d2e759c63a26e247f185ecb7926ed7a6624bc31c2a717
b",
"confirmations" : 2,
"size" : 248758,
"height" : 286384,
"version" : 2,
"merkleroot" : "9891747e37903016c3b77c7a0ef10acf467c530de52d84735bd55538719f9916",
"tx" : [
"46e130ab3c67d31d2b2c7f8fbc1ca71604a72e6bc504c8a35f777286c6d89bf0",
"2d5625725b66d6c1da88b80b41e8c07dc5179ae2553361c96b14bcf1ce2c3868",
"923392fc41904894f32d7c127059bed27dbb3cfd550d87b9a2dc03824f249c80",
"f983739510a0f75837a82bfd9c96cd72090b15fa3928efb9cce95f6884203214",
"190e1b010d5a53161aa0733b953eb29ef1074070658aaa656f933ded1a177952",
"ee791ec8161440262f6e9144d5702f0057cef7e5767bc043879b7c2ff3ff5277",
"4c45449ff56582664abfadeb1907756d9bc90601d32387d9cfd4f1ef813b46be",
"3b031ed886c6d5220b3e3a28e3261727f3b4f0b29de5f93bc2de3e97938a8a53",
"14b533283751e34a8065952fd1cd2c954e3d37aaa69d4b183ac6483481e5497d",
"57b28365adaff61aaf60462e917a7cc9931904258127685c18f136eeaebd5d35",
"8c0cc19fff6b66980f90af39bee20294bc745baf32cd83199aa83a1f0cd6ca51",
"1b408640d54a1409d66ddaf3915a9dc2e8a6227439e8d91d2f74e704ba1cdae2",
"0568f4fad1fdeff4dc70b106b0f0ec7827642c05fe5d2295b9deba4f5c5f5168",
"9194bfe5756c7ec04743341a3605da285752685b9c7eebb594c6ed9ec9145f86",
"765038fc1d444c5d5db9163ba1cc74bba2b4f87dd87985342813bd24021b6faf",
"bff1caa9c20fa4eef33877765ee0a7d599fd1962417871ca63a2486476637136",
"d76aa89083f56fcce4d5bf7fcf20c0406abdac0375a2d3c62007f64aa80bed74",
"e57a4c70f91c8d9ba0ff0a55987ea578affb92daaa59c76820125f31a9584dfc",
"9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
#[... many more transactions ...]
],
"time" : 1392660808,
"nonce" : 3888130470,
"bits" : "19015f53",
"difficulty" : 3129573174.52228737,
"chainwork" : "000000000000000000000000000000000000000000001931d1658fc04879e466",
"previousblockhash" : "0000000000000000177e61d5f6ba6b9450e0dade9f39c257b4d48b4941ac77e7",
"nextblockhash" : "0000000000000001239d2c3bf7f4c68a4ca673e434702a57da8fe0d829a92eb6"
这个区块包含367笔交易,并且如你所见,列出的第18笔交易(9ca8f9…
)就是存入50mBTC到我们地址的txid我们可以通过heigh条目来判断:这就是整个区块链中第286,384个区块。
我们同样可以使用getblockhash
命令通过区块高度来检索一个区块,这样需要将区块高度作为参数,并返回那个区块的区块哈希值。
$ bitcoin-cli getblockhash 0000000000019d6689c085ae165831e934ff763ae46a2a6c17↵
2b3f1b60a8ce26f
$ bitcoin-cli getblockhash
这里,我们获得了“创世区块”的区块哈希值,这是被中本聪所挖的第一个区块,高度为0。所获得的区块信息如下:
$ bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1↵
b60a8ce26f
{
"hash" : "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26↵f",
"confirmations" : 286388,
"size" : 285,
"height" : 0,
"version" : 1,
"merkleroot" : "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7af↵
deda33b",
"tx" : [
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
],
"time" : 1231006505,
"nonce" : 2083236893,
"bits" : "1d00ffff",
"difficulty" : 1.00000000,
"chainwork" : "0000000000000000000000000000000000000000000000000000000100010001",
"nextblockhash" : "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
}
getblock
、getblockhash
和gettransaction
命令可以按照一定编程准则,去探索区块链数据库。
3.2.7 基于UTXO(未花费的交易输出)的建立、签名与提交
命令:listunspent
、gettxout
、createrawtransaction
、decoderawtransaction
、signrawtransaction
、sendrawtransaction
比特币的交易是基于花费“输出”上的,即上一笔交易的支出,整个交易在地址之间转移所有权。我们的钱包现在收到了一笔向我们钱包地址发来的钱(输出)。一旦它被确定之后,那笔钱就属于我们了。
首先,我们可以使用listunspent
命令去查看我们钱包中所有剩余的从之前交易中已确认的支出:
$ bitcoin-cli listunspent
[
{
"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"vout" : 0,
"address" : "1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL",
"account" : "",
"scriptPubKey" : "76a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac",
"amount" : 0.05000000,
"confirmations" : 7
}
]
我们发现交易9ca8f9
建立了一个被指派到1hvzSo
地址的输出(“vout”一项为0)对于50mBTC数量的比特币在这个时间点已经收到了7次确认。通过参考交易之前的txit和vout指数,交易系统将先前的输出变为本次的输入。我们现在可以创立一个花费第0个vout的易9ca8f9
的账单。利用他的输入分配成新的输出,即给新地址赋值。
首先,让我们仔细观察输出的结果。我们可以使用gettxout
命令来得到未花费的输出的详细细节。交易输出通常可以参考txid和vout两个指标。以下就是我们通过gettxout
命令得到的结果:
$ bitcoin-cli gettxout 9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3 0
{
"bestblock" : "0000000000000001405ce69bd4ceebcdfdb537749cebe89d371eb37e13899fd9",
"confirmations" : 7,
"value" : 0.05000000,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 07bdb518fa2e6089fd810235cf1100c9c13d1fd2
OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL"
]
},
"version" : 1,
"coinbase" : false
}
在这里我们看到由50mBTC分配到我们的账户地址1hvz…
中。如果我们想用掉剩余的比特币,我们要重新建立一笔新的交易。首先,我们为这笔交易建立一个新的地址,告诉它将去往哪里:
$ bitcoin-cli getnewaddress 1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb
我们将25mBTC送往我们钱包中新的地址1LnfTn…
。在这笔新的交易中,我们将要花费 50mBTC并且放入25mBTC到这个新地址中。因为我们必须花费所有之前交易的输出,同时我们必然产生一些找零。我们将产生的找零放回1hvz…的地 址之中,即将找零放回到原先产生比特币的地址之中。最后,我们必须为这次交易支出一些费用——我们将0.5mBTC作为交易费,最终再存入 24.5mBTC的找零。新的输出(25mBTC+24.5mBTC=49.5mBTC)和输入(50 mBTC)之间的差额就是奖励给矿工的交易费。
我们用createrawtransaction
命令去建立一笔交易。我们将交易的收入(50已确认未支出的mBTC)和两笔交易的输出(送往新地址的比特币和从原先账户找回的零钱)作为createrawtransaction
的参数。
$ bitcoin-cli createrawtransaction '[{"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3", "vout" : 0}]' '{"1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb": 0.025, "1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL": 0.0245}'
0100000001e34ac1e2baac09c366fce1c2245536bda8f7db0f6685862aecf53ebd69f9a89c0000000000ffffffff02a0252600000000001976a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac00000000
createrawtransaction
命令产生了一个原始十六进制字符串,其中编码了这笔交易的诸多细节。我们首先要通过decoderawtransaction
命令来解码这个字符串,以确认所有的细节准确无误:
$ bitcoin-cli decoderawtransaction 0100000001e34ac1e2baac09c366fce1c2245536bda8f7db0f6685862aecf53ebd69f9a89c0000000000ffffffff02a0252600000000001976a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac00000000
{
"txid" : "0793299cb26246a8d24e468ec285a9520a1c30fcb5b6125a102e3fc05d4f3cba",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"vout" : 0,
"scriptSig" : {
"asm" : "",
"hex" : ""
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.02500000,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 d90d36e98f62968d2bc9bbd68107564a156a9bcf OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb"
]
}
},
{
"value" : 0.02450000,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 07bdb518fa2e6089fd810235cf1100c9c13d1fd2 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL"
]
}
}
]
}
结果无误!我们的交易“消耗了”从我们已确认的交易中未花费的输出,然后将它变成两笔输出,一个是走向了新地址的25mBTC,另一个是从原来地址返回的24.5mBTC零钱。这之间0.5mBTC的差额作为交易费,以奖励挖出包含我们这笔交易区块的矿工。
你有可能注意到,交易中包含一个空的条目scriptSig
,因为我们并没有给它签名。如果没有签名,那么交易是没有意义的;同时我们也不能证明我们拥有未花费的输出的来源地址的所有权。通过签名,我们移除了输出上的障碍同时证明了我们的输出可靠。我们使用signrawtransaction
命令去签名交易。它需要原始十六进制的字符串作为参数:
一个加密的钱包在签名之前必须解密,因为签名需要利用钱包中的秘钥。
$ bitcoin-cli walletpassphrase foo 360
$ bitcoin-cli signrawtransaction 0100000001e34ac1e2baac09c366fce1c2245536bda8↵
f7db0f6685862aecf53ebd69f9a89c0000000000ffffffff02a0252600000000001976a914d90↵
d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e↵
6089fd810235cf1100c9c13d1fd288ac00000000
{
"hex" : "0100000001e34ac1e2baac09c366fce1c2245536bda8f7db0f6685862aecf53ebd69f9a89c000000006a47304402203e8a16522da80cef66bacfbc0c800c6d52c4a26d1d86a54e0a1b76d661f020c9022010397f00149f2a8fb2bc5bca52f2d7a7f87e3897a273ef54b277e4af52051a06012103c9700559f690c4a9182faa8bed88ad8a0c563777ac1d3f00fd44ea6c71dc5127ffffffff02a0252600000000001976a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac00000000",
"complete" : true
}
输入signrawtransaction
命令后,得到另一串十六进制的原始加密交易。我们要对它进行解密,然后去查看发生的变化,请输入decoderawtransaction
命令:
$ bitcoin-cli decoderawtransaction0100000001e34ac1e2baac09c366fce1c2245536bda8f7db0f6685862aecf53ebd69f9a89c000000006a47304402203e8a16522da80cef66bacfbc0c800c6d52c4a26d1d86a54e0a1b76d661f020c9022010397f00149f2a8fb2bc5bca52f2d7a7f87e3897a273ef54b277e4af52051a06012103c9700559f690c4a9182faa8bed88ad8a0c563777ac1d3f00fd44ea6c71dc5127ffffffff02a0252600000000001976a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac00000000
{
"txid" : "ae74538baa914f3799081ba78429d5d84f36a0127438e9f721dff584ac17b346",
"version" : 1,
"locktime" : 0,
"vin" : [
{
"txid" : "9ca8f969bd3ef5ec2a8685660fdbf7a8bd365524c2e1fc66c309acbae2c14ae3",
"vout" : 0,
"scriptSig" : {
"asm" : "304402203e8a16522da80cef66bacfbc0c800c6d52c4a26d1d86a54e0a1b76d661f020c9022010397f00149f2a8fb2bc5bca52f2d7a7f87e3897a273ef54b277e4af52051a0601 03c9700559f690c4a9182faa8bed88ad8a0c563777ac1d3f00fd44ea6c71dc5127",
"hex" : "47304402203e8a16522da80cef66bacfbc0c800c6d52c4a26d1d86a54e0a1b76d661f020c9022010397f00149f2a8fb2bc5bca52f2d7a7f87e3897a273ef54b277e4af52051a06012103c9700559f690c4a9182faa8bed88ad8a0c563777ac1d3f00fd44ea6c71dc5127"
},
"sequence" : 4294967295
}
],
"vout" : [
{
"value" : 0.02500000,
"n" : 0,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 d90d36e98f62968d2bc9bbd68107564a156a9bcf OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb"
]
}
},
{
"value" : 0.02450000,
"n" : 1,
"scriptPubKey" : {
"asm" : "OP_DUP OP_HASH160 07bdb518fa2e6089fd810235cf1100c9c13d1fd2 OP_EQUALVERIFY OP_CHECKSIG",
"hex" : "76a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac",
"reqSigs" : 1,
"type" : "pubkeyhash",
"addresses" : [
"1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL"
]
}
}
]
}
现在,交易中的收入包含了scritSig
,一串证明钱包地址1hvz…
所有权的数字签名,同时移除了支出上的障碍,然后我们可以对钱包中的钱进行消费。签名可以让这笔交易被比特币交易网络中的任何节点验证,使他们变得可靠。
现在,该是提交新交易到比特币网络的时候了。我们使用由原始十六进制signrawtransaction
命令生成的sendrawtransaction
命令。以下就是和刚才解码时类似的字符串:
$ bitcoin-cli sendrawtransaction0100000001e34ac1e2baac09c366fce1c2245536bda8f7db0f6685862aecf53ebd69f9a89c000000006a47304402203e8a16522da80cef66bacfbc0c800c6d52c4a26d1d86a54e0a1b76d661f020c9022010397f00149f2a8fb2bc5bca52f2d7a7f87e3897a273ef54b277e4af52051a06012103c9700559f690c4a9182faa8bed88ad8a0c563777ac1d3f00fd44ea6c71dc5127ffffffff02a0252600000000001976a914d90d36e98f62968d2bc9bbd68107564a156a9bcf88ac50622500000000001976a91407bdb518fa2e6089fd810235cf1100c9c13d1fd288ac00000000ae74538baa914f3799081ba78429d5d84f36a0127438e9f721dff584ac17b346
当使用sendrawtransaction
命令发布交易到比特币网络时,它会返回交易的哈希值。我们现在可以通过gettransaction
命令查询交易ID:
$ bitcoin-cli gettransaction ae74538baa914f3799081ba78429d5d84f36a0127438e9f721dff584ac17b346
{
"amount" : 0.00000000,
"fee" : -0.00050000,
"confirmations" : 0,
"txid" : "ae74538baa914f3799081ba78429d5d84f36a0127438e9f721dff584ac17b346",
"time" : 1392666702,
"timereceived" : 1392666702,
"details" : [
{
"account" : "",
"address" : "1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb", "category" : "send",
"amount" : -0.02500000,
"fee" : -0.00050000
},
{
"account" : "",
"address" : "1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL", "category" : "send",
"amount" : -0.02450000,
"fee" : -0.00050000
},
{
"account" : "",
"address" : "1LnfTndy3qzXGN19Jwscj1T8LR3MVe3JDb", "category" : "receive",
"amount" : 0.02500000
},
{
"account" : "",
"address" : "1hvzSofGwT8cjb8JU7nBsCSfEVQX5u9CL", "category" : "receive",
"amount" : 0.02450000
}
]
}
和以前一样,我们同样可以通过使用getrawtransaction
和decodetransaction
命令来检查交易中的细节。这些命令会得到一个在发送到比特币网络之前进行编码和解码并且十分精准的原始十六进制字符串。
3.3 其他替代客户端、资料库、工具包
除了参考客户端(bitcoind),还可以使用其他的客户端和资料库去连接比特币网络和数据结构。这些工具都由一系列的编程语言执行,用他们各自的语言为比特币程序提供原生的交互。
其他的执行方式包括:
▷ libbitcoin和sx tools
一款C++,通过命令行完成的全节点多线程客户端与程序库
▷ bitcoinj
一款全节点java客户端和程序库
▷ btcd
一款全节点GO 语言的比特币客户端
▷ Bits of Proof(BOP)
一款Java企业级平台的比特币工具
▷ picocoin
一款轻量级比特币执行客户端
▷ pybitcointools
一款Python语言的比特币程序库
▷ pycoin
另一款Python语言的比特币程序库
在其他的编程语言中,还有许多形式的比特币(程序)库。开发者也尽其所能,一直在尽力创造新的比特币工具。
3.3.1 Libbitcoin和sx Tools
Libbitcoin程序是一款基于C++层面,可扩展、多线程、模块化的执行工具。它可以支持全节点客户端和一款叫做sx的命令行工具,并可以提 供我们本章所讨论的比特币命令相同的功能。Sx工具同时提供了管理和操作工具,是bitcoind所不能提供的,包括type-2型确定性密钥和密码助记 工具。
安装sx
若要安装sx工具以及相关libbitcoin库,请在Linux操作系统中下载并安装在线安装包:
$ wget http://sx.dyne.org/install-sx.sh
$ sudo bash ./install-sx.sh
现在你应当已经安装好了sx工具。输入没有参数的sx命令来显示帮助文档,帮助文档列出了所有可用的命令(详见附录4)。
sx工具提供了许多实用的编码与解码地址的命令,可以从不同的编码方式转化,也可以转化成不同的方式。通过他们,可以探索更多的编码方式,比如Base58,Base58Check,hex,等等。
3.3.2 pycoin
pycoin最初由Richard Kiss创立并维护,是一款基于Python库,并可以支持比特币密钥的操作和交易的客户端,甚至可以支持编译语言从而处理非标准交易。
Pycoin库同时支持Python2(2.7x)与Python3,以及一些便于使用的命令行工具,比如ku和tx。如果在Python3的虚拟环境下安装 pycoin0.42,请输入以下命令:
$ python3 -m venv /tmp/pycoin
$ . /tmp/pycoin/bin/activate
$ pip install pycoin==0.42
Downloading/unpacking pycoin==0.42
Downloading pycoin-0.42.tar.gz (66kB): 66kB downloaded
Running setup.py (path:/tmp/pycoin/build/pycoin/setup.py) egg_info for pack-age pycoin
Installing collected packages: pycoin
Running setup.py install for pycoin
Installing tx script to /tmp/pycoin/bin
Installing cache_tx script to /tmp/pycoin/bin
Installing bu script to /tmp/pycoin/bin
Installing fetch_unspent script to /tmp/pycoin/bin
Installing block script to /tmp/pycoin/bin
Installing spend script to /tmp/pycoin/bin
Installing ku script to /tmp/pycoin/bin
Installing genwallet script to /tmp/pycoin/bin
Successfully installed pycoin
Cleaning up...
$
这里有一个简单的Python脚本,通过pycoin库来交易比特币:
#!/usr/bin/env python
from pycoin.key import Key
from pycoin.key.validate import is_address_valid, is_wif_valid from pycoin.services import spendables_for_address
from pycoin.tx.tx_utils import create_signed_tx
def get_address(which):
while 1:
print("enter the %s address=> " % which, end='')
address = input()
is_valid = is_address_valid(address)
if is_valid:
return address
print("invalid address, please try again")
src_address = get_address("source")
spendables = spendables_for_address(src_address) print(spendables)
while 1:
print("enter the WIF for %s=> " % src_address, end='')
wif = input()
is_valid = is_wif_valid(wif)
if is_valid:
break
print("invalid wif, please try again")
key = Key.from_text(wif)
if src_address not in (key.address(use_uncompressed=False), key.address(use_un compressed=True)):
print("** WIF doesn't correspond to %s" % src_address)
print("The secret exponent is %d" % key.secret_exponent())
dst_address = get_address("destination")
tx = create_signed_tx(spendables, payables=[dst_address], wifs=[wif])
print("here is the signed output transaction")
print(tx.as_hex())
更多的ku与tx命令行样例,请参考附录2。
3.3.3 btcd
btcd是一款基于Go语言的全节点比特币工具。目前,它通过使用精准的规则(包括bugs),下载、验证和服务区块链。它同时依靠新发掘出来的区 块来维持交易池,同时依赖没有形成区块的单独交易。在缜密的规则以及检查下,确保了每笔独立交易的安全,并且可以过滤基于矿工需求的交易。
btcd与bitcoind的一个主要区别是btcd不包含比特币钱包的功能,其实这是一个精心的设计。这意味着你不能直接通过btcd进行比特币 交易。然而这项功能可以由正在研发的btcwallet与btcgui两个项目提供。另一个显著的区别是btcd同时支持HTTP POST(比如bitcoind)与推荐使用的Websockets两种通信协议的请求。并且btcd的RPC连接默认设置为TLS-开启。
安装btcd
若要安装Windows版btcd,请从GitHub下载并运行msi;如果你已经安装了Go语言,请在Linux中输入以下命令行:
$ go get github.com/conformal/btcd/...
若要更新btcd到最新版本,请输入:
$ go get -u -v github.com/conformal/btcd/...
调试btcd
btcd拥有许多配置选项,可以通过以下命令来查看:
$ btcd --help
btcd预装了许多好用的功能包,比如btcctl。它是一种可以通过RPC来控制和查询的令行工具。Btcd并没有默认开启了RPC服务器,你必须通过以下命令行来配置RPC用户名及密码:
▷ btcd.conf:
[Application Options]
rpcuser=myuser
rpcpass=SomeDecentp4ssw0rd
▷btcctl.conf:
[Application Options]
rpcuser=myuser
rpcpass=SomeDecentp4ssw0rd
若果你想要重写配置,请输入以下命令:
$ btcd -u myuser -P SomeDecentp4ssw0rd
$ btcctl -u myuser -P SomeDecentp4ssw0rd
可以通过以下命令来查询一系列的选项:
$ btcctl --help
==目录==
简介
前言
第1章 介绍
第2章 比特币的原理
第3章 比特币客户端
第4章 密钥、地址、钱包
第4章 高级密钥和地址
第5章 交易
第6章 比特币网络
第7章 区块链
第8章 挖矿与共识
第8章 区块链分叉、矿池、共识攻击
第9章 竞争币、竞争块链和应用程序
第10章 比特币安全
附录1 交易脚本的操作符、常量和符号
附录2 比特币改进协议
附录3 pycoin库、ku程序和tx交易程序
附录4 sx工具下一些的命令
本文原链接:http://zhibimo.com/read/wang-miao/Mastering-Bitcoin/Chapter03.html