跳转至

区块链中的密码学

在前几章中,我们介绍了与区块链相关的加密技术。尽管我们对其中一些密码原语有清晰的理解,但我们还没有探索它们在真实的区块链应用程序中的应用。在这一章中,我们将介绍加密原语的一些应用,包括散列函数和数字签名。我们将通过在一个基本的区块链应用程序中实际实现它们来深入讨论它们。

在本章中,我们将讨论以下主题:

  • 在区块链哈希
  • 区块链的数字签名

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

在区块链技术中,我们主要可以观察到三个层次。它们是对等网络层共识层,处理块创建和验证机制,以及应用层,利用底层区块链来构建应用。加密主要用于区块链的共识层和应用层。哈希算法主要用于创建块身份,确保区块链的完整性,也是共识算法的关键成分,如比特币的工作证明。另一方面,数字签名处理应用层,通过将事件嵌入到事务中来验证事件。

由于哈希和数字签名对不同层的区块链有影响,我们将在本章的不同部分讨论这些概念的意义。

在区块链哈希

哈希在区块链是一个重要的概念,在区块链应用程序的运行中有着巨大的作用。散列函数的应用范围从次要的区块链实现(如为大量数据创建摘要)到主要的实现(如维护链中数据块的完整性)。哈希函数也用于工作一致性算法的证明,以解决拜占庭故障问题,我们将在本章后面深入讨论。首先,我们将探索利用散列函数的区块链的一些概念。

区块链中的链接块

正如在介绍性章节中所定义的,区块链是一个不断增长的块集合,这些块通过使用加密作为一个关键成分而链接在一起形成一个开放的分类帐。区块链中的每个块都被赋予一个标识,以将该块标记为唯一的,这是通过使用将为该块生成摘要的散列函数来实现的。加密哈希函数的抗碰撞特性,如前一章所述,第二章一点密码学,保证了不可能找到两个会产生相同哈希值的块。因此,哈希函数保证了为块创建的身份的唯一性。

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

图 3.1:使用来自对等电子现金系统的哈希链接块

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

使用 SHA256 哈希算法链接块

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

方块图

让我们考虑一个简单的块,它的头和数据被组合起来创建一个数据结构,称为。每个块将包含以下内容:索引、以前的散列、时间戳、数据和它自己的散列值:

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 

前面的代码片段定义了一个名为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()

前面的类是使用哈希函数创建有效区块链的类方法的集合。Blockchain的构造函数将通过追加一个 genesis 块来初始化一个链,genesis 块是区块链的第一个块,并且没有对前一个块的任何引用:

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

    return Block(0, "0", 1465154705, "my genesis block!!", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7"
 ) 

起源块是附加到区块链开头的硬编码块。它是用静态内容创建的。前面的 genesis 块有一个使用 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()

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 

以下函数将通过构建创建一个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)

注意:我们已经基于静态时间戳值创建了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]] 

创造一个区块链

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

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))

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

时间戳被硬编码在 genesis 块中,并且有意地对所有块保持不变,以表明使用相似数据计算的哈希值每次都会生成相同的值:

[ 
  { 
    "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" 
  } 
] 

在实际执行期间,前面的输出将生成两次。输出显示了我们的区块链中的块如何使用加密哈希进行链接。区块链中的每个块都有一个与前一个块的哈希值相匹配的previous_hash值。索引0是没有previous_hash的硬编码的起源块,索引1具有与起源块的散列相匹配的previous_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)) 

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

