当前位置:   article > 正文

区块链基础知识(一)_区块链网络信息

区块链网络信息

原文:zh.annas-archive.org/md5/70e290e9f76882896adba256279d5064

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

当初次接触区块链技术时,它可能会让人感到非常害怕。但实际上,技术本身只是三个流行概念的结合:加密学、点对点网络和博弈论。虽然这些乍一看可能会很复杂,但对这三个基本概念的基本理解将帮助您建立一个坚实的基础,一旦完成,就可以作为理解区块链技术的高级基石。

本书旨在帮助您理解区块链技术的概念,并向您介绍加密货币以及几种区块链平台。它还对技术的潜力和关注点进行了深入分析,以便在实际添加价值的地方采用区块链。

本书适合对象

本书适用于任何希望深入了解区块链技术领域基础的人。虽然本书为初学者构建了区块链技术基础,但也可以被区块链开发人员用作快速参考指南,同时也可以深入了解技术的一些激动人心的主题。

本书涵盖内容

第一章,简介,通过探讨一些基本主题,如其定义和历史、背后的动机、其特征以及不同类型的区块链,概述了区块链技术。

第二章,一点点加密学,探讨了与区块链技术相关的加密学基础,以及一些实际示例。

第三章,区块链中的加密学,解释了区块链技术如何利用加密原语,如哈希函数和数字签名。

第四章,区块链中的网络,介绍了点对点网络概念,以实现区块链网络的去中心化。本章还涵盖了如何借助示例应用程序在去中心化网络中维护区块链。

第五章,加密货币,通过探索比特币的概念,深入了解了区块链技术的原始和最佳实现,并帮助区分加密货币和传统数字货币。

第六章,深入区块链 – 存在证明,通过实现一个使用案例:存在证明,介绍了使用 MultiChain 区块链框架进行去中心化应用程序开发。

第七章,深入区块链——所有权证明,通过实现一个用例,更深入地介绍了在 NEO 和以太坊区块链平台上的智能合约概念,特别关注所有权证明。

第八章,区块链项目,通过分类和了解一些知名的区块链项目,探讨了区块链领域的机遇。

第九章,区块链优化与增强,关注了可以优化区块链应用程序的技术,同时还介绍了一些现有区块链应用程序的增强功能,以添加有趣的功能。

第十章,区块链安全,通过指出可能的攻击方式以及如何预防来揭示区块链技术所需的安全级别。

第十一章,何时不应使用区块链,列出了区块链技术的特征,并解释了选择区块链应用程序正确用例时需要考虑的几种决策模型。

第十二章,区块链用例,利用决策模型分析了一些真实的区块链用例,并考虑为这些用例创建实现。

要充分利用本书

尽管本书建立了有关加密学和点对点网络概念的知识基础,具有实际 Python 编程经验和理论网络知识会是优势。

本书中使用的大多数应用程序都可以在任何平台上执行,示例展示是在 Ubuntu 16.04.5 LTS 上执行的。

您应该熟悉使用 APT 在 Ubuntu 上或 Mac 或 Windows 上的等效工具等包管理工具安装应用程序。

由于大多数应用程序源代码托管在 GitHub 上,您应该熟悉 Git 版本控制系统。

下载示例代码文件

您可以从您在www.packt.com上的账户中为本书下载示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,以便直接接收电子邮件中的文件。

您可以按照以下步骤下载代码文件:

  1. 登录或在www.packt.com注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保您使用最新版本的解压或提取文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

本书的代码包也托管在 GitHub 上,地址为:github.com/PacktPublishing/Foundations-of-Blockchain。如果代码有更新,将在现有的 GitHub 存储库中更新。

我们还提供了来自我们丰富书籍和视频目录的其他代码包。请查看!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在此处下载:www.packtpub.com/sites/default/files/downloads/9781789139396_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

CodeInText:指示文本中的代码字词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。这是一个例子:“将下载的WebStorm-10*.dmg磁盘映像文件安装为系统中的另一个磁盘。”

代码块设置如下:

from Crypto.Hash import SHA256

hash_object = SHA256.new(data=b'First')
print(hash_object.hexdigest())
  • 1
  • 2
  • 3
  • 4

当我们希望引起您对代码块的特定部分的注意时,相关行或项将以粗体显示:

from Crypto.Hash import SHA256

hash_object = SHA256.new(data=b'First')
print(hash_object.hexdigest())
  • 1
  • 2
  • 3
  • 4

任何命令行输入或输出均按如下格式编写:

$  curl -X POST http://localhost:10332 -H 'Content-Type: application/json' -d '{ "jsonrpc": "2.0", "id": 5, "method": "getversion", "params": [] }'
  • 1

粗体:表示新术语、重要词汇或屏幕上看到的词语。例如,菜单或对话框中的单词在文本中显示为这样。这里有一个例子:“通过修改标题中称为nonce的变量字段来创建一个不同的标题。”

警告或重要说明会出现在这样。

小贴士和技巧会像这样出现。

第一章:介绍

本书旨在帮助您探索令人兴奋的区块链技术,在本书的第一章中,我们将深入探讨其基本概念。 我们的目的是提供对区块链的相当广泛的概述,使您能够充分准备好我们将在后面章节更深入讨论的主题。 由于我们的意图是介绍区块链,因此以下主题将贯穿本章:

我们将从区块链的基础知识开始,包括其神话和历史。 我们将探讨一些关键区块链概念背后的思想,最后,我们将概述区块链技术的工作原理。 本章的主题旨在为您提供足够的动力和信心,以便您对我们将在本书后面讨论的主题感到自信和舒适。

区块链是什么

尽管区块链有各种定义,但最好将区块链描述为一个由区块链结构组成的数据结构,这些结构链接在一起形成一个称为分类帐的记录集,其中密码学是该过程的关键因素。 区块链没有存储机制;相反,它有一组规定信息生成方式的协议。 因此,区块链可以存储在平面文件中或数据库中。

区块链技术之所以受到欢迎,是因为其完整性不容易被破坏。 一个被破坏的区块链可以被识别出来,而且很容易被网络中的任何人拒绝。 这种完整性是通过密码学实现的,这就是将区块绑在一起的东西; 我们将在第二章中研究这个密码学的概念,密码学简介

区块链承诺提供如此强大的完整性,最终为在不受信任的点对点P2P)网络中共享数据链的想法铺平了道路。对区块链中的块进行验证是确保区块链具有全局有效状态并可被所有人接受的关键。由于区块链能够在没有任何中央机构管理的开放 P2P 网络中共享信息,因此该技术可以有许多不同的应用;然而,该技术不能立即在这些应用中部署而不经任何故障排除。尽管区块链技术从一开始就在应用去中心化应用方面发挥了重要作用,但它在应用于无信任环境时仍面临着几个挑战。其中最大的挑战之一是保持区块链在 P2P 网络的所有参与者之间的一致性。这个问题通过创建共识算法来解决,该算法同意如何在不受信任的环境中添加块以扩展链。

区块链这个术语实际上涵盖了许多概念,包括 P2P 网络管理、共识机制等等,所有这些都有助于创建去中心化应用。

区块链不是什么

正如我们刚刚讨论的那样,尽管区块链因其基于密码学的安全性、去中心化性质和几乎不可变的数据存储机制而引人入胜,但了解其局限性也非常重要。

区块链的理想实现是在原子事件或交易中,事件的最少信息被存储为一个交易;这些交易可以被组合在一个单一的块中并添加到区块链中。尽管区块链网络在处理全局状态方面表现良好,但在批量存储数据时,它不会增加太多价值,因为会存在可伸缩性问题。了解何时最好应用区块链技术来开发应用程序非常重要。我们将在第十一章中探讨何时不应该使用区块链。

区块链定义

你可能还记得我们在本章一开始就指出了对于区块链一词有几种不同的定义。在我们继续之前,让我们看看这个词的几种定义:

“区块链是由共识锻造的点对点分布式分类帐,结合了“智能合约”和其他辅助技术的系统。”

  • hyperledger.org

“区块链是分布式分类帐技术的一种特定形式或子集,它构建了一个块的时间顺序链,因此被称为“块链”。”

  • 安东尼·刘易斯,R3 研究总监

“区块链数据结构是一种有序的、反向链接的块列表。”

  • 安德烈亚斯·安东诺普洛斯,一位知名的比特币传道者

区块链与数据库有何不同?

区块链是一种只读和追加的存储方法。这意味着区块链分类账中的块只能被创建和读取。区块链中的块不能被更新或删除;块只能追加到区块链的末尾。公共区块链没有访问控制,因为它对读写操作都是开放的。

另一方面,关系型数据库遵循创建,读取,更新和删除CRUD)操作模型。与区块链不同,每个数据库在创建时都有一个管理员,他们将为其他用户分配访问控制。关系型数据库大多由一个实体维护,该实体控制所有应用程序数据,而区块链技术是为了去中心化应用而设计的。

图 1.1应该能帮助你形象地展示集中式数据库架构和区块链架构之间的区别:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.1:显示集中式数据库与区块链架构的图示。

注意:区块链本身提供不可变性安全性冗余性ISR),而传统数据库需要额外的投资来为它们保存的数据提供 ISR。基于区块链的解决方案相对于数据库的主要优势之一是几乎不需要投资于基础设施管理。

区块链的历史

我们知道,区块链技术现在主要应用于去中心化网络中的交易或事件追踪;当前,这种用例的最大范围是在金融领域。然而,事实上,区块链技术直到最近才以我们今天知道和使用的形式出现。最早的构建数字文档的防篡改时间戳的链条概念是在 1991 年提出的。但这个概念直到 2008 年,一个使用Satoshi Nakamoto假名的作者发表了一篇名为比特币:点对点电子现金系统的论文(bitcoin.org/bitcoin.pdf),展示了区块链及其在去中心化网络中的真正用途时,才为人熟知。

后来,在 2009 年,Satoshi Nakamoto 创建了区块链技术的一个参考实现,称为比特币。这是第一个也是目前最流行的基于区块链的电子现金系统实现。Satoshi 从之前的一些发明中获得了灵感,如 b-money 和 Hashcash,以创建一个去中心化的数字货币。

注意:尽管比特币是加密货币革命的始作俑者,但在它之前,有几次尝试使用密码协议普及电子现金。DigiCash 是美国科学家戴维·朝早期的一次尝试。还有一种分布式数字货币叫做 Bit Gold,由尼克·萨博提出,其架构与比特币类似。

比特币于 2009 年发布,当时中本聪进行了第一笔交易,该交易被插入到比特币区块链的第一个区块中。这个区块被称为创世区块,是整个区块链的合法性证明。中本聪于 2011 年停止了对比特币项目的贡献,现在允许开放社区为该项目做贡献。自那时起,它已经从一个相当简单的数字货币发展成为一个具有韧性的协议,已经成为每个区块链应用程序的参考实现。尽管比特币的市值波动很大,但截至 2018 年底,它已接近 2000 亿美元的市值,几乎占据了整个加密货币市场的一半。

比特币是第一个去中心化数字货币,解决了双花攻击的问题。保持一个开放的分布式区块链,并通过共识验证区块的有效性,是使比特币成为实际可实施的去中心化货币的主要因素。比特币利用一种称为工作证明PoW)算法的共识算法来证明节点确实在创建区块链的新区块时进行了工作。这个概念也被应用在一个叫做 Hashcash 的实现中,它旨在通过强制垃圾邮件发送者在发送每封电子邮件之前做一些工作来限制电子邮件垃圾邮件。这个系统防止了垃圾邮件发送者批量发送电子邮件,因为在发送每封电子邮件之前需要进行计算任务。接收者所需要做的就是验证发送者所做的工作。同样,比特币中实现的 PoW 共识算法防止了任何节点用自己创建的区块淹没区块链,从而防止任何单一实体主导区块链。

区块链 2.0

比特币真正催生了区块链技术,此后,出现了几种新的分布式区块链数据库的应用,最显著的是在 2014 年初。这个时代被广泛称为区块链 2.0 时代。Namecoin 是最早出现的概念之一。它扩展了比特币的区块链范围,并被引入为基于比特币的分布式命名系统。然而,与比特币不同的是,它能够在公共区块链中将数据存储为键值对。这个概念影响了许多后来获得流行的区块链 2.0 应用程序。

区块链 2.0 带来的最受欢迎的增强功能之一是智能合约的引入。 开发了几个区块链平台,允许用户编写更高级别的脚本,并不必担心实际的区块链实现。 其中一个在这方面最成功的平台是以太坊,由 Vitalik Buterin 于 2013 年底提出。 以太坊的初衷是充分利用比特币使用的技术。 比特币的初始实现旨在用于数字货币的流动。 现在,虽然已经为其他应用程序(如资产转移)实现了一种脚本语言,但它非常原始,并且只有少数用例。 以太坊的联合创始人 Vitalik Buterin 建议,比特币需要一种用于去中心化应用程序开发的脚本语言,以扩大其范围。 未能达成协议,Vitalik 提议开发一种具有更通用脚本语言的新平台。 由于在其环境中实现了智能合约,该平台以太坊变得受欢迎。 智能合约是在以太坊虚拟机EVM)上运行的高级脚本。 已经使用以太坊平台开发了许多去中心化应用程序。

以太坊平台激发了开发人员提出一个框架,该框架将使用比特币的核心协议并构建一个开发去中心化应用程序的平台。 这是一种革命性的方法,因为该技术将被用于修改任何需要第三方的应用程序,这既费钱又多余。

实际上,有很多动机促使开发人员在现有应用程序中集成区块链技术。 在下一节中,我们将更深入地解释这一点。

创建了几个区块链平台,用于构建可扩展的去中心化应用程序;以下是一些最受欢迎的平台:

  • Corda:这是一个旨在记录、管理和自动化商业合作伙伴之间法律协议的分布式账本平台。 它由 R3 与全球最大的金融机构合作设计,这使 Corda 适用于转向分布式账本技术的金融企业。

  • Hyperledger:这是一个推动跨行业区块链技术进步的开源努力。 它由 Linux 基金会托管,并实现了各种行业和组织之间的合作。 例如,IBM 和英特尔是 Hyperledger 项目的积极贡献者。 有许多 Hyperledger 项目,旨在使用区块链解决不同的企业级问题。

  • Multichain:这是一个简单而强大的与比特币兼容的私有区块链框架。 它支持完整的资产周期管理。 由于支持访问控制,它是开发权限区块链应用程序的理想框架。

  • NEO:以前被称为 Antshares,这个应用程序经常被称为“赛里斯的以太坊”。它使用区块链技术和数字身份来数字化资产。

区块链背后的动机

每一次新的创新都是试图解决问题的结果。区块链技术也不例外。通过了解区块链技术的演变,很明显它的出现是因为需要解决现有经济中不确定性的必然性。

不确定性永远无法被消除,但只能降低:总会有机构充当第三方立法者,以降低不确定性或缺乏信任,每当需要协议达成时。一个典型的例子就是在 eBay 上购买物品。你总是需要尽可能多的确定性关于这笔交易。一方期望公平的商品,另一方期望约定的款项。现在,尽管买方和卖方没有理由相互信任,他们仍然完成交易,因为他们信任 eBay 这第三方,eBay 向他们保证交易合法。再次,有必要信任这些“中介”机构。信任一个机构需要大量的研究和知识。区块链承诺通过以分散和安全的方式实现应用程序,确保一定程度的确定性来克服这些问题。这是区块链在无需信任的社会中被广泛采用的主要原因之一。

我们知道区块链是在无需信任环境中实施的理想技术。但单单区块链并不是完整实施成功的唯一原因。它还受到其他几个协议的帮助,使得它成为强大和有韧性的技术。区块链之所以能够在无需信任的网络中实施,主要是由于在密集的 P2P 网络中计算的分散化以及对整个区块链的安全和公开分布式账本的维护,这使得整个区块链具有完全的透明性。P2P 协议确保每个节点均持有区块链的最新状态。

区块链技术背后的关键动机是去中心化的需求,而去中心化是通过将计算任务分配给区块链网络的所有节点来实现的。去中心化解决了传统系统的几个问题;单点故障就是其中之一。例如,在银行这样的集中式系统中,用户总是与同一个第三方银行通信以获取其账户详细信息。尽管这种交易几乎每次都可能成功,但不能保证 100%的正常运行时间,因为该服务器是集中式的,并且只有几个备份服务器用于负载平衡。可能会出现所有服务器都被请求淹没,导致崩溃和服务器关闭的情况。即使在设计完美的服务器中,这种停机也是不可避免的。如果在去中心化网络中面临同样的情况,这不会是一个问题,因为所有交易数据都分布在所有节点之间,这意味着每个节点在故障时都可以充当备份节点,保持数据的完整性(基于区块链解决方案的另一个关键优势)。这是通过维护分布式账本的区块链数据来实现的。区块链的不可变性是信任区块链完整性的关键因素,它确保了账本的完整性,并且对所有节点公开可访问。

