[譯] 用 Java 創造你的第一個區塊鏈-第一部分

再也不悍跳的阿哲發表於2019-03-02

用 Java 創造你的第一個區塊鏈,第一部分。

這系列文章旨在幫助你瞭解如何使用開發區塊鏈技術。

本文會講到:

  • 創造你的第一個(十分)基礎的‘區塊鏈’
  • 實現一個簡單的驗證性(挖礦)系統。
  • 奇蹟是有可能發生的.

( 本文假設你對於物件導向程式設計已經有了基本的瞭解 )

值得注意的是,文中講到的並不是一個功能完整,可以上線的區塊鏈系統。相反,這只是一個概念驗證性工作,來幫助你理解什麼是區塊鏈以便閱讀未來的教程.

你可以通過以下方式來支援本文和將來的教程 :)

btc: 17svYzRv4XJ1Sfi1TSThp3NBFnh7Xsi6fu


準備工作.

本文準備使用 Java 作為開發語言,但是你應該能夠使用任何物件導向語言來跟著一起學習。我會使用 Eclipse,不過你也可以使用任何其他喜歡的編輯器( 雖然你會錯過很多方便的功能 )。

你需要:

  • 安裝 Java 和 JDK。
  • Eclipse ( 或者其他 IDE/編輯器 ).

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

你的 eclipse 介面也許會看起來和我的不一樣,不過沒關係,那是因為我使用了深色主題。

你可以安裝 GSON library by google (這是什麼 ???),當然這是可選項。它可以讓我們將 object 轉換成 Json \o/。這是一個超級實用的庫,在後面我們也將它用到 peer2peer 上,但你隨時可以用一個類似的方法去替換它。

在 Eclipse 中 建立一個 Java 專案(file > new > )。我將我的專案命名為“noobchain”,接著建立一個新的同名 ClassNoobChain)。

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

不要想著立馬複製我的專案名稱哦 ( ͠° ͟ ͜ʖ ͡°)

我們開了個不錯的頭,可以往下繼續了 :)


創造區塊鏈

一個區塊鏈只是一個個區塊的連結/列表。區塊鏈中的每一個區塊都會有自己的數字簽名,前一個區塊的數字簽名和一些資料(例如一些交易資料)。

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

我希望中本聰永遠都不會看到這個.

Hash = Digital Signature.

每一個區塊不僅僅包含前一個區塊的 hash 值,其自己的 hash 值,有一部分是根據前一個區塊的 hash 值計算出來的。如果前一個區塊的資料發生了變化,那麼前一個區塊的 hash 值也會隨之變化(因為它有一部分是根據區塊的資料進行計算的),並會依次影響所有區塊的 hash 值。通過計算和比較 hash 值,我們可以判斷區塊鏈是否合法。

這意味著什麼?修改鏈中的任意資料,都會改變數字簽名,進而破壞整個區塊鏈

那麼首先讓我們來建立組成區塊鏈的 Block 類:

import java.util.Date;

public class Block {

	public String hash;
	public String previousHash;
	private String data; //我們的資料是一條簡單的訊息
	private long timeStamp; //從 1/1/1970 起至現在的總毫秒數.

	//Block 類的構造方法.
	public Block(String data,String previousHash ) {
		this.data = data;
		this.previousHash = previousHash;
		this.timeStamp = new Date().getTime();
	}
}
複製程式碼

你可以看到,我們的基礎 Block 類包含一個 String hash,它代表了數字簽名。previousHash 變數為前一個區塊的 hash 值,它和 String data 組成了這個區塊的資料。

接著我們需要一種方法去生成數字簽名

有很多加密演算法可供我們選擇,當然 SHA256 演算法正好適合我們這個例子。我們可以通過 import java.security.MessageDigest; 來使用 SHA256 演算法。

我們在 StringUtil ‘工具’ 中建立了一個方便使用的方法,以便在接下來去使用 SHA256 演算法:

import java.security.MessageDigest;

public class StringUtil {
	//使用 Sha256 演算法加密一個字串,返回計算結果
	public static String applySha256(String input){		
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-256");	        
			//對輸入使用 sha256 演算法
			byte[] hash = digest.digest(input.getBytes("UTF-8"));	        
			StringBuffer hexString = new StringBuffer(); // 它會包含16進位制的 hashfor (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);
		}
	}	
}
複製程式碼

上面基本上是複製的這篇文章中的方法 www.baeldung.com/sha-256-has…

如果你不理解這個輔助方法的內容,也不用擔心。 你只需要知道,它接受一個字串作為輸入,並對其使用 SHA256 演算法,最後將返回的字串作為數字簽名。

現在讓我們在 Block class 中的一個新方法裡使用 applySha256 輔助方法來計算 hash 值。我們必須根據區塊中那些不想被篡改的資料來計算 hash 值。對於本文中的區塊,我們會包含 previousHashdatatimeStamp

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(); //Making sure we do this after we set the other values.
	}
複製程式碼

是時候做些測試了...

讓我們在主類 NoobChain 中新建一些區塊物件並將其 hash 值列印到螢幕上,來確保一切工作正常有序。

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

開始測試...

第一個區塊被命名為起始區塊,由於它前面沒有區塊,所以我們用 “0” 作為其前一個區塊的 hash 值。

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

這段程式的輸出應該長下面這樣:

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

由於時間戳不一樣,你的 hash 值和我的應該會不同。