[ 
  { 
    "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的块数据被修改,但所有其他块的哈希值都不同于前面的块输出。这是由于连锁或涟漪效应。因为每个块都存储前一个块的哈希值,所以每个块都会受到此修改的影响。这导致了一个新区块链的诞生。这就是为什么区块链是安全的:单个块不能被修改而不影响分类帐中的其他块。

前面的示例区块链应用程序的完整脚本可以在本书的 GitHub 存储库中找到(https://GitHub . com/packt publishing/Foundations-of-block chain),以及 Python 打包。

区块链的拜占庭失败问题

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

拜占庭式失败是一种向不同观察者呈现不同症状的故障。当系统中需要达成共识的服务丢失时,就会发生这种情况。这种失败在分布式系统中有目共睹,在分布式系统中,很难收集关于组件状态的信息,并且不良参与者的存在使得达成共识更加困难。

拜占庭失败一词来源于拜占庭将军问题,是一群代表拜占庭军队的将军在计划进攻一座城市的协议问题。一些将军可能会决定进攻,而另一些则会撤退。他们应该就进攻还是撤退达成一致,这样任务才会成功。将将军们的选票传达给彼此是一项困难的任务,因为他们彼此相距遥远,没有方便的通信方式。因此,将军们之间会有延迟或沟通不畅。由于不可靠的将军的存在,这个问题变得更加严重,因为他们可能试图在投票时作弊,从而导致任务失败。如果这种制度无法获得大多数选票的同意,它将导致任务失败,因为决定进攻的军队可能没有得到其余将军的足够支持。这是一个经典的协议问题,没有单一的解决方案。解决拜占庭将军问题的办法是在诚实的将军中找到多数票。

显示出拜占庭容错 ( BFT )的系统是能够克服拜占庭故障问题的系统。在数字系统中,诸如数字签名之类的密码原语可以通过创建不可伪造的消息签名来为安全关键系统提供容错。实现数据完整性可以在一定程度上防止拜占庭故障问题,但这不是一个完整的解决方案。

既然我们理解了拜占庭问题,我们可以注意到这个问题适用于任何分布式系统。这个问题也存在于区块链网络中,那里的参与者分散在一个分散的对等网络中。在一个分散的网络中维护一个单一的真相是一项艰巨的任务,而网络中不良行为者的参与使得这项任务更加艰巨。区块链的分散网络必须在单个状态上达成一致,以使区块链在所有区块链节点之间保持一致。区块链网络中拜占庭问题的出现是不可避免的,因为区块链网络存在于一个分散的无信任的环境中。网络的节点应该就如何实现一个普遍的区块链国家达成共识。尤其是矿商,他们应该达成共识,因为是他们为区块链的发展做出了贡献。

区块链矿工是一个节点,它不仅验证区块链的数据,还提供资源在区块链分类帐中创建新块。

比特币是第一个解决拜占庭问题的去中心化应用。它通过使用一种被称为工作证明的共识算法来实现这一点,这种算法受到了英国密码学家 Adam Back 在 1997 年提出的 Hashcash 系统的启发。Hashcash 的开发是为了验证合法用户,并通过创建一个需要一定计算量的戳记来减少垃圾邮件。Hashcash 戳是使用哈希算法创建的。尽管创建图章非常耗时,但可以立即执行验证。同样,比特币的工作证明也使用了一种加密哈希函数来实现网络中的共识。

在区块链中,有几种共识算法可以实现共同的全局视图。堆栈证明、活动证明、容量证明和运行时间证明只是几个例子。甚至流行的以太坊区块链框架目前也使用工作共识证明,但是已经有了积极的发展努力来在以太坊的未来版本中包括利益证明。

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

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

比特币的工作证明共识算法旨在确保区块链数据是不可变的,不能被坏人轻易更改。区块链网络中的多数决策由最长链表示;这是因为它有证据证明做了最多的工作。虽然这个系统可以在一个分散的网络中达成共识,但是如果一个坏的参与者试图用一些欺诈交易创建一个替代的区块链呢?当工作证明被使用时,这并不容易。每当坏的参与者对先前创建的块进行修改时,所有后续的块都将被重新创建,所有这些块都将重做工作。重新创建所有块将需要很长时间,因为这个过程需要大量的计算能力。然而,你会发现,一个坏演员的工作经常会被网络的大多数拒绝,因为它无法跟上诚实节点所做的工作。

因此,工作证明有助于实现 BFT 系统,即使存在不诚实的节点。

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

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

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

区块链网络中的块是由一种称为挖掘器的特殊类型的验证器节点创建的。这些挖掘器节点相互竞争来解决散列难题,以便产生要附加到分类帐的块。

每当区块链矿工有需要包含在区块中的数据(通常是一组交易)时,他们就会开始解决哈希难题。下图图 3.2 显示了基于工作证明的区块链应用中使用的块头的基本结构。解谜者通常使用 SHA256 散列函数创建标题的散列值。这里的难题是找到报头的散列值,以便散列以已知数量的零比特开始:

图 3.2:块标题的基本结构

我们以前提到过散列函数的特征;也就是说,哈希值无法预测,因为哈希值的创建是一个单向过程。因此,很难预测头的内容,这将导致哈希值以一定数量的零开始。实现这一点的唯一方法是反复尝试不同的头值,并使用散列函数计算散列值。通过改变报头中名为 nonce 的可变字段来创建不同的报头。随机随机数被分配来为散列函数创建不同的头部。一旦挖掘器找到一个随机数,该随机数将产生一个具有所需数量的零比特的散列值,难题就解决了,并且该随机数被记录在块头中。

使用散列函数来执行工作证明被证明是一种好方法,因为由于散列值的加密特性,很难欺骗性地计算散列值。哈希函数确保一定量的 CPU 工作用于计算哈希值,并且计算机的哈希速率是该过程中工作的证明。

工作证明的示例实现

在上一节中,我们介绍了工作证明如何使用散列算法。我们还研究了如何计算目标散列来解决散列难题。我们现在将使用 SHA-256 算法实现一个工作证明算法,以分析哈希和概率如何影响这种一致性算法。

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

在工作证明中解决的每个难题都有一个难度级别,它决定了要创建的目标哈希值。难度级别由结果哈希值开头所需的零位数决定。通过增加哈希值中所需的 0 位的数量来增加难题的难度。这也是因为由于较小的样本空间,找到小散列值的概率低于找到包括大散列值在内的任何散列值的概率。

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

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) 

演示 nonce 的代码灵感来自 Andreas M. Antonopoulos掌握比特币第一版中的代码片段。

前面的代码片段是一个生成散列来解决工作证明散列难题的简单示例。随机数是以增量方式创建的,并附加到输入数据中。使用 SHA-256 算法来计算哈希值,并且对所有随机数值重复该操作。

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

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... 

虽然前面的哈希值的每个输入仅在最后两位数上有所不同,但由于哈希函数的属性,哈希输出值完全不同。这就是为什么检测将产生目标散列值并因此解决难题的随机数是不可行的。

查找 nonce 以解决工作证明的示例

下面的例子将说明如何在 SHA-256 算法的帮助下,通过强力查找随机数,以便找到将满足目标散列的散列值。目标哈希值通过设置工作证明算法中的难度位来确定。我们将修改我们之前创建的区块链链接器,以便在创建新块时包含工作证明算法。首先,让我们修改区块链例子中的几个函数,使其包含一致性算法。

用于创建要添加到区块链的新块的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 

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 

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) 

然后修改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 

proof_of_work方法通过增加随机数的值来对每个随机数执行搜索,以便找到小于目标值的散列值。使用提供的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 

下面的 Python main 方法将创建一个新的 Blockchain 对象类,它将创建一个带有 genesis 块的新链。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) 