区块链的特性

简单来说,区块链是一系列通过加密相互保护的块。密码哈希指针被用作公共账本中区块链中每个块的引用。虽然这听起来非常安全,因为没有入侵者能够破解区块链并插入他们自己的块版本,但它并不是完全安全的。由于账本是完全透明和公开的,任何节点都可以插入自己的块以复制整个区块链并创建自己的版本。最终,他们可以将块传播到网络中的每个节点,并证明他们的区块链是合法的。这表明,仅通过连接所有块并形成安全账本是无法实现不可变性的。实现不可变性需要通过某种去中心化的经济机制来辅助,使每个网络节点都有公平的机会对块创建进行投票,并且一旦附加后,重新构建块也变得更加困难。

中本聪对这个问题提出的解决方案是比特币在去中心化环境中可以实现的唯一原因。比特币使用的 PoW 共识算法是第一个 – 也是迄今为止最为人所知的 – 解决方案。它向公共分类账承诺了很高程度的不可变性,并在一个无信任的网络中保护它。在加密货币方面,执行 PoW 的节点被称为挖矿节点。顾名思义,挖矿是指锻造新区块以追加到区块链中。挖矿所需的工作量确保了区块链的不可变性,并且几乎不可能篡改任何过去的交易。

这是因为任何想要篡改过去数据的节点都应该能够通过提供 PoW 并与所有其他挖矿节点竞争来重建所有区块。这几乎是不可能的,除非问题节点拥有网络中大多数计算能力,这种情况下,攻击者有机会击败所有节点。这就是为什么比特币的共识算法被广泛用于公共区块链应用以实现更高的记录不可变性。

然而,不可变性并不是区块链技术展现的唯一特征。由于区块链的去中心化性质,区块链中的每一笔交易都会在网络的所有节点上复制。信息的复制为区块链提供了更大的韧性。复制的交易必须由每个节点验证才能达成共识。这确保了交易是公开可见的,并且所有的区块链数据对网络是透明的。区块链提供的透明性对某些用例可能是一种福音,但对其他用例可能是一种诅咒。这就是为什么区块链的变体被创建的原因,正如本章后面描述的那样。

区块链的所有这些特征使其成为完美的公共分类账,或者说是有效的分布式分类账技术DLT)的一个典型实例。到目前为止,比特币的区块链以及其共识机制是最具韧性的 DLT。

DLT 背景

自网络发明以来,人们一直在讨论计算机架构的集中化和去中心化问题。我们看到这两种计算机架构模型之间的兴趣随时间波动。企业使用大型机架构来容纳大量的计算能力、内存和存储。它们在很大程度上是集中化的,终端没有太多的计算能力,用于连接到这些机器以执行所需的操作。然后,个人计算机被引入到家庭中使用,具有足够的计算能力、内存和存储来执行基本操作。这催生了客户端-服务器架构,其中客户端与服务器通信以执行计算。服务器通常在分布式系统中执行大量计算,并将结果与客户端同步。

云计算架构提供了从任何计算设备轻松访问服务器的方式,因为架构本身是全球可访问的。然而,云计算架构是集中化的,其硬件资源是分布式的,对客户端不透明。云供应商和最终用户之间仍然存在信任缺乏。这就是我们目睹从其他计算模型向去中心化过渡的原因。DLT 是实现这一里程碑、开启去中心化时代的关键。

分布式分类账在其核心是一个复制和共享的数字数据库,分布在地理区域之间。为确保有效的分布式分类账,需要一个 P2P 网络和共识算法。区块链技术是实现分布式分类账的技术之一,但并非 DLT 的唯一数据结构:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.2:计算机架构的演变,直至分布式分类账的诞生(来源:https://en.wikipedia.org)

背景:可以与 DLT 相比较的最早的事物可以追溯到公元 500 年,当时太平洋岛屿雅浦岛依赖一种称为莱的货币,这些石头的重量可能超过 200 公斤。由于这些石头非常沉重,因此每个岛上的成年人都会记住莱的所有权。口头所有权记录确保无需单一方维护谁拥有莱的记录。

不同类型的区块链

区块链的应用案例不断增加,人们对其局限性的认识也在增加,这导致了多种成功的区块链实现的出现。在本节中,我们将尝试掌握每一个实现的精髓。

公共区块链(无需许可)

由于其透明性以及每个节点参与促进区块链增长的方式,区块链概念已被广泛应用和改编。早期的区块链模型,即比特币的产物,完全是开放和无许可的,通常被称为公共区块链。公共区块链因其节点被公平对待的方式而受欢迎。

由于记录的不可变性,公共区块链在无信任网络中运行良好。比特币、以太坊以及一些继承了类似 PoW 的共识算法的其他项目确保记录的交易不可编辑。

公共区块链非常适合加密货币项目,记录的交易不应被修改。然而,如果不实施必要的改变,公共区块链往往会在某个时候面临可扩展性问题。比特币最明显的问题之一是其挖矿方法(PoW),挖矿所需的电费非常昂贵。区块的创建平均时间为 10 分钟。因此,挖矿的难度水平已经调整以维持这个时间。由于矿工之间的竞争,这导致了一个非常昂贵的 PoW 环境。由于这些复杂的属性,我们无法预测比特币或任何其他公共区块链的未来,只有技术的自然演进才能决定其命运。

鉴于这些利弊,无许可或公共区块链非常适用于透明的应用程序,其中区块链应从本质上保护系统,因为网络是不受信任的。

私有区块链(许可)

私有区块链主要是为了拓宽区块链技术的应用范围而引入的。许可区块链,顾名思义,采用了与公共区块链相反的方法。私有区块链主要出现是为了解决公共区块链中出现的一些问题,并使区块链技术可扩展。

许可区块链引入了访问控制,为网络中的参与者提供特定的访问权限。每个许可区块链都将有一个管理员,他为网络中的参与者分配角色。许可区块链确保恶意行为者不参与验证或区块创建过程,从而消除了对区块链的任何潜在攻击。涉及许可区块链的网络主要是一个值得信赖的网络。

私有区块链适用于只需要在内部共享账本的组织。许可区块链通常是可变的或者不严格不可变的,它们的交易可以通过一些努力进行修改;这与公共区块链形成了鲜明对比,公共区块链几乎不可能被修改。许可区块链仍然是去中心化的账本,但是在组织内部会有一些功能有限的节点,而公共区块链中的节点是公平对待的。

注:私有区块链不使用比特币的 PoW 共识算法作为它们的共识算法。事实上,私有区块链是为了消除公共区块链的昂贵共识方法而创建的,以使区块链技术适用于受信任的环境。

联盟区块链

联盟区块链是一种半中心化的混合区块链。它结合了无许可和有许可区块链的最佳特征。与将大多数任务分配给单个组织不同,联盟区块链将相同的任务分配给由多个组织维护的节点。与单个验证节点不同,可以有多个节点。尽管联盟区块链是有许可的,但比私有区块链更为去中心化。

区块概述

现在我们对区块链有了一个相当了解,我们将概述区块,它负责构建区块链。

区块属性

如果我们将区块链视为一种数据结构,那么区块就是用于形成区块链的聚合数据集。区块链的形成类似于链表的形成,其中每个节点都有对序列中下一个节点的引用。在区块链的情况下,每个区块都有对上一个节点的引用,从而形成一条链直到链的初始块(称为创世块)。正如我们之前提到的,区块链可以存储在平面文件或数据库格式中。比特币使用LevelDB存储所有下载到磁盘上的区块的元数据。

就像链表节点一样,每个区块都有一个指针,这个指针是区块的标识符。这些只是区块头数据的哈希值。有关哈希的更多细节将在第二章,一点点密码学中讨论。我们可以将哈希视为表示每个区块的固定大小的唯一标识符;没有两个区块会有相同的标识符。由于所有区块都通过这个哈希值链接在一起,每个区块都将具有前一个区块的标识符。前一个区块被称为父区块,每个区块只能有一个父区块。

每个区块也可以通过区块链的高度引用。这个高度只是区块与创世块的距离,或者区块计数。高度是区块链的重要属性,因为用一个简单的数字而不是一个冗长的哈希值来引用一个区块更容易。区块哈希不是整个区块的哈希值,而是仅包含元数据的区块头的哈希值。在比特币中,SHA256 哈希算法用于对区块头进行哈希,并为区块创建一个唯一标识符。

区块结构

尽管所有区块链都由链接区块组成以形成不可变的账本,但根据应用程序可以采用不同的区块结构。例如,许可和无许可区块链的区块结构略有不同。我们

我们将使用比特币的无许可区块结构作为参考,尝试识别其特征:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.3:区块的结构

区块由图 1.3中提到的组件组成。区块头交易是区块的最重要部分,因为它们负责哈希值,即区块的标识。区块大小是整个区块的大小。区块头包含区块的所有元数据,交易计数器记录交易数量。最后,所有的交易都存储在区块中。

如前所述,区块链始于一个称为创世区块的初始区块。如果从任何给定的区块向后遍历链条,最终会回到创世区块,证明整个链条是合法和有效的。创世区块通常在公共或无许可区块链中静态编码,但在许可区块链的情况下,它是由第一个参与者创建的。

区块头

如前所述,区块头由区块的元数据组成。这包含了链接区块所需的信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.4:区块头的结构

每个区块头都有图 1.4中概述的组件。这些是无许可区块链(如比特币)中所需的最小字段,以便有效地创建一个可附加到不可变区块链的区块。前一个区块哈希字段是对上一个创建的区块的引用。Merkle 根是 Merkle 哈希树的值;它总结了区块中的所有交易。时间戳难度目标随机数是 PoW 共识算法用来解决哈希难题的。我们将在本书中更深入地讨论这些概念。

注:与无许可区块链不同,在其中共识算法用于生成区块,许可区块链使用区块创建者的签名来表示区块标识。然而,与无许可区块链一样,许可区块链中的区块保留了先前的区块标识符。

链接区块

我们知道,区块链中的区块使用引用链接,就像在链表中一样,但这里的区块是通过引用前一个区块的哈希值(标识符)来链接的。区块链网络中的每个完整节点都将维护一个完整的区块链,并在有一个要附加的区块时附加一个新区块。由于区块链的分散特性,每个节点在将区块链接到本地区块链记录之前都会验证该区块。

每个区块的计算哈希值是前一个区块的哈希和自身区块数据的组合。这导致相邻区块之间存在依赖关系,并且几乎不可破解的链接:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.5:使用哈希链接区块,摘自《比特币:一个点对点的电子现金系统》,S. Nakamoto

Satoshi 解释了时间戳概念的使用方法。所有项目都被散列,然后区块被时间戳标记,意味着后续的区块将包含此时间戳,从而创建一个有序的区块链。

区块链网络中的每个节点都遵循一种简单的过程,将新区块附加到其现有的本地区块链中。每当节点从网络中接收到一个区块时,它都会检查前一个区块的哈希值。如果哈希值与节点本地区块链上的最后一个区块的哈希值匹配,则节点接受此区块并将其附加到当前区块链中。只要这是已知的最长区块链,基于 PoW 的区块链中的所有对等方都将认为这些区块是有效的。

摩尔定律对区块链技术的影响

戈登·摩尔是 Fairchild Semiconductor 和英特尔的联合创始人,他观察到每个电子集成电路的组件数量每年至少增长一倍。早在 1965 年,他还预测这种增长率至少会持续另一个十年。多年来,他将预测修订为每两年翻倍一次。这一观察是针对密集集成电路中的晶体管数量,并已被用于半导体行业设定研究和开发目标。但它不仅限于芯片制造领域;它还被用于观察技术和社会变革、生产率和经济增长。

摩尔定律已被应用于近似网络容量的变化率、图像中的像素、存储设备大小等。区块链是未来的技术,可能必须克服多种限制才能实现健康的长期发展。摩尔定律将有助于决定任何区块链应用程序所需的复杂性,以便应用程序不必为未来的可伸缩性问题而苦苦挣扎。

由于网络中的每个节点都维护完整的区块链账本,随着时间的推移,区块链数据的大小不断增加。这引发了一些关于可伸缩性的担忧,因为每个节点都需要在本地维护区块链(这是分布式网络的性质)。中本聪曾提到,区块头大小的增长率每年约为 4.2 MB,而摩尔定律将保证每年至少增长 1.2 GB RAM(在 2008 年),即使这些数据存储在节点内存中也不会造成任何问题。

公共区块链,比如比特币,需要应对硬件的哈希率用于它们的共识算法。比特币挖矿硬件已经能够按照摩尔定律保持步伐,根据不断增长的难度率提供所需的哈希率。然而,比特币挖矿的未来依赖于摩尔定律和硬件能够跟上困难而不会给矿工造成太大损失:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.6:比特币难度目标的指数增长(来源:blockchain.info)

摘要

本章介绍了我们在接下来的章节中研究区块链所需的一切。在了解了区块链技术的背景,包括其目的和工作原理之后,我们现在应该充分理解这项技术的潜力以及它如何解决当前某些系统中的问题。

现在我们对这项技术有了一定的了解,在下一章,第二章,密码学小点,我们将详细讲解围绕区块链技术的基本概念。在那一章中,我们将探索区块链理解的基本构建模块。

第二章:一点点密码学

本章将涵盖你理解它在区块链技术中发挥的重要作用所需的所有密码学基础知识。我们将深入探讨区块链依赖的密码学的所有方面。我们将用实际术语解释一些概念,以便我们可以在后面的章节中轻松地实施它们。这些包括以下内容:

  • 区块链中的密码学

  • 古典密码学

  • 密码学原语

  • Merkle 树

  • 编码方案

现代密码学是研究私人或安全通信的学科。密码学的基本目标是使两个人能够在不安全的媒介上进行通信。这是通过对发送者的明文进行加密以形成只能由接收者解密的密文来实现的,发送者与接收者共享一个秘密。然而,第三方可以访问传输密文的通道,但文本对它没有任何意义,因此通道是安全的与否都无所谓。密码学已经发展,并且现在可以应用于各种领域,包括区块链。我们将从一个基础和基本的密码实现开始概述密码学,然后我们将进入高级和现代密码学主题。

密码学对于身份验证、保密性和完整性等信息安全服务至关重要。在 19 世纪,奥古斯特·凯克霍夫 概述了后来被称为凯克霍夫原则的原则:一个加密系统应该是安全的,即使系统的一切都是公开的,除了密钥。密钥是密码学中唯一需要保密和保护免受入侵攻击的资产。

区块链中的密码学

虽然我们已经提到密码学对区块链技术的成功至关重要,但我们并没有探讨任何特定的主题。大多数密码学原语在创建去中心化区块链应用程序中都扮演着某种角色。我们将在本章中研究所有有助于区块链的原语。

哈希算法被大多数区块链应用程序用于创建区块之间的链接。它还用于诸如工作量证明之类的共识算法,基本上利用了形成区块链网络的计算系统的哈希能力。数字签名用于签署和验证诸如交易之类的事件。非对称密钥密码学是区块链应用程序中的核心概念,它为网络参与者赋予身份或证明资产的所有权。

因此,密码学是实现取代可信第三方和在分散网络中创建无信任环境所需任务的绝佳工具。

古典密码学

在本节中,我们将研究一些在历史密码中使用过的加密技术。这些临时密码不够安全,不能用于现代应用,但因其简单性,可以鼓励我们更多地了解密码学。探索古典密码学的弱点还有助于我们更多地了解一些密码学原理。看一下下面的图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.1:传统加密模型

图 2.1显示了使用与其他用户共享的秘密密钥通过安全通道加密明文的传统加密模型。想要阅读文本的用户将使用秘密密钥解密密文,将返回原始明文。密钥是私密的,加密和解密算法是公开的,因为没有密钥就无法解密密文。

有两种操作用于将明文转换为密文:替换和换位。这两种技术都可以确保操作是可逆的,因此它们可以用于加密算法。

替换密码是一种加密方法,其中明文中的字符以固定的方式被其他字符所替换。替换密码的最简单示例是凯撒密码,其中明文字母通过将字母表向后移动三个位置来进行替换:A 替换为 D,B 替换为 E,依此类推。这种密码的明显问题在于方法是固定的,并且没有涉及到密钥。引入了凯撒密码的变种,称为移位密码,其中从明文到密文的移位量变化,并且这个移位量可以作为密钥。尽管这解决了当前的问题,但它并不够实用,因为密钥可以通过暴力破解或穷举搜索攻击来猜测。多表密码是密文进化的下一个阶段。这种密码在消息的不同位置引入了多个替换。

一种换位密码是一种加密方法,其中明文字母的位置根据已知系统进行了移位。只有明文的顺序发生了改变。明文的所有字母都保持不变。栅栏密码和转位密码是两种著名的换位密码。这种密码技术可以通过使用变位分析法找到换位模式来进行解密。

密码基元

密码基元是低级密码算法,用于构建应用程序使用的加密协议。这些是设计密码系统的构建模块。计划在系统中实施加密协议的设计人员无需担心基元的低级抽象,可以完全集中精力构建应用程序:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.2:密码学原语的分类

图 2.2显示了密码学原语的详细分类。区块链技术利用这些密码学原语中的大多数来实现基本的区块链功能并在分散式网络上保护数据:非对称加密用于管理密钥;数字签名用于交易;而最重要的是哈希,它是区块链的支柱,是最常用的密码学原语之一。我们将涵盖所有这些原语以及其他一些内容,以便更清晰地了解它们。

对称密钥加密

对称密钥是一种基于密钥的加密,其算法使用相同的密钥来执行明文的加密和密文的解密。这些密钥在两个参与方之间通过安全信道共享。拥有共享密钥的任何参与者都可以对数据执行加密和解密操作。对称密钥密码可以是流加密或块加密。

对于基于区块链的应用程序来说,对称密钥加密并不起到重要作用。然而,在我们研究非对称加密之前,它会更好地理解基于密钥的加密。

流密码

流密码使用对称密钥加密。每个明文字符都是一个接一个地加密,就像流一样,以创建密文。一个密钥流,或字符流,用于加密明文字符。使用伪随机字符串,其作为密钥流。这个伪随机字符串是使用数字移位寄存器(生成器)从一个随机种子值生成的,如图 2.3所示。使用的种子是秘密密钥,它也用于解密创建的密文。

为了使流密码安全,其伪随机生成器应该是不可预测的,并且用于生成密钥流的种子值不应该被重复使用以减少可能的攻击。流密码通常比块密码更快,并且硬件要求低,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.3:流密码的流程图

块密码

块密码是一种对明文中的固定长度字符块进行加密的密码。这种密码技术被广泛用于对大量数据进行加密。通常的块大小为 64 位、128 位和 256 位。例如,64 位块密码将接收 64 位明文作为输入并输出 64 位密文。在某些明文长度不足填满一个块的情况下,明文将填充一些块。由于块密码中使用的密钥相当长,它们能够抵御穷举攻击。这些密码也是其他加密协议的基本构件,如哈希函数和随机数生成器。数据加密标准DES)、高级加密标准AES)、国际数据加密算法IDEA)和Blowfish都是一些常见的块密码算法。

