Part1 :使用 Java 创建你的第一个区块链[译]

 理解一个新事物,我觉得对于程序员来说,最好的方式就是用尝试用代码着去实现它。当然,区块链并不是一个新事物,2008 年由中本聪提出,至今也有十年了,但是对于我而言,它确实又是新的,这种严重迟到,绝对不是一件好事情。平时没事,还是要多逛逛技术论坛,看看新事物,最起码不会距离这个世界太远。
 回到正题,近来终于找到一篇关于如何使用 Java 创建区块链的入门文章,本意是借此熟练掌握区块链相关概念,原文地址在此,以下是我翻译的内容。
 这个系列的教程,旨在帮助你理解区块链技术的开发。在本教程中,我们将

  • 创建一个非常基本的 区块链
  • 实现一个很简单的 Proof of Work 系统(挖矿)
  • 惊叹这种可行性、可能性

 值得注意的是,这不是一个功能完备的、产品级别的区域链,而是对这些概念实现做了一个验证,以帮助更好的在后续章节中理解区块链。

准备工作

 虽然我们将使用 Java 来完成这项工作,所以你应能够遵守 (OOP)面向对象编程语言 的原则。我将使用的 IDE 是 Eclipse ,当然,你可以选择自己喜欢的编辑器。总之,你需要:

  • 会使用 Java ,并安装 JDK
  • Eclipse(或者其他 IDE ,或者文本编辑器)

 最好,你还要能够掌握 Google 的 GSON 库。这个库将帮助我们将一个对象转换为 JSON。这是一个非常有用的库,后续我们将继续在 p2p 的过程中用到,当然了,你也可以选择其他工具或者库来完成这个工作(将对象转换为 JSON)。
 在 Eclipse 中创建一个 JAVA 工程。我将这个工程叫做 “noobchain” ,然后创建一个同名的类 NoobChain.

创建区块链

 所谓区块链,说白了就是区块组成的链,或者说列表。每个区块链中的区块都有自己的数字签名、上个区块的数字签名、以及一些数据(举例来说,这些数据可能是一些交易数据)。

Hash = Digital Signature

 每个区块不仅仅包含了上个区块的哈希值,同时上个区块的哈希值将参与到本区块哈希值的计算。如果上个区块的数据发生了变化,那么它的 hash 值将发生变化,这将引发链式反应,后面的所有区块的 hash 值都将发生变化。因此,计算和比较 hsah 值使得我们可以判断一个区块链是否合法。
 什么意思呢?…其实就是说,改变链中的任何一点数据,都会改变签名,从而导致断链。

创建组成区块链的区块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.Date;

public class Block {

public String hash;
public String previousHash;
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.

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

 正如上面你所看到的那样,一个区块包含了 string hash (数字签名), perviousHash (上一个区块的签名), Stirng data(数据块的数据内容)。

生成数据签名

 我们可以选择的加密算法有很多,然而 SHA256 就可以满足需求了。我们可以通过*** import java.security.MessageDigest; *** 来获取 SHA256 算法。
 接下来,为了使用 SHA256 ,我们首先创建一个工具类 StringUtil 来帮助我们完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.security.MessageDigest;

public class StringUtil {
//Applies Sha256 to a string and returns the result.
public static String applySha256(String input){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
//Applies sha256 to our input,
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
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);
}
}
}

&emsp;如果你无法理解上面这个帮助方法,没有关系,你只需要知道它能返回一个字符串的数据签名即可。
&emsp;现在我们在区块类 Block 中使用 applySha256 这个方法来计算 hash 值。我们必须计算好所有不会被篡改的区块的 hash 值。

1
2
3
4
5
6
7
8
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash +
Long.toString(timeStamp) +
data
);
return calculatedhash;
}

&emsp;现在,让我们将这个方法添加到 Block 的构造函数中

1
2
3
4
5
6
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.
}
下面可以进行测试了

&emsp;在 NoobChain 类中我们创建一些区块,同时将它们的 hash 值打印在屏幕上。

