赞
踩
区块链(BlockChain)起源于比特币,2008年11月1日,一位自称中本聪(Satoshi Nakamoto)的人发表了《比特币:一种点对点的电子现金系统》一文,阐述了基于P2P网络技术、加密技术、时间戳技术、区块链技术等的电子现金系统的构架理念,这标志着比特币的诞生。
在比特币系统中,区块(Block)是一个一个的存储单元,记录了一定时间内各个区块节点全部的交流信息。各个区块之间通过随机散列(也称哈希算法)实现链接,后一个区块包含前一个区块的哈希值,随着信息交流的扩大,一个区块与一个区块相继接续,形成的结果就叫区块链(BlockChain)。
在知乎上看到一个漫画讲得很形象:
看着这些概念头脑中也难以形成一个具体的印象,不如实现一个区块链的demo来看一看。
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算法接收输入字符串计算并返回哈希字符串 * @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(); }
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; } }
@Test public 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); } }
public void mineBlock(int difficulty) {
//生成目标字符串:此处是包含指定数量(difficulty)个连续的0的字符串
String target = new String(new char[difficulty]).replace('\0', '0');
//检查当前块的hash值中从0到difficulty部分是否与target字符串相同,如果不相同,则修改nonce,重新计算hash
while(!hash.substring(0, difficulty).equals(target)) {
nonce++;
try {
hash = calculateHash();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("nonce:" + nonce);
}
if(!curBlock.hash.substring(0, difficulty).equals(target)) {
//如果不满足难度标准,也无效
System.out.println("当前块未满足难度标准!");
return false;
}
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(); } } }
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集合中获取对应的UTXO for(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; } //计算交易id try { 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; } }
public class TransactionInput {
/**
* 这笔交易输入从该ID的交易输出来(类似你曾经收到的某张钞票的编号)
*/
public String transactionOutputId;
/**
* UTXO 未花费交易输出(你要使用的具体钞票)
*/
public TransactionOutput UTXO;
public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}
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; //前面属性均赋值后再计算id try { 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); } }
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; } //建立动态数组用来记录作为交易输入使用的UTXO ArrayList<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; } }
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; } }
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而使用merkleRoot String 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,重新计算hash while(!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; } }
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; } }
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设为0 ZJChain.genesisTransaction.transactionId = "0"; //因为初始交易是凭空生成,与普通交易不同,所以很多参数需要手动设置 ZJChain.genesisTransaction.outputs.add(new TransactionOutput( ZJChain.genesisTransaction.recipient, ZJChain.genesisTransaction.value, ZJChain.genesisTransaction.transactionId)); //将本次交易输出添加到UTXOs ZJChain.UTXOs.put(ZJChain.genesisTransaction.outputs.get(0).id, ZJChain.genesisTransaction.outputs.get(0)); System.out.println("挖矿生成第一个区块。。。"); //前面的哈希值为手动设为0 Block 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(); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。