数据加密标准

DES 曾经是最广泛使用的块密码,并且也是工业标准。尽管仍然很受欢迎,但在许多应用中已被其他先进的块密码所替代。DES 使用 64 位块和 64 位密钥。在密钥中的 8 位被用作奇偶校验位以用于错误检测,因此密钥实际上为 56 位。已经证明它容易受到穷举攻击和一些密码分析攻击的影响,这是由于其有限的密钥长度所致。为了克服这个问题,3DES 通过使用不同的 56 位密钥三次运行 DES 来进行加密。但是 3DES 被证明比其他块密码(如 AES)更慢。

DES 在传输或存储密钥时使用 8 位密钥作为奇偶校验位,用于错误检测。第 8、16、24、…、64 位的位用于计算奇校验,即密钥每个字节中的 1 的个数是奇数。

高级加密标准

AES 是现代应用中最广泛使用的块密码之一。Rijndael 算法被选定为 AES,在经过 5 年的公开竞赛后成为 DES 的替代品。其固定的块大小为 128 位,密钥大小为 128、192 或 256 位。AES 是迭代密码:迭代轮数取决于密钥长度。

AES 对所有已知的攻击都是安全的。看起来没有比穷尽搜索更快的攻击 AES 的方法。攻击 AES 的最佳方法只适用于迭代轮数最少的密码变体。

AES 的一个示例实现

让我们使用名为PyCryptodome的 Python 加密库来实现 AES 密码技术。在本章节中,我们将使用这个库来实现其他密码和哈希算法。

PyCryptodome 是一个自包含的低级密码原语 Python 包。PyCryptodome 是PyCrypto库的分支项目,是一个具有扩展原语支持的活跃项目。因此,它几乎可以替代旧的PyCrypto库。

我们将从Crypto.Cipher包中使用 AES 模块,还将从Crypto.Random包中导入一个模块来为 AES 生成一个随机密钥,如下所示:

from Crypto.Cipher import AES 
from Crypto.Random import get_random_bytes 
  • 1
  • 2

加密端将使用随机选择的对称密钥创建密文。一旦导入所需模块,使用Crypto.Random包生成一个 16 字节的密钥。这个密钥被写入一个需要保密的文件中:

with open("aes.key", "wb") as file_out: 
    key = get_random_bytes(16) 
    file_out.write(key) 
  • 1
  • 2
  • 3

通过传递密钥创建 AES 密码对象。代码中使用了 Cipher 模式 EAX。此对象用于加密数据。Nonce、标签和密文被存储并传输到解密端:

data = "plaintext for AES" 
cipher = AES.new(key, AES.MODE_EAX) 
cipher_text, tag = cipher.encrypt_and_digest(data.encode()) 
with open("encrypted.bin", "wb") as file_out: 
    [file_out.write(x) for x in (cipher.nonce, tag, cipher_text)] 
print("Data is encrypted and stored in a file") 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

AES 的解密部分使用在加密过程中生成的相同的 16 字节对称密钥。理想情况下,这个密钥必须通过安全渠道传输给接收方。读取接收到的加密二进制文件以获取 Nonce、标签和密文本身。使用相同的密钥和 Nonce 值创建 AES 密码对象。最后,通过提供cipher_texttag执行解密的decrypt_and_verify方法。标签用于执行验证;它检查密文中是否有任何修改:

with open("aes.key", "rb") as file_in: 
    key = file_in.read(16) 
with open("encrypted.bin", "rb") as file_in: 
    nonce, tag, cipher_text = [file_in.read(x) for x in (16, 16, -1)] 

cipher = AES.new(key, AES.MODE_EAX, nonce) 
data = cipher.decrypt_and_verify(cipher_text, tag) 
print("Decrypted data is : \"{}\"".format(data.decode())) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

成功执行加密和解密操作将产生以下输出:

Data is encrypted and stored in a file
Decrypted data is : "plaintext for AES"  
  • 1
  • 2

当运行 AES 程序的加密和解密部分时,我们在解密后获得原始数据。对密文进行任何修改都会导致 MAC 检查错误,并且 Python 会抛出ValueError: MAC check failed

本章中包含的详细的 Jupyter Notebook 和脚本可以在本书的 GitHub 存储库中找到。

非对称密钥密码学

非对称密钥密码学是现代密码学中广泛使用的加密技术。除了加密之外,它还有许多其他应用。它也经常在区块链的几个元素中使用,因此我们将深入介绍这种密码学技术及其原语。

对称密钥密码学使用共享密钥进行加密和解密。这样做的最大问题是共享密钥需要在参与者之间通过安全渠道交换,这可能非常难以实现。如果我们首先就有一个安全的通信渠道,这也会破坏加密的目的。这就是非对称密码学的用武之地。它使用称为公钥/私钥对的一对密钥。公钥是从私钥构造的,可以自由广播给其他用户。

1978 年,Ronald Rivest、Adi Shamir 和 Leonard Adleman 创建了第一个公钥算法,称为 RSA 算法。

公钥算法使得可以从随机生成的私钥创建公钥。创建的公钥不能用于推断私钥。换句话说,从私钥创建公钥是一个单向过程。这是公钥加密安全性依赖的概念。公钥算法不仅执行加密,还提供认证功能。

持有私钥的用户可以使用该密钥对已知用户公钥的系统进行身份验证,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.4:非对称密钥加密

正如我们在图中看到的,与对称加密不同,不需要安全通道来共享密钥。加密和解密算法是相同的,构造的密钥对在加密/解密过程中起着至关重要的作用。正如前面讨论的,非对称密钥算法也可以用于提供认证。这种机制的一个应用是数字签名:只有具有私钥的用户才能签署消息,任何拥有公钥的人都可以验证消息的真实性。数字签名也可以用于不可否认。区块链应用,特别是加密货币,利用数字签名用私钥签署交易以证明所有权。因此,区块链技术主要依赖于非对称加密算法。Diffie-Hellman 密钥交换,DSA,ElGamal,RSA 和椭圆曲线密码ECC)是一些非对称密钥密码学的方法。

公钥密码系统的强度取决于从公开可用的有关密钥的信息中推断出私钥的可行性。虽然这是不可行的,但并非不可能,安全性完全依赖于密钥大小和密钥生成机制。由于其复杂性和加密/解密大型文件所需的时间,非对称密钥并不广泛使用。它们通常用于数字签名或密钥交换机制,而不是加密协议。

所有非对称密钥算法都基于确保密钥生成和加密解密过程所需特性的数论问题。根据在数论中解决数学问题的不同方式,非对称密钥生成在三个方面广泛特征化:素数分解,离散对数和椭圆曲线。所有公钥私钥算法都基于这些数学问题。所有这些问题在功能上类似于陷门函数。

陷阱门函数是一个函数,其中计算一方面的值很容易,但是从结果中找到逆是不可行的。这意味着很难从结果中找到供给函数的原始输入值。这个功能在非对称加密中广泛使用。

素数分解

素数分解是关于将数字分解为两个素数乘积的数论概念。素数分解是整数分解的一个子集,其中复合数被分解为任意两个整数的乘积。

要找出半素数(由两个素数相乘得到的数字)的因数是具有挑战性的,因为它们只有一对因数,并且随着用于乘积的素数的大小增加,找到因数的复杂性增加。当数字达到一定大小时,没有已知的高效分解算法用于找到因数。RSA 使用素数分解,假定从素数乘积中暴露的私钥是非常困难的。这种假设的困难性是密码学中使用素数分解的原因。

离散对数

离散对数是基于离散对数的模算术设置的,在这里找到解决方案是不可行的。对数log[b]a是一个离散对数,具有整数解x,使得b^x = a。对于找到离散对数的解决方案,没有通用的高效方法。当离散对数与模算术一起使用时,它被称为模指数,这个问题变得非常困难。这个问题通常与 Diffie-Hellman 密钥交换算法一起使用。

让我们考虑一个模指数的例子:

33 mod 5 = 2
  • 1

很容易找到前述函数的结果,即 2,但是从结果中找到指数值 3 是困难的。前述的模操作也可以表示为同余,即 3³ ≅ 2 (mod 5)

假设ab是两个整数,m是一个正整数。那么短语a ≅ b (mod m)被称为同余,并读作"a 模 m 同余 b",这表明m除以a-b

椭圆曲线

椭圆曲线是具有方程y² = x³ + ax + b形式的平面实代数曲线。椭圆曲线应该是一个非奇异曲线,意味着没有尖点、自交点或孤立点。在密码系统中使用了有限域上的椭圆曲线。ECC 在比特币中用于生成私钥-公钥对,因此我们将在本章的后面部分深入讨论此问题:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.5:一个椭圆曲线(类似于比特币中使用的曲线)

在比特币等加密货币中,非对称加密中的公私钥概念用于识别资产的所有者。私钥用于代表加密货币的所有权。

RSA 加密系统

RSA 是公私密钥密码学的最初实现之一。它使用素数因子分解原理生成公私密钥对,这充当了一个陷阱函数。加密使用公钥进行,而解密使用秘密保存的私钥进行。

对称公私密钥密码系统的概念归功于惠特菲尔德·迪菲和马丁·赫尔曼,他们在 1976 年发表了这个概念。

利用两个大质数计算公私钥对。公钥发布给用户,私钥保密。大质数也要保密。只要使用的大质数足够大,就不可能从公钥计算出私钥。整个 RSA 密码系统基于整数因子分解的数论问题,确保了素数分解的困难程度与使用的素数大小成正比。

RSA 参数生成

在讨论 RSA 加密和解密之前,我们需要考虑 RSA 参数生成过程。以下是此过程涉及的步骤:

  1. 选择两个不同的大质数pq

  2. 计算n = pqφ(n) = (p − 1)**(q − 1).

  3. 选择一个随机整数* e*,使得1 < e < φ(n),且gcd (e, φ(n)) = 1 ,即整数* e* 和* φ(n)* 为互质数。

  4. 找到d ≡ e^(-1) (mod φ(n)),其中 e^(-1)是e的模乘逆元。

整数a的模乘逆元是整数x,使得乘积ax对于模数m是同余于1

更具体地说,找到d,使得de ≡ 1 (mod φ(n)),即找到一个值d*,使得当deφ(n)除时余数为1*。

  1. 公钥用*(e, n)表示,私钥用(d, p, q)*表示。这里,e称为公钥指数,d称为私钥指数。

使用 RSA 进行加密和解密

RSA 中使用分发的公钥进行加密。消息M被转换为整数m,使得0 ≤ m < n。密文c使用公开的公钥指数计算如下:

c ≡ m^e mod (n)

拥有公钥指数的任何人都可以对消息进行加密并将其传输给拥有私钥指数的人。拥有密文字和私钥指数的任何人可以按以下方式进行解密:

m ≡ c^d mod (n)

从解密的整数m中可以重新生成消息M。这就是 RSA 如何利用素数分解技术来进行加密和解密的方式。对于小消息,这个过程可以相对快速地完成,但它不是大消息首选的加密方式。这就是为什么 RSA 广泛用于加密原语,如数字签名,而不是加密的原因。

RSA 的一个示例实现

示例中使用了 Python PyCryptodome 库中的 RSA 包。以下包被导入以进行 RSA 密钥生成和加密解密操作:

from Crypto.PublicKey import RSA 
from Crypto.Cipher import PKCS1_OAEP 
  • 1
  • 2

使用 RSA 包中的 generate 方法创建了一个 2048 位的 RSA 密钥。从这个生成的密钥中导出了公钥,并公开了。应该保密 key 对象。使用公钥创建了一个密码对象,并使用该对象对消息进行加密:

message = "plaintext for RSA" 
key = RSA.generate(2048) 
public = key.publickey() 

cipher = PKCS1_OAEP.new(public) 
cipher_text = cipher.encrypt(message.encode()) 
print("Data is encrypted")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

解密操作的执行方式与加密操作类似,但使用的是密钥对的私有部分而不是公共部分。密文被作为输入传递给 decrypt 方法,该方法对其进行解密并返回解密后的消息:

cipher = PKCS1_OAEP.new(key) 
message = cipher.decrypt(cipher_text) 
print("Decrypted data is : \"{}\"".format(message.decode())) 
  • 1
  • 2
  • 3

前述脚本的成功执行将输出以下内容:

Data is encrypted
Decrypted data is : "plaintext for RSA"
  • 1
  • 2

椭圆曲线密码学

ECC 是基于前述椭圆曲线的公私密钥加密。它在椭圆曲线上执行点的加法以计算公私钥对。ECC 需要比其他非对称密钥密码系统(如 RSA)更小的密钥尺寸。ECC 在密钥交换机制和数字签名中被广泛使用,并且在加密系统中很少使用。

ECC 提供了与 RSA 相同的安全级别,但密钥尺寸更小。256 位 ECC 密钥相当于 3,072 位 RSA 密钥。同样,384 位 ECC 密钥提供了与 7,680 位 RSA 密钥相同的安全级别,依此类推。我们可以清楚地看到由于密钥尺寸较小而导致的较少计算时间的优势。

由于与 RSA 相比的密钥尺寸优势,ECC 在 比特币 的地址系统中被使用,以及在交易签名操作中被使用。它还在其他区块链应用中很受欢迎。ECC 的其他应用包括 Tor、iMessages、SSH 和 SSL/TLS。

在深入研究 ECC 的密码应用之前,让我们看看它的一些特性:

  • 椭圆曲线由一个三次方程式表示:

y^(2 )= x³ + ax + b

  • 椭圆曲线具有水平对称性

  • 非垂直线将在曲线上最多相交三个点

RSA 加密使用素数分解。半素数的分解是非常困难的。在这个领域使用时,它形成一个陷阱门(单向)函数。同样,基于椭圆曲线的算法可以使用离散对数。找到与同一曲线上的点相关的椭圆曲线上的随机元素的离散对数是一个严重的问题。我们将逐步介绍如何从私钥构建公钥,并研究 ECC 密钥生成过程的单向性质。

椭圆曲线上的运算

在密码学中使用的椭圆曲线是在有限域中构造的曲线。它们的形式如下:

y^(2 )= x³ + ax + b mod §

p的模运算表示曲线在阶为p的素数的有限域上。在进入密码应用程序之前,我们需要了解一些椭圆曲线的术语和操作。

