PHP也能實現(xiàn)區(qū)塊鏈?基礎(chǔ)結(jié)構(gòu)篇
引言
什么是區(qū)塊鏈?官方的解釋是:區(qū)塊鏈?zhǔn)且粋€分布式記賬系統(tǒng),是藉用密碼學(xué)串接并保護其內(nèi)容的串連交易記錄(又稱區(qū)塊)。每一個區(qū)塊包含了前一個區(qū)塊的加密散列、對應(yīng)的時間戳記以及交易數(shù)據(jù)(通常用默克爾樹算法計算的散列值表示),這樣的設(shè)計使得區(qū)塊內(nèi)容具有難以被竄改的特性。用區(qū)塊鏈所串接的分布式賬本能讓兩方有效率地紀(jì)錄交易,且此交易可永久被查驗。
但這個解釋對于初學(xué)者來說太抽象了,所以接下來我們將會使用PHP來實現(xiàn)一個簡易的區(qū)塊鏈來加深對區(qū)塊鏈的理解。
區(qū)塊
大家應(yīng)該玩過成語接龍,規(guī)則是這樣:我先說一個成語“人上人海”,下一個玩家需要使用我說的成語的最后一個字作為下一個成語的開頭,就是說需要使用“海”這個字作為新成語的開頭,這時就可以接一個“海闊天空”。
而區(qū)塊鏈的形式有點像成語接龍,就是下一個區(qū)塊必須使用上一個區(qū)塊的Hash值作為憑據(jù)來生成下一個區(qū)塊。如下圖:
這樣做的好處是:從任意一個區(qū)塊開始都可以通過前一個區(qū)塊的Hash值可以不斷的追溯整條區(qū)塊鏈,直到創(chuàng)世區(qū)塊(也就是區(qū)塊鏈的第一個區(qū)塊)。如果有人惡意攻擊,也必須更改整條區(qū)塊鏈的數(shù)據(jù)。但是計算Hash值是一個耗時的操作,所以要更改整條區(qū)塊鏈的數(shù)據(jù)基本是不可能達到,這就保證了區(qū)塊鏈的安全性。
下面我們使用PHP代碼來定義區(qū)塊:
- <php
- class Block {
- public $prevHash;
- public $hash;
- public $timeStamp;
- public $data;
- }
字段 | 解釋 |
prevHash |
前一個區(qū)塊的Hash值 |
hash | 當(dāng)前區(qū)塊的Hash值 |
timeStamp | 區(qū)塊生成的時間戳 |
data | 區(qū)塊保存的數(shù)據(jù) |
prevHash、hash和timeStamp這幾個字段在區(qū)塊鏈中被稱為區(qū)塊頭,區(qū)塊的Hash值使用SHA-256算法計算。計算方法如下:
- <php
- class Block {
- ...
- public function setBlockHash() {
- $data = serialize($this);
- $this->hash = hash('sha256', $data);
- }
- }
首先我們使用serialize()函數(shù)把整個區(qū)塊序列化,然后使用hash()函數(shù)計算區(qū)塊的Hash值,并賦值給hash字段。
區(qū)塊對象的構(gòu)造函數(shù)如下:
- <php
- class Block {
- ...
- public function __construct($prevHash, $data) {
- $this->prevHash = $prevHash;
- $this->timeStamp = time();
- $this->data = $data;
- $this->setBlockHash();
- }
- }
另外我們提供一個獲取區(qū)塊Hash值的方法:
- <?php
- class Block
- {
- ...
- public function getBlockHash()
- {
- return $this->hash;
- }
- }
區(qū)塊鏈
前面說了,區(qū)塊鏈就是按照一定的規(guī)則連接起來的區(qū)塊,連接的規(guī)則就是下一個區(qū)塊的區(qū)塊頭中必須包含前一個區(qū)塊的Hash值。我們編寫一個區(qū)塊鏈對象來保存整條區(qū)塊鏈,代碼如下:
- <?php
- include('block.php');
- class Blockchain
- {
- public $blocks = [];
- }
區(qū)塊鏈對象內(nèi)部使用了一個數(shù)組來保存所有的區(qū)塊,現(xiàn)階段我們還沒有使用到數(shù)據(jù)庫來保存區(qū)塊鏈,所以現(xiàn)在只需要把區(qū)塊鏈保存在內(nèi)存即可。
向區(qū)塊鏈添加一個新的區(qū)塊代碼如下:
- <?php
- include('block.php');
- class Blockchain
- {
- ...
- public function addBlock($data)
- {
- $prevBlock = $this->blocks[count($this->blocks)-1];
- $this->blocks[] = new Block($prevBlock->getBlockHash(), $data);
- }
- }
因為生成新區(qū)塊必須包含前一個區(qū)塊的Hash值,所以在添加新區(qū)塊時需要獲取區(qū)塊鏈中最后一個區(qū)塊作為新區(qū)塊的前一個區(qū)塊,然后把前一個區(qū)塊的Hash包含到新區(qū)塊的區(qū)塊頭中。
可能聰明的讀者會發(fā)現(xiàn),在區(qū)塊鏈剛創(chuàng)建時并沒有任何區(qū)塊,那么添加新區(qū)塊時拿哪個區(qū)塊作為前一個區(qū)塊呢?答案就是創(chuàng)世區(qū)塊。創(chuàng)世區(qū)塊不用包含前一個區(qū)塊的Hash值,而且隨著區(qū)塊鏈的創(chuàng)建被創(chuàng)建,代碼如下:
- <?php
- include('block.php');
- class Blockchain
- {
- ...
- public function __construct()
- {
- $this->blocks[] = new Block('', 'Genesis Block');
- }
- }
創(chuàng)世區(qū)塊并不需要包含前一個區(qū)塊的Hash值,所以在創(chuàng)建創(chuàng)世區(qū)塊時把前一個區(qū)塊的Hash值設(shè)置為空。
OK!我們的簡易區(qū)塊鏈已經(jīng)完成了,現(xiàn)在來測試一下我們的代碼吧:
- <?php
- include('blockchain.php');
- $bc = new Blockchain();
- $bc->addBlock('This is block1');
- $bc->addBlock('This is block2');
- foreach ($bc->blocks as $block) {
- printf("PrevHash: %s\n", $block->prevHash);
- printf("Hash: %s\n", $block->hash);
- printf("Data: %s\n", $block->data);
- printf("\n");
- }
我們來運行一下測試代碼,運行結(jié)果如下:
很好,結(jié)果符合我們的預(yù)期。
總結(jié)
本文只是實現(xiàn)了一個最簡易的區(qū)塊鏈,離完整的區(qū)塊鏈還有非常遠的距離。在我們現(xiàn)在的實現(xiàn)中存在很多不足,如:添加一個區(qū)塊的成本很低,沒有實現(xiàn)分布式,不能保存到本地磁盤(重啟機器數(shù)據(jù)就會丟失)等,接下來的教程將會不斷完善這些問題。