1. 程式人生 > >用Java建立你第一個區塊鏈-part1

用Java建立你第一個區塊鏈-part1

      比特幣是區塊鏈中最重要的一個應用,在比特幣大漲之後又出現了很多幣種,比如萊特幣、以太幣等等,更在2014年以太坊的出現,市面上出現了各種各樣的幣種以及ICO,在此先簡單介紹一下以太坊(Ethereum)並不是一個機構,而是一款能夠在區塊鏈上實現智慧合約、開源的底層系統,從資料資料中顯示,以太坊從誕生到2017年5月,全球已有了200多個以太坊應用的誕生。以太坊是一個平臺和一種程式語言,使開發人員能夠建立和釋出下一代分散式應用,包括建立各種加密貨幣、智慧合約、還有基於區塊鏈的“去中心化自治組織”,但是以太坊是基於go、python以及自制的一些語言,對於廣泛使用java語言的開發人員以及底層框架就是Java的系統而言,很難進行擴充套件和實際應用,所以我會一步一步的用Java語言幫助你建立屬於你的第一個區塊鏈應用。

      寫本系列教程的一個最終目的就是,幫助你實現區塊鏈技術,並希望你可以應用區塊鏈技術到特有的業務邏輯中去,因為除了已知的比特幣、以太坊,區塊鏈在非金融行業、產業鏈以及傳統金融行業中都可以廣泛的應用起來。所以在這個教程中將會實現:

      1、建立你第一個非常基本的區塊鏈

      2、實現一個簡單的工作量證明系統即挖礦

      3、在此基礎上實現各種可能性

      在此我必須要首先說明,這裡建立的區塊鏈並不是功能完全的完全適合應用與生產的區塊鏈,相反只是為了幫助你更好的理解區塊鏈的概念。事實上現在的區塊鏈技術也沒有非常的成熟,還屬於探索研究的階段,換個想法,這也代表了我們有很大的機遇不斷的去發展區塊鏈技術,並利用區塊鏈技術來顛覆我們現有的商業世界。

      第一步、建立區塊鏈

      區塊鏈就是一串或者是一系列區塊的集合,類似於連結串列的概念,每個區塊都指向於後面一個區塊,然後順序的連線在一起。那麼每個區塊中的內容是什麼呢?在區塊鏈中的每一個區塊都存放了很多很有價值的資訊,主要包括三個部分:自己的數字簽名,上一個區塊的數字簽名,還有一切需要加密的資料(這些資料在比特幣中就相當於是交易的資訊,它是加密貨幣的本質)。每個數字簽名不但證明了自己是特有的一個區塊,而且指向了前一個區塊的來源,讓所有的區塊在鏈條中可以串起來,而資料就是一些特定的資訊,你可以按照業務邏輯來儲存業務資料。

    這裡的hash指的就是數字簽名 。

    所以每一個區塊不僅包含前一個區塊的hash值,同時包含自身的一個hash值,自身的hash值是通過之前的hash值和資料date通過hash計算出來的。如果前一個區塊的資料一旦被篡改了,那麼前一個區塊的hash值也會同樣發生變化(因為資料也被計算在內),這樣也就導致了所有後續的區塊中的hash值。所以計算和比對hash值會讓我們檢查到當前的區塊鏈是否是有效的,也就避免了資料被惡意篡改的可能性,因為篡改資料就會改變hash值並破壞整個區塊鏈。

    就像上圖中看到了一個每一個區塊包含一個自身的hash(數字簽名),而變數PreviousHash是由前一個區塊的hash值和data資料進行hash計算獲得而來的。

     我定義的區塊鏈格式如下,只包含了最重要的一些資訊,你可以在此基礎上進行擴充套件。

public class Block {        public String hash;        public String previousHash;        private String data; //資料        private long timeStamp; //時間戳        //區塊建構函式        public Block(String data,String previousHash ) {                this.data = data;                this.previousHash = previousHash;                this.timeStamp = new Date().getTime();        }}

        第二步、建立數字簽名

        熟悉加密演算法的朋友們,Java方式可以實現的加密方式有很多,例如BASE、MD、RSA、SHA等等,我在這裡選用了SHA256這種加密方式,SHA(Secure Hash Algorithm)安全雜湊演算法,這種演算法的特點是資料的少量更改會在Hash值中產生不可預知的大量更改,hash值用作表示大量資料的固定大小的唯一值,而SHA256演算法的hash值大小為256位。之所以選用SHA256是因為它的大小正合適,一方面產生重複hash值的可能性很小,另一方面在區塊鏈實際應用過程中,有可能會產生大量的區塊,而使得資訊量很大,那麼256位的大小就比較恰當了。

      下面我建立了一個StringUtil方法來方便呼叫SHA256演算法。

public class StringUtil { //應用sha256演算法讓一個輸入轉變成256位的hash值 public static String applySha256(String input){ try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(input.getBytes("UTF-8")); StringBuffer hexString = new StringBuffer(); for (int i = 0; i < hash.length; i++) { String hex = Integer.toHexString(0xff & hash[i]); if(hex.length() == 1) hexString.append('0'); hexString.append(hex); } return hexString.toString(); } catch(Exception e) { throw new RuntimeException(e); } } }