有限域是由参数p定义的具有有限元素数量的域,其中p是素数。因此,有限域为F[p] = {0, . . ., p-1}。它在方程中由模p表示。

在 ECC 中使用的所有元素必须由密码学参与者达成一致。这些元素称为椭圆曲线域参数。{p, a, b, G, n, h}是 ECC 中使用的参数。这些参数的定义如下:

  • p:有限域由这个素数定义。

  • ab:这些是方程中使用的常数。

  • G:这个生成器定义了曲线上所有点的集合,也称为基点。

  • n:这代表基点或生成器G的阶数,一个最小的正数n,使得nG = ∞

  • h:这是余因子,是组和子组(n)的阶数的比值,必须很小(h <= 4),通常h=1

比特币的椭圆曲线数字签名算法

(ECDSA)曲线使用在 secp256k1 中定义的一组唯一的域参数。您可以在本章的后续部分找到 secp256k1 中使用的曲线的技术规格。

在椭圆曲线上执行的操作称为点运算,它们是点加法和点加倍。我们将使用几何方法来解释这两种操作,以便清晰理解。与这些操作相关的 Python 脚本和笔记本可以在本书的 GitHub 仓库中找到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.6:具有坐标和网格的椭圆图(使用 www.desmos.com 创建)

我们将使用图 2.6中的椭圆曲线来执行所有操作。

点加法

假设PQ是椭圆曲线上的两个点。P不等于Q;它们是曲线上的两个不同的点。点加法的几何解释如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.7:点PQ的加法

在椭圆曲线上执行如下步骤来添加两个点,如图 2.7所示。

  1. 在点*P(x1, y1)Q(x2, y2)*之间画一条直线

  2. 这条线将在椭圆曲线上的点处相交

  3. 关于x轴的点的反射给出了点R(x3, y3),这是PQ的加法结果

点加倍

点加倍是与点加法类似的操作,唯一的区别在于点Q移动到与点*P(P = Q)*相同的位置:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.8:点P的加倍

在椭圆曲线上执行如下步骤来计算点加倍,如图 2.8所示:

  1. P点处作一条切线(因为只有一个点)。

  2. 这条线将在点处与曲线交汇。

  3. 关于x轴的镜像得到点R,这就是点加倍或R的倍数(2R

点加倍是 ECC 中用于从私钥构造公钥的概念。下一节将深入解释点加倍如何用于生成公钥。

计算公钥

既然我们已经定义了点加倍,我们可以计算曲线上一点是给定点发生器的倍数的点,点 G(例如,4G = G + G + G + G),这可以使用点加倍计算。

让我们使用这个概念在不对称加密系统中计算公钥-私钥对。

给定规格的每个曲线域参数都是相同的。请参阅本章后面用于 Bitcoin 和其他区块链应用数字签名算法的 secp256k1 标准的技术规格。假设k是随机选择的私钥,K是要生成的公钥。曲线的生成器G具有标准值。可以通过在曲线上执行以下操作来计算公钥:

K = kG*

我们可以使用这个方程在椭圆曲线上使用点加倍来生成公钥。在椭圆曲线上的点加倍是一个单向操作。因此,在找到所需点K后计算乘值k是一项具有挑战性的任务:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.9:使用点加倍将生成器乘以整数

图 2.9显示了在给定曲线上使用点加倍从基点G派生出点2G4G的过程。这种几何方法可以通过将生成器G乘以私钥k次数来生成公钥K

secp256k1 的技术细节

Bitcoin 使用特定的椭圆曲线,曲线中使用的域参数在 secp256k1 标准中得到定义。这条曲线在素数次序p的有限域内由以下立方方程表示:

y² mod § = x³ + 7 mod §

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.10:secp256k1 的椭圆曲线对实数

如名称所示,secp256k1 的密钥大小可达 256 位。secp256k1 使用的域参数的细节用十六进制字符串表示,如下所示:

  • 大素数被用在有限域中。

    • *p = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE *FFFFFC2F

p的前面十六进制表示将具有以下十进制值:

p = 2²⁵⁶ - 2³² - 2⁹ - 2⁸ - 2⁷ - 2⁶ - 2⁴ - 1

  • y² = x³ + 7曲线的常数如下:

    • a = 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

    • b = 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000007

  • 基点G的原始表示形式有一个较长的十六进制字符串,但可以压缩表示形式如下:

    • *G = 02 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B *16F81798
  • G的顺序n和余因子如下:

    • *n = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B *BFD25E8C D0364141

    • h = 01

所有这些值对于 secp256k1 中的任何计算都保持不变。而且这个规范足够强大,能够抵御通过公钥计算私钥的穷举尝试。

数字签名

到目前为止,我们已经涵盖了对称和非对称密码学的各种不同加密方法。我们还研究了对称加密技术与非对称技术相比的一些优点。因此,非对称密码学是一种很少使用的加密方法。但不对称密钥的独特设计使其成为非加密应用的合适技术之一,数字签名就是其中之一。

数字签名是提供数字文档所有权证明的方法。由于其不对称密钥属性,公私密钥加密术在数字签名领域被广泛使用。所有者可以使用私钥对消息或文档进行签名,验证者可以使用公钥验证他们的所有权,这个公钥被分发给所有人。

这个过程类似于在现实世界中使用的手写签名,在那里,资产所有者可以使用他们的签名对该资产执行任何操作,任何人都可以通过将其与以前使用过的签名进行比较来验证签名。数字签名比手写签名更安全,因为在不拥有私钥的情况下,伪造签名是不可能的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.11:数字签名的设计图示

数字签名可以用作确保行动的真实性、不可否认性和完整性的机制。我们可以以软件公司向其客户分发更新为例。这些客户如何确保他们可以信任这些软件更新?这就是数字签名在通过允许客户使用分发的公钥验证更新来提供真实性和完整性方面发挥作用的地方。只有软件所有者才能签署软件更新,因为他们拥有私钥。

它是如何工作的?

图 2.11所示,数字签名过程包括两部分:签署和验证。与加密不同,数字签名先执行第一个操作,即使用私钥进行签署。验证使用由签署者分发的公钥。

哈希算法生成一个唯一的固定长度值,用于数字签名的构建和验证。详细的密码哈希解释请参考下一节。

签名过程。

拥有消息所有者使用私钥执行签名操作以证明其真实性。假设 Alice 是拥有具有消息 m 的文档的所有者,并希望将其分发给网络中的其他人。现在,Alice 最初将对消息进行哈希处理,并使用她的私钥对文档进行签名。签名如下创建,其中 F[s] 是签名函数,F[h] 是哈希函数,m 是消息,dA 是 Alice 的私钥:

S = F[s] (F[h] (m), dA)

Alice 现在将与消息一起分发她的签名给网络中的每个人。

验证过程。

验证是由任何拥有业主公开信息的人执行的过程。公开信息通常包括公钥、消息和消息的签名。假设 Bob 拥有所有公开信息并希望验证消息以检查其真实性。Bob 使用签名验证算法,该算法需要消息的哈希值、公钥和签名。该算法将验证消息是否被任何人篡改。在本章后面可以找到签名和验证过程的实现示例。

椭圆曲线数字签名算法(ECDSA)。

ECDSA 是一种数字签名算法,利用 ECC 创建用于数字签名的密钥对。由于 ECC 相对于其他公钥算法的优势,它通常在区块链应用中用于签署交易或事件。

ECDSA 利用临时密钥对计算签名对 RS。在椭圆曲线上随机选择一个临时私钥 k,并计算对应的公钥为 P = kG*。签名计算如下:

S = k^(-1) (Hash(m) + dA * R) mod §

用于签名操作的变量定义如下:

  • k 是临时私钥。

  • R 是临时公钥的 x 坐标。

  • dA 是私钥。

  • m 是消息。

  • p 是椭圆曲线的素数阶。

在 ECDSA 中,使用 RS 对和公钥进行验证。点 P 的导出如下:

*P = S^(-1)*Hash(m)G + S^(-1)RQa

用于验证操作的变量定义如下:

  • Qa 是签名者的公钥。

  • m 是消息。

  • G 是椭圆曲线的生成点。

Bitcoin 中使用 ECDSA 数字签名算法为交易签名,该交易由所有者使用其自己的私钥创建。

ECDSA 创建和验证数字签名的示例。

以下包用于执行哈希、ECC 密钥创建和签名创建和验证:

from Crypto.Hash import SHA256 
from Crypto.PublicKey import ECC 
from Crypto.Signature import DSS 
  • 1
  • 2
  • 3

密钥是使用 ECC.generate 方法在 secp256k1 椭圆曲线上生成的,并且公钥和私钥都被导出:

key = ECC.generate(curve='P-256') 
with open('ecc.pub', 'wt') as f: 
    f.write(key.public_key().export_key(format='PEM')) 
with open('ecc.pem', 'wt') as f: 
    f.write(key.export_key(format='PEM')) 
  • 1
  • 2
  • 3
  • 4
  • 5

需要签名的消息使用 SHA256 算法进行哈希处理,然后通过提供私钥使用 DSS 包创建签名者对象。 然后,所有者签署了哈希消息:

message = b'ECDSA message for signature' 
key = ECC.import_key(open('ecc.pem').read()) 
h = SHA256.new(message) 
signer = DSS.new(key, 'fips-186-3') 
signature = signer.sign(h) 
  • 1
  • 2
  • 3
  • 4
  • 5

下面代码中的签名验证类似于签名。 收到的消息最初被哈希化,因为发送方也进行了哈希化。 分布式公钥被导入并用于创建新的 DSS 对象进行验证。 使用哈希消息和接收到的签名进行验证。 如果消息或签名被篡改,verify 函数会抛出 ValueError

h = SHA256.new(message) 
key = ECC.import_key(open('ecc.pub').read()) 
verifier = DSS.new(key, 'fips-186-3') 
try: 
    verifier.verify(h, signature) 
    print("The message is authentic.") 
except ValueError: 
    print("The message is not authentic.") 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

密码哈希

密码哈希函数是一种将任意大小的数据映射到称为哈希的固定大小字符串的函数类型。 哈希函数具有使它们非常适合在密码学中使用的某些属性。

哈希函数广泛用于哈希表数据结构中。 哈希表将数据存储在键值对中。 当需要使用哈希函数将大键转换为小键时,将值映射到这些小键。 这使得键到值的映射非常容易,并且可以在 O(1) 的时间复杂度内实现。 这是因为哈希函数具有恒定的时间复杂度。

我们已经多次提到,哈希是区块链架构的支柱,它具有几个属性,使其非常有价值并且非常适合于区块链实现。

每个哈希函数都具有以下属性:

  • 预像阻力:给定计算的哈希 h = hash (m),其中 m 是消息,应该无法从给定的哈希值中找到消息。

  • 第二个预像阻力:给定消息 m1,应该很难找到另一条消息 m2 使得 hash (m1) = hash (m2)

  • 碰撞阻力:当至少有两条消息产生相同的哈希值时,哈希被认为发生了碰撞。 应该无法找到两个消息 m1m2 其中 hash (m1) = hash (m2),也就是说,应该很难找到具有相同哈希值的两个消息。 这类似于第二个预像阻力,但是这里可以选择任意两个消息。 因此,此属性意味着第二个预像阻力。

尽管每个哈希函数都具有这些属性,但是一个良好的哈希函数应该具有额外的属性以提供强大的安全性:

  • 哈希函数对于任何输入应该需要恒定的时间。

  • 消息中的任何位的更改应该导致与先前消息的哈希值相比产生全新的哈希值。 分析由哈希函数创建的哈希值应该非常困难。

在区块链中,散列用于为每个区块创建一个唯一的身份字符串,方法是计算其散列值。每个区块都将维护前一个区块的散列值,从而形成区块链。散列为区块链分类帐的块提供了完整性。

散列算法

散列算法根据其实现、生成的摘要大小等进行分类。一些分类包括消息摘要、安全散列算法SHA)和 RACE Integrity Primitives Evaluation Message DigestRIPEMD)。

消息摘要

这是 1990 年代早期使用的流行哈希算法组之一。它们是 128 位哈希函数,其中 md4md5 是它的变体。自采用以来,已经检测到许多漏洞。尽管如此,这些函数被用来创建文件摘要以确保其完整性。

安全散列算法(SHA)

SHA-0 是 SHA 算法的第一个版本。在 2004 年,该算法暴露出了几个弱点,导致了 SHA-0 的更强版本 SHA-1 的创建。在 2005 年,对 SHA-1 的攻击报告称,它将在更少的散列操作中找到碰撞。

SHA-2 被创建来克服 SHA-1 的弱点,它可以以 224、256、384 和 512 位的摘要大小实现。SHA-2 是现代密码应用中广泛使用的标准。比特币使用 SHA-256 变体作为散列算法来解决工作量证明难题。

SHA-3 是具有 224、256、384 和 512 位变体的最新函数族。

使用 SHA-256 算法的散列示例

以下示例脚本使用 SHA-256 散列算法计算消息的摘要:

from Crypto.Hash import SHA256 

hash_object = SHA256.new(data=b'First') 
print(hash_object.hexdigest()) 

hash_object.update(b'd') 
print(hash_object.hexdigest()) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

让我们考虑上述脚本的输出并做出一些观察:

a151ceb1711aad529a7704248f03333990022ebbfa07a7f04c004d70c167919f
18902d9ed3b47effdb6faf90ea69b2ef08ef3d25c60a13454ccaef7e60d1cfe1  
  • 1
  • 2

正如我们所见,输出中的两个散列值都有 64 个十六进制数字(256 位),而消息的大小则无关紧要。第一个散列值有一个消息“First”,第二个散列值有一个消息“Firstd”(更新函数将新消息附加到上一个消息)。尽管末尾有一个字符的差异,但整个 SHA-256 散列值看起来完全不同。SHA-256 的这种属性确保它是预像抗性的,因此很难被破解。

Merkle 哈希树

Merkle 树是一种二叉树,其中所有叶节点代表数据块的哈希值。每个父节点具有其子节点的哈希值的哈希值。哈希继续直到树的根节点。Merkle 树用于总结大量数据并为每组数据创建指纹。

树是计算机科学中的一种数据结构,由根节点和一组父节点和子节点的子树组成,并通过将根节点放置在顶部来表示。二叉树是每个父节点最多有两个节点的树。 Merkle 树用于比特币、以太坊和其他区块链应用程序中,用于总结每个区块中包含的所有交易。SHA-256 在比特币的 Merkle 树中用作哈希函数,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.12:总结所有叶子节点的 Merkle 树

Merkle 树是从叶节点向上构建的。在 图 2.12 中,叶节点将只包含数据块 A、B、C 和 D 的哈希值,分别由 H[A]、H[B]、H[C] 和 H[D] 表示。每个父节点将通过连接子节点的哈希值并再次哈希来构造其哈希:

H[AB] = Hash (H[A] + H[B])

此过程一直持续到计算出根节点哈希值 H[ABCD] 为止。

由于每个 Merkle 树节点(除了叶节点)都根据其子节点计算其哈希,因此它必须保持平衡的树形结构,即每个节点(除了叶节点)应该有两个子节点。这可以通过复制现有的单个子节点来实现。

Merkle 树不仅提供了总结整个数据块的方式,而且还可以有效地验证数据块是否存在。验证可以在 log2 复杂度内完成。

编码方案

编码方案通常用于数据存储或通过媒介传输文本数据。你经常可以观察到二进制到文本编码方案的转换在原始加密实现中。

编码方案提供了一种紧凑的方式来使用基数表示长字符序列。例如,十进制系统使用基数 10,使用 0-9 的字符,十六进制系统使用十进制系统中的数字以及 A-F 的附加字符。系统的基数越大,编码字符串的大小就越小。

Base64 是一种广泛用于存储和传输大文件(例如图像)的编码方案。它使用 26 个小写字母、26 个大写字母、10 个数字字符和 2 个特殊字符(“+”和“/”)。

Base58 是为比特币开发的一种编码方案,用于几种区块链应用程序中。Base58 实际上是 Base64 的子集,旨在提供更好的可读性。在 Base58 中省略的 Base64 字符是 0(零)、O(大写 o)、l(小写 L)、I(大写 i)以及特殊字符“+”和“/”。

比特币的 34 位 Base58 编码钱包地址如下:

16RhN7MhhTRMDdrS3szys5pEpmS2YGTMsk

总结

本章涵盖了所有基本的密码学主题,从经典的密码技术到高级的密码原语。我们在章节开始时讨论了经典的密码技术。我们探讨了对称加密和非对称加密,以及一些示例。诸如哈希和数字签名之类的密码原语将会被更详细地讨论,因为它们将作为本书中涵盖的区块链概念的基础。

