1. 程式人生 > >初識區塊鏈——用JS構建你自己的區塊鏈

初識區塊鏈——用JS構建你自己的區塊鏈

前言

區塊鏈太複雜,那我們就講點簡單的。用JS來構建你自己的區塊鏈系統,寥寥幾行程式碼就可以說明區塊鏈的底層資料結構、POW挖礦思想和交易過程等。當然了,真實的場景遠遠遠比這複雜。本文的目的僅限於讓大家初步瞭解、初步認識區塊鏈。 文章內容主要參考視訊:Building a blockchain with Javascript (https://www.youtube.com/playlist?list=PLzvRQMJ9HDiTqZmbtFisdXFxul5k0F-Q4) 感謝原作者,本文在原視訊基礎上做了修改補充,並加入了個人理解。

認識區塊鏈

區塊鏈顧名思義是由區塊連線而成的鏈,因此最基本的資料結構是Block。每個Block都含有timestamp、data、hash、previousHash等資訊。其中data用來儲存資料,previousHash是前一個區塊的hash值。示意如下: 1543399023366-4e873559-f595-4687-a01d-75
hash是對區塊資訊的摘要儲存,hash的好處是任意長度的資訊經過hash都可以對映成固定長度的字串,如可用sha256:
calculateHash() {
    return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString(); } 

Block的資料結構

Block的最基本資料結構如下:
class Block { constructor(timestamp, data, previousHash = '') { this.timestamp = timestamp; this.data = data; this.previousHash = previousHash; //對hash的計算必須放在最後,保證所有資料賦值正確後再計算 this.hash = this.calculateHash(); } calculateHash() { return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString(); } } 

BlockChain的資料結構

多個Block連結而成BlockChain,顯然可用用陣列或連結串列來表示,如:
class BlockChain { constructor() { this.chain = []; } } 

創世區塊

正所謂萬物始於一,區塊鏈的第一個區塊總是需要人為來手動建立,這個區塊的previousHash為空,如:
createGenesisBlock() {
    return new Block("2018-11-11 00:00:00", "Genesis block of simple chain", ""); } 
區塊鏈的構造方法也應改為:
class BlockChain { constructor() { this.chain = [this.createGenesisBlock()]; } } 

新增區塊

每新加一個區塊,必須保證與原有區塊鏈連線起來,即:
class BlockChain { getLatestBlock() { return this.chain[this.chain.length - 1]; } addBlock(newBlock) { //新區塊的前一個hash值是現有區塊鏈的最後一個區塊的hash值; newBlock.previousHash = this.getLatestBlock().hash; //重新計算新區塊的hash值(因為指定了previousHash); newBlock.hash = newBlock.calculateHash(); //把新區塊加入到鏈中; this.chain.push(newBlock); } ... } 

校驗區塊鏈

區塊鏈資料結構的核心是保證前後連結、無法篡改,但是如果有人真的篡改了某個區塊,我們該如何校驗發現呢?最笨也是最自然是想法就是遍歷所有情況,逐一校驗,如:
isChainValid() {
    //遍歷所有區塊 for (let i = 1; i < this.chain.length; i++) { const currentBlock = this.chain[i]; const previousBlock = this.chain[i - 1]; //重新計算當前區塊的hash值,若發現hash值對不上,說明該區塊有資料被篡改,hash值未重新計算 if (currentBlock.hash !== currentBlock.calculateHash()) { console.error("hash not equal: " + JSON.stringify(currentBlock)); return false; } //判斷當前區塊的previousHash是否真的等於前一個區塊的hash,若不等,說明前一個區塊被篡改,雖然hash值被重新計算正確,但是後續區塊的hash值並未重新計算,導致整個鏈斷裂 if (currentBlock.previousHash !== previousBlock.calculateHash) { console.error("previous hash not right: " + JSON.stringify(currentBlock)); return false; } } return true; } 

Just run it

跑起來看看,即:
let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20})); console.log(JSON.stringify(simpleChain, null, 4)); console.log("is the chain valid? " + simpleChain.isChainValid()); 
結果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
{
    "chain": [ { "timestamp": "2018-11-11 00:00:00", "data": "Genesis block of simple chain", "previousHash": "", "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89" }, { "timestamp": "2018-11-11 00:00:01", "data": { "amount": 10 }, "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89", "hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529" }, { "timestamp": "2018-11-11 00:00:02", "data": { "amount": 20 }, "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529", "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34" } ] } is the chain valid? true 
注意看其中的previousHash與hash,確實是當前區塊的previousHash指向前一個區塊的hash。

篡改下試試

都說區塊鏈不可篡改,是真的嗎?讓我們篡改第2個區塊試試,如:
let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20})); console.log("is the chain valid? " + simpleChain.isChainValid()); //將第2個區塊的資料,由10改為15 simpleChain.chain[1].data = {amount: 15}; console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4)); 
結果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
is the chain valid? true hash not equal: {"timestamp":"2018-11-11 00:00:01","data":{"amount":15},"previousHash":"fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89","hash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529"} is the chain still valid? false { "chain": [ { "timestamp": "2018-11-11 00:00:00", "data": "Genesis block of simple chain", "previousHash": "", "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89" }, { "timestamp": "2018-11-11 00:00:01", "data": { "amount": 15 }, "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89", "hash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529" }, { "timestamp": "2018-11-11 00:00:02", "data": { "amount": 20 }, "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529", "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34" } ] } 
顯然,篡改了資料之後,hash值並未重新計算,導致該區塊的hash值對不上。

再篡改下試試

那麼,如果我們聰明點,篡改後把hash值也重新計算會如何?
let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20})); console.log("is the chain valid? " + simpleChain.isChainValid()); //篡改後重新計算hash值 simpleChain.chain[1].data = {amount: 15}; simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash(); console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4)); 
結果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js 
is the chain valid? true previous hash not right: {"timestamp":"2018-11-11 00:00:02","data":{"amount":20},"previousHash":"150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529","hash":"274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34"} is the chain still valid? false { "chain": [ { "timestamp": "2018-11-11 00:00:00", "data": "Genesis block of simple chain", "previousHash": "", "hash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89" }, { "timestamp": "2018-11-11 00:00:01", "data": { "amount": 15 }, "previousHash": "fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89", "hash": "74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1" }, { "timestamp": "2018-11-11 00:00:02", "data": { "amount": 20 }, "previousHash": "150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529", "hash": "274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34" } ] } 
顯然,第3個區塊的previousHash並未指向第2個區塊的hash。

是真的無法篡改嗎

其實並不是,如果我們再聰明一點,把後續區塊的hash值也重新計算一下,不就OK了嗎? 確實如此,如:
let simpleChain = new BlockChain(); simpleChain.addBlock(new Block("2018-11-11 00:00:01", {amount: 10})); simpleChain.addBlock(new Block("2018-11-11 00:00:02", {amount: 20})); console.log("is the chain valid? " + simpleChain.isChainValid()); //篡改第2個區塊 simpleChain.chain[1].data = {amount: 15}; simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash(); //並把第3個區塊也重新計算 simpleChain.chain[2].previousHash = simpleChain.chain[1].hash; simpleChain.chain[2].hash = simpleChain.chain[2].calculateHash(); console.log("is the chain still valid? " + simpleChain.isChainValid()); console.log(JSON.stringify(simpleChain, null, 4

 

原文連結
本文為雲棲社群原創內容,未經允許不得轉載。