区块链的简单实现
定义
区块链就是一串或者是一系列区块的集合,类似于链表的概念,每个区块都指向于后面一个区块,然后顺序的连接在一起。在区块链中的每一个区块都存放了很多很有价值的信息,主要包括三个部分:自己的数字签名,上一个区块的数字签名,还有一切需要加密的数据(这些数据在比特币中就相当于是交易的信息,它是加密货币的本质)。每个数字签名不但证明了自己是特有的一个区块,而且指向了前一个区块的来源,让所有的区块在链条中可以串起来,而数据就是一些特定的信息,你可以按照业务逻辑来保存业务数据。这里的hash指的就是数字签名所以每一个区块不仅包含前一个区块的hash值,同时包含自身的一个hash值,自身的hash值是通过之前的hash值和数据data通过hash计算出来的。如果前一个区块的数据一旦被篡改了,那么前一个区块的hash值也会同样发生变化(因为数据也被计算在内),这样也就导致了所有后续的区块中的hash值。所以计算和比对hash值会让我们检查到当前的区块链是否是有效的,也就避免了数据被恶意篡改的可能性,因为篡改数据就会改变hash值并破坏整个区块链。
区块链的实现
定义区块的结构体
- 包含的数据
- 自己的hash值,由前一个区块的hash值,自己保存的数据,时间戳,工作量一起使用数字摘要计算而来
- 前一个区块的hash值 pervhash
- 保存的数据 data
- 时间戳 timestamp
- 工作量的证明 nonce
- 实现的方法
- calculateHash(),使用数字摘要算法计算自己的hash值
- mineBlock(int difficult),挖矿算法,我写的是计算出前difficult个字符为0的hash值作为区块的hash值作为挖矿算法,例如mineBlock(3),就是计算出前三个字符为0的hash值。difficult代表挖矿的难度
@Data
public class Block {
private String hash;
private String prevHash;
private String data;
private final long timestamp;
private long nonce;
public Block(String data, String prevHash) throws Exception {
this.data = data;
this.prevHash = prevHash;
this.timestamp = System.currentTimeMillis();
this.hash = calculateHash();
}
public String calculateHash() throws Exception {
return Digest.getDigest(prevHash + data + timestamp + nonce, "SHA-256");
}
public void mineBlock(int difficult) throws Exception {
String target = new String(new char[difficult]).replace('\0', '0');
while(!hash.substring(0, difficult).equals(target)) {
nonce++;
hash = calculateHash();
}
System.out.println("mine:" + hash);
}
}
数字摘要算法
用于计算hash值,使用指定的数字摘要算法计算出hash值,并转换成16进制显示
public class Digest {
private static String toHex(byte[] digest) throws Exception {
// 创建对象用来拼接
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
// 转成 16进制
String s = Integer.toHexString(b & 0xff);
if (s.length() == 1){
// 如果生成的字符只有一个,前面补0
s = "0"+s;
}
sb.append(s);
}
// System.out.println("16进制数据的长度:" + sb.toString().getBytes().length);
return sb.toString();
}
public static String getDigest(String input, String algorithm) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
// 消息数字摘要
byte[] digest = messageDigest.digest(input.getBytes());
// System.out.println("密文的字节长度:" + digest.length);
return toHex(digest);
}
}
区块链的实现
- 实现一个简单的区块链需要的数据
- 一个用于保存区块的集合chain
- 表示挖矿的难度 DIFFICTLT 变量
- 保存数据存储位置的变量DATA_PATH,用于保存在区块链中的数据,需要使用到数据时,根据路径找到需要的数据,然后反序列化使用该数据
- 实现的方法
- serialized(Object obj, String path)将指定的对象序列化到指定的路径
- deserialized(Class c, String methodName, int index),根据索引区块链中查找到指定的区块数据,通过反序列化执行对象的方法
- isValidChain(),判断是否是一条有效的区块链,通过判断当前块的hash值是否符合difficult难度指定的hash值形式,前一个块的hash值是否正确,当前块的hash值是否正确,三个条件判断是否为有效的区块链
- isPreviousValid(Block curBlock, Block prevBlock),判断前一个块是否有效
- isCurrentValid(Block curBlock, String hashTarget),判断当前块是否有效
- 主方法
首先序列化一个需要存储的对象,然后创建一个块来保存对象序列化的路径,第一个块没有前驱hash值,我们用0来填充,通过循环创建10个区块,每个区块都保存着一个序列化的对象,然后调用挖矿算法获取指定难度的hash值,最后装入区块链中
创建完成后判断区块链是否有效,然后就可以使用区块链了,通过反序列化指定区块保存的对象,就可以调用该对象的方法。
public class BlockChain {
private static final List<Block> chain = new ArrayList<>();
private static final int DIFFICULTY = 3;
private static final String DATA_PATH = "F:\\Java_WorkSpace_idea\\java_cryptography\\src\\main\\java\\com\\lzl\\";
public static void main(String[] args) throws Exception {
serialized(new TestBlock(), DATA_PATH + "testBlock.txt");
Block first = new Block(DATA_PATH + "testBlock.txt", "0");
first.mineBlock(DIFFICULTY);
chain.add(first);
for(int i = 0; i < 10; i++) {
TestBlock testBlock = new TestBlock();
serialized(testBlock, DATA_PATH + i + ".txt");
Block block = new Block(DATA_PATH + i + ".txt", chain.get(i).getHash());
block.mineBlock(DIFFICULTY);
chain.add(block);
}
if (!isValidChain()) {
System.out.println("不是有效的区块链");
}
System.out.println(JSON.toJSON(chain));
deserialized(TestBlock.class, "sayHello", 5);
deserialized(TestBlock.class, "sayHello", 2);
deserialized(TestBlock.class, "sayHello", 3);
}
private static void deserialized(Class c, String methodName, int index) throws Exception {
Block block = chain.get(index);
String path = block.getData();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
Object o = objectInputStream.readObject();
c.getDeclaredMethod(methodName).invoke(o);
}
private static void serialized(Object obj, String path) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
objectOutputStream.writeObject(obj);
}
public static boolean isValidChain() throws Exception {
Block curBlock;
Block prevBlock;
String hashTarget = new String(new char[DIFFICULTY]).replace('\0', '0');
for(int i = 1; i < chain.size(); i++) {
curBlock = chain.get(i);
prevBlock = chain.get(i - 1);
if (!isCurrentValid(curBlock, hashTarget) || !isPreviousValid(curBlock, prevBlock)) return false;
}
return true;
}
private static boolean isPreviousValid(Block curBlock, Block prevBlock) {
if (!prevBlock.getHash().equals(curBlock.getPrevHash())) {
System.out.println("前驱块的hash不相等");
return false;
}
return true;
}
private static boolean isCurrentValid(Block curBlock, String hashTarget) throws Exception {
if (!curBlock.getHash().equals(curBlock.calculateHash()) || !curBlock.getHash().substring(0, DIFFICULTY).equals(hashTarget)) {
System.out.println("当前块hash不相等");
return false;
}
return true;
}
}
用于序列化的对象
public class TestBlock implements Serializable {
private int count = 0;
private static final long serialVersionUID = -8324022222992414658L;
public void sayHello() throws Exception {
System.out.println(count + " : Hello world!");
}
public TestBlock() {
count += new Random().nextInt(10);
}
}
运行截图:
通过运行可以看到,生成的hash值都是指定difficult的hash值,然后生成了一条包含11个区块的区块链,每一个区块都包含保存的数据路径,前驱hash值,工作量的证明,自己的hash值以及时间戳,最后输出了调用指定hash块的方法的输出信息。