      或許你完全不理解上述程式碼的含義,但是你只要理解所有的輸入呼叫此方法後均會生成一個獨一無二的hash值(數字簽名),而這個hash值在區塊鏈中是非常重要的。

       接下來讓我們在Block類中應用 方法 applySha256 方法,其主要的目的就是計算hash值,我們計算的hash值應該包括區塊中所有我們不希望被惡意篡改的資料,在我們上面所列的Block類中就一定包括previousHash,data和timeStamp,

   public String calculateHash() {         String calculatedhash = StringUtil.applySha256( previousHash + Long.toString(timeStamp) + data );         return calculatedhash;        }

     然後把這個方法加入到Block的建構函式中去。

        public Block(String data,String previousHash ) { this.data = data; this.previousHash = previousHash; this.timeStamp = new Date().getTime(); this.hash = calculateHash(); //確保hash值的來源 }

       第三步、是時候進行一下測試了

       在主方法中讓我們建立一些區塊,並把其hash值打印出來,來看看是否一切都在我們的掌控中。

       就在我的“10分鐘理解區塊鏈究竟是什麼”中提到的,區塊鏈中的第一個區塊我們稱之為創世紀區塊,因為它是頭區塊,所以previousHash值我們設定為0。

public class NoobChain { public static void main(String[] args) {

                //創世紀區塊

Block genesisBlock = new Block("Hi im the first block", "0"); System.out.println("Hash for block 1 : " + genesisBlock.hash);

                //第二個區塊,連結在創世紀區塊之後

Block secondBlock = new Block("Yo im the second block",genesisBlock.hash); System.out.println("Hash for block 2 : " + secondBlock.hash);  //第三個區塊,連結在第二個區塊之後 Block thirdBlock = new Block("Hey im the third block",secondBlock.hash); System.out.println("Hash for block 3 : " + thirdBlock.hash); }}

       輸出結果類似於下面


      每一個區塊都必須要有自己的資料簽名即hash值,這個hash值依賴於自身的資訊(data)和上一個區塊的數字簽名(previousHash),但這個還不是區塊鏈,下面讓我們儲存區塊到陣列中,這裡我會引入gson包,目的是可以用json方式檢視整個一條區塊鏈結構。

import java.util.ArrayList;import com.google.gson.GsonBuilder;public class NoobChain { public static ArrayList<Block> blockchain = new ArrayList<Block>(); public static void main(String[] args) { //增加區塊到陣列中去 blockchain.add(new Block("Hi im the first block", "0")); blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash)); blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash)); String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain); System.out.println(blockchainJson); }}

這樣的輸出結構就更類似於我們所期待的區塊鏈的樣子。

        第四步、檢查區塊鏈的完整性。

        在主方法中增加一個isChainValid()方法,目的是迴圈區塊鏈中的所有區塊並且比較hash值,這個方法用來檢查hash值是否是於計算出來的hash值相等,同時previousHash值是否和前一個區塊的hash值相等。或許你會產生如下的疑問,我們就在一個主函式中建立區塊鏈中的區塊,所以不存在被修改的可能性,但是你要注意的是,區塊鏈中的一個核心概念就是去中心化,每一個區塊可能是在網路中的某一個節點中產生的,所以很有可能某個節點把自己節點中的資料修改了,那麼根據上述的理論資料改變會導致整個區塊鏈的破裂,也就是區塊鏈就無效了。

public static Boolean isChainValid() { Block currentBlock; Block previousBlock; //迴圈遍歷區塊鏈來檢查hash值 for(int i=1; i < blockchain.size(); i++) { currentBlock = blockchain.get(i); previousBlock = blockchain.get(i-1); //比對hash值和計算的hash值 if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){ System.out.println("Current Hashes not equal"); return false; } //比對前一個區塊的hash值和previousHash值 if(!previousBlock.hash.equals(currentBlock.previousHash) ) { System.out.println("Previous Hashes not equal"); return false; } } return true;}

      任何區塊鏈中區塊的一絲一毫改變都會導致這個函式返回false,也就證明了區塊鏈無效了。

      在比特幣網路中所有的網路節點都分享了它們各自的區塊鏈,然而最長的有效區塊鏈是被全網所統一承認的,如果有人惡意來篡改之前的資料,然後建立一條更長的區塊鏈並全網釋出呈現在網路中,我們該怎麼辦呢?這就涉及到了區塊鏈中另外一個重要的概念工作量證明,這裡就不得不提及一下hashcash,這個概念最早來自於Adam Back的一篇論文,主要應用於郵件過濾和比特幣中防止雙重支付。

      hashcash其本質就是一個數學難題,希望你可以做大量的工作,也就是付出cpu的計算能力得到正確的結果才能夠獲取某些資源(比如往你的郵箱中傳送垃圾郵件),郵件過濾正是基於這樣的原理,我們設定一個規則,所有想給我傳送電子郵件的人,我都要求他滿足一個計算結果才會接受,要滿足這個計算結果必須付出cpu的計算代價。即使一次計算只需要幾秒鐘,對於垃圾郵件的系統來說都是致命的,因為這些系統每天要傳送數以萬計的垃圾郵件,多出的cpu時間對它們來說代價是非常大的。

      我們在這裡實現了hashcash的理論,也就是為了建立一個新的區塊必須花費大量的時間和計算能力,所以攻擊者想要篡改資料那麼他必須比其他節點擁有更多的計算能力。

      第五步、挖礦

      這裡我們要求挖礦者做工作量證明,具體的方式是在區塊中嘗試不同的引數值直到它的hash值是從一系列的0開始的。下面我們在Block類中增加了一個隨機數nonce,在計算hash值中也把nonce計算在內。

import java.util.Date;public class Block { public String hash; public String previousHash; private String data; //資料 private long timeStamp; //時間戳 private int nonce;//增加一個隨機數 //建構函式 public Block(String data,String previousHash ) { this.data = data; this.previousHash = previousHash; this.timeStamp = new Date().getTime(); this.hash = calculateHash(); } //計算hash值(把新增的隨機數也計算在內) public String calculateHash() { String calculatedhash = StringUtil.applySha256( previousHash + Long.toString(timeStamp) + Integer.toString(nonce) + data ); return calculatedhash; } public void mineBlock(int difficulty) {

                //建立一個string值由難度的位數來決定

String target = new String(new char[difficulty]).replace('\0', '0'); while(!hash.substring( 0, difficulty).equals(target)) { nonce ++; hash = calculateHash(); } System.out.println("Block Mined!!! : " + hash); }}

mineBlock()方法中引入了一個int值稱為difficulty難度,低的難度比如1和2,普通的電腦基本都可以馬上計算出來,我的建議是在4-6之間進行測試,普通電腦大概會花費3秒時間,在萊特幣中難度大概圍繞在442592左右,而在比特幣中每一次挖礦都要求大概在10分鐘左右,當然根據所有網路中的計算能力,難度也會不斷的進行修改。

我們在主方法中增加difficulty這個靜態變數。

         public static int difficulty =5;

這樣我們必須修改主方法中讓建立每個新區塊時必須觸發mineBlock()方法,而isChainValid()方法用來檢查每個區塊的hash值是否正確,整個區塊鏈是否是有效的。

import java.util.ArrayList;import com.google.gson.GsonBuilder;public class NoobChain { public static ArrayList<Block> blockchain = new ArrayList<Block>(); public static int difficulty = 5; public static void main(String[] args) { //把區塊增加到陣列中去 blockchain.add(new Block("Hi im the first block", "0")); System.out.println("Trying to Mine block 1... "); blockchain.get(0).mineBlock(difficulty); blockchain.add(new Block("Yo im the second block",blockchain.get(blockchain.size()-1).hash)); System.out.println("Trying to Mine block 2... "); blockchain.get(1).mineBlock(difficulty); blockchain.add(new Block("Hey im the third block",blockchain.get(blockchain.size()-1).hash)); System.out.println("Trying to Mine block 3... "); blockchain.get(2).mineBlock(difficulty); System.out.println("\nBlockchain is Valid: " + isChainValid()); String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain); System.out.println("\nThe block chain: "); System.out.println(blockchainJson); } public static Boolean isChainValid() { Block currentBlock; Block previousBlock; String hashTarget = new String(new char[difficulty]).replace('\0', '0'); for(int i=1; i < blockchain.size(); i++) { currentBlock = blockchain.get(i); previousBlock = blockchain.get(i-1); if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){ System.out.println("Current Hashes not equal"); return false; } if(!previousBlock.hash.equals(currentBlock.previousHash) ) { System.out.println("Previous Hashes not equal"); return false; } //增加hash值是否已經計算過 if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) { System.out.println("This block hasn't been mined"); return false; } } return true; }}         執行此主函式可以得到以下的輸出結果。


       經過測試增加一個新的區塊即挖礦必須花費一定時間,大概是3秒左右,你可以提高difficulty難度來看,它是如何影響資料難題所花費的時間的。

        如果有人在你的區塊鏈系統中惡意篡改資料:

        1、他們的區塊鏈是無效的。

        2、他們無法建立更長的區塊鏈

        3、網路中誠實的區塊鏈會在長鏈中更有時間的優勢

        因為篡改的區塊鏈將無法趕上長鏈和有效鏈,除非他們比你網路中所有的節點擁有更大的計算速度,可能是未來的量子計算機或者是其他什麼。

        由此你的第一個區塊鏈已經建立成功了

總結一下:你的第一個區塊鏈實現的功能包括

        1、有很多區塊組成用來儲存資料

        2、有數字簽名讓你的區塊鏈連結在一起

        3、需要挖礦的工作量證明新的區塊

        4、可以用來檢查資料是否是有效的同時是未經篡改的

        在下一個部分,我會講述交易、數字簽名以及錢包等概念,請持續關注。