用java實現一個簡單的區塊鏈

韓數發表於2019-11-27

前言:

之前其實一直有考慮要不要寫一篇這篇文章,因為類似的文章在網上實在太多了,而且只要你細讀幾篇會發現,很多文章除了內容都是出奇的雷同之外,大多數都是源於國外大神的兩篇關於java區塊鏈的教程的生硬翻譯,這就很容易導致一個問題,就是你明明把他程式碼跑起來了,最後卻還不知道區塊鏈是個啥,比如是如何做到去中心化的?又是如何做到不可篡改行的,以及比特幣為什麼越挖越少這些問題依然得不到很好的解釋,本篇文章呢,依然源用的是那篇教程的程式碼,除了英文註釋我會手動翻譯成中文之外,其他的變數名什麼的都不會改,畢竟人家思路是對的,沒有必要重新造輪子,改個變數就說程式碼是我自己寫的,那我和網上那些抄襲轉載的人也沒什麼區別了。本篇文章並不是對作者源論文的機械翻譯,只是借用了相關程式碼,希望大家看過之後會大致明白區塊鏈具體是一項什麼樣的技術,我們為什麼需要區塊鏈等等。

不說廢話,先上東西。

什麼是區塊鏈:

區塊鏈是分散式資料儲存、點對點傳輸、共識機制、加密演算法等計算機技術的新型應用模式。,區塊鏈是比特幣的一個重要概念,它本質上是一個去中心化的資料庫,同時作為比特幣的底層技術,是一串使用密碼學方法相關聯產生的資料塊,每一個資料塊中包含了一批次比特幣網路交易的資訊,用於驗證其資訊的有效性(防偽)和生成下一個區塊 - 百度百科。

??? 沒看懂,抬走下一個。

區塊鏈到底是什麼,區塊鏈就是談戀愛

還不明白,且看下面一個小故事,受限於篇幅,就不寫地花裡胡哨的了。

明天就是小明和小紅在一起100天的紀念日了,小明想給小紅一個大大的驚喜,就是把送她自己這三個月辛苦研發的機器人,想到小紅收到禮物手舞足蹈的樣子,小明就覺得這簡直太他媽浪漫了。

當第二天小明把準備好的禮物送給小紅的時候,意外發生了。

小黑: 你們不能在一起,其實小紅...

小明:臥槽,難道小紅是我親妹妹?這是什麼狗血劇情啊

小黑: 不,我想說的是,其實小紅是我的女朋友。

小明:臥槽?你憑什麼說小紅是你的女朋友?

小黑:那你憑什麼說小紅是你的女朋友,我說是我的就是我的。

重點來了,知識點吶朋友們,要記住:

小明:我們談戀愛的第一天,我送了小紅一個滑鼠,第二天,我送了她一個鍵盤,第三天,我送了她一個螢幕,第四天..... 第一百天,我送了她一個我辛苦開發的機器人,這些,就是證據!

小黑:啊啊啊啊啊,我輸了,好吧,小紅是你的女朋友。

小黑由於故意篡改小紅是小明女朋友的事實,被拉入黑名單,從此再也沒有找到女朋友。

在這個例子中,小紅小明,和小黑就是區塊鏈中的鏈,而之前小明小紅從相識,到相知,再到相愛期間每發生一個故事就會形成一個區塊。而且小明和小紅之間發生的所有故事都會以直播的形式被區塊鏈中的所有鏈知道(太狠了吧),所以小黑說小紅是他的女朋友自然就不可能是真的了,因為整個區塊鏈所有的鏈都見證了小明和小紅是情侶這個事實,如果小黑要篡改事實說小紅是他的女朋友,那麼他要修改整個區塊鏈中所有鏈對於小明和小紅這對情侶的記憶,這是幾乎無法做到的。

這就好比全世界都知道川普是現在美國的總統,你現在說川普不是美國總統,美國總統是川建國,你這麼說外人一看就知道是假的,如果要把它變成真的,就需要改變全世界所有知道這件事的人的記憶才行。

這就是區塊鏈的不可篡改性

同時,如果有一天小明不愛小紅了,愛上了小綠,於是把手機上和小紅有關的東西全部刪掉,告訴小綠,小綠是自己的初戀,小明只愛小綠一個人。這樣做有用嗎?沒用,因為整個區塊鏈中的鏈都觀看過小明和小紅的直播,記錄著小明和小紅曾經在一起過的證據。

這就是區塊鏈中的不可修改性

大家認真想一想,如果區塊鏈應用於金融會怎麼樣?

之前我們的錢都是存在銀行的賬戶管理系統裡面,如果有人侵入銀行的賬戶管理系統,只需要把他賬號下代表餘額的那串數字改了就可以決定自己有多少錢了,而在區塊鏈中,每個人都是銀行,每個人都是賬戶管理系統,如果需要修改自己賬戶的餘額,則需要修改全網所有節點的資訊才行,這幾乎是不可能實現的,所以大大的提高了安全性。更不要說應用於其他領域了。

