# Block-chain **Repository Path**: dafeigy/block-chain ## Basic Information - **Project Name**: Block-chain - **Description**: 区块链基本内容的python仿真,主要为感受学习使用。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2021-09-18 - **Last Updated**: 2025-09-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 区块链技术的基本原理以及简单应用 2008年11月1日,一位自称中本聪(Satoshi Nakamoto)的人发表了《比特币:一种点对点的电子现金系统》一文,阐述了基于P2P网络技术、加密技术、时间戳技术、区块链技术等的电子现金系统的构架理念,这标志着比特币的诞生。两个月后理论步入实践,2009年1月3日第一个序号为0的创世区块诞生。几天后2009年1月9日出现序号为1的区块,并与序号为0的创世区块相连接形成了链,标志着区块链的诞生。下面将阐述区块链的基本原理与功能并用代码实现区块链仿真以及一个简单的应用,同时讨论它的安全性与可靠性。 ## 区块链技术的原理 ### 问题的提出 区块链技术源于中本聪提出的一个问题:**能否搭建一种基于密码学原理而非信用的电子现金系统,使得交易双方不需要通过第三方或其他金融机构?** 这个问题在现实中就是:能否实现一种没有第三方监管,但又能保证每个人都不会造假的交易方案?在日常生活中常见的网络支付手段是先从端发起通过支付软件查询对应的人民银行(第三方)账户检验账户余额是否充足,随后在这个账户中记录这笔钱的去向。当每笔交易都被双方正确记录下时,我们就认为这是一个可靠可行的支付系统。这个系统可行的前提是,我们相信从软件到人民银行这个过程中,所有数据都是不会被修改的。如果没有这些可信任的第三方,我们又如何完成一笔交易? ### 一个解决方案 中本聪的提出的解决方法是构建一个去中心化的网络,即大家一起记账。系统中每产生一笔交易就要广播一次,目的是让系统中的每个人知道发生了一笔交易。交易会被记录在一个”账本“里,也就是“区块”,当区块连接起来后就成为了区块链。区块的结构组成如下表格所示: | 大小 | 字段 | 描述 | | --------------- | ---------- | -------------------- | | 4 Bytes | 区块大小 | 用字节表示区块大小 | | 80 Bytes | 区块头 | 组成区块头的大小 | | 1-9(可变整数) | 交易计数器 | 交易数量 | | / | 交易 | 记录区块链的交易消息 | 在这个系统中每一个"人"都拿着一个相同且实时更新的”账本“。显然账本的可靠性奠定了支付的安全性,因此接下来有两个新的问题: * 谁去记账? * 如何保证账本没有造假? ### 共识机制&工作量证明的引入 假设系统中每一个人都参与记账,那么会出现大家记录的交易信息以及记录的时间都不一样,这导致了账本无法被认可的问题,所以我们需要推选出一个可以承担”记账人“角色的人完成这一工作,也就是"**共识机制(Consensus Mechanism)**"。中本聪提出的解决方法是做题:"每一个区块前面加入一个随机数,然后进行散列函数运算(如SHA256),但只接受前面有若干个(Nonce)0的结果。通过改变随机数使得前面的0的个数等于Nonce,那么就完成一次记账的工作"。 实际中的区块的哈希值是由区块头数据进行二次SHA256生成的,即: ``Hash=SHA256(SHA256(blockHeader))`` 比特币中的区块头包含了如下信息: | 大小 | 字段 | 描述 | | -------- | ------------ | ------------------------------------------ | | 4 Bytes | 版本 | 版本号,追踪区块链版本 | | 32 Bytes | 父区块哈希值 | 引用区块链中父区块的哈希值 | | 32 Bytes | Merkel根 | 该区块中交易的Merkel树根的哈希值 | | 4 Bytes | 时间戳 | 该区块产生的近似时间(精确到秒的Unix时间戳) | | 4 Bytes | 难度目标 | 该区块工作量证明算法的难度目标 | | 4 Bytes | Nonce | 用于工作量证明算法的计数器 | 由于SHA256单向散列函数的特性,只能通过暴力运算才能试出满足条件的随机数值,暴力运算所消耗的资源(计算机的算力、电力等)被称为"**工作量证明(Proof-of-Work)**"。当某一个人先试验出来这个随机数后,便会向系统其他人广播自己运算的结果。其他人可以马上拿到他算出的结果进行检验,并保存下来,这样就保证了系统中每一个人都能拿到实时最新的账本。为了鼓励记账,系统会对第一个完成运算的实施奖励(比特币)以鼓动记账的积极性。SHA256的实现原理如下图所示: image-20210625165355912 ### 解决造假的问题。 规定在区块头里加入上一个区块的哈希值,也称为哈希指针,通过哈希指针可以让每一个区块首尾相连直到第一个区块(创世区块)。如果改变任意一个区块里的数据,对应的哈希值就会改变,当哈希指针无法与上一个区块的哈希值对应时,该区块往后的区块就失效了,因此要重新计算往后区块的哈希值,直到修改完这个区块后的所有区块。 ### 原理框图 下图展示的是当前的比特币区块链的组成结构以及运行原理图: ## 区块链的基本模块实现 上文讨论了区块链的基本原理,下文即用代码实现区块链各个模块以及功能。尽量对照中本聪原始论文进行模拟、设计编程。 ### 交易 我们定义,一枚电子货币(an electronic coin)是这样的一串数字签名:每一位所有者通过对前一次交易和下一位拥有者的公钥(Public key) 签署一个随机散列的数字签名,并将这个签名附加在这枚电子货币的末尾,电子货币就发送给了下一位所有者。而收款人通过对签名进行检验,就能够验证该链条的所有者。在程序实现中,我们采用`json`数据进行交易信息的存储。之所以应用json,是因为它的通用性和高效解析性,方便程序运算。 ### 时间戳服务器/区块的类 原始论文中指出,”时间戳服务器通过对以区块(block)形式存在的一组数据实施随机散列而加上时间戳,并将该随机散列进行广播,就像在新闻或世界性新闻组网络(Usenet)的发帖一样......每个时间戳应当将前一个时间戳纳入其随机散列值中,每一个随后的时间戳都对之前的一个时间戳进行增强(reinforcing),这样就形成了一个链条(Chain)。“因此我们可以将区块类的属性简化成如下模型: ```python class Block: def __init__(self, index, timestamp, transactions, previous_proof, previous_hash): # 一个区块包含的基本元素(索引,时间戳,工作量证明,交易信息,前一个区块的哈希值) self.index = index self.timestamp = timestamp self.transactions = transactions self.previous_hash = previous_hash self.proof = self.proof_of_work(previous_proof) self.hash = self.hash_block() ``` ### 工作量证明函数 工作量证明本质上是一个计数器,在中本聪的论文中,他提到的方法是寻找满足条件的随机数。在这里无意检验算SHA256的运算难度,仅仅观察系统运行性能,因此更改了工作量证明函数,即不断寻找能同时被9和上一个符合的随机数整除的数。代码如下所示: ```python def proof_of_work(self, previous_proof): # index为0表明为创世区块,工作量证明设置为0 if self.index == 0: incrementor = 0 return incrementor # 非创世区块工作量证明计算 if previous_proof == 0: # 避免除0出错的问题 previous_proof = 1 incrementor = previous_proof + 1 while not (incrementor % 9 == 0 and incrementor % previous_proof == 0): incrementor += 1 return incrementor ``` 如果执意要使用中本聪的方案,下面也有实现方法,但不建议在模拟使用: ```python def proof_of_work(self, previous_proof): # index为0表明为创世区块,工作量证明设置为0 if self.index == 0: incrementor = 0 return incrementor # 非创世区块工作量证明计算 if previous_proof == 0: # 避免除0出错的问题 previous_proof = 1 incrementor = previous_proof + 1 while not (sha256(str(incrementor)+transactions) [0:diff]!= diff * '0':#diff是设定的难度,现行比特币一般取72 incrementor += 1 return incrementor ``` ### 哈希值的生成 这里为了简化了一下运算难度,仅进行一次SHA256运算。 ```python def hash_block(self): sha = hashlib.sha256() sha.update((str(self.index) + str(self.timestamp) + str(self.transactions) + str(self.previous_hash) + str( self.proof)).encode('utf-8')) return sha.hexdigest() ``` ### 创世区块的生成 创世区块是最特殊的一个区块。我们仅需规定它的时间戳属性即可,其他的函数已经完成了它的其他属性计算。 ```python def create_genesis_block(): # index为0,时间为生成该区块的时间,工作量证明为0,交易信息为空,哈希值为0 return Block(0, date.datetime.now(), [], 0, "0") ``` ### 区块链中添加区块 这一步的函数只完成了添加的操作,本质上是列表的添加。 ```python def add_block_to_blockchain(blockchain, new_transactions): # 新区块各个要素的值 last_block = blockchain[-1] this_index = last_block.index + 1 this_timestamp = date.datetime.now() current_transactions = new_transactions previous_proof = last_block.proof previous_hash = last_block.hash # 生成新块并添加到链 blockchain.append(Block(this_index, this_timestamp, current_transactions, previous_proof, previous_hash)) return blockchain ``` 在实际应用中,可以增加条件判断来完成哈希指针的检验工作。至此为止,区块链所有的基本模块已经实现。更具有现实意义的、更复杂的如Merkel树、Merkel树根等属性在这里不再加以展开。 ### 网络节点 区块链网络工作的过程是: 1. 新的交易向全网进行广播; 2. 每一个节点(Node)都将收到的交易信息纳入一个区块中; 3. 每个节点都尝试在自己的区块中找到一个具有足够难度的工作量证明; 4. 当一个节点找到了一个工作量证明,它就向全网进行广播; 5. 当且仅当包含在该区块中的所有交易**都是有效的且之前未存在过的**,其他节点才认同该区块的有效性; 6. 其他节点表示它们接受该区块,表示接受的方法则是:在跟随该区块的末尾,制造新的区块以延长该链条,而将被接受区块的随机哈希值视为先于新区快的随机哈希值。 在区块链的运作过程所,每一个节点在争取“记账”的权力——有共识的区块,因此节点们都在努力算,首先算出来的可被接受。 在我们的程序实现中,我们可以采用Flask或Django等Web框架进行仿真模拟各个节点。 ## 区块链的简单应用 ### 应用场景 由于区块链具有的难以篡改的特性,因此用于记录一些重要的但数量多的数据有较大优势。在区块链中,传递的数据是`json`文件,在网页端中传递数据通常都会使用到它。引入一个场景:在当今自媒体呈爆炸式增长的时代,每个人获取的信息也越来越多,其中有很多信息存在时效性以及疑义性,二者不可兼得。目前的情况是由于碎片化的时间,很少有人会去追溯新闻事件的发展,因此造成了信息传播的误差。在这里我们试图利用区块链技术完成一个可溯的新闻客户端,持续追踪新闻信息的发展与变动。 ### 实现方法 利用Flask框架搭建一个Web测试平台,观察区块链的实现。通过添加节点、以及模块功能分离,可以模拟节点运作。为了节约前端开发时间,故不进行前端页面的渲染,使用Postman作为简单的交互工具进行区块链系统中的操作模拟。在这之前,我们要先获取合适的数据。通过对[后续](houxv.app)网站爬虫,我们可以得到`json.json`中的数据: ![image-20210625192801969](https://i.loli.net/2021/06/25/FfMlk6P9gUbcIWa.png) 这些数据可以作为一个交易,流通在网络中。为更好模拟,我们采取功能分离的方法,设置三个节点完成对应的操作: * 挖矿节点`Mine` * 交易放入节点`Transaction` * 广播节点`chain` 挖矿节点用于调用工作量证明函数,交易放入节点只是提供POST方法接口,放入新的交易,广播节点用于观察数据变化。 代码如下: ```python # 实例化节点(创建一个节点) node = Flask(__name__) # 为该节点生成一个全局唯一的地址(为节点创建一个随机的名字) node_identifier = str(uuid4()).replace('-', '') # 查看运行信息 @node.route('/') def run_message(): return 'Hello! Runing on http://127.0.0.1:5000/' # 挖矿 @node.route('/mine', methods=['GET']) def mine(): # 获取交易信息的一个拷贝(非引用),避免“广播”错误,否则仅发送交易信息,就直接导致原有区块交易信息的改变 # 只有在挖矿的时候,才会将new_transaction信息加入到新块中 new_transactions = this_node_transactions.copy() add_block_to_blockchain(blockchain, new_transactions) mined_block = blockchain[-1] # # 添加到此处 del (this_node_transactions[:]) return jsonify([{ "index": mined_block.index, "timestamp": str(mined_block.timestamp), "transactions": mined_block.transactions, "proof": mined_block.proof, "hash": mined_block.hash }]) # 查看区块链 @node.route('/chain', methods=['GET']) def full_chain(): response = [] for block in blockchain: block_index = str(block.index) block_timestamp = str(block.timestamp) block_transactions = str(block.transactions) block_proof = str(block.proof) block_hash = block.hash block = { "index": block_index, "timestamp": block_timestamp, "transactions": block_transactions, "proof": block_proof, "hash": block_hash } response.append(block) response.append({'len': len(blockchain)}) return jsonify(response) # 发送交易 @node.route('/transaction', methods=['POST']) def transaction(): # if request.method == 'POST': # 检查所需的字段是否在POST数据中 values = request.get_json() required = ['id', 'type', 'object','publish_at','object_key','title','summary'] if not all(k in values for k in required): return 'Missing values', 400 # Then we add the transaction to our list this_node_transactions.append(values) # Because the transaction was successfully # submitted, we log it to our console print("New transaction") print("type: {}".format(values['type'])) print("object: {}".format(values['object'])) print("publish_at: {}".format(values['publish_at'])) print("object_key: {}".format(values['object_key'])) print("title: {}".format(values['title'])) print("summary: {}".format(values['summary'])) return "Transaction submission successful\n" ``` ### 实现效果 由于没有编写前端页面,故采用POSTMAN观察数据交互过程: #### 初始化 在打开http://127.0.0.1:5000/chain 并点击Send后,得到如下结果: ![image-20210625193627211](https://i.loli.net/2021/06/25/NQ2Oeiv1In3tBjA.png) 可以清楚看到创世区块的建立时间,以及交易部分的空白。 #### 放入交易 当在`transaction`节点中放入一笔交易后(使用Post方法在transaction上传一段json数据),结果如图所示: ![image-20210625193920139](https://i.loli.net/2021/06/25/qyJjXxwnQvFDcPW.png) 返回结果显示”交易“成功。观察刚刚的广播节点: image-20210625194104079 没有观察到"交易"的产生,因为还没有人“挖矿”。 #### 挖矿并广播 在`mine`节点中使用GET方法,完成广播工作: image-20210625194642407 回到广播节点页,观察此时的数据: image-20210625194808133 可以观察到此时的`Transaction`已经不是空数组了,同时还记录了挖矿完成的时间。放入一笔交易对同一节点重复多次挖矿操作后再操作,观察时间消耗: ![image-20210625195249084](https://i.loli.net/2021/06/25/8dtEQsLnqFzBKvC.png) 在后台中的记录: image-20210625195816783 通过简单的Timestamp相减计算,可以观察到计算的时间在成倍的增加,而工作量证明的数据也正如我们所设的那般指数增加,满足如下数学关系: $$ proof=9\times2^{n-1} $$ ## 区块链安全性的讨论 从上面的仿真应用中我们可以看出,运算的难度其实是随着所设的工作量证明函数有关的。因此设计合理的工作量证明函数在区块链技术中非常重要。设想如下场景: *“一个攻击者试图比诚实节点产生链条更快地制造替代性区块链。即便它达到了这一目的,但是整个系统也并非就此完全受制于攻击者的独断意志了,比方说凭空创造价值,或者掠夺本不属于攻击者的货币。这是因为节点将不会接受无效的交易,而诚实的节点永远不会接受一个包含了无效信息的区块。一个攻击者能做的,最多是更改他自己的交易信息,并试图拿回他刚刚付给别人的钱。”* 要篡改区块链,攻击节点就要比诚信节点更多地挖出区块,让自己的虚假链生产速度大于诚信节点的生产速度。我们可以简化一下这个数学问题: > 假设攻击者制造出下一区块的成功率$p$,诚信节点制造下一个区块的成功率为$q$,$q_z$为消除了$z$个差距的成功率,则有 > $$ > q_z=\begin {cases} > {1,q\geq p\\ > (\frac{p}{q} )^z,q \end {cases} > $$ > 假如攻击者算力不如诚实节点算力,那么它攻破这条链的成功几率就随着时间呈指数下降越来越小。现实中我们的交易需要一个确认时间,在这种情况下,运气好的攻击者在等待的时间攻破了这条长链的话,可以将准备好的假链接上在区块链中,从而攻破区块链,因此要设定一个合适的等待时间。 > > 假设诚信节点的区块生产速度是恒定的一个定值$t$,而且它并不知道攻击者的运算进展,这样潜在攻击者的运算进展就成为一个泊松分布,分布期望为: > $$ > E(\lambda)=z ·\frac{q}{p} > $$ > 当此情形,为了计算攻击者追赶上的概率,将攻击者取得进展区块数量的泊松分布的概率密度,乘以在该数量下攻击者依然能够追赶上的概率。 > $$ > P(z)={\sum_{k!}^{\lambda^ke^{-\lambda}}· > \begin {cases} > (\frac{q}{p})^{z-k},k\leq z\\ > 1,k>z > \end {cases}}\\ > =1-\sum_{k=0}^{z}\frac{\lambda^k e^{- \lambda}}{k!}[1-(\frac{q}{p})^{z-k}] > $$ 编程对该函数求和,并计算取不同q、p计算z值。
image-20210625212342045
image-20210625212409923
image-20210625212746175
纵向比较可以观察到,不论等待时间取多大,在攻击成功概率为0.5时,都能成功篡改区块链,因此破坏区块链的条件是拥有超过全网50%的算力进行运算; 横向对比可以观察到,增加等待时间后,同等攻击成功概率的情况下,篡改区块链的成功率越来越低,接近为零,因此增加等待时间是一个有效的保护措施,但这个保护时间不宜太长,否则不利于交易池中放入新的交易。 ## 总结 通过python、Postman实现基本的区块链仿真以及简单应用,让我收获良多,其中区块链的去中心化思想、区块化、工作量证明机制非常吸引人,相信能在未来拥有更多的应用场景。本次编程考虑到了时间紧迫以及主力机性能,故没有完全复现中本聪所提出的其他机制如硬盘空间回收、简化支付确认、价值组合与分割以及隐私的实现,但在日后会完成进一步的应用实践与区块链的能力探索。