現在,每一個區塊應該擁有自己的基於區塊資料和前一個區塊簽名計算出來的數字簽名

目前,這還並不是區塊,所以讓我們將區塊儲存在一個 ArrayList 中並匯入 gson 庫來將其輸出為 Json 字串。(點選這裡檢視如何匯入 gson 庫)

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) {	
		//將我們的區塊加入到區塊鏈 ArrayList 中:
		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);
	}

}
複製程式碼

現在我們的輸出應該更加接近我們期望的區塊鏈的樣子。

現在我們需要一種方法來檢查區塊鏈的完整合法性

讓我們在 NoobChain 中新建一個返回值為 BooleanisChainValid() 方法,它會迴圈鏈中所有的區塊並比較其 hash 值。這個方法需要能夠檢查當前區塊的 hash 值和計算出來的 hash 值是否相等以及前一個區塊的 hash 值是否等於當前區塊儲存的 previousHash 值。

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 的驗證工作意味著計算機需要大量的時間和計算能力來建立新的區塊。因此,攻擊者需要比其他同行擁有更多的計算能力。

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

hashcash, 那需要很大的工作量哦.

開始挖礦吧!!!

我們要求 miners 去做驗證性工作,通過在區塊中嘗試不同的引數值直到其 hash 值以若干個 0 開頭。

讓我們新增一個 int 型別的 nonce 變數,並將其使用到 calculateHash() 方法和十分重要的 mineBlock() 方法中:

import java.util.Date;

public class Block {
	
	public String hash;
	public String previousHash; 
	private String data; //我們的資料是一條簡單的訊息
	private long timeStamp; //從 1/1/1970 起至現在的總毫秒數.
	private int nonce;
	
	//Block 類構造方法.  
	public Block(String data,String previousHash ) {
		this.data = data;
		this.previousHash = previousHash;
		this.timeStamp = new Date().getTime();
		
		this.hash = calculateHash(); //Making sure we do this after we set the other values.
	}
	
	//根據區塊內容計算其新 hash 值
	public String calculateHash() {
		String calculatedhash = StringUtil.applySha256( 
				previousHash +
				Long.toString(timeStamp) +
				Integer.toString(nonce) + 
				data 
				);
		return calculatedhash;
	}
	
	public void mineBlock(int difficulty) {
		String target = new String(new char[difficulty]).replace('\0', '0'); //建立一個用 difficulty * "0" 組成的字串
		while(!hash.substring( 0, difficulty).equals(target)) {
			nonce ++;
			hash = calculateHash();
		}
		System.out.println("Block Mined!!! : " + hash);
	}
}
複製程式碼

實際上,每個挖礦者會從一個隨機點開始迭代計算。一些挖礦者甚至會嘗試使用隨機數作為 nonce。值得注意的是,更復雜的解決方案的計算值可能會超過 integer 最大值,這時挖礦者可以嘗試更改時間戳。

mineBlock() 方法接受一個 int 型別的 difficulty 引數,這是程式需要計算處理的 0 的數量。像 1 或 2 這樣低難度的 difficulty 值,也許一臺計算機就可以解決了。所以我建議將 difficulty 的值設定為 4-6 來做測試。現在萊特幣挖礦的 difficulty 值約為 442,592。

讓我們在 NoobChain 類中新增一個靜態變數 difficulty:

public static int difficulty = 5;
複製程式碼

我們應該更新 NoobChain 去觸發每個新區塊的 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) {	
		//將我們的區塊新增至區塊鏈 ArrayList 中:
		
		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');
		
		//迴圈區塊鏈來檢查 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;
			}
			//檢查 hash 值是否已經存在
			if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
				System.out.println("This block hasn't been mined");
				return false;
			}
		}
		return true;
	}
}
複製程式碼

同時我們還檢查了 isChainValid 值,並將其列印出來。

執行這個程式的輸出應該像下面這樣:

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

對每個區塊的計算都需要花費一些時間! (大約3秒)你應該仔細研究下 difficulty 值,看看它是如何影響每個區塊的計算時間的 :)

如果有人試圖去篡改 ? 你係統中區塊鏈的資料:

  • 他們的區塊鏈會變得不合法。
  • 他們將無法建立一個更長的區塊鏈。
  • 網路中合法的區塊鏈在鏈長度上將會具有時間優勢。

一個被篡改的區塊鏈不會同時合法且具有長度優勢的。*

*除非它們的計算速度遠遠超過網路中所有其他節點的總和。比如有一臺未來量子計算機之類的。

恭喜你,你已經實現了自己的基礎區塊鏈!

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

拍拍你自己的肩膀把。

你的區塊鏈:

> 是由儲存資料的一個個區塊組成的。

> 有一個將你所有的區塊串連起來的數字簽名。

> 對於新加入的區塊,需要一系列的挖礦驗證性工作去檢查其合法性。

> 可以檢查資料是否合法和是否被篡改。

你可以在 Github 上下載本文的專案。

[譯] 用 Java 創造你的第一個區塊鏈-第一部分

你可以關注我,當下個教程和其他區塊鏈開發文章釋出時便可以及時得到通知。十分歡迎任何反饋資訊。謝謝。

Creating Your First Blockchain with Java. Part 2:

下個教程的內容將涉及區塊鏈的交易簽名錢包

聯絡: kassCrypto@gmail.com

提問discord.gg/ZsyQqyk (我在 discord 上的區塊鏈開發者俱樂部)。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章