由于我们已经涵盖了密码学中一些基本概念,并查看了它们各自的应用,在下一章中,我们将通过查看一个简单的区块链示例来尝试实现一些适用于区块链协议的密码学概念。

第三章:区块链中的密码学

在前几章中,我们介绍了与区块链相关的密码学。虽然我们对其中一些密码原语有清晰的理解,但我们尚未探讨它们在实际区块链应用中的应用。在本章中,我们将涵盖一些密码原语的应用,其中将包括哈希函数和数字签名。我们将通过在基本区块链应用中实际实现它们来深入研究它们。

在本章中,我们将涵盖以下主题:

  • 区块链中的哈希

  • 区块链中的数字签名

需要注意的一点是,每个与区块链技术一起使用的加密原语都有不同的作用。哈希函数和数字签名是与区块链广泛使用的两个加密概念。

我们主要可以观察到区块链技术中的三个层次。这些是点对点网络层,负责块创建和验证机制的共识层,以及利用底层区块链构建应用程序的应用层。密码学主要用于区块链的共识和应用层。哈希算法主要用于创建块标识,确保区块链的完整性,并且还作为共识算法的关键组成部分,例如比特币的工作证明。另一方面,数字签名处理应用层,在这里它用于通过将其嵌入到交易中来验证事件。

由于哈希和数字签名在不同层次上为区块链做出了贡献,我们将在本章的不同部分中涵盖这些概念的重要性。

区块链中的哈希

哈希在区块链中是一个重要的概念,在区块链应用程序的功能中起着巨大作用。哈希函数的应用范围从较小的区块链实现,例如为大量数据创建摘要,到主要实现,例如维护链中块的完整性。哈希函数还用于工作证明共识算法中解决拜占庭失败问题,我们将在本章稍后深入探讨。首先,我们将探索一些利用哈希函数的区块链概念。

在区块链中链接块

正如在前言中定义的那样,区块链是一个持续增长的区块集合,通过使用加密技术作为关键要素来链接在一起形成一个开放账本。区块链中的每个区块都被赋予一个标识以标记该区块的唯一性,这是通过使用哈希函数生成该区块的摘要来实现的。正如在前一章节中提到的密码哈希函数的碰撞抵抗性属性,第二章, 加密的一点*,* 保证了找到两个结果相同的哈希值的区块是不可行的。因此,哈希函数保证了为区块创建的标识的唯一性。

当创建新区块时,它将使用上一个区块的摘要作为后向引用,从而将该区块链接到区块链上。修改任何区块都会由于新的哈希值而改变该区块的身份。因此,这将破坏链,因为一个区块引用将因为新生成的哈希值而无效。因此,修改区块以生成与以前相同的哈希值是不可行的。这是由于密码哈希函数的前像抗性属性,即使我们拥有哈希值,也无法预测区块的数据。这就是为什么一旦创建了一系列区块,就会确保链的完整性,因为每个区块都引用了前一个区块。修改区块数据的唯一方法是通过修改所有后续区块,并更新其对前一个区块的引用:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.1:使用哈希链接块,来自 点对点的电子现金系统,S. 中本聪

前文 图 3.1 展示了来自原始论文 比特币:一个点对点的电子现金系统 的区块链设计,该论文的作者是比特币原始参考实现者 中本聪。它显示每个区块的哈希值受到前一个区块哈希值的影响,从而链接了区块链中的每个区块。持有区块链账本副本的任何人都可以通过验证每个区块的哈希与下一个区块的哈希来验证区块链中的所有区块是否有效。

使用 SHA256 哈希算法链接区块

区块链中的区块通过引用前一个区块的哈希值链接在一起。SHA256 是区块链平台中最流行的哈希算法,因为它在比特币实现中使用过。首先,我们将定义区块的结构和功能,最后利用哈希算法构建区块链。

区块结构

让我们考虑一个简单的区块,其头部和数据合并以创建一个称为Block的数据结构。每个区块将包含以下内容:索引、前一个哈希、时间戳、数据及其自身的哈希值:

class Block(object): 
    """A class representing the block for the blockchain""" 

    def __init__(self, index, previous_hash, timestamp, data, hash): 
        self.index = index 
        self.previous_hash = previous_hash 
        self.timestamp = timestamp 
        self.data = data 
        self.hash = hash 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

前面的代码片段定义了一个名为Block的 Python 类,它具有区块链区块的所有基本属性。通常,一个区块将包含一个头部和一个主体,头部包含有关区块的元数据。然而,前面的示例并未区分头部和主体。像比特币这样的典型区块链应用程序将拥有大量可能以交易形式出现的数据,但在示例中,我们将考虑数据为string类型。

一个典型的区块还将在头部包含一个随机数和一个难度目标。这些信息在共识算法中使用,如工作证明。由于我们的目的仅是描述一个区块链,这些字段不在本节的讨论范围内。

区块链功能

区块链接过程包括几个要素,如从信息中创建结构、计算区块的哈希值,并将其添加到区块链中。

让我们将这些功能逐一细分为区块链方法:

class Blockchain(object): 
    """A class representing list of blocks""" 

    def __init__(self): 

        self._chain = [self.get_genesis_block()] 
        self.timestamp = int(datetime.now().timestamp()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

前面的类是一个类方法的集合,这些方法使用哈希函数创建一个有效的区块链。Blockchain的构造函数将通过添加一个创世区块来初始化链条,这是区块链的第一个区块,且不引用任何前一个区块:

def get_genesis_block(self): 
    """creates first block of the chain""" 

    return Block(0, "0", 1465154705, "my genesis block!!", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7"
 ) 
  • 1
  • 2
  • 3
  • 4
  • 5

创世区块是一个硬编码的区块,被添加到区块链的开头。它是用静态内容创建的。前面的创世区块有一个使用 SHA-256 创建的硬编码哈希值,如下所示:

SHA256.new(data=(str(0) + "0"+ str(1465154705) +"my genesis 
 block!!").encode()).hexdigest() 

def calculate_hash(self, index, previous_hash, timestamp, data): 
    """calculates SHA256 hash value""" 

    hash_object = SHA256.new(data=(str(index) + previous_hash + 
 str(timestamp) + data).encode()) 
    return hash_object.hexdigest()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

calculate_hash是区块链中的一个关键方法,因为这个方法创建了将所有区块绑定在一起的哈希值。使用 PyCryptodome 包创建 SHA-256 哈希值,如上一章所示。这个方法将区块索引、前一个区块的哈希值、时间戳和创建所需的数据串联起来,生成需要被哈希处理的字符串。SHA256 哈希函数生成的摘要就是该区块的哈希值。

在创建下一个区块时,我们需要找到前一个区块的哈希值。下面的函数用于识别添加到链上的最后一个区块:

def get_latest_block(self): 
    """gets the last block from the blockchain""" 

    try: 
        return self._chain[-1] 
    except IndexError as e: 
        return None 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下面的函数将通过构建创建Block对象所需的所有属性来构建一个区块。它还会计算当前区块的哈希值。最终将创建一个由区块结构组成的新的Block对象:

def create_block(self, block_data): 
    """creates a new block with the given block data""" 

    previous_block = self.get_latest_block() 
    next_index = previous_block.index + 1 
    next_timestamp = self.timestamp 
    next_hash = self.calculate_hash(next_index, 
 previous_block.hash, next_timestamp, block_data) 
    return Block(next_index, previous_block.hash, next_timestamp, 
 block_data, next_hash)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意:我们根据在创建区块链对象时创建的静态时间戳值创建了next_timestamp。尽管在实际的区块链中这不是真实的,但我们故意这样做是为了在代码执行期间解释一个特定的情况。

以下函数用于添加、重置和读取区块链的块。add_block方法和chain属性是唯一需要向用户公开的类成员:

def add_block(self, data): 
    """appends a new block to the blockchain""" 

    self._chain.append(self.create_block(data)) 

@property 
def chain(self): 
    """created a dict containing list of block objects to view""" 

    return self.dict(self._chain) 

def dict(self, chain): 
    """converts list of block objects to dictionary""" 

    return json.loads(json.dumps(chain, default=lambda o: 
 o.__dict__)) 

def reset(self): 
    """resets the blockchain blocks except genesis block""" 

    self._chain = [self._chain[0]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

创建一个区块链

现在我们已经定义了简单区块链链接器的所有必需功能,我们将通过创建一些块和一个区块链来模拟它:

new_chain = Blockchain() 
new_chain.add_block(data="first block data") 
new_chain.add_block(data="second block data") 
new_chain.add_block(data="third block data") 

print(json.dumps(new_chain.chain))

new_chain.reset() 

new_chain.add_block(data="first block data") 
new_chain.add_block(data="second block data") 
new_chain.add_block(data="third block data") 

print(json.dumps(new_chain.chain))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

前面的代码片段创建了一个Blockchain对象,并向其添加了三个块,以及一个现有的创世块。在重置区块链后再次执行此操作。这里的一个重要观察是,new_chain.chain的两个输出都将产生包含以下输出中显示的块哈希的块列表。这是因为在执行期间贡献到哈希值创建的所有属性都相同。如果输入相同,哈希函数总是产生相同的哈希值。

时间戳在创世块中是硬编码的,故意保持所有块的时间戳恒定,以显示使用相似数据计算的哈希值每次都将生成相同的值:

[ 
  { 
    "index": 0, 
    "data": "my genesis block!!", 
    "hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "previous_hash": "0", 
    "timestamp": 1465154705 
  }, 
  { 
    "index": 1, 
    "data": "first block data", 
    "hash": "c8028a8a867a639fec693243f88a4e04f0ab5872f6913da53210316bd97d6ebb", 
    "previous_hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "timestamp": "1521059029" 
  }, 
  { 
    "index": 2, 
    "data": "second block data", 
    "hash": "aba71ef94fdc7d70bd39e5aa3eeef6fd53ac8e7fc102c2f638126c8a74d5cefe", 
    "previous_hash": "c8028a8a867a639fec693243f88a4e04f0ab5872f6913da53210316bd97d6ebb", 
    "timestamp": "1521059029" 
  }, 
  { 
    "index": 3, 
    "data": "third block data", 
    "hash": "f208c8375036ad785c9226d09585bd50a2b3993300f75e041dc3f2f0b6cfdd2b", 
    "previous_hash": "aba71ef94fdc7d70bd39e5aa3eeef6fd53ac8e7fc102c2f638126c8a74d5cefe", 
    "timestamp": "1521059029" 
  } 
] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在实际执行期间,前面的输出将生成两次。输出显示了我们区块链中的块如何使用加密哈希链接在一起。区块链中的每个块都有一个previous_hash值,该值与前一个块的哈希值匹配。索引0是硬编码的创世块,没有previous_hash,索引1previous_hash值与创世块的哈希匹配。所有其他块都以相同的方式链接。

让我们尝试修改一个块中的数据,并将其余块插入到链中:

new_chain.reset() 
new_chain.add_block(data="modified first block data") 
new_chain.add_block(data="second block data") 
new_chain.add_block(data="third block data") 

print(json.dumps(new_chain.chain)) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这将产生以下区块链中的块列表:

[ 
  { 
    "hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "data": "my genesis block!!", 
    "index": 0, 
    "timestamp": 1465154705, 
    "previous_hash": "0" 
  }, 
  { 
    "hash": "06045fb547175c5cd32b3ba326ce9768c22771c3e128f801bbec19ea1eb20052", 
    "data": "modified first block data", 
    "index": 1, 
    "timestamp": "1521086845", 
    "previous_hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7" 
  }, 
  { 
    "hash": "40c54c31afda040d037dae637ab1ec6e5eb9b132c761b9eadda21e68c0897a65", 
    "data": "second block data", 
    "index": 2, 
    "timestamp": "1521086845", 
    "previous_hash": "06045fb547175c5cd32b3ba326ce9768c22771c3e128f801bbec19ea1eb20052" 
  }, 
  { 
    "hash": "466083f34143e7f99196de01cd7777c52b0763624acd2895f0d28047c670eb41", 
    "data": "third block data", 
    "index": 3, 
    "timestamp": "1521086845", 
    "previous_hash": "40c54c31afda040d037dae637ab1ec6e5eb9b132c761b9eadda21e68c0897a65" 
  } 
] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

前面的块列表显示了与之前区块链相似的块链接属性,但有一个有趣的观察是,尽管只修改了索引1的块数据,但所有其他块的哈希值与前一个块的输出不同。这是由于链式效应或涟漪效应造成的。因为每个块都存储着前一个块的哈希值,所以每个块都受到此修改的影响。这导致了新的区块链的创建。这就是为什么区块链是安全的原因:不能修改单个块而不影响分类账中的其他块。

在书籍的 GitHub 存储库中可以找到前述示例区块链应用程序的完整脚本(github.com/PacktPublishing/Foundations-of-Blockchain),以及 Python 打包。

区块链中的拜占庭失效问题

在前面的部分中,我们看到了如何将区块追加在一起形成区块链。我们还看到了加密哈希函数如何在确保区块链完整性方面发挥关键作用。尽管区块链保持了完整性,但并不能保证在分散网络中可以维护单个版本的区块链。由于区块创建并不是一项困难的任务,网络中的每个节点都能够维护自己的区块链版本。这是一个众所周知的分布式系统问题,称为拜占庭将军问题,或拜占庭失败。

拜占庭失败是一种对不同观察者呈现不同症状的故障。当系统需要达成共识时,会出现服务丢失。这种类型的故障在分布式系统中常见,由于很难收集关于组件状态的信息,并且恶意行为者的存在使得达成共识更加困难。

拜占庭失败这个术语源自拜占庭将军问题,这是一个协议问题,一组代表拜占庭军队的将军正在计划攻击一座城市。一些将军可能决定进攻,而另一些则撤退。他们应该就是攻击还是撤退达成一致,以便任务能够成功完成。将将军的投票结果传达给彼此是一项困难的任务,因为他们相距甚远,没有方便的沟通方式。由于这个原因,将军之间可能会出现延迟或误解。存在不可靠将军的问题进一步加剧了这个问题,因为他们可能会在投票时试图欺骗,以使任务失败。如果这样的系统无法获得大多数投票的一致意见,那么就会导致任务失败,因为决定进攻的军队可能没有足够的支持来自其他将军。这是一个经典的协议问题,没有一个单一的解决方案。解决拜占庭将军问题的方法是找到诚实将军中的多数票。

一个展示拜占庭容错BFT)的系统可以克服拜占庭失败问题。在数字系统中,诸如数字签名之类的加密基元可以通过创建无法伪造的消息签名来为安全关键系统提供容错性。实现数据完整性可以在一定程度上抵御拜占庭失败问题,但这并不是一个完整的解决方案。

现在我们了解了拜占庭问题,我们可以注意到这个问题适用于任何分布式系统。该问题也存在于区块链网络中,其中参与者分布在去中心化的点对点网络中。在去中心化网络中保持一个真理是一项艰巨的任务,而网络中的不良行为者的参与使任务变得更加困难。区块链的去中心化网络必须就如何实现全局一致的区块链状态达成一致。在区块链网络中发生拜占庭问题是不可避免的,因为区块链网络存在于去中心化的不可信环境中。网络的节点应该就如何实现一个通用的区块链状态达成共识。矿工特别应该达成共识,因为他们是促进区块链增长的人。

区块链矿工是不仅验证区块链数据,而且还为区块链分类帐创建新块的节点。

比特币是第一个解决拜占庭问题的去中心化应用程序。它通过使用一种名为工作证明的共识算法来实现这一点,这种算法受到了 1997 年由英国密码学家亚当·贝克提出的 Hashcash 系统的启发。Hashcash 的开发目的是验证合法用户并通过创建需要一定计算量的邮票来减少电子邮件垃圾邮件。Hashcash 邮票是使用哈希算法创建的。尽管创建邮票很耗时,但验证可以立即执行。同样,比特币的工作证明也使用了密码哈希函数来在网络中达成共识。

有几种共识算法可以在区块链中实现一个共同的全局视图。Proof of Stack、Proof of Activity、Proof of Capacity 和 Proof of Elapsed Time 只是其中的几个例子。即使是流行的以太坊区块链框架目前也使用工作证明共识,但已经有积极的开发工作在未来的以太坊发布中包括了 Proof of Stake。

工作证明如何确保拜占庭容错性?

工作证明是一种共识算法,旨在确保网络中创建块的每个参与节点都必须证明它在插入公共区块链分类帐之前对该块进行了一定量的工作。

比特币的工作量证明共识算法旨在确保区块链数据不可变,并且不容易被不良行为者改变。区块链网络中的多数决策由最长链代表;这是因为它证明了做了最多的工作。尽管这种系统会在去中心化网络中实现共识,但是如果不良行为者尝试创建带有一些欺诈性交易的备用区块链会怎样呢?当使用工作量证明时,这并不容易。每当不良行为者修改了早期创建的区块时,所有后续区块都将被重新创建,其中的所有区块都将重新进行工作。重新创建所有区块将需要很长时间,因为这个过程需要大量的计算能力。然而,你会发现,通常情况下,大多数网络会拒绝不良行为者的工作,因为它无法跟上诚实节点的工作。

因此,工作量证明有助于在不诚实节点存在的情况下实现 BFT 系统。

虽然工作量证明为拜占庭失效问题提供了实际解决方案,但一种名为 51%的攻击理论上可能导致拜占庭失败。在 51%攻击中,区块链网络中的大多数计算能力被不诚实的实体控制。这意味着比特币可以拥有 50%的错误节点,但仍能正常运行。这就是比特币共识机制的容错性。51%攻击在第十章的《区块链安全》中有更详细的介绍。

工作量证明如何使用密码学?

工作量证明是一种共识算法,它使用密码哈希谜题确保在创建区块之前完成了一定数量的工作。比特币的工作量证明使用 SHA-256 哈希函数创建哈希谜题。

区块链网络中的区块是由一种特殊类型的验证者节点——矿工节点创建的。这些矿工节点互相竞争解决哈希谜题,以便产生要追加到分类账中的区块。

区块链矿工将在有数据(通常是一组交易)需要包含在一个区块中时开始解决哈希谜题。下图图 3.2显示了工作量证明区块链应用中使用的区块头的基本结构。谜题求解器通常会使用 SHA256 哈希函数创建头的哈希值。这里的谜题是找到一个头的哈希值,使得哈希以已知数量的零位开始:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.2:一个区块头的基本结构

我们之前提到过哈希函数的特性;也就是哈希值是不可预测的,因为哈希值的生成是一个单向过程。因此,很难预测标题的内容,这会导致哈希值以一定数量的零位开始。实现这个唯一的方法是不断尝试不同的标题值,并使用哈希函数计算哈希值。通过改变标题中的名为nonce的变量字段来创建不同的标题。随机 nonce 被分配给哈希函数以创建一个不同的标题。一旦矿工找到一个 nonce,将会生成一个具有所需数量零位的哈希值,那么难题就解决了,并且 nonce 记录在块标题中。

使用哈希函数进行工作量证明证明是一个很好的方法,因为由于其加密特性,欺诈性计算哈希值是困难的。哈希函数确保已经使用了一定数量的计算机 CPU 功耗来计算哈希值,并且在这个过程中计算机的哈希率是工作证明。

工作量证明的示例实现

在前一部分中,我们介绍了工作量证明如何使用哈希算法。我们还看了如何计算目标哈希值来解决哈希难题。现在,我们将使用 SHA-256 算法来实现工作量证明算法,以分析哈希和概率如何对这个共识算法产生影响。

由于工作量证明算法的主要目的是找到一个 nonce,当附加到区块链标题时,会产生所需的目标哈希值,因此这里的任务是随机猜测 nonce 值,并建立区块的摘要值。由于哈希函数的特性,使得猜测 nonce 值非常困难和非确定性,找到 nonce 的唯一方法就是使用哈希函数实际尝试每一个 nonce,并找到能满足目标哈希值的 nonce。虽然解决方案是非确定性的,但由于哈希函数的特性,工作量证明受概率的影响。虽然找到解决方案取决于运气,但通常情况下,完成最多工作量的矿工节点会解决难题。这是因为找到 nonce 的概率随着所做工作量的增加而增加。

在工作量证明中解决的每个难题都有一个确定目标哈希值的难度级别。难度级别是由所需数量的零位在结果哈希值开始处决定的。增加所需零位数量将增加难题的难度级别。这是因为由于较小的样本空间,找到一个小哈希值的概率比找到任何哈希值的概率都小。

这个工作量证明的例子实现将阐明概率在这个共识算法中的作用:

from Crypto.Hash import SHA256 

text = "I am Satoshi Nakamoto" 

for nonce in range(20): 

    input_data = text + str(nonce) 

    hash_data = SHA256.new(input_data.encode()).hexdigest() 

    print(input_data, '=>', hash_data) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

示范 nonce 的代码灵感源自*《精通比特币-第一版》Andreas M. Antonopoulos*的代码片段。

上面的代码片段是一个简单的例子,用于生成哈希值以解决工作证明哈希难题。Nonce 以增量的方式创建并附加到输入数据中。使用 SHA-256 算法计算哈希值,并对所有 nonce 值重复此操作。

该程序将为附加 nonce 的数据生成以下哈希值:

I am Satoshi 
 Nakamoto0=>a80a81401765c8eddee25df36728d732acb6d135... 
I am Satoshi 
 Nakamoto1=>f7bc9a6304a4647bb41241a677b5345fe3cd30db... 
I am Satoshi 
 Nakamoto2=>ea758a8134b115298a1583ffb80ae62939a2d086... 
I am Satoshi 
 Nakamoto3=>bfa9779618ff072c903d773de30c99bd6e2fd70b... 
I am Satoshi 
 Nakamoto4=>bce8564de9a83c18c31944a66bde992ff1a77513... 
I am Satoshi 
 Nakamoto5=>eb362c3cf3479be0a97a20163589038e4dbead49... 
I am Satoshi 
 Nakamoto6=>4a2fd48e3be420d0d28e202360cfbaba410bedde... 
I am Satoshi 
 Nakamoto7=>790b5a1349a5f2b909bf74d0d166b17a333c7fd8... 
I am Satoshi 
 Nakamoto8=>702c45e5b15aa54b625d68dd947f1597b1fa571d... 
I am Satoshi 
 Nakamoto9=>7007cf7dd40f5e933cd89fff5b791ff0614d9c60... 
I am Satoshi 
 Nakamoto10=>c2f38c81992f4614206a21537bd634af7178964... 
I am Satoshi 
 Nakamoto11=>7045da6ed8a914690f087690e1e8d662cf9e56f... 
I am Satoshi 
 Nakamoto12=>60f01db30c1a0d4cbce2b4b22e88b9b93f58f10... 
I am Satoshi 
 Nakamoto13=>0ebc56d59a34f5082aaef3d66b37a661696c2b6... 
I am Satoshi 
 Nakamoto14=>27ead1ca85da66981fd9da01a8c6816f54cfa0d... 
I am Satoshi 
 Nakamoto15=>394809fb809c5f83ce97ab554a2812cd901d3b1... 
I am Satoshi 
 Nakamoto16=>8fa4992219df33f50834465d30474298a7d5ec7... 
I am Satoshi 
 Nakamoto17=>dca9b8b4f8d8e1521fa4eaa46f4f0cdf9ae0e69... 
I am Satoshi 
 Nakamoto18=>9989a401b2a3a318b01e9ca9a22b0f39d82e48b... 
I am Satoshi 
 Nakamoto19=>cda56022ecb5b67b2bc93a2d764e75fc6ec6e6e... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

尽管前面哈希值的每个输入仅差最后两位数,但由于哈希函数的性质,哈希输出值完全不同。这就是为什么无法检测出一个可以产生目标哈希值并因此解决难题的 nonce 的原因。

解决工作量证明的寻找 nonce 的例子

以下示例将演示如何通过暴力破解,使用 SHA-256 算法,来找到满足目标哈希值的哈希值的 nonce。目标哈希值是通过设置工作量证明算法中的难度比特位来确定的。我们将修改之前创建的区块链链接器,以在创建新区块时包括工作量证明算法。首先,让我们修改区块链示例的一些函数,以包括共识算法。

用于创建要添加到区块链中的新区块的Block类被修改以接受两个额外成员,称为difficulty_bitsnonce。我们还将在任何基于工作量证明的区块链应用的头部中包括difficulty_bits

from Crypto.Hash import SHA256 
from datetime import datetime 

class Block(object): 
    """A class representing the block for the blockchain""" 

    def __init__(self, index, previous_hash, timestamp, data, 
 difficulty_bits, nonce, hash): 
        self.index = index 
        self.previous_hash = previous_hash 
        self.timestamp = timestamp 
        self.data = data 
        self.difficulty_bits = difficulty_bits 
        self.nonce = nonce 
        self.hash = hash 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

self.difficulty_bits也被包含在Blockchain类中作为矿工执行工作证明算法时的一个参数:

class Blockchain(object): 
    """A class representing list of blocks""" 

    def __init__(self): 

        self._chain = [self.get_genesis_block()] 
        self.timestamp = int(datetime.now().timestamp())
        self.difficulty_bits = 0 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

create_block函数将添加noncedifficulty_bits,用户在挖掘新区块时会同时设置它们。此信息包含在区块中,以便稍后广播到网络中的每个节点进行验证:

def create_block(self, block_data): 
    """creates a new block with the given block data""" 

    previous_block = self.get_latest_block() 
    next_index = previous_block.index + 1 
    next_timestamp = self.timestamp 
    next_hash, next_nonce = self.calculate_hash(next_index, 
 previous_block.hash, next_timestamp, block_data) 
    return Block(next_index, previous_block.hash, next_timestamp, 
 block_data, self.difficulty_bits, next_nonce, next_hash) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后修改calculate_hash方法以计算一个标头,并调用函数执行工作量证明来计算 nonce:

def calculate_hash(self, index, previous_hash, timestamp, data): 
    """calculates SHA256 hash value by solving hash puzzle""" 

    header = str(index) + previous_hash + str(timestamp) + data + 
 str(self.difficulty_bits) 

    hash_value, nonce = self.proof_of_work(header) 
    return hash_value, nonce 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

proof_of_work方法通过递增其值进行搜索每个 nonce,以找到小于目标值的哈希值。目标值是通过使用提供的difficulty_bits值进行计算的。

每次使用 SHA256 哈希函数计算哈希值时,得到的十六进制摘要将被转换为一个十进制值,并与目标十进制值进行比较。如果计算得到的哈希值小于目标值,则表示该哈希值以大于或等于difficulty_bits的值开头,因此返回 nonce 和哈希值:

def proof_of_work(self, header): 

    target = 2 ** (256 - difficulty_bits) 

    for nonce in range(max_nonce): 
        hash_result = SHA256.new(data=(str(header) + 
 str(nonce)).encode()).hexdigest() 

        if int(hash_result, 16) < target: 
            print("Success with nonce %d" % nonce) 
            print("Hash is %s" % hash_result) 
            return (hash_result, nonce) 

    print("Failed after %d (max_nonce) tries" % nonce) 
    return nonce 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

以下 Python 主方法将创建一个新的 Blockchain 对象类,该类将创建一个带有起源块的新链。for 循环将创建一个新的块,每次将 difficulty_bits 增加 1。每次创建一个块时将调用 proof_of_work 函数:

max_nonce = 2 ** 32  # 4 billion 

if __name__ == '__main__': 

    new_chain = Blockchain() 

    for difficulty_bits in range(32): 
        difficulty = 2 ** difficulty_bits 
        new_chain.difficulty_bits = difficulty_bits 
        print("Difficulty: %ld (%d bits)" % (difficulty, 
 difficulty_bits)) 
        print("Starting search...") 

        start_time = datetime.now() 

        new_block_data = 'test block with transactions' 
        new_chain.add_block(data=new_block_data) 

        end_time = datetime.now() 

        elapsed_time = (end_time - start_time).total_seconds() 
        print("Elapsed Time: %.4f seconds" % elapsed_time) 

        if elapsed_time > 0: 

            hash_power = float(int(new_chain.chain[-
 1].get("nonce")) / elapsed_time) 
            print("Hashing Power: %ld hashes per second" % 
 hash_power) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我们将找到工作证明示例的输出如下所示。它打印出所使用系统的哈希功率、随机数以及在创建块期间经过的时间:

Difficulty: 1 (0 bits) 
Starting search... 
Success with nonce 0 
Hash is 
 365190b63a9ae8443e9dfb7463bcac6c207c29cdd0e8a5f251285d4d5ddbacb3 
Elapsed Time: 0.0029 seconds 
Hashing Power: 0 hashes per second 
Difficulty: 2 (1 bits) 
Starting search... 
Success with nonce 0 
Hash is 
 67aad7ed255c1f7f3b6427cb75c60b6a9520c1ed19747d4f62b701691958f3b7 
Elapsed Time: 0.0001 seconds 
Hashing Power: 0 hashes per second 

[...] 

Difficulty: 64 (6 bits) 
Starting search... 
Success with nonce 4 
Hash is 
 0061db8a0100345e7a1675d39f8c5dae34c89712365d9761e30546c0dbb17e6d 
Elapsed Time: 0.0002 seconds 
Hashing Power: 17316 hashes per second 
Difficulty: 128 (7 bits) 
Starting search... 
Success with nonce 22 
Hash is 
 0129f5f8dfae6063b09da9b6655848e4797c0ac22e1dba97dca6d9e6bfdbf6cb 
Elapsed Time: 0.0009 seconds 
Hashing Power: 23732 hashes per second 

[...] 

Difficulty: 33554432 (25 bits) 
Starting search... 
Success with nonce 3819559 
Hash is 
 0000001085152816d24bf7f32625295a0617a719b72f4f868e06003329975a9d 
Elapsed Time: 122.2431 seconds 
Hashing Power: 31245 hashes per second 
Difficulty: 67108864 (26 bits) 
Starting search... 
Success with nonce 12980169 
Hash is 
 0000003435ba522e2c2d52fc7ad31b144103a99694299621a2a0573fb6f6be9c 
Elapsed Time: 410.8903 seconds 
Hashing Power: 31590 hashes per second 
Difficulty: 134217728 (27 bits) 
Starting search... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

很明显,从这个例子输出中可以看出,计算解决方案所用的时间与使用的难度位数直接成正比。确切地说,对于难度级别的每增加一位,找到随机数的概率就减半,因为目标空间减半。尽管偶尔运气可能帮助我们解决一些难题,但在工作证明共识算法的大多数情况下,概率论成立。

区块链中的数字签名

数字签名是非对称密钥密码学的产物,它是在一个无信任环境中建立信任的好方法。正如本章开头提到的那样,数字签名被用在区块链的应用层。它们主要用于验证插入块中的交易中的事件。它们被用于验证交易,因为任何拥有生成的公私钥对的公钥的人都可以进行验证。

非对称密钥密码学提供了一种识别实体的方法。任何人都可以通过拥有私钥来证明身份。为参与者创建身份允许他们执行资产管理等操作。我们将在本节中深入探讨数字身份和资产管理,以了解数字签名在区块链中的作用。

创建身份

正如我们在前一章中介绍的数字签名和公私钥一样,我们已经了解了数字签名的属性和安全性。简而言之,它们为拥有私钥的节点提供了一种签署消息以证明其身份的方法。这可以由任何拥有分布式公钥的人验证。

通过生成一个公私钥对来创建身份。这类似于在区块链网络中创建一个帐户。以下代码展示了如何使用椭圆曲线通过 ecdsa Python 包生成一个公私钥对:

import binascii 
from ecdsa import SigningKey, VerifyingKey, SECP256k1, keys 

class Wallet: 
    def __init__(self): 

        self.private_key = None 
        self.public_key = None 

    def generate_key_pair(self): 

        sk = SigningKey.generate(curve=SECP256k1) 
        self.private_key = binascii.b2a_hex(sk.to_string()).decode() 
        self.public_key = 
 binascii.b2a_hex(sk.get_verifying_key().to_string()).decode() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用特殊的椭圆曲线 secp256k1 生成密钥对,该曲线也用于比特币的数字签名生成。以下代码将创建一个公私钥对:

account = Wallet() 
account.generate_key_pair() 
print("Generated public key: %s" % account.public_key)
print("Generated private key: %s" % account.private_key)
  • 1
  • 2
  • 3
  • 4

以十六进制格式生成一个 64 字符(256 位)的私钥和一个 128 字符的公钥如下所示:

Generated public key: 
 b7f5edffe6d3532ed743e07c4de5551c2d7476a4053221999ce40edec2607bb4ef
 7ecb9fc6ecf735fd3802fada56c42e18474f8bad269a965f95863f9fc38158 
Generated private key: 
 6eb9035be1dabd01fadcb6a9f92946decc868046184c7810a43806eb6cc46237 
  • 1
  • 2
  • 3
  • 4
  • 5

私钥始终保密,而公钥用于生成用户的公共地址,然后嵌入到交易中。

交易中的签名

数字签名在交易中被使用,因为它们具有确保交易内容完整性和对交易中任何事件不可否认的属性。嵌入到区块中的交易将包含由拥有私钥的某人签名的某个动作。拥有私钥因此证明了签名者的身份。

以下代码展示了一个简单的交易,包含交易 ID、签名和公钥。该交易只能由拥有相应私钥的公钥的所有者签名。比特币和其他区块链平台的交易在交易中有几个字段用于进行价值转移,而加密货币的交易在第五章 加密货币 中有更详细的介绍:

class Transaction: 

    def __init__(self, public_key): 
        self.id = randint(1, 10**5) 
        self.signature = None 
        self.public_key = public_key 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

签名是通过使用相应公钥的私钥对交易的散列内容进行签名而创建的:

def sign(self, private_key): 

        data_to_sign = SHA256.new(str(self.id).encode()).digest() 

        sk = SigningKey.from_string(bytes.fromhex(private_key),  
 curve=SECP256k1) 
        self.signature =  
 binascii.b2a_hex(sk.sign(data_to_sign)).decode() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

该交易随后将在只使用交易内容、创建的签名和公钥的帮助下进行验证。只有当签名或交易内容被修改时,验证才会失败。这个操作还验证了交易的完整性:

def verify(self): 

        vk = VerifyingKey.from_string(bytes.fromhex(self.public_key), 
 curve=SECP256k1) 

        try: 
            vk.verify(bytes.fromhex(self.signature), 
 SHA256.new(str(self.id).encode()).digest()) 

        except keys.BadSignatureError: 
            print('invalid transaction signature') 
            return False 

        return True
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

只要签名或数据未被更改,每次交易都可以成功验证。以下代码生成了一个签名并成功验证了它:

tx = Transaction(account.public_key) 
tx.sign(account.private_key) 
print("Generated signature: %s" % tx.signature) 
tx.verify() 

Generated signature: 
 943ed91d7ceb2a57d4e972845acda7ea818b994a840d3101d192ebe33a7c1f4d68
 55e50ed7b882cd4d372d540187f52f2d5b3a6144a58fc20098095f1726849f 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

当交易内容被修改或者在这种情况下,交易 ID 被修改时,验证失败:

tx.id = '1234' 
tx.verify() 
  • 1
  • 2

本节构建的交易演示了基本的签名和验证过程。实际交易可以用于将价值从一个用户转移到另一个用户。在下一节中,我们将介绍可以使用交易执行的基本资产管理。

区块链中的资产所有权

区块链网络是一个分散的对等网络,其中节点相互通信以创建、交换和验证块。区块链网络中的大多数用户都对区块链的应用层感兴趣,可以通过创建交易来执行操作。可以在网络中轻松创建身份,就像我们在本章的前一节中所看到的。节点可以执行诸如资产创建或资产转移等操作。每个涉及资产的操作如果得到资产所有者的批准,则是有效的。资产所有者通过使用他们的私钥对交易进行签名来证明他们的身份。

资产管理操作,如资产转移,只能由所有者执行,但可以由网络中的任何人验证。所有操作细节都嵌入在交易中,资产所有者使用数字签名对这些交易进行签名。然后,他们将这些交易广播到区块链中的每个节点,以便它们被包含在下一个要附加到区块链分类帐的块中。

资产转移

一项资产的所有权可以通过拥有该资产所属地址(公钥)的私钥来证明。每当需要转移资产的所有权时,用户使用他们的私钥签署交易,首先证明他们的所有权,然后将所有权转移到所需用户。

现在,我们将看一个详细的例子来更好地解释资产所有权。Alice 是一个在区块链网络中声称拥有资产的用户。她希望将此资产转移给她的朋友 Bob。她可以访问自己的私钥,并使用它来创建一个包含数字签名的交易,证明她拥有该资产。签名过程是使用数字签名执行的,类似于本章早期使用 ECDSA 作为签名算法时使用的数字签名,该签名还使用了 ECC 密钥对。下面的图 3.3显示了 Alice 如何通过签署包含资产转移信息的交易内容来创建签名:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.3:Alice 签署一个用于转移资产的交易

此处提供的创建交易示例中的所有信息都是从高层次得出的,不是实际区块链应用中交易的准确代表。交易的详细解释超出了本章的范围,但将在第五章,加密货币中进行介绍。

传输交易

用户签署资产转移信息后,提供其他信息,例如用于验证的用户的公钥,目标公共地址以及其他验证所需的信息。此信息被广播到所有节点,以便包含在区块链中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.4:交易中的信息

如前述图 3.4所示,Alice 将在交易中包含诸如她的公钥和 Bob 的地址等信息。交易中提供的信息应足以使 Bob 在交易被包含在区块链的任何一个区块中后主张该资产属于他。

节点的公共地址充当节点的标识符,并通过执行哈希和编码从公钥构造而成。第五章,加密货币,将详细介绍此过程。

认领资产

当资产通过交易所需的信息进行转移和传输时,区块链网络将验证交易和包含交易的区块后,将其包含在区块链中。当交易包含在区块链中时,每个人都将能够看到这笔交易,但只有被寄往的所有者能够认领资产:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.5:Bob 通过公共信息验证交易

数字签名中使用的非对称密钥加密提供的安全级别将确保只有拥有与公共地址对应的私钥的节点才能索要转移至该地址的资产。

前述的图 3.5显示了当 Bob 的节点识别到一笔交易发生时,资产已被转移到他的地址时,他会尝试通过交易中提供的公共信息验证交易。一旦他验证了 Alice 创建了有效的交易,他就可以通过提供他的私钥执行任何关于资产的操作,从而证明资产属于他。这就是数字签名确保资产可以通过创建交易轻松转移,并且区块链确保交易在分散的网络中得到分发的方式。

区块链钱包

区块链钱包是一款保存特定用户拥有的所有私钥的软件。而物理钱包保存实体现金,区块链钱包则保存用户拥有的所有私钥,这将帮助用户索要属于自己的资产。

钱包是加密货币中一个著名的概念。持有私钥的用户在交易记录在区块链中时将能查看他们的账户余额。比特币中使用的钱包节点称为**简化支付验证(SPV)**节点。

一个单独的钱包可以存储任意数量的密钥,这意味着一个节点可以有多个目标地址。这些密钥有两种不同的创建方式:确定性和非确定性。

一个非确定性钱包是一个由随机创建的私钥组成的集合,这些私钥彼此之间没有关系。使用这些钱包创建的私钥难以维护,因为一旦丢失很难重建。因此,钱包中的每个密钥都必须备份,以防在钱包故障时发生任何损失。

一个确定性钱包也称为种子钱包,因为该钱包中的所有密钥都由一个单一的种子衍生而来。通过访问种子,所有密钥可以轻松地重新生成。一个简单的确定性钱包中的所有密钥都是通过对一个字符串和一个增量 nonce 进行哈希而创建的。在钱包发生故障的情况下,仅凭种子信息就足以恢复私钥,因此无需备份钱包中的所有密钥。

摘要

在本章中,我们通过识别加密货币在基本区块链架构中的应用,介绍了加密学在区块链中的基础知识。这将为我们在后续章节中讨论的更高级的区块链主题奠定基础。

现在我们已经介绍了基本的区块链架构,以加密学作为其支柱,是时候转向区块链技术的对应部分 - 去中心化网络。这将在下一章中介绍,并将帮助我们了解区块链在无需信任的网络中的应用。

第四章:区块链中的网络

在前几章中,我们讨论了区块链在无信任网络中运行所必需的加密概念。但是我们还没有讨论无信任网络是什么。去中心化网络促进了无信任环境的形成。在本章中,我们将探讨区块链如何通过点对点P2P)网络实现去中心化。

在本章中,我们将涵盖以下主题:

  • P2P 网络

  • 网络发现

  • 区块同步

  • 在 P2P 网络中构建简单的区块链

我们知道区块链是为了消除对单一中央机构的信任而创建的,它通过构建一个去中心化的网络来分散所有在其他情况下集中在单一实体中的任务。P2P 网络是一种用于在区块链应用中实现这种去中心化的架构风格。我们将从探讨 P2P 网络的定义、历史和架构开始这一章节。

点对点(P2P)网络

P2P 网络的基本定义是一种网络,其中称为节点的独立计算机组成的群体相互连接,共享数据而无需任何集中式服务器的帮助。它是建立在互联网之上的架构。在这种类型的网络中,参与者或节点被称为对等方,因为它们都是平等的,在网络中具有平等的责任。由于在 P2P 网络中没有特殊节点,因此每个对等方既是服务提供者又是消费者。

P2P 网络的历史

早期的万维网愿景与 P2P 网络的概念一致,其中每个用户都将成为网络的积极编辑和贡献者。USENET 于 1979 年首次开发,强制执行了一种分散式模型,其中 USENET 服务器彼此通信以共享新闻文章。

尽管 P2P 模型在互联网的早期阶段被使用,但它最常见的用途是在文件共享服务中实现。P2P 在文件共享中的应用由音乐共享应用 Napster 推广开来。然而,许多音乐服务都采用了类似的 P2P 文件共享模型。尽管 Napster 是先驱,但 P2P 中的文件共享由于 BitTorrent 协议而受到了很多关注,该协议允许任何数字媒体的文件共享。

P2P 网络架构

P2P 网络是一种架构,其中每个对等方同时充当服务器和客户端。由于区块链网络通常在公共网络上实现,因此很难创建适合 P2P 网络的物理拓扑结构。为了创建这种架构,必须在实际物理网络拓扑结构上构建虚拟或逻辑网络覆盖。创建逻辑网络以在公共网络中实现资源索引和对等方发现的便利。虽然形成了覆盖网络,但数据将在 TCP/IP 网络上交换。

尽管底层物理网络可以遵循任何网络拓扑,但 P2P 架构中的逻辑网络将形成网状拓扑,以实现对等方之间更好的通信:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.1:部分物理网状拓扑

在物理网状拓扑中,每个节点都可以与任何其他对等方建立通信,无论是直接还是通过一些中间节点。如果拓扑是全网状拓扑,那么每个节点都将能够直接通信,其中每个节点都连接到网络中的所有对等方。偏好部分网状配置,因为构建全网状拓扑是昂贵的。

有两种主要的 P2P 网络分类,基于节点如何连接:非结构化结构化 P2P 网络

在非结构化网络中,对等方之间没有以有组织的方式相连。每个节点是随机连接到对等方,形成一个逻辑网状。建立非结构化网络很容易,由于节点的冗余分布,它们非常健壮。然而,这些网络也有缺点,例如可能导致请求泛滥,这是由于对资源分布的缺乏知识引起的。

结构化 P2P 网络叠加是按照特定的网络拓扑形式形成的,以确保节点能够高效地在网络上执行活动。创建结构化网络可以确保资源可以在网络的某个地方在一定时间内被获取。分布式哈希表DHT)是一个广泛使用的结构化网络实现,提供分散式查找服务。DHT 中的资源信息可以使用哈希表从表中存储的键/值对的键来检索。与键相关联的值提供了拥有资源的节点的信息。DHT 也被用于 BitTorrent 文件共享协议,作为对中心化查找服务(如跟踪器)的替代。

DHT 是在分布式系统中维护的查找服务。分布式系统中的节点负责维护从键到值的映射,提供资源信息。

现在我们已经了解了 P2P 网络架构的概述,让我们深入了解一些在 P2P 网络中用于构建分散式区块链网络的区块链技术概念。

网络发现

P2P 网络中的网络发现至关重要。当新节点启动时,没有定义网络。新节点必须检测到至少一个区块链节点才能成为网络的一部分。节点可以通过多种方式确定对等方,从而发现网络。

不同的区块链框架使用自己的协议进行对等发现和高效路由。我们将从探索比特币的原始实现开始,以了解基本的 P2P 网络发现。

寻找要连接的节点列表的最简单方式是在几个众所周知的节点中硬编码。使用维护节点列表的中央服务器是另一种方法。比特币保存了关于 DNS 种子的信息,这提供了节点在最初设置时的高可靠性,并且会以比特币节点的 IP 地址列表作为响应。一旦检测到种子节点,节点将建立 TCP 连接以与该节点握手。握手通过发送版本、地址、本地区块链信息和其他相关信息来验证节点。

一旦节点与被其发现的节点建立连接,节点就可以查询关于连接到其对等节点的其他节点的信息。同样地,节点可以将自己的地址信息广播给连接的对等节点,以提高其可达性。每个节点还确保保持一定数量的活动连接的阈值,以避免不必要的带宽使用。

一些区块链平台,比如以太坊,使用了一个名为 RLPx 的密码 P2P 网络协议套件,它提供了一个通用的传输和接口,用于通过 P2P 网络进行应用程序通信。RLPx 利用类似 Kademlia 的路由来确保网络的均衡形成。在初始节点握手后,数据包被封装为帧,然后进行加密。

区块同步

加入区块链网络的每个节点都需要更新其区块链的本地副本,以将其状态与网络其他部分的全局状态同步。这是通过区块同步实现的。需要更新其区块链的节点会发送一个包含区块高度信息的消息。任何拥有更长区块链的节点都会发送一个包含关于需要添加到主机节点的固定数量区块的元数据清单。现在节点通过引用收到的清单向所有对等节点发起请求,以获取单独的区块。节点应该确保在发送区块请求时不要让网络被大量区块请求所淹没。

区块同步是对于新加入节点来说一个漫长的过程。然而,一旦所有的区块都更新到最新状态,它就能验证区块中的信息,比如资产上的交易。只要节点重新连接网络后变得活跃,区块同步过程就可以重新初始化。

在 P2P 网络中构建简单的区块链

在第三章中,《区块链中的加密》,我们探讨了如何利用诸如工作证明等算法在去中心化网络中实现共识。由于共识算法确保了拜占庭失败问题可以得到解决,因此可以在对等方之间没有信任的去中心化网络中保持全局真相。尽管共识算法提供了一种方便的方法来维护公共分类帐,但每个节点都必须执行一组操作来在分布式网络中维护分类帐。

我们已经创建了一个简单的区块链应用程序,可以在有新数据需要插入时连续扩展其记录。因为我们的区块链应用程序是部署在单个系统中的,并且区块是在其中创建的,所以我们还没有添加任何验证区块的机制。但是当我们将区块链部署为公共分类帐时,每个节点在接收到来自节点对等方的区块时都需要对其进行验证。

每个节点必须执行以下过程以在网络中达成共识:

  • 验证每个传入的区块以确保其完整性,以便将其附加到本地区块链中。

  • 选择去中心化网络中一个对等方发布的最长有效链。

  • 每当有一些数据需要插入公共分类帐时,就创建一个有效的区块。

我们将构建一个简单的区块链应用程序,将区块链部署在 P2P 网络中。该应用程序是一个参考实现,将帮助我们理解区块链网络的去中心化。尽管此应用程序执行网络发现、区块同步和区块验证,但为简单起见,它并未遵循所有必需的协议。

现在让我们在深入实施之前探讨一下构建应用程序时需要考虑的一些设计考虑因素。以下各节涵盖了在此应用程序中使用的验证、区块同步和基本接口设计。

验证新区块。

尽管区块是由矿工节点在验证链中的所有先前区块后创建的,但每个网络中的节点都有责任执行区块验证,以确保该区块可以附加到本地区块链的副本中。区块验证是一个简单的过程,检查最新的区块是否具有指向上一个区块的指针或哈希引用。当一个区块包含复杂数据,例如一组交易时,必须验证独立的交易以验证该区块。由于我们将不处理区块内的任何复杂数据,因此验证交易超出了本章的范围。

选择最长的链。

在分散网络中找到全局真相是一项艰巨的任务,各种共识算法有助于实现这一目标。在上一章中讨论的工作量证明算法是第一个也是最好的解决方案。它确保由网络中合法节点贡献的工作量最多的链被创建为最长链。出于简单起见,本应用将不包含任何共识算法。

冲突解决

尽管选择一个区块可能看起来是一个明显的任务,但会出现两个或更多节点同时创建一个由于其区块中包含的不同内容而具有不同标识的区块的情况。当一个节点接收到相同高度的不同版本的区块时,它将根据到达的顺序将其中一个添加到区块链中,然后拒绝另一个或将其保留在内存池中。由于这个原因,网络中的节点可能会拥有不同版本的区块链。这称为临时分叉或软分叉。

软分叉是区块链中创建两个或更多不同版本的区块链的一种类型,原因是由于同时创建区块或某些故障。这种分叉是暂时的,并在较长的区块链通过网络传播后立即纠正。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.2:由同时创建区块引起的区块冲突

图 4.2 展示了由于同时创建相同高度的区块而创建了两个区块链的情况。这两个区块链具有相同的高度和相同的区块,直到索引11。但是有两个不同版本的区块12。正如我们在图中所见,其中一个区块的哈希以**…bfa9779618ff结尾,另一个哈希以…bce8564de9a8结尾。这两个区块都是有效的,因为它们的上一个哈希值与上一个区块的哈希值…ea758a8134b1**相匹配。在这个阶段,其中一个区块将被插入到主链中,另一个将被拒绝。

尽管一个节点上的冲突已经解决,但是在整个网络中不会有区块链的全局真相,因为其他节点可能已经决定接受不同的区块。这将在区块链中创建一个临时分叉,因为一些节点拥有一个版本的区块链,而另一些节点拥有另一个版本。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.3:通过接受更长链解决冲突

这种临时分叉不会长时间存在。一旦创建了一个更长的区块链,并且当一个新的区块被附加到其中一个链上时,每个节点都将拒绝较小的区块链,并用更长的区块链替换自己的区块链。图 4.3展示了在索引12处具有哈希值为**…bce8564de9a8**的区块链将被接受,因为它通过添加额外的区块增加了自己的区块链,从而形成了更长的链。

节点之间的区块交换

在本应用程序中,区块交换使用广播查询消息和区块来执行。新加入的节点经历一个初始块同步过程来更新其本地区块链的副本。

初始块同步

当一个新节点加入网络时,它会连接到网络中的一个可用对等节点;然后尝试与对等节点交换其区块信息,并在需要时更新本地区块链。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.4:初始块同步

节点在连接到对等节点后发送链长度或最后一个块查询消息。请求链长度等同于请求最后一个区块以确定其索引。如果接收的区块由于哈希不匹配而无法附加到本地区块链中,则节点会向其对等节点发送消息,要求其发送所有区块。节点也可以广播一条消息,请求所有区块,以确保其接收到最长的链。

广播情景

节点通过建立多个一对一连接在 P2P 网络中相互通信,因为没有一个单一的对等节点是完全可信的。保持多个连接强制每个节点向其连接的对等节点广播任何信息,以便将数据分发到整个网络并保持全局真相。

在我们的应用程序中,我们将在图 4.5 中所示的状态转换图中展示的几种情况下广播信息:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.5:广播的状态转换图

网络中的节点在以下情况下执行广播:

  • 当一个节点创建一个新区块时,它必须将其广播到其所有连接的对等节点,以传播区块的信息

  • 当节点从其对等节点接收到一个新区块时,它必须广播该区块,以便在 P2P 网络中传播该区块

  • 当节点接收到一个与当前区块链不匹配的区块时,它会向其所有对等节点发送查询消息,以找到最长的链

应用程序接口

该应用程序提供两个用户接口:一个 HTTP API 接口用于访问和操作区块链信息,以及在所有节点上使用 WebSocket 接口以创建一个长期的双向通信通道:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4.6:应用程序的接口

深入代码

这个应用程序由三个不同的类组成,分别是Server,它将处理区块链网络上的 P2P 通信。另外两个类是BlockchainBlock,它们在上一章节中有所涉及,并执行基本的区块链操作。Blockchain类还执行增强功能以验证从对等节点接收的区块,如应用程序架构中所讨论的。

服务器接口

服务器接口是使用名为Sanic的 Python Web 服务器框架创建的,该框架有助于以异步方式编写 Web 服务器逻辑:

背景:Sanic 是一个轻量级的 Web 服务器框架,可用于构建高速 Web 服务器。它支持通过支持 Shiny 异步请求处理程序来进行异步编程。它利用名为uvloop的事件循环来实现更好的性能。

class Server(object): 

    def __init__(self): 

        self.app = Sanic() 
        self.blockchain = Blockchain() 
        self.sockets = [] 
        self.app.add_route(self.blocks, '/blocks', methods=['GET']) 
        self.app.add_route(self.mine_block, '/mineBlock', methods=['POST']) 
        self.app.add_route(self.peers, '/peers', methods=['GET']) 
        self.app.add_route(self.add_peer, '/addPeer', methods=['POST']) 
        self.app.add_websocket_route(self.p2p_handler, '/') 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Server类的上述代码片段创建了四个用于显示区块、挖掘(创建)新区块、显示节点当前对等体以及添加新对等体的 REST API 接口。我们将使用使用add_route方法创建的端点与我们的节点交互。

我们将使用 WebSocket 协议在节点之间执行 P2P 通信,因为它允许连接节点之间的双向通信,同时保持活动连接而不会对 Web 服务器造成过大的负载。

WebSocket 是建立在 TCP 之上的通信协议,提供了双向通信通道,不同于 HTTP。因为双方都可以同时发送和接收消息,所以可以用于 P2P 通信。

前面片段中定义的所有 API 接口都会调用以下方法:

    async def mine_block(self, request): 
        try: 
            newBlock = self.blockchain.generate_next_block(request.json["data"]) 
        except KeyError as e: 
            return json({"status": False, "message": "pass value in data key"}) 
        self.blockchain.add_block(newBlock) 
        await self.broadcast(self.response_latest_msg()) 
        return json(newBlock) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

前面的方法实现了生成用于追加新块所需的功能。由于它是一个 HTTP POST 方法,它接受一个称为data的参数,该参数在 HTTP 请求正文中发送。这些数据用于创建新块。如前所述,在应用程序设计部分,该方法将广播新创建的块。

本地区块链中存在的区块将通过此方法格式化并返回:

     async def blocks(self, request): 
         return json(self.blockchain.blocks) 
  • 1
  • 2

每当用户想要显式添加一个对等体时,他们将使用以下接口:

    async def add_peer(self, request): 

        asyncio.ensure_future(self.connect_to_peers
([request.json["peer"]]),        
        loop=asyncio.get_event_loop()) 
        return json({"status": True}) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这个方法将一个异步的connect_to_peers任务添加到 Web 服务器的事件循环中。所有连接对等体的套接字信息都会被维护以供未来广播使用。

以下的实现从节点在初始连接后维护的所有对等体的套接字对象中提取地址和端口信息:

    async def peers(self, request): 
        peers = map(lambda x: "{}:{}".format(x.remote_address[0], x.remote_address[1]), self.sockets) 
        return json(peers)] 

    async def connect_to_peers(self, newPeers): 
        for peer in newPeers: 
            logger.info(peer) 
            try: 
                ws = await websockets.connect(peer) 

                await self.init_connection(ws) 
            except Exception as e: 
                logger.info(str(e)) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