而比特幣,就是區塊鏈在割韭菜領域的一個重要應用。

用java實現一個簡單的區塊鏈:

知道區塊鏈是什麼了以後,我們接下來回歸一個開發者的本心,從技術的角度去簡單的看一下區塊鏈是如何實現的。

區塊鏈,區塊鏈,首先我們得有一個區塊類,而且這個類要有一個最重要的特徵,能夠形成鏈。(??神邏輯)

我們新建一個Block 類,程式碼如下:

public class Block {

    /**
     * 當前區塊的hash
     */
    public String hash;

    /**
     * 前一個區塊的hash,本例中,靠這個實現鏈的
     */
    public String previousHash;

    /**
     * 當前區塊的資料,比如交易資訊啊等等,在談戀愛例子中代表小紅和小明具體發生的事件
     */
    private String data;

    /**
     * 時間戳
     */
    private long timeStamp;

    private int nonce;

    public Block(String hash, String previousHash, String data) {
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
    }

    public Block(String data, String previousHash) {
        this.previousHash = previousHash;
        this.data = data;
        this.timeStamp = new Date().getTime();
        this.hash = calculateHash();
    }

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

    public void mineBlock(int difficulty) {
        //Create a string with difficulty * "0"
        String target = new String(new char[difficulty]).replace('\0', '0'); 
        while(!hash.substring( 0, difficulty).equals(target)) {
            nonce ++;
            hash = calculateHash();
        }
    }
}

複製程式碼

變數解釋:

  • hash :當前區塊的雜湊值
  • previousHash :上一個區塊的雜湊值,就是靠這個實現鏈的,怎麼有點像連結串列(噓)
  • data:當前區塊所儲存的資訊,比如小明給小紅買口紅這一件事。
  • timeStamp:時間戳, 比如小明給小紅買口紅這一件事發生的時間資訊。
  • nonce: 只是一個普通的基數變數。

這個類中需要大家去理解的可能就是mineBlock ()calculateHash()這個兩個方法了,而nonce就是關鍵變數

mineBlock()方法中會不停的執行hash運算,直到算出符合條件的區塊,而nonce就是算出來改區塊所需要的次數,在calculateHash()中,我們加上nonce次數,就可以一下子計算出來這個hash了。

而且大家看這個calculateHash()這個方法,我們在執行hash計算的時候,是以上一個區塊的hash為引數進行的,一旦上一個區塊的hash是個假的,或者被篡改了,那麼無論怎麼計算,calculateHash()方法返回的hash值,和該區塊本身的hash值是幾乎不可能一樣的,也就很容易發現區塊被人篡改了。

而加密演算法呢,作者選用的是SHA-256, 也是比特幣所採用的加密演算法,被公認為最安全最先進的演算法之一 .

StringUtil類的程式碼如下所示,由於這個和我們今天要講的主題關係不大,所以就不過多闡述了。

public class StringUtil {

    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);
        }
    }
    
}
複製程式碼

JsonUtil類程式碼如下:

public class JsonUtil {
    public static String toJson(Object object){
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setPrettyPrinting();
        Gson gson = gsonBuilder.create();
        return gson.toJson(object);
    }
}
複製程式碼

既然最基礎的區塊已經寫好了,那麼我們就來簡單地測試一下,看看是不是真的能挖到礦。

新建三個Block區塊,把mineBlock()方法的引數設定為5.

測試類程式碼如下:

public class BlockChainTest {

    public static void main(String[] args) {
        //第1個區塊
        Block firstBlock = new Block("我是第1個區塊", "0");
        firstBlock.mineBlock(5);
        System.out.println("第1個區塊hash: " + firstBlock.hash);

        //第2個區塊
        Block secondBlock = new Block("我是第2個區塊", firstBlock.hash);
        secondBlock.mineBlock(5);
        System.out.println("第2個區塊hash: " + secondBlock.hash);

        //第3個區塊
        Block thirdBlock = new Block("我是第3個區塊", secondBlock.hash);
        thirdBlock.mineBlock(5);
        System.out.println("第3個區塊hash: " + thirdBlock.hash);
    }
}
複製程式碼

執行結果如下所示:

第1個區塊hash: 0000052659276be66678fd482825b20bd0819a800246d23d171da6270e92589c
第2個區塊hash: 000000d1f338b2dc6b02cca8ec158ca7acafa9cbf699ca97fc1ed7b260a65652
第3個區塊hash: 0000072381c11b9a160b1b1d93b75cb477c286db63bb541fcede7ab163ac696c
複製程式碼

發現什麼了嗎?即我們挖到的三個區塊,前五個數字都是0誒,好神奇哦

0000052659276b

所以挖礦的本質其實就是通過雜湊計算得到符合條件的hash的過程。

所以知道比特幣為什麼越挖越少了吧。

