区块链与比特币概念

  • 区块链(BlockChain)起源于比特币,2008年11月1日,一位自称中本聪(Satoshi Nakamoto)的人发表了《比特币:一种点对点的电子现金系统》一文,阐述了基于P2P网络技术、加密技术、时间戳技术、区块链技术等的电子现金系统的构架理念,这标志着比特币的诞生。

  • 在比特币系统中,区块(Block)是一个一个的存储单元,记录了一定时间内各个区块节点全部的交流信息。各个区块之间通过随机散列(也称哈希算法)实现链接,后一个区块包含前一个区块的哈希值,随着信息交流的扩大,一个区块与一个区块相继接续,形成的结果就叫区块链(BlockChain)。

  • 在知乎上看到一个漫画讲得很形象:

  • 看着这些概念头脑中也难以形成一个具体的印象,不如实现一个区块链的demo来看一看。

  • 参考教程

  • 代码地址

1. 实现Block结构

  • 区块链(BlockChain)顾名思义,是将一个个区块(Block)链接起来形成。所以我们实现区块链的第一步是实现Block结构。
  • 区块链的链不是传统的通过指针等技术实现,而是通过哈希值来链接。所以一个Block中需要包含自身的哈希值,前一个Block的哈希值,还有自身的数据(这个数据与交易信息有关)。而当前块的哈希值是通过前一个块的哈希值、当前块的创建的时间以及当前块的数据三者根据加密算法计算得出的。所以Block中还要包含时间戳变量表示块创建时间。
  • Block类实现如下:
public class Block {public String hash;public String prevHash;private String data;private long timestamp;public ZJChain.Block(String data, String prevHash) {this.data = data;this.prevHash = prevHash;this.timestamp = new Date().getTime();//初始化哈希值必须在其它属性都已初始化之后try {this.hash = calculateHash();} catch (Exception e) {e.printStackTrace();}}/*** 计算当前块的哈希值* @return* @throws Exception*/public String calculateHash() throws Exception {String calculatedHash = StringUtil.applySha256(prevHash+data+timestamp);return calculatedHash;}
}
  • 应用SHA256算法来计算哈希值:
    /*** 应用SHA256算法接收输入字符串计算并返回哈希字符串* @param input* @return* @throws Exception*/public static String applySha256(String input) throws Exception {//返回实现指定摘要算法的 MessageDigest 对象。此处是SHA-256算法MessageDigest digest = MessageDigest.getInstance("SHA-256"); //getInstance有异常//根据输入的bytes数组完成哈希计算。byte[] hash = digest.digest(input.getBytes("UTF-8"));//getBytes有异常StringBuffer hexString = new StringBuffer();for(int i = 0; i < hash.length; i++) {//将生成的哈希字节数组每一字节(8bit)转换16进制数字符串String hex = Integer.toHexString(0xff & hash[i]);if(hex.length() == 1) {//当生成的16进制数只有一位时,在末尾添0,丢弃生成的16进制数(因为8位应是两位的16进制数,除非前面全为0)hexString.append("0");}//将每一个字节的转换结果连接hexString.append(hex);}return hexString.toString();}
  • SHA256:

    • SHA256的中文全称叫做“安全哈希算法”。所谓的“哈希”是Hash的音译,而Hash就是进行Hash函数的意思。通常来说,Hash函数的运算有一个共同特点。就是不论原始数据有多少位,只要通过Hash运算后,得到结果的长度都是固定的。
    • Hash函数的类型有很多种,包括SHA224、SHA256、SHA384、SHA512、SHA512/224、SHA512/256等。但是比特币仅选用了SHA256。这个256代表的意思是,数据经过函数运算后得到的结果必须是一个256位的2进制数字。
    • 每次Hash计算后得到的结果有三个要求:第一、输入Hash函数之前的数据和通过Hash函数处理过后得到的编号必须一一对应。第二、每一个编号的长度都是固定的。第三、我们无法通过编号倒推出数据的内容。
  • 因为哈希值的涉及到前一个块的哈希值,时间,数据等,所以哈希链表是无法在中间插入修改的。

2. 实现区块链(BlockChain)结构

  • 前面构造了区块(Block),现在把他们连接起来存储就形成了区块链。我们采用ArrayList结构来组织这些Block。
public class ZJChain {//blockChain为静态属性,所有对象都是在对同一个blockchain修改public static ArrayList<Block> blockChain = new ArrayList<Block>();/*** 检查区块链的有效性* @return*/public boolean isChainValid() {Block curBlock;Block prevBlock;//遍历blockchain,从1开始,保证prevblock的有效性for(int i = 1; i < blockChain.size(); i++) {curBlock = blockChain.get(i);prevBlock = blockChain.get(i - 1);try {//检查hash值计算有效性if(!curBlock.hash.equals(curBlock.calculateHash())) {System.out.println("block的hash值计算错误");return false;}} catch (Exception e) {e.printStackTrace();}//检查hash值前后对应关系正确性if(!prevBlock.hash.equals(curBlock.prevHash)) {System.out.println("当前block与前面block的hash值不对应");return false;}}return true;}/*** 向区块链中添加块* @param block*/public void addBlock(Block block) {blockChain.add(block);}/*** 将blockChain转换为json字符串本地存储* @return*/public String toJson() {String blockChainString = JSON.toJSONString(blockChain);return blockChainString;}
}
  • 因为ArrayList是内存中的数据结构,需要长期保存的话需要转换为JSON字符串写入文件中保存,通过alibaba的fastjson包实现。
  • 构建区块链的测试:
   @Testpublic void test1() {//初始化区块链ZJChain zjChain = new ZJChain();//向区块链中添加10个块for(int i = 0; i < 10; i++) {//创建新块Block block;if(zjChain.blockChain.size() == 0) {block = new Block("Block: " + i, "0");} else {block = new Block("Block: " + i, zjChain.blockChain.get(zjChain.blockChain.size() - 1).hash);}zjChain.addBlock(block);}for(ZJChain.Block block : zjChain.blockChain) {System.out.println("hash: " + block.hash + " prevHash: " + block.prevHash);}}

3. 准备挖矿!!!

  • 提起比特币、区块链,便离不开挖矿这个话题。那什么是挖矿?比特币挖矿就是找到一个随机数(Nonce)参与哈希运算Hash,使得最后得到的哈希值符合难度要求(在很多种组合中试出满足要求的组合, 有一点运气成分),用公式表示就是Hash <= target。具体的说就是使生成的哈希值的开头至少有指定数目个0。实现如下:
    public void mineBlock(int difficulty) {//生成目标字符串:此处是包含指定数量(difficulty)个连续的0的字符串String target = new String(new char[difficulty]).replace('\0', '0');//检查当前块的hash值中从0到difficulty部分是否与target字符串相同,如果不相同,则修改nonce,重新计算hashwhile(!hash.substring(0, difficulty).equals(target)) {nonce++;try {hash = calculateHash();} catch (Exception e) {e.printStackTrace();}}System.out.println("nonce:" + nonce);}
  • 简单来看挖矿难度的高低就是生成区块头的哈希值有多少0,前面固定的0越多,满足条件的值就越少,计算得到符合标准的hash值的概率就低,宏观上说需要计算的次数就越多。difficulty每增加1,运算量都是呈几何速率增加,十分恐怖。
  • 当难度为4:
  • 当难度为5:
  • 当难度为6:(好家伙,直接跑了8分多钟。。。)
  • 对比可以看出,难度增加1,运算量(nonce可以表示运算的次数)直接增加一个量级,怪不得作为工作量衡量的标准(proof-of-work)。
  • 这里还可以看出,挖矿之所以很耗算力,并不是因为这个哈希计算本身有多么复杂,而是它是需要重复这个哈希计算很多次(我这随便提一点难度,都到千万级了。。。)直到满足要求。这也是为什么GPU挖矿效率比CPU高的原因:其实不是GPU运算速度比CPU快,而是GPU运算的数据是单一的,是经过CPU运算往后分离出来的单一数据。CPU运算的所需求的东西许多,而且不是单一的某种数据。CPU可以运行更复杂的指令。如果是做一个简单的数学计算,一个最大16核的CPU最多只能同时跑16个线程,而一个普通的GPU就可以同时跑3000多个线程,所以做简单数学,GPU就比CPU要快几个数量级,而生成区块要做的哈希计算偏偏就是一个很简单的数学题。
  • 另外,在检查区块链的有效性(isChainValid)的时候,还需要增加检查hash值是否满足难度要求这一点:
    if(!curBlock.hash.substring(0, difficulty).equals(target)) {//如果不满足难度标准,也无效System.out.println("当前块未满足难度标准!");return false;}

4. 创建钱包

  • 比特币是一种点对点的电子现金系统,没有实物形态,可以存储在比特币钱包里。日常生活中,钱包是用来放钱的,但比特币钱包里却没有比特币,而只是确立比特币所有权的工具:比特币被记录在比特币网络的区块中(即Block,实际记录的也不是具体的比特币而是一笔笔交易记录)中,比特币的所有权是通过数字密钥、比特币地址和数字签名(接下来要实现的)来确立的。
  • 数字密钥并不存储在网络中,而是由用户生成并存储在一个文件或简单的数据库中,称为钱包。比特币钱包里存储着你的比特币信息,包括比特币地址(类似于你的银行卡账号)和数字秘钥。
  • 数字秘钥是用公钥加密创建一个密钥对,用于控制比特币的获取。密钥对包括一个私钥,和由其衍生出的唯一的公钥。公钥用于接收比特币,而私钥用于生成比特币支付时的交易签名(类似于你银行卡的密码)。支付比特币时,比特币的当前所有者需要在交易中提交其公钥和签名(每次交易的签名都不同,但均从同一个私钥生成)。比特币网络中的所有人都可以通过所提交的公钥和签名进行验证,并确认该交易是否有效,即确认支付者在该时刻对所交易的比特币拥有所有权。比特币私钥就用来保护你的钱包,如果私钥丢失,你将永远失去这笔比特币。
  • Wallet实现:
public class Wallet {public PublicKey publicKey;public PrivateKey privateKey;public Wallet() {generateKeyPair();}/*** 生成公私钥*/public void generateKeyPair() {try {//指定算法ECDSA生成密钥对KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");SecureRandom random = SecureRandom.getInstance("SHA1PRNG");ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");//初始化并生成密钥对keyGen.initialize(ecSpec, random);KeyPair keyPair = keyGen.generateKeyPair();//获取公私钥privateKey = keyPair.getPrivate();publicKey = keyPair.getPublic();} catch (Exception e) {e.printStackTrace();}}
}
  • ECDSA:椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm,缩写ECDSA)是一种被广泛应用于数字签名的加密算法。

5. 实现交易(Transaction)

  • 既然是一种货币,那么最重要的功能就是用来交易,作为最早出现的加密货币,比特币采用了 UTXO 模型作为其底层存储的数据结构,其全称为 Unspent Transaction output,也就是未被使用的交易输出。
  • UTXO 模型的加密货币中,某一个账户中的余额并不是由一个数字表示的,而是由当前区块链网络中所有属于当前账户的 UTXO 组成的。每一个UTXO就跟现实世界中的一张纸钞类似,一个UTXO只能用一次,如果数额超出需要支付的价钱则发给自己新的UTXO(自己给自己找零)。
  • 比特币实质上没有存储货币,它有的不过是在一个个交易中记录的数字变化,而这个数字的源头来自矿工。我们挖矿产生的Block实质上是一个账本,其中记录一笔笔交易的记录。每挖出一个Block,就会从无到有生成可以用于交易的value(交易中的数字)给矿工,这便是比特币产生的地方。
  • 交易中每个人用钱包来保管自己的UTXO,以及公私钥。公钥就类似于银行卡号,别人通过指定你的公钥来转账给你。私钥类似于你的密码,但是又有不同:每次你发起转账,都需要通过你的私钥结合交易的内容来生成一个签名,通过验证签名与公钥来确认身份。签名与交易是一一对应的,即使暴露了也无妨,但是私钥是绝对不能泄漏的。
  • UTXO 其实就是交易的一部分,基于 UTXO 模型的交易由输入和输出两个部分组成:UTXO 模型中的每一笔交易都是由多个交易输入组成的,这些输入其实就是 UTXO + 签名:属于某个人的UTXO加上这个人的签名(由秘钥生成)则可以表示这个人授权使用这个UTXO。每一个交易都可能会有多个输出,每一个输出都可以指向不同的地址,其中也有当前输出包含的值 value,这个value也就是比特币的计量数。
  • 交易实现:
    • Transaction.java
public class Transaction {/*** 交易号*/public String transactionId;/*** 交易序号,用于记录交易数量*/public static int sequence = 0;/*** 发送方的地址/public key*/public PublicKey sender;/*** 接收方的地址/public key*/public PublicKey recipient;/*** 交易额*/public float value;/*** 发送方的签名*/public byte[] signature;/*** 本次交易所涉及到的所有交易输入*/public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();/*** 本次交易所涉及到的所有交易输出(第0位output是发给别人的,第1位output是发给自己的)*/public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) {this.sender = from;this.recipient = to;this.value = value;this.inputs = inputs;}/*** 计算用于标识交易的transactionId* @return* @throws Exception*/private String calculateHash() throws Exception {sequence++;return StringUtil.applySha256(StringUtil.getStringFromKey(sender) +StringUtil.getStringFromKey(recipient) +value + sequence);}/*** 根据私钥和其它数据生成数字签名* @param privateKey*/public void generateSignature(PrivateKey privateKey) {String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + value;signature = StringUtil.applyECDSASig(privateKey, data);}/*** 检查发送方数字签名,以验证数据没有损坏或者被修改* @return*/public boolean verifySignature() throws Exception {String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + value;return StringUtil.verifyECDSASig(sender, data, signature);}/*** 实现一次交易* @return*/public boolean processTransaction() {//验证交易的发送方的数字签名是否有效try {if(!verifySignature()) {System.out.println("交易签名验证失败");return false;}} catch (Exception e) {e.printStackTrace();}//根据交易输出的id从整个区块链中有效的UTXO集合中获取对应的UTXOfor(TransactionInput input : inputs) {input.UTXO = ZJChain.UTXOs.get(input.transactionOutputId);}//检测交易输入额是否符合最小标准if(getInputsValue() < ZJChain.minimumTransaction) {System.out.println("交易输入数额:" + getInputsValue() + " 小于最小交易额");return false;}//计算交易输入还有多少剩余(类似找零)float leftover = getInputsValue() - value;if(leftover < 0) {System.out.println("金额不足,交易终止!");return false;}//计算交易idtry {transactionId = calculateHash();} catch (Exception e) {e.printStackTrace();}//建立指向收款方的交易输出outputs.add(new TransactionOutput(this.recipient, value, transactionId));//如果需要找零才找零if(leftover > 0) {//建立指向发送方的交易输出(将交易输出中没有用完的还给自己,实现找零功能)outputs.add(new TransactionOutput(this.sender, leftover, transactionId));}//将本次交易中的所有交易输出添加到整个区块链的UTXO集合中(实现向所有用户通报这笔交易)for(TransactionOutput output : outputs) {ZJChain.UTXOs.put(output.id, output);}//移除整个区块链中本次交易中所有交易输入所对应的UTXO(每个UTXO只能用来支付一次)for(TransactionInput input : inputs) {if(input.UTXO != null) {ZJChain.UTXOs.remove(input.UTXO.id);}}return true;}/*** 获取所有交易输入中的总价值(计算拥有的钱的总数)* @return*/public float getInputsValue() {float sum = 0;for(TransactionInput i : inputs) {if(i.UTXO != null) {sum += i.UTXO.value;}}return sum;}/*** 获取所有交易输出中的总价值(要支付的钱的总数)* @return*/public float getOutputsValue() {float sum = 0;for(TransactionOutput output : outputs) {sum += output.value;}return sum;}}
  • TransactionInput.java
public class TransactionInput {/*** 这笔交易输入从该ID的交易输出来(类似你曾经收到的某张钞票的编号)*/public String transactionOutputId;/*** UTXO 未花费交易输出(你要使用的具体钞票)*/public TransactionOutput UTXO;public TransactionInput(String transactionOutputId) {this.transactionOutputId = transactionOutputId;}}
  • TransactionOutput.java
public class TransactionOutput {/*** 交易输出编号id*/public String id;/*** 这笔交易输出的接收方公钥(类似收款方银行账号)*/public PublicKey recipient;/*** 交易输出额*/public float value;/*** 创建这个交易输出的交易id*/public String parentTransactionId;public TransactionOutput(PublicKey recipient, float value, String parentTransactionId) {this.recipient = recipient;this.value = value;this.parentTransactionId = parentTransactionId;//前面属性均赋值后再计算idtry {this.id = StringUtil.applySha256(StringUtil.getStringFromKey(recipient) + this.value + this.parentTransactionId);} catch (Exception e) {e.printStackTrace();}}//检查UTXO(未消费的交易输出)是否是指定publickey的拥有者的public boolean isMine(PublicKey publicKey) {return (publicKey == recipient);}
}

6. 完善钱包(Wallet)

  • 一个钱包中保存着每个账户的公私钥,并且具备统计该账户拥有的UTXO并计算余额的能力,此外钱包还有创建交易发起支出的能力。
public class Wallet {//公私钥public PublicKey publicKey;public PrivateKey privateKey;/*** 钱包存储属于自己的UTXO(未消费交易输出)*/public HashMap<String, TransactionOutput> UTXOs = new HashMap<>();public Wallet() {generateKeyPair();}/*** 生成公私钥*/public void generateKeyPair() {try {//指定算法ECDSA生成密钥对KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA", "BC");SecureRandom random = SecureRandom.getInstance("SHA1PRNG");ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");//初始化并生成密钥对keyGen.initialize(ecSpec, random);KeyPair keyPair = keyGen.generateKeyPair();//获取公私钥privateKey = keyPair.getPrivate();publicKey = keyPair.getPublic();} catch (Exception e) {e.printStackTrace();}}/*** 计算钱包的总余额* @return*/public float getBalance() {float sum = 0;//遍历Map集合获取键值对对象for(Map.Entry<String, TransactionOutput> item : ZJChain.UTXOs.entrySet()) {TransactionOutput UTXO =  item.getValue();//检查该UTXO是否属于该钱包if(UTXO.isMine(publicKey)) {//添加到钱包的UTXOs集合中UTXOs.put(UTXO.id, UTXO);sum += UTXO.value;}}return sum;}/*** 创建交易,支出* @param _recipient* @param value* @return*/public Transaction sendFunds(PublicKey _recipient, float value) {//检查余额是否足够if(getBalance() < value) {System.out.println("余额不足,交易终止!");return null;}//建立动态数组用来记录作为交易输入使用的UTXOArrayList<TransactionInput> inputs = new ArrayList<>();//查找钱包的UTXO,直到总金额达到要支付的金额float total = 0;for(Map.Entry<String, TransactionOutput> item : UTXOs.entrySet()) {TransactionOutput UTXO = item.getValue();total += UTXO.value;inputs.add(new TransactionInput(UTXO.id));if(total >= value) {break;}}//创建交易Transaction newTransaction = new Transaction(publicKey, _recipient, value, inputs);newTransaction.generateSignature(privateKey);//将已经使用的UTXO从钱包中移除for(TransactionInput input : inputs) {UTXOs.remove(input.transactionOutputId);}return  newTransaction;}}

7. 完善其它类

  • 在StringUtil工具类中,增加生成和验证数字签名的功能。同时还有根据添加到区块中的交易记录生成merkleRoot的功能,merkelRoot用于标识区块及其中的唯一性。
public class StringUtil {/*** 应用SHA256算法接收输入字符串计算并返回哈希字符串* @param input* @return* @throws Exception*/public static String applySha256(String input) throws Exception {//返回实现指定摘要算法的 MessageDigest 对象。此处是SHA-256算法MessageDigest digest = MessageDigest.getInstance("SHA-256"); //getInstance有异常//根据输入的bytes数组完成哈希计算。byte[] hash = digest.digest(input.getBytes("UTF-8"));//getBytes有异常StringBuffer hexString = new StringBuffer();for(int i = 0; i < hash.length; i++) {//将生成的哈希字节数组每一字节(8bit)转换16进制数字符串String hex = Integer.toHexString(0xff & hash[i]);if(hex.length() == 1) {//当生成的16进制数只有一位时,在末尾添0,丢弃生成的16进制数(因为8位应是两位的16进制数,除非前面全为0)hexString.append("0");}//将每一个字节的转换结果连接hexString.append(hex);}return hexString.toString();}/*** 根据秘钥获得字符串* @param key* @return*/public static String getStringFromKey(Key key) {return Base64.getEncoder().encodeToString(key.getEncoded());}/*** 根据ECDSA算法,由privatekey生成数字签名(字节数组)* @param privateKey* @param data* @return*/public static byte[] applyECDSASig(PrivateKey privateKey, String data) {Signature dsa;//提前声明变量,避免最后不能返回有效值byte[] output = new byte[0];try{dsa = Signature.getInstance("ECDSA", "BC");dsa.initSign(privateKey);byte[] strByte = data.getBytes();dsa.update(strByte);byte[] realSig = dsa.sign();output = realSig;} catch (Exception e) {e.printStackTrace();}return output;}/*** 由publickey验证数字签名是否正确* @param publicKey* @param data* @param signature* @return*/public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) throws Exception {Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC");ecdsaVerify.initVerify(publicKey);ecdsaVerify.update(data.getBytes());return ecdsaVerify.verify(signature);}/*** 根据交易生成merkleRoot标志区块* @param transactions* @return*/public static String getMerkleRoot(ArrayList<Transaction> transactions) {int count = transactions.size();ArrayList<String> previousTreeLayer = new ArrayList<>();for(Transaction transaction : transactions) {previousTreeLayer.add(transaction.transactionId);}ArrayList<String> treeLayer = previousTreeLayer;while(count > 1) {treeLayer = new ArrayList<>();for(int i = 1; i < previousTreeLayer.size(); i++) {try {treeLayer.add(StringUtil.applySha256(previousTreeLayer.get(i - 1) + previousTreeLayer.get(i)));} catch (Exception e) {e.printStackTrace();}count = treeLayer.size();previousTreeLayer = treeLayer;}}String merkleRoot = (treeLayer.size() == 1) ? treeLayer.get(0) : "";return merkleRoot;}
}
  • Block类的设置中,用merkleRoot来做数据生成哈希值,同时新增将交易记录添加到区块中的功能。
public class Block {public String hash;public String prevHash;public long timestamp;public int nonce; //用于挖矿的变量public ArrayList<Transaction> transactions = new ArrayList<>();//merkleRoot充当data的作用(因为区块block本质就是个账本,用交易来充当数据最合理)public String merkleRoot;public Block(String prevHash) {this.prevHash = prevHash;this.timestamp = new Date().getTime();//初始化哈希值必须在其它属性都已初始化之后try {this.hash = calculateHash();} catch (Exception e) {e.printStackTrace();}}/*** 计算当前块的哈希值* @return* @throws Exception*/public String calculateHash() throws Exception {//取消使用data生成hash而使用merkleRootString calculatedHash = StringUtil.applySha256(prevHash+merkleRoot+timestamp+nonce);return calculatedHash;}/*** 挖矿计算* @param difficulty*/public void mineBlock(int difficulty) {//挖矿前计算merkleRoot值merkleRoot = StringUtil.getMerkleRoot(transactions);//生成目标字符串:此处是包含指定数量(difficulty)个连续的0的字符串String target = new String(new char[difficulty]).replace('\0', '0');//检查当前块的hash值中从0到difficulty部分是否与target字符串相同,如果不相同,则修改nonce,重新计算hashwhile(!hash.substring(0, difficulty).equals(target)) {nonce++;try {hash = calculateHash();} catch (Exception e) {e.printStackTrace();}}System.out.println("nonce:" + nonce);}/*** 在将交易添加到块时执行交易* @param transaction* @return*/public boolean addTransaction(Transaction transaction) {//验证交易的有效性if(transaction == null) return false;if(!prevHash.equals("0")) {if(!transaction.processTransaction()) {System.out.println("交易处理失败!");return false;}}//将交易记录添加到区块中transactions.add(transaction);System.out.println("交易成功添加到Block中!");return true;}
}
  • ZJchain类:加强验证区块链有效功能,在将一个块添加到区块前进行挖矿计算。
public class ZJChain {                                                                                   //blockChain为静态属性,所有对象都是在对同一个blockchain修改                                                            public static ArrayList<Block> blockChain = new ArrayList<Block>();                                  public static int difficulty = 5;                                                                    /**                                                                                                  * 用于记录所有有效的UTXO,键是String类型的TransactionOutputId                                                      */                                                                                                  public static HashMap<String, TransactionOutput> UTXOs = new HashMap<>();                            /**                                                                                                  * 每次交易的最小交易额                                                                                        */                                                                                                  public static float minimumTransaction = 0.1f;                                                       public static Wallet walletA;                                                                        public static Wallet walletB;                                                                        /**                                                                                                  * 初始交易(创建区块链时初始化第一笔交易)                                                                              */                                                                                                  public static Transaction genesisTransaction;                                                        /**                                                                                                  * 检查区块链的有效性                                                                                         * @return                                                                                           */                                                                                                  public boolean isChainValid() {                                                                      Block curBlock;                                                                                  Block prevBlock;                                                                                 //用于检验挖矿难度是否达标的字符串                                                                               String target = new String(new char[difficulty]).replace('\0', '0');                             HashMap<String, TransactionOutput> tempUTXOs = new HashMap<>();                                  tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));          //遍历blockchain,从1开始,保证prevblock的有效性                                                              for(int i = 1; i < blockChain.size(); i++) {                                                     curBlock = blockChain.get(i);                                                                prevBlock = blockChain.get(i - 1);                                                           try {                                                                                        //检查hash值计算有效性                                                                           if(!curBlock.hash.equals(curBlock.calculateHash())) {                                    System.out.println("block的hash值计算错误");                                               return false;                                                                        }                                                                                        } catch (Exception e) {                                                                      e.printStackTrace();                                                                     }                                                                                            //检查hash值前后对应关系正确性                                                                           if(!prevBlock.hash.equals(curBlock.prevHash)) {                                              System.out.println("当前block与前面block的hash值不对应");                                          return false;                                                                            }                                                                                            if(!curBlock.hash.substring(0, difficulty).equals(target)) {                                 //如果不满足难度标准,也无效                                                                          System.out.println("当前块未满足挖矿难度标准!");                                                     return false;                                                                            }                                                                                            TransactionOutput tempOutput;                                                                for(int t = 0; i < curBlock.transactions.size(); t++) {                                      Transaction currentTransaction = curBlock.transactions.get(t);                           //检查交易的签名                                                                                try {                                                                                    if(!currentTransaction.verifySignature()) {                                          System.out.println("第" + t + "个交易的签名无效!");                                       return false;                                                                    }                                                                                    } catch (Exception e) {                                                                  e.printStackTrace();                                                                 }                                                                                        //检查交易的交易输出额和交易输入额是否相等                                                                   if(!(currentTransaction.getInputsValue() == currentTransaction.getOutputsValue())) {     System.out.println("第" + t + "个交易的交易输出与交易输入额不相等!");                                  return false;                                                                        }                                                                                        //检查交易输入是否正确(交易输入要么来源于初始交易(和矿工),要么来源于其它交易输出)                                             for(TransactionInput input : currentTransaction.inputs) {                                tempOutput = tempUTXOs.get(input.transactionOutputId);                               if(tempOutput == null) {                                                             System.out.println("第" + t + "个交易的交易输入不存在!");                                    return false;                                                                    }                                                                                    if(input.UTXO.value != tempOutput.value) {                                           System.out.println("第" + t + "个交易的交易输入的值无效!");                                   return false;                                                                    }                                                                                    tempUTXOs.remove(input.transactionOutputId);                                         }                                                                                        //将交易输出加入临时UTXOs                                                                         for(TransactionOutput output : currentTransaction.outputs) {                             tempUTXOs.put(output.id, output);                                                    }                                                                                        if(currentTransaction.outputs.get(0).recipient != currentTransaction.recipient) {        System.out.println("第" + t + "个交易的交易输出目的方错误!");                                      return false;                                                                        }                                                                                        if(currentTransaction.outputs.get(1).recipient != currentTransaction.sender) {           System.out.println("第" + t + "个交易的找零的交易输出没有发给发送者!");                                 return false;                                                                        }                                                                                        }                                                                                            }                                                                                                System.out.println("区块链有效!");                                                                   return true;                                                                                    }                                                                                                    /**                                                                                                  * 向区块链中添加块                                                                                          * @param newBlock                                                                                   */                                                                                                  public void addBlock(Block newBlock) {                                                               //先完成挖矿工作才能加入区块链中                                                                                newBlock.mineBlock(difficulty);                                                                  blockChain.add(newBlock);                                                                        }                                                                                                    /**                                                                                                  * 将blockChain转换为json字符串本地存储                                                                         * @return                                                                                           */                                                                                                  public String toJson() {                                                                             String blockChainString = JSON.toJSONString(blockChain);                                         return blockChainString;                                                                         }                                                                                                    /**                                                                                                  * 设置挖矿难度                                                                                            * @param difficulty                                                                                 */                                                                                                  public void setDifficulty(int difficulty) {                                                          this.difficulty = difficulty;                                                                    }                                                                                                    }

8. 测试

  • 在一个测试类中走一遍基本功能:
    public void test2() {//用于支撑验证签名的功能Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //初始化区块链ZJChain zjChain = new ZJChain();//创建钱包Wallet walletA = new Wallet();Wallet walletB = new Wallet();//初始交易的钱包Wallet coinBase = new Wallet();//创建初始交易//最初的交易的value从coinbase凭空出现System.out.println("第一次交易:coinbase向walletA转账100");ZJChain.genesisTransaction = new Transaction(coinBase.publicKey, walletA.publicKey, 100f, null);//生成coinbase对此次交易的签名ZJChain.genesisTransaction.generateSignature(coinBase.privateKey);//初始交易id设为0ZJChain.genesisTransaction.transactionId = "0";//因为初始交易是凭空生成,与普通交易不同,所以很多参数需要手动设置ZJChain.genesisTransaction.outputs.add(new TransactionOutput(ZJChain.genesisTransaction.recipient,ZJChain.genesisTransaction.value,ZJChain.genesisTransaction.transactionId));//将本次交易输出添加到UTXOsZJChain.UTXOs.put(ZJChain.genesisTransaction.outputs.get(0).id,ZJChain.genesisTransaction.outputs.get(0));System.out.println("挖矿生成第一个区块。。。");//前面的哈希值为手动设为0Block genesis = new Block("0");//添加交易genesis.addTransaction(ZJChain.genesisTransaction);//将该块加入区块链中zjChain.addBlock(genesis);System.out.println("第二笔交易: walletA向walletB转账20");System.out.println("WalletA的余额:" + walletA.getBalance());System.out.println("WalletB的余额:" + walletB.getBalance());//新生成一个区块用于记账System.out.println("挖矿生成第二个区块。。。");Block block1 = new Block(genesis.hash);block1.addTransaction(walletA.sendFunds(walletB.publicKey, 20f));zjChain.addBlock(block1);System.out.println("第二笔交易结束");System.out.println("WalletA的余额:" + walletA.getBalance());System.out.println("WalletB的余额:" + walletB.getBalance());zjChain.isChainValid();}
  • 结果:

9. 写在最后

  • 不得不说,比特币的出现是一次思维的革命。写这个小项目的时候虽然我没有去涉及高深的密码学算法,但是其中蕴含的天才思维逻辑也依旧让我震撼不已(同时也迷糊不已,即使已经粗略写了一下,很多地方还是不能理解得很清楚。。。)。
  • 再想到比特币的创始人至今不知道其真实姓名,此时看这神秘的“中本聪”仿佛有了看金庸笔下的隐世高手的感觉。这或许就是程序员的浪漫吧!

Java实现区块链Demo相关推荐

  1. 招募英雄-招募前端开发/Java开发/区块链开发工程师

    大量招聘前端开发/Java开发/区块链开发工程师,详情见大图,简历速速砸来哦.可以发送简历到图中的邮箱.

  2. web3j官网的完整中文翻译(java开发区块链以太坊应用的开源类库)

    2019独角兽企业重金招聘Python工程师标准>>> web3j是一个轻量级.高度模块化.响应式.类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以 ...

  3. java开发区块链只需150行代码

    本文目的是通过java实战开发教程理解区块链是什么.将通过实战入门学习,用Java自学开发一个很基本的区块链,并在此基础上能扩展如web框架应用等.这个基本的java区块链也实现简单的工作量证明系统. ...

  4. hyperledger部署java_Hyperledger Fabric(超级账本) Java 开发区块链 部署运行智能合约

    部署并运行 Java 链代码示例 您已经定义并启动了本地区块链网络,而且已构建 Java shim 客户端 JAR 并安装到本地 Maven 存储库中,现在已准备好在之前下载的 Hyperledger ...

  5. java开发区块链_使用Java语言从零开始创建区块链

    目前网络上关于区块链入门.科普的文章不少,本文就不再赘述区块链的基本概念了,如果对区块链不是很了解的话,可以看一下我之前收集的一些入门学习资源: 对区块链技术感到新奇的我们,都想知道区块链在代码上是怎 ...

  6. Java 与 区块链技术_java区块链技术有哪些主要的特点和应用

    java区块链技术有哪些主要的特点是什么,人们对于区块链技术比较熟悉,但是却并不知道它到底有哪一些概念,java区块链技术有哪些人有接触过呢?有很多人都会觉得区块链技术就好像是我们最初所认识的互联网一 ...

  7. java实现区块链中的区块hash难度系数的设计

    区块链的难度系数:是设计区块链挖矿难易的关键因子,难度系数越低,挖矿越容易.难度系数越高,相应越难.例如比特币的难度系数是18. 难度系数一般是hash值的前置0的个数. java 区块链中设计合理的 ...

  8. java和区块链哪个难_java 区块链中设计合理的难度系数

    区块链的难度系数:是设计区块链挖矿难易的关键因子,难度系数越低,挖矿越容易.难度系数越高,相应越难.例如比特币的难度系数是18. 难度系数一般是hash值的前置0的个数. java 区块链中设计合理的 ...

  9. 区块链技术,模仿B特币,java模拟区块链技术,(摘自GitHub)

    准备一个钱包 在加密货币中,在区块链作为交易时,货币所有权可以进行转移,每个参与者都有一个自己私有的地址来发送或者是收取货币.,钱包可以存储这些地址.因此钱包就是可以在区块链上进行新交易的软件. Do ...

最新文章

  1. 2D图片3秒变立体,变换视角流畅自然:Adobe实习生的智能景深算法,登上顶级期刊...
  2. python socketpool:通用连接池
  3. Eclipse下编写C++程序——CDT环境搭建
  4. Android中设置RecyclerView的每一个item的单项点击事件
  5. XCTF WEB webshell
  6. 用函数式编程,从0开发3D引擎和编辑器(三):初步需求分析
  7. 鹅厂后台开发工程师的工作日常
  8. 易语言 java支持_开源Java客户端可以连接易语言服务器
  9. 前端学习(2929):vue改变多个样式的组合
  10. 【Linux】Ubuntu安装命令
  11. ⌊N/1⌋,⌊N/2⌋,...⌊N/N⌋的值的集合的分析
  12. Java并发编程实战-高屋建瓴
  13. Windows 10注册表损坏该如何修复?
  14. 面试总结系列(一)------ 国际化中台事业部
  15. 还只会用小黄鸭解压?看来你不是一个时髦的程序员!
  16. 基于Visual Studio 2017的opengl安装
  17. ORACLE+10G+win7下载地址
  18. javascript函数总结
  19. 百度网盘批量重命名文件免费脚本---2020.07
  20. From tomorrow

热门文章

  1. 图像分割评价指标: Dice, MIoU, MPA等
  2. JAVA 8 免费版本下载,附安装教程
  3. 【地理空间】轨迹匹配算法学习总结
  4. win10提示没有注册类,什么都打不开了
  5. 基于Java+Springboot+vue在线版权登记管理系统设计实现
  6. 关于漏洞扫描器AWVS的安装和破解
  7. 硬核梳理2022Java面试题,图文并茂,这还拿不下你?
  8. 使用readelf和objdump查看ELF常见段
  9. Python上的高等数学实验
  10. 环旭拟约4.5亿美元收购欧洲第二大EMS公司