connect_to_peers 方法初始化与所有已知对等体的 WebSocket 连接。这个方法也在使用 HTTP 接口添加新对等体时被调用。使用init_connection函数执行初始块同步。p2p-handler是一个 WebSocket 处理程序,负责侦听连接。它创建一个套接字并使用init_connection执行块同步:

    async def p2p_handler(self, request, ws): 
        logger.info('listening websocket p2p port on: %d' % port) 
        try: 
            await self.init_connection(ws) 
        except (ConnectionClosed): 
            await self.connection_closed(ws) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当对等体断开连接时,套接字信息将被移除:

    async def connection_closed(self, ws): 

        logger.critical("connection failed to peer") 
        self.sockets.remove(ws) 
  • 1
  • 2
  • 3
  • 4

使用init_connection方法执行块同步,通过发送请求对等体的区块链最后一个块的查询消息。它还添加了一个消息处理程序,不断侦听来自对等体的消息:

    async def init_connection(self, ws): 

        self.sockets.append(ws) 
        await ws.send(JSON.dumps(self.query_chain_length_msg())) 

        while True: 
            await self.init_message_handler(ws) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

每个节点上的消息处理程序监听三种类型的消息。其中两种是查询消息,节点会通过查询本地区块链来响应。RESPONSE_BLOCKCHAIN 消息是本地节点发出的查询的响应。 这条消息将由 handle_blockchain_response 进一步处理:

    async def init_message_handler(self, ws): 
        data = await ws.recv() 
        message = JSON.loads(data) 
        logger.info('Received message: {}'.format(data)) 

        await { 
            QUERY_LATEST: self.send_latest_msg, 
            QUERY_ALL: self.send_chain_msg, 
            RESPONSE_BLOCKCHAIN: self.handle_blockchain_response 
        }[message["type"]](ws, message) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

