
本文共 10107 字,大约阅读时间需要 33 分钟。
来,一起创建你的第一个区块链吧!!
前言
过去的一年,虚拟货币市场可以用风生水起四个字来加以形容,掀起了全民挖矿风潮,投资者的心情则随其价格走势变得像在坐过山车。到了今年,虚拟货币和区块链最火标的竟是股市,凡沾上此概念的股票,其涨幅似乎比虚拟货币更夸张。1月8日,最大的加密货币更是触及到了近4.2万美元的纪录。
那么我们今天来看看这牵动万千投资者心的东西,究竟是何方神圣,它究竟为何如此倍受投资者的青睐。没错,它就是我们今天的主角——“区块链”。
本篇文章旨在帮助你了解什么是区块链技术以及如何开发区块链,文章使用的编程语言为:Java 。
区块链
可能拿到这个话题时,你和我一样,很疑惑——“什么是区块链?”
查了很多资料,都没有找到一个通俗易懂的解说,但是发现了【百度百科】对区块链的标识为【数据结构】。
基于这点发现,我查看了一些区块链开发的实例,以及对区块链技术原理的说明博客,发现“区块链类似于链表”,那么相信到这里你已经有一点区块链的概念了。
那么该上硬菜了。
“区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。” — 百度百科
没错,我也晕了。咱们先把这一个个专业名词留给未来,相信未来我们能学到更深的知识,来解释它们。
其实,区块链就是一串或者是一系列区块的集合,类似于链表的概念,每个区块都指向于后面一个区块,然后顺序的连接在一起。那么每个区块中的内容是什么呢?在区块链中的每一个区块都存放了很多很有价值的信息,主要包括三个部分:自己的数字签名,上一个区块的数字签名,还有一切需要加密的数据(这些数据在比特币中就相当于是交易的信息,它是加密货币的本质)。每个数字签名不但证明了自己是特有的一个区块,而且指向了前一个区块的来源,让所有的区块在链条中可以串起来,而数据就是一些特定的信息,你可以按照业务逻辑来保存业务数据。
所以每一个区块不仅包含前一个区块的Hash值,同时包含自身的一个Hash值,自身的Hash值是通过之前的Hash值和数据data,利用Hash计算出来的。如果前一个区块的数据一旦被篡改了,那么前一个区块的Hash值也会同样发生改变(因为数据也需要Hash计算),这样也就导致了所有后续的区块中的Hash值发生改变。所以计算和比对Hash值就会让我们检查到当前的区块链是否有效,也就避免了数据被恶意篡改的可能性,因为篡改数据就会改变Hash值并破坏整个区块链。
来吧,咱们开始构建我们的第一个区块链吧!!!
(此处创建的区块链并不具有完全的功能,不适合应用与生产,只是为了帮助你理解区块链的概念)
创建区块链
定义区块链的块类
package blockChain;import java.util.Date;/* * 区块链的块类 */public class Block {
public String hash; //当前hash值 public String previousHash; //前一个区块的hash值 private String data; //当前区块存储的数据 private long timeStamp; // 时间戳(自1970/1/1起) //构造函数 public Block(String data,String previousHash) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime(); }}
正如你看到的,区块链的基本块包含String hash ,它将保存我们的数字签名。变量 previoushash 保存前一个块的 hash 和 String data 来保存我们的块数据。
创建数字签名
熟悉加密算法的朋友们,Java方式可以实现的加密方式有很多,例如BASE、MD、RSA、SHA等等,我在这里选用了 SHA256 这种加密方式,SHA(Secure Hash Algorithm)安全散列算法,这种算法的特点是数据的少量更改会在Hash值中产生不可预知的大量更改,hash值用作表示大量数据的固定大小的唯一值,而SHA256算法的hash值大小为256位。之所以选用SHA256是因为它的大小正合适,一方面产生重复hash值的可能性很小,另一方面在区块链实际应用过程中,有可能会产生大量的区块,而使得信息量很大,那么256位的大小就比较恰当了。
下面我创建了一个 HashUtil 方法来方便调用SHA256算法
package ChainBlock;import java.security.MessageDigest;/* * 创建数字签名 工具类 */public class HashUtil {
//这里使用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();
for(int i= 0;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();
} }}
或许你不完全理解上述代码的含义,但是你只要理解所有调用此方法后均会生成一个独一无二的Hash值(数字签名),而这个Hash值在区块链中是十分重要的。
接下来让我们在 Block 类中加入方法 applySha256 方法,其主要的目的就是计算Hash值,我们计算的Hash值应该包括区块中所有我们不希望被恶意篡改的数据,在我们上面所列的 Block 类中就一定包括 previousHash, data 和 timeStamp 。
// 计算数字签名 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(); }
Test
在主方法中让我们创建一些区块,并把其Hash值打印出来,来看看是否一切都在我们的掌控中。
第一个块称为创世纪区块,因为它是头区块,所以我们只需输入“0”作为前一个块的 previousHash。
package ChainBlock;import java.util.ArrayList;import com.google.gson.GsonBuilder;public class MineChain {
public static void main(String[] args) {
//创建创世纪区块
Block genesisBlock = new Block("世纪区块", "0");
System.out.println("第一块区块的Hash值 : " + genesisBlock.hash);
Block secondBlock = new Block("第二块区块",genesisBlock.hash);
System.out.println("第二块区块的Hash值 : " + secondBlock.hash);
Block thirdBlock = new Block("第三块区块",secondBlock.hash);
System.out.println("第三块区块的Hash值: " + thirdBlock.hash); } }
每一个区块都必须要有自己的数字签名(Hash值),这个Hash值依赖于自身的信息(data)和上一个区块的数字签名(previousHash),但这个还不是区块链。
(链接:https://mvnrepository.com/artifact/com.google.code.gson/gson/2.8.6)
下面我们将这些创建的区块存储到数组中,这里我们引入一个gson包,目的是可以以JSON格式查看整个区块结构。
package blockChain;import java.util.ArrayList;import com.google.gson.GsonBuilder;public class MineChain {
public static ArrayListblockChain = new ArrayList (); public static void main(String[] args) {
System.out.println("创建我的第一个区块链........");
System.out.println();
//创建创世纪区块
blockChain.add(new Block("世纪区块","0"));
blockChain.add(new Block("第二块区块", blockChain.get(blockChain.size()-1).hash));
blockChain.add(new Block("第三块区块", blockChain.get(blockChain.size()-1).hash));
blockChain.add(new Block("第四块区块", blockChain.get(blockChain.size()-1).hash));
//将数组数据转换为JSON格式字符串
String blockChainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockChain);
System.out.println(blockChainJson);
}}
打印:
相信你看到这样的输出结构,对区块链的概念就有更深入的理解了,而这样的输出结构就更类似于我们期待的区块链的样子了。
检查区块链的完整性
在前面我们说过区块链中的有一个核心概念就是去中心化,每一个区块可能是在网络中的某一个节点中产生的。因此,如果其中某个节点把自己节点中的数据修改了,这该怎么办?还将该节点加入到区块链中去吗?
这里我们提出检查区块链完整性的方法,本地创建区块和区块链不会出现修改问题,所以不再测试。
isChainValid()方法(检查区块链的完整性)
思路:循环区块链中的所有区块并且比较当前节点的Hash是否与计算出来的Hash值相等,同时检查previousHash与前一个区块的Hash是否相等。
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
String hashTarget = new String(new char[difficulty]).replace('\0', '0');
//循环检查区块的Hash值
for(int i=1;i
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//比较注册过的Hash和计算的Hash
if(!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("当前Hash不相等");
return false;
}
//比较前一个Hash和当前注册的区块的前一个Hash
if(!previousBlock.hash.equals(currentBlock.previousHash)){
System.out.println("与前一个Hash值不相等");
return false;
}
}
return true; }
根据上述的理论,区块数据改变会导致整个区块链的“破裂”,也就是使得区块链变得无效。只要区块链中任何一个区块的一丝一毫改变都会导致这个方法函数返回false,也就证明了区块链无效。因此,为什么说区块链难篡改,也就是这么一个原因。
其实,在比特币网络中,所有的网络节点都会分享它们各自的区块链,然而最长的有效区块链才是被全网所统一承认的。如果有人恶意来篡改之前的数据,就需要创建一条比全网最长的还要长的区块链发布并呈现在网络中被大家所承认,从而达到篡改,可就目前单节点计算机的算力是很难比过除它之外节点算力之和的。但如果真有这样的情况,我们该怎么办呢?这就涉及到了区块链中另外一个重要的概念了——“工作量证明(POW))”,简单理解就是一份证明,用来确认你做过一定量的工作,通过对工作的结果进行认证来证明完成了相应的工作量。这个概念最早来自于Adam Back的一篇论文,主要应用于邮件过滤和比特币中防止双重支付。
POW(挖矿)
说到工作量证明,我们就不得不说挖矿了,其实挖矿就是工作量证明的俗称。具体的方式就是在区块中尝试不同的参数值直到它的Hash值是从一系列的0开始的。
既然说到这里了,那么就来提一提工作量证明的原理。
工作量证明系统主要特征是客户端需要做一定难度的工作得出一个结果,验证方却很容易通过结果来检查出客户端是不是做了相应的工作。这种方案的一个核心特征是不对称性:工作对于请求方是适中的,对于验证方则是易于验证的。它与验证码不同,验证码的设计出发点是易于被人类解决而不易被计算机解决。
举个技术底层的例子,给定的一个基本的字符串"Hello, world!",我们给出的工作量要求是,可以在这个字符串后面添加一个叫做nonce的整数值,对变更后(添加nonce)的字符串进行SHA256哈希运算,如果得到的哈希结果(以16进制的形式表示)是以"00000"开头的,则验证通过。为了达到这个工作量证明的目标。我们需要不停的递增nonce值,对得到的新字符串进行SHA256哈希运算。按照这个规则,我们需要经过1578202次计算才能找到恰好前5位为0的哈希散列。
那么如何实现这样一个工作量证明呢?
我们在Block 类中添加一个名为 nonce 的Integer 类型的工作量证明量,同时包含到我们计算数字签名 calculatehash() 中去,以及需要的挖矿函数 mineblock() 中去。
package ChainBlock;import java.util.Date;/* * 区块链的块类 */public class Block {
public String hash; //当前hash值 public String previousHash; //前一个区块的hash值 private String data; //当前区块存储的数据 private long timeStamp; // 时间戳(自1970/1/1起) private int nonce; //构造函数 public Block(String data,String previousHash) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash=calculateHash(); } // 计算数字签名 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');
System.out.println("工作量证明:"+target);
while(!hash.substring( 0, difficulty).equals(target)) {
nonce ++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash); }}
mineblock() 方法中引入了一个Integer值称为difficulty(难度),低的难度比如1和2,普通的电脑基本都可以马上计算出来,我的建议是在4-6之间进行测试,普通电脑大概会花费3秒时间,在莱特币中难度大概围绕在442592左右,而在比特币中每一次挖矿都要求大概在10分钟左右,当然根据所有网络中的计算能力,难度也会不断的进行修改。
我们在 MineChain 类 中增加 difficulty 这个静态变量。
public static int difficulty = 6;
这样我们必须修改主方法中让创建每个新区块时必须触发mineBlock()方法,而isChainValid()方法用来检查每个区块的hash值是否正确,整个区块链是否是有效的。
package ChainBlock;import java.util.ArrayList;import com.google.gson.GsonBuilder;public class NoobChain {
//使用链表来存储创建的区块 public static ArrayListblockchain = new ArrayList<>(); //挖矿难度 public static int difficulty = 6; public static void main(String[] args) {
//创建创世纪区块
blockchain.add(new Block("创建创世纪区块","0"));
System.out.println("尝试创建创世纪区块.....");
blockchain.get(0).mineBlock(difficulty);
for(int i=1;i<3;i++) {
blockchain.add(new Block("第"+(i+1)+"个区块", blockchain.get(blockchain.size()-1).hash));
System.out.println("尝试创建第"+(i+1)+"个区块.....");
blockchain.get(i).mineBlock(difficulty);
}
if(isChainValid()) {
System.out.println(" MineBlock 有效 :true");
}
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
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
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i-1);
//比较注册过的Hash和计算的Hash
if(!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("当前Hash不相等");
return false;
}
//比较前一个Hash和当前注册的区块的前一个Hash
if(!previousBlock.hash.equals(currentBlock.previousHash)){
System.out.println("与前一个Hash值不相等");
return false;
}
//检查是否解决了hash是被处理过的
if(!currentBlock.hash.substring(0,difficulty).equals(hashTarget)) {
System.out.println("这个区块不是我创建的....");
return false;
}
}
return true; }}
打印:
建议别将difficulty 设置到6以上。wuwuwu
经过测试增加一个新的区块(挖矿)必须花费一定时间,(difficulty=6)大概一分钟,根据difficulty 难度来看。
因此,如果有人想在你的区块链系统恶意篡改数据,他们的区块链因为工作量证明机制,他们的区块链就是无效的,同时他们也无法创建更长的区块链(篡改过的区块链无法赶上长链和有效链,除非他们比你网络中所有节点拥有的计算速度更大的计算速度,也许未来量子计算或者其他什么可以实现。),对于网络中诚实的区块链会在长链中也会更具有时间的优势。
ok! 咱们的第一个区块链就完成了。据此,你的区块链就可以用来存储数据,并根据数字签名将这些区块链接起来,如果创建新区块就需要工作量证明,所以区块链技术可以用来检查数据是否具有有效的同时检验是否未经篡改的。
恭喜你,你开发出了你的第一个区块链噢。
转载地址:https://blog.csdn.net/weixin_43452424/article/details/113268559 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
关于作者