&emsp;第一块区块被成为 创世区块,因为它没有上一个区块,因此它的 previous hash 为0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

}
}

&emsp;输出是这样的:

&emsp;每个区块现在都有了一个建立在它所有拥有的信息的基础之上的数据签名了。同时,也拥有了上一个去区块的签名。
&emsp;现在,区块还不是很多,我们先把它们都存在一个 ArrayList 中,然后使用 gson 将数据转为 JSON 格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {
//add our blocks to the blockchain 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);
}

}

&emsp;现在,我们离期望的区块链越来越近了。

检查区块链的完整性

&emsp;让我们创建一个 isChainValid() 方法,判断区块链的完整性。这个方法需要判断数据签名是否正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;

//loop through blockchain to check hashes:
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//compare registered hash and calculated hash:
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//compare previous hash and registered previous hash
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
}
return true;
}

&emsp;任何区块的变化,都将导致该方法返回 false。
&emsp;在比特币网络上,结点共享它们的区块链,最长且合法的区块链被网络所接受。但是,如何防止有人篡改旧块的数据然后沿着旧块创建一个新的更长的数据链?Proof of work(工作量证明)。hashcash 工作量证明系统的使用,意味着要生成一个区块,需要花费大量的时间和计算能力。因此,攻击者需要比所有剩下的节点的算力大才能实现数据篡改(译注:即百分之五十一的算力)。

开始挖矿吧

&emsp;通过在区块中尝试不同的值,直到整个区块的 hash 值以一定的 0 开头,这就是我们为矿工提供的工作量证明。
&emsp;让我们在 calculateHash() 方法中添加一个 nonce 的 int 值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.Date;

public class Block {

public String hash;
public String previousHash;
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.
private int nonce;

//Block Constructor.
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.
}

//Calculate new hash based on blocks contents
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'); //Create a string with difficulty * "0"
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
}
}

&emsp;mineBlock() 方法使用了一个称为 difficulty 的 int 值,这个数代表了要解决的 0 的个数。如果 difficulty 非常低,比如 1 或者 2,计算机会非常快速的完成计算。我建议使用 4-6 进行测试。截止到写作为止,莱特币的难度系数大概是 442592(译注:这里的难度系统并不代表 0 的个数)
&emsp;下面,让我们在 NoobChain 类中添加一个静态变量代表难度系数。

1
public static int difficulty = 5;

&emsp;更新 NoobChain 触发 mineBlock 方法作用于每个新的区块中。而 isChainValid() 方法也将用以检测每个区块是否有合理的 hash 值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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) {
//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... ");
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');

//loop through blockchain to check hashes:
for(int i=1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//compare registered hash and calculated hash:
if(!currentBlock.hash.equals(currentBlock.calculateHash()) ){
System.out.println("Current Hashes not equal");
return false;
}
//compare previous hash and registered previous hash
if(!previousBlock.hash.equals(currentBlock.previousHash) ) {
System.out.println("Previous Hashes not equal");
return false;
}
//check if hash is solved
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("This block hasn't been mined");
return false;
}
}
return true;
}
}

&emsp;运行结果如下所示:

&emsp;我们可以看到,挖矿所需大概三秒钟左右。你可以通过改变 难度系数 看看对时间花费所产生的影响。

&emsp;如果有人先伪造区块中的数据:

  • 区块链会变得不合法
  • 他们必须创建一个更长的区块链
  • 但是,原先的区块链在成为最长链的可能性上有时间优势。

&emsp;Ok,现在你已经完成了一个最基本的区块链,在区块链中

  • 在区块中存入了数据
  • 通过数字签名将所有区块链在一起
  • 通过工作量证明验证区块的合法性
  • 只要数据是合法的并没有改变,就可以在区块链中检查到。

&emsp;你可以在 (Github)[https://github.com/CryptoKass/NoobChain-Tutorial-Part-1] 中下载整个项目。在下一个部分,我们将处理交易、签名和钱包。

© 2024 YueGS