Java實現簡單區塊鏈
參考地址:Creating Your First Blockchain with Java
準備
開發環境
- java1.8~
- maven
- 任選IDE
區塊鏈概述
顧名思義,區塊鏈就是很多“區塊”形成的“鏈”。
每個“區塊”上包含的資料有:
- 它自身的數字指紋(digital fingerprint)
- 上一個區塊的數字指紋
- 一些額外資訊,如交易資訊(tansaction infomation)等等
數字指紋一般是一個雜湊值
當前區塊的數字指紋計算方式:會根據上一個區塊的指紋,以及當前區塊的資訊來計算
故當前區塊的資訊發生改變,影響這條鏈上之後的所有區塊的數字指紋
所以,區塊鏈的一般結構為:
編碼
區塊Block
import java.util.Date; 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(); } }
“數字指紋”生成
採用sha256雜湊演算法生成雜湊值(String表示)
import java.security.MessageDigest; public class StringUtil { /** * 對輸入input使用sha256演算法進行雜湊, * 返回雜湊值的16進位制字串 * @param input * @return */ 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); } } }
接下來可以修改Block類,增加一個生成數字指紋的方法
在Block類的建構函式中,給變數hash賦初值
public class Block {
// ... 省略成員變數
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) + // 當前區塊的時間戳
data); // 當前區塊的額外資訊
return calculatedhash;
}
}
區塊鏈NoobChain
Noob是“菜鳥,新手”的意思。
要是去英文程式設計論壇裡問問題,
別人這麼標記這個問題,
不要誤會為不可能回答的意思。
先把主函式放入這個類中,進行測試:
創世區塊是指區塊鏈的第一個區塊
創世區塊沒有上一個區塊,所以把該區塊的“上一個區塊數字指紋”設為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);
}
}
輸出為:
修改pom.xml檔案,新增Gson依賴
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
修改NoobChain類,用連結串列ArrayList儲存區塊
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));
// 按json格式輸出
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println(blockchainJson);
}
}
區塊鏈完整性校驗
之前提到,在一個區塊鏈上,
如果當前區塊的資訊發生變化,
後序所有區塊的數字指紋都要重新計算,
所以需要校驗是否所有區塊都滿足前後依賴關係。
這裡在NoobChain類中設計一個例項函式isChainValid()
public class NoobChain {
// 區塊鏈
public static ArrayList<Block> blockchain = new ArrayList<Block>();
// 區塊鏈完整性校驗
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
// 從前往後遍歷所有區塊
for (int i = 1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i - 1);
// 重新計算當前區塊的數字指紋(例項變數hash)
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;
}
}
return true;
}
}
“區塊鏈上線”
區塊鏈系統是典型的分散式系統
每個機器(節點)都能執行區塊鏈程式
每個節點都能執行上述NoobChain類中的主函式
在主函式中,主要執行的任務是什麼呢?就是往區塊鏈後新增區塊
例如,比特幣(bitcoin)系統的每個節點是共享區塊鏈的,
而所有節點中的最長區塊鏈會被整個系統接受。
為了防止某個節點隨意建立“最長的區塊鏈”,比特幣有工作量證明機制(Proof of work),
也就是在“新增區塊至區塊鏈”時會消耗很多時間和算力
攻擊者的攻擊需要有超過整個系統中所有剩餘節點的算力之和
“挖礦”
挖礦:簡單理解,挖礦等價於“新增區塊至區塊鏈”;
由於存在工作量證明機制,新增區塊至區塊鏈時會很難;
下面模擬一種工作量證明機制(使得“新增區塊至區塊鏈”變難):
在區塊Block類中新增一個nonce屬性,nonce的作用是:
- 在計算數字指紋(hash例項變數)時也需加上這個nonce值;
- 只有當新建區塊的數字指紋以一定數量的0開頭,才能把新建區塊新增入區塊鏈
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;
}
/**
* 模擬:只有當hash數字指紋 以difficulty個0開頭,
* 才能結束while迴圈,
* 然後去把已經建立好的區塊新增至區塊鏈
*
* @param difficulty
*/
public void mineBlock(int difficulty) {
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);
}
}
更新NoobChain類中的main函式,實現:只有滿足工作量證明後,才建立區塊
public class NoobChain {
// 區塊鏈
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static int difficulty = 1;
// 模擬挖礦
public static void main(String[] args) {
// add our blocks to the blockchain ArrayList:
blockchain.add(new Block("Hi im the first block", "0"));
System.out.println("Trying to Mine block 1... ");
// 得完成工作量證明,才能算成功把新建區塊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... ");
// 得完成工作量證明,才能算成功把新建區塊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... ");
// 得完成工作量證明,才能算成功把新建區塊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);
}
// ... 其他程式碼省略
}
個人想法
如果某人想篡改區塊鏈中已經確定好的區塊,那麼這個人就必須得趕在其他所有人的前頭,把他自己節點上的那條區塊鏈變得更長,他所需要的算力必須比其他人加起來還要多,不然其他人中就會出現至少一個人把未經篡改的區塊鏈變長,繼而把他那條篡改過的區塊鏈淘汰。