[译]用java编写你自己的第一个区块链应用 Part 1

 

Creating Your First Blockchain with Java. Part 1.

本教程旨在帮助您了解如何进行区块链编程.

在这部分,我们会完成下面的目标:

  • 创建一个最简单的区块链demo
  • 实现一个简单的工作量证明系统
  • 说明他的可用性

假定您已经掌握了面向对象编程

这个教程所讲述的并不能应用于生产环境,它只能帮助您理解区块链.

前提说明

我将使用java语言作为讲解,但是你应该明白任何面向对象的语言都是可以的.编辑器我选择了eclipse,你可以选择其他的甚至是文本编辑器.

准备:

  • jdk ok
  • IDE ok

这里还需要添加一个第三方的依赖包,这里使用了gson,它很优秀.你也可以随时更换为其他的jar,比如org.json

在Eclipse中创建一个java project.工程名叫noobchain,再创建一个同名的类NoobChain.

demo制作

区块链从名字上理解就是由很多块连接起来形成链条.每个块中包含有自身块的数字签名,以及上一个块的签名,当然还包含最重要的数据(比如是交易数据),在这个教程中,我们假定要保存的是一句话,即 String 对象.

HASH = 数字签名

每一个新生成的区块,不仅仅持有前一个块的签名,也持有根据前一个块进行一些计算得到的新的签名.换句话说,只要前一个块的数据发生了变化,那么前一个块的签名也会发生变化,反过来影响之后的所有块.计算并验证签名的值可以帮助我们确认块是否被篡改.

这是什么意思呢?..这意味着更改此列表中的任意的数据,都将会改变其签名并最终break the chain(打断这个链条? 不太好翻译)

第一步,我们创建一个表示块对象的类用于构成区块链

import java.util.Date;

public class Block {
    public String hash;
    public String previousHash;
    private String data; //存放数据
    private long timeStamp; //时间戳的long值

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

hash属性会保存数字签名,previousHash保存上一个块的签名,data会保存数据信息.

继续添加生成数字签名的方法

数字签名有很多种加密方式可以选择,但是作为演示 SHA-256已经足够了.使用java.security.MessageDigest包.

创建一个工具类方法

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);
        }
    }	
}

如果你看不明白这段代码,不要担心,你只需要知道入参是数据,返回值是该数据对应的签名.

添加一个生成数字签名的方法,为了防止伪造,数字签名应该根据前一个块的签名,本块的时间戳,本块的数据来生成.

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

再将其添加到构造方法中

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.
}

添加一个main方法进行测试

第一个块是创始块,因为它没有前一个块,所以将它的previous hash 设置为’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);
    }
}

让我们将创造的块保存到ArrayList中,生成一个简单的区块链,并且将这个链以json字符串的方式看一下.

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);
    }

}

好的,我们生成了一个区块链,链中包含有三个区块.

验证区块链的完整性,即没有被人恶意篡改

NoobChain类中添加isChainValid()方法,返回一个布尔类型的结果.他会迭代链中所有的区块并检查数字签名.要保证生成签名的正确以及持有的上一个区块的签名与上一个区块的签名进行比较.

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;
}

链中任何一个数据的变动,都会使结果返回false.

在比特币网络中,矿工通过声明更长的链被别人接受来保持比特币网络的运转.怎么保证不被其他人恶意提交一个假的交易信息(比如,二次支付)来生成更长的链呢.比特币网络使用了一个一种称之为工作量证明的方式来保证.这保证了生成新的区块并不是无成本的,他需要时间和计算能力.因此攻击者需要比其他同行加起来更多的计算能力。

开始挖矿

我们假定矿工必须不断的尝试新的变量值,以便生成的签名以'0'开始 在Block类中添加一个属性nonce ,修改calculateHash() 添加mineBlock()

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.
    }
    //根据块的属性生成数字签名
    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);
    }
}

矿工们,可能会从任意一个随机数开始尝试.当尝试了Integer.MAX_VALUE 之后,可以通过修改时间戳来尝试

mineBlock() 方法有一个入参控制难度系数.一般的1-2对于现代计算机是没有压力的,我建议4-6用于测试.在我编写这篇文章时,莱特币的难度系数是442,592.

在NoobChain类中添加一个静态变量保存难度系数

public static int difficulty = 5;

修改NoobChain类的代码,以便在生成新的区块时,触发mineBlock()方法.修改isChainValid()方法,确保签名是可用的.

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;
    }
}

运行一下,生成每个新的区块花费了一定的时间(大概3s).可以多修改几次难度值,比较一下每次生成新的区块所花费的时间

如果有人想在你的区块链中捣乱:

  • 他的链是不会被验证通过的
  • 他是没有足够快的时间去生成更长的链
  • 诚实的矿工在生成新块上会有时间上的优势

篡改链是赶不上正常生成验证过的区块的速度的

除非他拥有了超过所有人计算能力的计算机,未来的量子计算机.

一个简单的区块链已经完成了

您的区块链:

  • 由存储数据的块组成。
  • 有一个数字签名将您的数据块连接在一起。
  • 需要工作证明来验证新块。
  • 可以检查其中的数据是否有效并保持不变,防止篡改。

您可以在Github上下载源码