因為在所有計算結果之中,符合條件的hash是有限的,而且越算越少,剛開始計算的時候,由於符合條件的hash實在是太多了,所以所需的算力比較小,很容易就計算出來了,而越往後,未被計算出來過的符合條件的hash值就越少,算出來所需要的算力越大,當最後只剩一個符合條件的hash的時候,那時候就真的無異於大海撈針了。這可能就是為什麼十年前隨便一臺電腦都能輕輕鬆鬆地挖出來比特幣,而現在卻需要幾千臺礦機的礦場才能挖出來的原因吧

也就是剛開始沙漠裡有五百萬個寶藏,全世界的人都去找,剛開始寶藏很多,大家走一步就發現一個寶藏,後來寶藏被找的只剩幾個了,那麼大的沙漠,為了挖到寶藏,就不得不派出更多的人去找。

這個時候可能有人會提出疑問了,我怎麼知道你這個區塊是不是合法的,看著是這樣,萬一他不合法我也不知道啊。被改了我也不知道。別慌我們慢慢來。

public class BlockChainListTest {

    //這玩意就是我們的區塊鏈,儲存我們所有的區塊資訊。(簡陋版)
    public static ArrayList<Block> blockChain = new ArrayList();

    //挖礦的難度,就是計算出來的hash前幾個字元是0才是合法的。
    public static int difficulty = 5;

    public static void main(String[] args) {
        blockChain.add(new Block("我是第1個區塊", "0"));
        blockChain.get(0).mineBlock(difficulty);

        blockChain.add(new Block("我是第2個區塊", blockChain.get(blockChain.size() - 1).hash));
        blockChain.get(1).mineBlock(difficulty);

        blockChain.add(new Block("我是第3個區塊", blockChain.get(blockChain.size() - 1).hash));
        blockChain.get(2).mineBlock(difficulty);

        System.out.println("區塊鏈是否合法: " + isChainValid());
        System.out.println(JsonUtil.toJson(blockChain));
    }
    

    public static Boolean isChainValid(){

        Block currentBlock;
        Block previousBlock;
        boolean flag = true;
        String hashTarget = new String(new char[difficulty]).replace('\0', '0');

        //迴圈遍歷列表檢驗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("當前hash不相等");
                flag=false;
            }
            //比較當前的前一個hash與註冊的前一個hash
            if(!previousBlock.hash.equals(currentBlock.previousHash)){
                System.out.println("前一個hash不相等");
                flag=false;
            }

            //檢查該區塊是不是已經被算出來過了。
            if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
                System.out.println("這個區塊還沒有被開採,也就是你這個區塊他不是合格的");
                flag=false;
            }
        }

        return flag;
    }
}

複製程式碼

執行之後結果如下所示:

區塊鏈是否合法: true
[
  {
    "hash": "00000dd5f9b665c79f454adb1491af1042883f43818938191a8337202929cdeb",
    "previousHash": "0",
    "data": "我是第1個區塊",
    "timeStamp": 1574822479084,
    "nonce": 614266
  },
  {
    "hash": "0000063312c3a5832a8990ad064e962c98e8ef9ffca969cc0c049cfd84773fdc",
    "previousHash": "00000dd5f9b665c79f454adb1491af1042883f43818938191a8337202929cdeb",
    "data": "我是第2個區塊",
    "timeStamp": 1574822480646,
    "nonce": 429710
  },
  {
    "hash": "00000304ecc09cca5eac2aed4875c2097a34efcab3518f4f886708a133c513db",
    "previousHash": "0000063312c3a5832a8990ad064e962c98e8ef9ffca969cc0c049cfd84773fdc",
    "data": "我是第3個區塊",
    "timeStamp": 1574822481635,
    "nonce": 262515
  }
]
複製程式碼

isChainValid()就是們檢查區塊鏈是否合法的方法了。

下面開始技術總結:

今天這篇文章呢,花費了大量的篇幅在如何去理解區塊鏈這個概念上,我們得先知道這玩意是什麼,能幹什麼,我們再學習的過程中思路就會清晰許多,後期的文章呢,如果還有的話(馬上課就多了),依然沿著國外大佬的思路,去實現一個可以交易的區塊鏈,當然,程式碼依然不是下一篇文章的重點,考慮到大多數人只是擴充套件自己的知識,去了解一個區塊鏈這項技術,並沒有什麼打算去轉行搞區塊鏈什麼的,所以下一篇會集中在 UTXO(Unspent Transaction Outputs ) 未花費的交易輸出這個比特幣核心概念的理解上,去簡單的瞭解一下區塊鏈是如何去中心化交易的。

最後我們則將會將我們的程式遷移到web上,實現一個跨時代的區塊鏈產品-別逼幣

相關程式碼已經上傳至本人github。一定要點個star啊啊啊啊啊啊啊

萬水千山總是情,給個star行不行

韓數的開發筆記

歡迎點贊,關注我,有你好果子吃(滑稽)

相關文章