我们将发现工作证明示例的输出如下。它打印出所用系统的散列能力、nonce 以及块创建过程中经过的时间:

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... 

从这个示例输出中可以明显看出,计算解决方案所需的时间与使用的难度位数成正比。准确地说,难度级别每增加一位,找到 nonce 的概率就会减少一半,因为目标空间会减少一半。虽然偶尔运气可能会帮助我们解决一些难题,概率论适用于大多数情况下的工作共识算法的证明。

区块链的数字签名

数字签名是非对称密钥加密的产物,是在不信任环境中各方之间建立信任的一种很好的方式。如本章开头所述,数字签名用于区块链的应用层。它们主要用于验证插入块中的事务中的事件。它们用于验证交易,因为拥有生成的公钥-私钥对的公钥的任何人都可以执行验证。

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

创建身份

由于我们在前一章中已经介绍了数字签名和公钥-私钥,我们已经知道了数字签名的属性和安全性。概括地说,它们为拥有私钥的节点提供了一种签署消息以证明其身份的方法。然后,任何有权访问分发的公钥的人都可以验证这一点。

通过生成公钥-私钥对来创建身份。这类似于在区块链网络中创建一个帐户。下面的代码展示了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() 

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

account = Wallet() 
account.generate_key_pair() 
print("Generated public key: %s" % account.public_key)
print("Generated private key: %s" % account.private_key)

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

Generated public key: 
 b7f5edffe6d3532ed743e07c4de5551c2d7476a4053221999ce40edec2607bb4ef
 7ecb9fc6ecf735fd3802fada56c42e18474f8bad269a965f95863f9fc38158 
Generated private key: 
 6eb9035be1dabd01fadcb6a9f92946decc868046184c7810a43806eb6cc46237 

私钥总是保密的,公钥用来生成用户的公开地址,然后嵌入到交易中。

交易签名

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

下面的代码显示了一个简单的事务,带有事务 id、签名和公钥。该事务只能由公钥的相应私钥的所有者签名。比特币和其他区块链平台的交易在交易中有几个字段来执行价值转移,而加密货币的交易在第五章加密货币中有更详细的介绍:

class Transaction: 

    def __init__(self, public_key): 
        self.id = randint(1, 10**5) 
        self.signature = None 
        self.public_key = public_key 

然后,通过用相应公钥的私钥签署事务的散列内容来创建签名:

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() 

然后,仅借助于交易内容、创建的签名和公钥来验证交易。只有当签名或交易内容被修改时,验证才会失败。该操作还验证事务的完整性:

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

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

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

Generated signature: 
 943ed91d7ceb2a57d4e972845acda7ea818b994a840d3101d192ebe33a7c1f4d68
 55e50ed7b882cd4d372d540187f52f2d5b3a6144a58fc20098095f1726849f 

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

tx.id = '1234' 
tx.verify() 

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

区块链的资产所有权

区块链网络是一种分散的对等网络,其中的节点相互通信以创建、交换和验证块。区块链网络中的大多数用户都对区块链的应用层感兴趣,在应用层中可以通过创建事务来执行操作。正如我们在本章上一节所看到的,在网络中可以很容易地创建一个身份。节点可以执行资产创建或资产转移等操作。如果得到资产所有者的批准,处理资产的每个操作都是有效的。资产所有者通过使用他们的私钥签署交易来证明他们的身份。

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

转移资产

可以通过拥有资产所属地址的私钥(公钥)来证明资产的所有权。每当需要转移资产的所有权时,用户使用他们的私钥来签署交易,首先证明他们的所有权,并从那里将所有权转移给期望的用户。

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

图 3.3: Alice 签署交易以转移资产

All the information provided in this example of creating a transaction is from a high level and is not an accurate representation of transactions in practical blockchain applications. A detailed explanation of transactions is outside the scope of this chapter, but it will be covered in Chapter 5, Cryptocurrency.

传输交易

一旦用户已经签署了资产转移信息,就提供其他信息,例如用于验证的用户公钥、目的地公共地址以及验证所需的其他信息。该信息被广播到所有节点,以便它可以被包括在区块链中:

图 3.4:交易中的信息

如前面的图 3.4 所示,Alice 将在事务中包含她的公钥和 Bob 的地址等信息。一旦交易被列入区块链的任何区块,交易中提供的信息应足以让 Bob 声称该资产属于他。

节点的公共地址充当节点的标识符,并且是通过执行散列和编码从公共密钥构建的。第 5 章加密货币,将对此进行详细介绍。

要求资产

当资产与交易所需的信息一起转移和传输时,区块链网络将确保在验证交易及其所在的区块后,将其纳入区块链。当交易包含在区块链中时,每个人都可以看到该交易,但只有接收该交易的所有者才能要求资产:

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

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

前面的图 3.5 显示,当 Bob 的节点认识到已经发生了一个资产已经转移到他的地址的交易时,他试图用交易中提供的公共信息来验证该交易。一旦他验证了 Alice 已经创建了一个有效的事务,他就可以通过提供他的私钥来对该资产执行任何操作,从而证明该资产属于他。这就是数字签名如何通过创建交易来确保资产可以被容易地转移,以及区块链如何确保交易在整个分散网络中的分布。

区块链钱包

区块链钱包是一种保存特定用户所有私人密钥的软件。虽然实体钱包持有现金,但区块链钱包持有用户拥有的所有私钥,这将有助于用户认领属于他们的资产。

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

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

非确定性钱包是随机创建的私有密钥的集合,这些密钥彼此之间没有关系。用这些钱包创建的私钥很难维护,因为在丢失的情况下很难重建密钥。因此,钱包中的每一把钥匙都必须备份,以防止钱包故障时可能发生的任何丢失。

一个确定性钱包也被称为种子钱包,因为这个钱包中的所有密钥都来自一个种子。所有的密钥都可以很容易地通过访问种子来复制。简单确定性钱包中的所有密钥都是通过散列一个字符串和一个增量 nonce 创建的。在钱包故障的情况下,仅种子信息就足以恢复私钥,因此不需要备份钱包中的所有密钥。

摘要

在这一章中,我们已经通过识别它在基本区块链体系结构中的应用,介绍了区块链加密技术的基础。这将为我们在后面的章节中讨论的更高级的区块链主题打下基础。

现在我们已经了解了以密码学为基础的基本区块链架构,是时候转向区块链技术的对应部分了——分散式网络。这将在下一章讨论,它将帮助我们理解区块链在不可信网络中的使用。


我们一直在努力

apachecn/AiLearning

【布客】中文翻译组