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

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

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

本文會講到:java

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

( 本文假設你對於面向對象編程已經有了基本的瞭解 )android

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

你能夠經過如下方式來支持本文和未來的教程 :)git

btc: 17svYzRv4XJ1Sfi1TSThp3NBFnh7Xsi6fugithub


準備工做.

本文準備使用 Java 做爲開發語言,可是你應該可以使用任何面向對象語言來跟着一塊兒學習。我會使用 Eclipse,不過你也可使用任何其餘喜歡的編輯器( 雖然你會錯過不少方便的功能 )。算法

你須要:編程

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

你的 eclipse 界面也許會看起來和個人不同,不過不要緊,那是由於我使用了深色主題。後端

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

在 Eclipse 中 建立一個 Java 項目(file > new > )。我將個人項目命名爲「noobchain」,接着建立一個新的同名 ClassNoobChain)。

不要想着立馬複製個人項目名稱哦 ( ͠° ͟ ͜ʖ ͡°)

咱們開了個不錯的頭,能夠往下繼續了 :)


創造區塊鏈

一個區塊鏈只是一個個區塊的連接/列表。區塊鏈中的每個區塊都會有本身的數字簽名,前一個區塊的數字簽名和一些數據(例如一些交易數據)。

我但願中本聰永遠都不會看到這個.

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 值打印到屏幕上,來確保一切工做正常有序。

開始測試...

第一個區塊被命名爲起始區塊,因爲它前面沒有區塊,因此咱們用 「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);
		
	}
}
複製代碼

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

因爲時間戳不同,你的 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 的驗證工做意味着計算機須要大量的時間和計算能力來建立新的區塊。所以,攻擊者須要比其餘同行擁有更多的計算能力。

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 值,並將其打印出來。

運行這個程序的輸出應該像下面這樣:

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

若是有人試圖去篡改 😒 你係統中區塊鏈的數據:

  • 他們的區塊鏈會變得不合法。
  • 他們將沒法建立一個更長的區塊鏈。
  • 網絡中合法的區塊鏈在鏈長度上將會具備時間優點。

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

*除非它們的計算速度遠遠超過網絡中全部其餘節點的總和。好比有一臺將來量子計算機之類的。

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

拍拍你本身的肩膀把。

你的區塊鏈:

> 是由存儲數據的一個個區塊組成的。

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

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

> 能夠檢查數據是否合法和是否被篡改。

你能夠在 Github 上下載本文的項目。

你能夠關注我,當下個教程和其餘區塊鏈開發文章發佈時即可以及時獲得通知。十分歡迎任何反饋信息。謝謝。

Creating Your First Blockchain with Java. Part 2:

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

聯繫: kassCrypto@gmail.com

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


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

相關文章
相關標籤/搜索