以下方法将响应数据序列化为要发送给对等节点的查询消息请求:

    async def send_latest_msg(self, ws, *args): 
        await ws.send(JSON.dumps(self.response_latest_msg())) 

    async def send_chain_msg(self, ws, *args): 

        await ws.send(JSON.dumps(self.response_chain_msg())) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

当对等节点查询区块链时,整个区块链将被发送给连接的对等节点:

    def response_chain_msg(self): 
        return { 
            'type': RESPONSE_BLOCKCHAIN, 
            'data': JSON.dumps([block.dict() for block in self.blockchain.blocks])} 
  • 1
  • 2
  • 3
  • 4

以下代码片段从区块链中获取最后一个区块,并将其格式化为 JSON,以便通过套接字通道发送:

    def response_latest_msg(self): 

        return { 
            'type': RESPONSE_BLOCKCHAIN, 
            'data': JSON.dumps([self.blockchain.get_latest_block().dict()]) 
        } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

以下代码片段是用于查询连接的对等节点以获取区块链的查询请求消息:

    def query_chain_length_msg(self): 

        return {'type': QUERY_LATEST} 

    def query_all_msg(self): 

        return {'type': QUERY_ALL} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

以下方法在持续监听对等节点的请求和响应的消息处理程序中被调用。此方法被调用以处理对等节点发送的区块链信息:

    async def handle_blockchain_response(self, ws, message): 
        received_blocks = sorted(JSON.loads(message["data"]), key=lambda k: k['index']) 
        latest_block_received = received_blocks[-1] 
        latest_block_held = self.blockchain.get_latest_block() 

        if latest_block_received["index"] > latest_block_held.index: 
            logger.info('blockchain possibly behind. We got: ' + str(latest_block_held.index) 
                  + ' Peer got: ' + str(latest_block_received["index"])) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果区块的最后索引不大于本地区块链的最后索引,则会被拒绝,因为节点只对最长的链感兴趣:

            if latest_block_held.hash == latest_block_received["previous_hash"]: 
                logger.info("We can append the received block to our chain") 

                self.blockchain.blocks.append
(Block(**latest_block_received)) 
                await self.broadcast(self.response_latest_msg()) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果最后接收的区块满足哈希条件,则将其附加到本地区块链。如果满足条件,则将其广播到所有节点:

            elif len(received_blocks) == 1: 
                logger.info("We have to query the chain from our peer") 
                await self.broadcast(self.query_all_msg()) 
            else: 
                logger.info("Received blockchain is longer than current blockchain") 
                await self.replace_chain(received_blocks) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果最后一个区块不满足哈希条件,那么本地区块链可能落后一个以上区块。如果是完整的链,则本地副本将被接收的区块链替换。否则,将广播一条消息来查询整个区块链。

当一个节点接收到比当前本地副本更长的区块链时,它将验证整个区块链,然后替换整个本地区块链:

    async def replace_chain(self, newBlocks): 

        try: 
            if self.blockchain.is_valid_chain(newBlocks) and len(newBlocks) > len(self.blockchain.blocks): 
                logger.info('Received blockchain is valid. Replacing current blockchain with ' 'received blockchain') 
                self.blockchain.blocks = [Block(**block) for block in newBlocks] 
                await self.broadcast(self.response_latest_msg()) 
            else: 
                logger.info('Received blockchain invalid') 
        except Exception as e: 
            logger.info("Error in replace chain" + str(e)) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

broadcast 方法将所有请求和响应通过建立的套接字连接发送给与节点连接的所有对等节点:

    async def broadcast(self, message): 

        for socket in self.sockets: 
            await socket.send(JSON.dumps(message)) 
  • 1
  • 2
  • 3
  • 4

区块和区块链接口

BlockBlockchain 类方法与上一章中用于构建简单区块链的方法类似,唯一的增强功能是用于验证接收的区块的验证方法。

以下方法被调用以验证每个区块是否链接到其前一个区块:

    def is_valid_new_block(self, new_block, previous_block): 
  • 1

以下条件验证了新区块的索引:

        if previous_block.index + 1 != new_block.index: 
            logger.warning('invalid index') 
            return False 
  • 1
  • 2
  • 3

此条件通过比较区块的哈希值来执行哈希验证:

        if previous_block.hash != new_block.previous_hash: 
            logger.warning('invalid previous hash') 
            return False 
  • 1
  • 2
  • 3

此条件检查了新添加区块的完整性,通过计算其摘要:

        if self.calculate_hash_for_block(new_block) != new_block.hash: 
            logger.info(type(new_block.hash) + ' ' + type(self.calculate_hash_for_block(new_block))) 
            logger.warning('invalid hash: ' + self.calculate_hash_for_block(new_block) + ' ' + new_block.hash) 
            return False 
        return True 
  • 1
  • 2
  • 3
  • 4
  • 5

当节点希望使用从对等节点检索的区块链替换本地区块链时,将使用以下方法来验证整个区块链:

    def is_valid_chain(self, blockchain_to_validate): 
        if self.calculate_hash_for_block(Block(**blockchain_to_validate[0])) != self.get_genesis_block().hash: 
            return False 
  • 1
  • 2
  • 3

此条件验证了硬编码的起源块:

        temp_blocks = [Block(**blockchain_to_validate[0])] 
        for currentBlock in blockchain_to_validate[1:]: 
            if self.is_valid_new_block(Block(**currentBlock), temp_blocks[-1]): 
                temp_blocks.append(Block(**currentBlock)) 
            else: 
                return False 
        return True 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

通过调用 is_valid_new_block 迭代验证收到的每个区块:

运行区块链节点

尽管每个节点都有两个处理程序,一个用于 HTTP 接口,一个用于 WebSocket 接口。一个 Web 服务器应用程序实例足以为它们提供服务。Sanic使用uvloop作为调度程序。它异步处理请求:

if __name__ == '__main__': 

    server = Server() 
    server.app.add_task(server.connect_to_peers(initialPeers)) 
    server.app.run(host='0.0.0.0', port=port, debug=True) 
  • 1
  • 2
  • 3
  • 4
  • 5

服务器应用程序被实例化,并创建了一个任务来连接节点到initialPeers,这是每个节点硬编码的。应用程序将使用默认端口创建一个 Web 服务器。

每个节点都将运行服务器应用程序以加入网络。一旦节点连接到其中一个对等方,其区块链就会与网络的更新后的区块链同步。

现在,我们将通过创建三个节点实例来运行该应用程序:node1、node2 和 node3。Node2 将 node1 作为其初始对等方,而 node3 将 node2 作为其初始对等方。当我们运行所有三个节点实例时,它们将形成一个类似环形的网络。

通过调用每个节点的 HTTP API 端点/peers,让我们检查每个节点的对等信息:

  • 节点 1:[“127.0.0.1:51160”]

  • 节点 2:[“127.0.0.1:3001”,“127.0.0.1:35982”]

  • 节点 3:[“127.0.0.1:3002”]

3001是分配给 node1 的端口号。Node2 的端口号是3002,node3 的端口号是3003。随机端口号是试图与 node2 通信的对等方的端口号。前述信息清楚地显示,node2 已将 node1 添加为其对等方,而 node3 已将 node2 添加为其对等方。

当我们调用/blocks HTTP API 端点时,所有节点都返回以下结果:

[ 
  { 
    "data": "my genesis block!!", 
    "hash": 
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "index": 0, 
    "previous_hash": "0", 
    "timestamp": 1465154705 
  } 
] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

通过使用/mineBlock HTTP API 端点,通过发送带有载荷{"data": "created at node2"}的 POST 请求,在 node2 处挖掘一个新的区块。由于所有节点都相互连接,形成一个网格,每个节点都将接收到新广播的区块,并更新其本地区块链账本。现在,每个节点反映以下区块链:

[ 
  { 
    "data": "my genesis block!!", 
    "hash": 
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "index": 0, 
    "previous_hash": "0", 
    "timestamp": 1465154705 
  }, 
  { 
    "data": "created at node2", 
    "hash": 
 "29630fab36aa1e3abf85b62aee8f84b08438b90e9e19f39d18766cc9208b585c", 
    "index": 1, 
    "previous_hash": 
 "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", 
    "timestamp": "1522069707" 
  } 
] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

根据这个示例:可以在该书的 GitHub 存储库中找到整个 P2P 区块链应用程序。它具有可用于使用 Docker Compose 设置 Docker 集群的配置。这将帮助您在单台机器上设置任意数量的区块链节点。

摘要

本章涵盖了在区块链应用中使用的 P2P 网络方面,通过构建具有基本功能的简单 P2P 应用程序。本章获得的基本架构知识将帮助您理解一些区块链平台中使用的高级网络概念。

现在,我们在涵盖了几个密码学和网络概念之后对区块链技术有了扎实的背景,我们将介绍区块链技术的原始和最强大的用例之一 - 加密货币。在下一章中,我们将应用到目前为止介绍的大部分概念,以理解和实现加密货币的用例。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/790378
推荐阅读
相关标签
  

闽ICP备14008679号