用Java模拟区块链开发和比特币挖掘
使用到的.java文件及其用处
Block.java 生成一个新的区块
NoobChain.java 将生成的区块穿成链状,就是区块链
StringUtil.java 对各种字符串的操作,包括生成hashcode,包括生成merkle树
Tansaction.java 这是一个交易类,生成一个新交易
TransactionInput.java 交易输入类,比特币规定每一笔新的交易的输入必须是某笔交易未花费的输出
TransactionOutput.java 交易输出类
Wallet.java 使用者的钱包,在这里,钱包就相当于进行交易的人
1、看看每一个文件的代码,而且他们都有什么具体功能:
Block.java:
package My_Block;
import java.util.ArrayList;
import java.util.Date;
public class Block {
public String hash;
public String previousHash;
public String merkleRoot;
public ArrayList<Transaction> transactions=new ArrayList<Transaction>();//数据为交易的列表
//private String data;
private long timeStamp;//时间戳
private int nonce;
public Block(String previousHash) {
this.previousHash=previousHash;
this.timeStamp=new Date().getTime();//返回一个long型的毫秒数,1970年01月01日8点以来的毫秒数
this.hash=calculateHash();
}
public String calculateHash() {
//String calculatedHash=StringUtil.applySha256(previousHash+Long.toString(timeStamp)+Integer.toString(nonce)+data);
String calculatedHash=StringUtil.applySha256(previousHash+Long.toString(timeStamp)+Integer.toString(nonce)+merkleRoot);//生成这个区块的hash值
return calculatedHash;
}
public void mineBlock(int difficulty) {
//创建一个string值由难度的位数决定
merkleRoot=StringUtil.getMerkleRoot(transactions);//对于这个交易列表求出一个merklehash值
String target=new String(new char[difficulty]).replace('\0', '0');//创建一个字符串前几个字符全是0
while(!hash.substring(0,difficulty).equals(target)) {//string的substring方法将字符串从0-difficulty索引之间的子字符串拿出来
nonce++;
hash=calculateHash();
}
System.out.println("Block Mined!!!!! : "+hash);
}
public boolean addTransaction(Transaction transaction) {
//处理交易并检查是否有效,创世纪区块将被忽略
if(transaction==null) {
return false;
}
if(previousHash!="0") {
if(transaction.processTransaction()!=true) {//在这里通过交易信息进行交易
System.out.println("Transaction failed to proess.Discarded");
return false;
}
}
transactions.add(transaction);//向交易信息列表中添加这次交易
System.out.println("Transaction Successfully added to Block");
return true;
}
}
Block.java文件主要功能就是生成这个区块的信息,这个区块的hash值是上个区块的hash值+一个随机数+当前的时间戳+这个区块所存储的交易信息对应的哈希值merklehash通过sha256加密生成一个新的字符串,这个字符串就是当前区块的hash值。在calculateHash()函数中调用StringUtil类中的方法生成新hash值。
是否可以创建一个区块的条件是:计算机进行一种特殊的算法计算,当计算出来的hash值的前几位0的个数等于设置的难度梯度要求的0的个数是,这个区块将被创建出来,并且添加到区块链中
NoobChain.java:
package My_Block;
import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
//保存了一个额外的集合称之为未使用的交易作为可用的输入
public static HashMap<String,TransactionOutput> UTXOs=new HashMap<String,TransactionOutput>();//未使用的输出集合
public static int difficulty = 6;
public static double time = 0;
public static float minimumTransaction=0.1f;
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction;
public static void main(String[] args) {
// 调用Bouncey castle作为安全性的提供类
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// 创建两个钱包
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase=new Wallet();//创建一对私钥和公钥
//创建创世纪交易,将100个货币发给walletA
genesisTransaction=new Transaction(coinbase.publicKey,walletA.publicKey,100f,null);
genesisTransaction.generateSignature(coinbase.privateKey);//对创世纪交易进行签名
genesisTransaction.transactionID="0";//默认设置创世纪交易的输入为0
//手动添加交易输出
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient,genesisTransaction.value,genesisTransaction.transactionID));
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));//保存我们第一个交易到UTXO列表中
System.out.println("Creating and Mining Genesis block..");
Block genesis=new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis);//进行区块的挖取
//测试
Block block1=new Block(genesis.hash);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("\nWalletA is Attempting to send funds (40) to WalletB...");
block1.addTransaction(walletA.sendFunds(walletB.publicKey,40f));//sendFunds返回的是一个创建的交易
addBlock(block1);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance());
Block block2=new Block(block1.hash);
System.out.println("\nWalletA Attempting to send more funds (1000) than it has...");
block2.addTransaction(walletA.sendFunds(walletB.publicKey, 1000f));
addBlock(block2);
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance());
Block block3=new Block(block2.hash);
System.out.println("\nWalletB is Attempting to send funds (20) to WalletA...");
block3.addTransaction(walletB.sendFunds(walletA.publicKey, 20));
System.out.println("\nWalletA's balance is: " + walletA.getBalance());
System.out.println("WalletB's balance is: " + walletB.getBalance());
isChainValid();
}
public static Boolean isChainValid() {
Block currentBlock;// public类型的Block类型可以在包内任意地方访问
Block previousBlock;
String hashTarget=new String(new char[difficulty]).replace('\0', '0');
HashMap<String,TransactionOutput> tempUTXOs=new HashMap<String,TransactionOutput>();//未完成的临时交易列表
tempUTXOs.put(genesisTransaction.outputs.get(0).id,genesisTransaction.outputs.get(0));
//循环便利区块链进行hash检查
for(int i=1;i<blockchain.size();i++) {
currentBlock=blockchain.get(i);
previousBlock=blockchain.get(i-1);
if(!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("#Current Hashes not equal");
return false;
}
if(!previousBlock.hash.equals(currentBlock.previousHash)) {
System.out.println("#Previous Hashes not equal");
return false;
}
if(!currentBlock.hash.substring( 0, difficulty).equals(hashTarget)) {
System.out.println("#This block hasn't been mined");
return false;
}
//循环遍历区块链交易
TransactionOutput tempOutput;
for(int t=0;t<currentBlock.transactions.size();t++) {
Transaction currentTransaction=currentBlock.transactions.get(t);
if(!currentTransaction.verifySignature()) {
System.out.println("#Signature on Trnasacrtion("+t+") is Invalid");
return false;
}
if(currentTransaction.getInputsValue()!=currentTransaction.getOutputsValue()) {
System.out.println("#Inputs are note equal to outputs on Transaction(" + t + ")");
return false;
}
for(TransactionInput input:currentTransaction.inputs) {
tempOutput=tempUTXOs.get(input.transactionOutputId);
if(tempOutput==null) {
System.out.println("#Referenced input on Transaction(" + t + ") is Missing");
return false;
}
if(input.UTXO.value!=tempOutput.value) {
System.out.println("#Referenced input Transaction(" + t + ") value is Invalid");
return false;
}
tempUTXOs.remove(input.transactionOutputId);
}
for(TransactionOutput output: currentTransaction.outputs) {
tempUTXOs.put(output.id, output);
}
if(currentTransaction.outputs.get(0).reciepient != currentTransaction.reciepient) {
System.out.println("#Transaction(" + t + ") output reciepient is not who it should be");
return false;
}
if(currentTransaction.outputs.get(1).reciepient!=currentTransaction.sender) {
System.out.println("#Transaction(" + t + ") output 'change' is not sender.");
return false;
}
}
}
System.out.println("Blockchain is valid");
return true;
}
public static void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);//区块生成后,在这里进行挖取区块
blockchain.add(newBlock);
}
}
这个类相当于区块链的主体:区块串成的链,其中的blockchain即为我们所说的区块链。
StringUtil.java:
package My_Block;
import java.security.Key;
/*
* 每一个区块生成独一无二的hash值
*/
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Base64;
public class StringUtil {
// 应用ECDSA签名并产生私钥对应的字符串数组
// applyECDSASig方法的输入参数为付款人的私钥和需要加密的数据信息,签名后返回字符数组
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();// 将输入的字符串变成byte类型
dsa.update(strByte);// update方法将dsa更新成strByte里面的数据
byte[] realSig = dsa.sign();// 返回byte里面所有已更新数据的签名字节
output = realSig;
} catch (Exception e) {
throw new RuntimeException(e);
}
return output;
}
// 应用ECDSA验证数字签名,用公钥解开数字签名并验证签名是否有效
// 输入参数为签名、公钥和需要加密的数据
public static boolean verifyECDSASig(PublicKey publicKey, String data, byte[] signature) {
try {
Signature ecdsaVerify = Signature.getInstance("ECDSA", "BC");
ecdsaVerify.initVerify(publicKey);// 初始化验证签名的公钥
ecdsaVerify.update(data.getBytes());
return ecdsaVerify.verify(signature);// 验证所有更新字节的签名
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 将字符串转化为sha256的hash值
public static String applySha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();
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);
}
}
// 返回key对应的编码字符串
public static String getStringFromKey(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/*可追踪的交易列表并返回一个merkleroot
但是在一个单一的区块中可能存放了1000个交易,
这就会导致大量的hash计算,
不用担心在这里我们使用了交易的merkle树*/
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
int count =transactions.size();
ArrayList<String> previousTreeLayer=new ArrayList<String>();
for(Transaction transaction:transactions) {
previousTreeLayer.add(transaction.transactionID);
}
ArrayList<String> treeLayer=previousTreeLayer;
while(count>1) {
treeLayer=new ArrayList<String>();
for(int i=1;i<previousTreeLayer.size();i++) {
treeLayer.add(applySha256(previousTreeLayer.get(i-1)+previousTreeLayer.get(i)));
}
count=treeLayer.size();
previousTreeLayer=treeLayer;
}
String merkleRoot=(treeLayer.size()==1)?treeLayer.get(0):"";
return merkleRoot;
}
}
StringUtil.java中存储的是对字符串的各种操作,比如说应用ECDSA签名并产生私钥对应的字符串数组,应用ECDSA验证数字签名,用公钥解开数字签名并验证签名是否有效,将字符串转化为sha256的hash值,返回key对应的编码字符串,生成merkle树来存储大量的交易。
Transaction.java:
package My_Block;
/*
* 交易类,是中间的服务器
*/
import java.security.*;
import java.util.ArrayList;
public class Transaction {
public String transactionID;//这个交易的hash编号
public PublicKey sender;//付款人地址 公钥
public PublicKey reciepient;//接收人地址 公钥
public float value;//转移的金额
public byte[] signature;//数字签名,防止其他人从我们钱包中发送资金
//输入列表
public ArrayList<TransactionInput> inputs=new ArrayList<TransactionInput>();
//输出列表
public ArrayList<TransactionOutput> outputs=new ArrayList<TransactionOutput>();
//被创建交易的数量
public static int sequence=0;
public Transaction(PublicKey from,PublicKey to,float value,ArrayList<TransactionInput> inputs) {
this.sender=from;
this.reciepient=to;
this.value=value;
this.inputs=inputs;
}
//计算交易的hash值,用作交易的编号
public String calculateHash() {
sequence++;//增加sequence的值,防止出现hash值一样的情况
return StringUtil.applySha256(StringUtil.getStringFromKey(sender)+StringUtil.getStringFromKey(reciepient)+Float.toString(value)+sequence);
}
//签名我们不想被篡改的数据
public void generateSignature(PrivateKey privateKey) {
String data=StringUtil.getStringFromKey(sender)+StringUtil.getStringFromKey(reciepient)+Float.toString(value);
signature=StringUtil.applyECDSASig(privateKey, data);
}
//验证签名的数据有没有被修改
public boolean verifySignature() {
String data=StringUtil.getStringFromKey(sender)+StringUtil.getStringFromKey(reciepient)+Float.toString(value);
return StringUtil.verifyECDSASig(sender, data, signature);
}
//用boolean值来说明新的交易是否被创建
public boolean processTransaction() {
//验证签名
if(verifySignature()==false) {
System.out.println("#Transaction Signature failed to verify");
return false;
}
//收集交易的输入(必须注意的是输入是未被使用的)
for(TransactionInput i:inputs) {
i.UTXO=NoobChain.UTXOs.get(i.transactionOutputId);
}
//检查交易是否有效
if(getInputsValue()<NoobChain.minimumTransaction) {
System.out.println("Transaction Inputs to small:"+getInputsValue());
return false;
}
//创建交易输出
float leftOver=getInputsValue()-value;//获得输入的剩余金额
transactionID=calculateHash();
outputs.add(new TransactionOutput(this.reciepient,value,transactionID));//发送金额给收款人
outputs.add(new TransactionOutput(this.sender,leftOver,transactionID));//把剩余金额返回给付款人
//把输出增加到未使用的列表中
for(TransactionOutput o:outputs) {
NoobChain.UTXOs.put(o.id, o);
}
//把已经使用的交易输入从UTXO中移除
for(TransactionInput i:inputs) {
if(i.UTXO==null)continue;//如果交易找不到就跳过
NoobChain.UTXOs.remove(i.UTXO.id);
}
return true;
}
//返回余额
public float getInputsValue() {
float total=0;
for(TransactionInput i:inputs) {
if(i.UTXO==null)//将所有未交易的输出拿出
continue;
total+=i.UTXO.value;
}
return total;
}
//返回输出的总和
public float getOutputsValue() {
float total=0;
for(TransactionOutput o:outputs) {
total+=o.value;
}
return total;
}
}
这个类主要功能是创建一个新的交易,其中包含这个交易的id,付款人的地址(公钥),接收人的地址(公钥),要转移的金额,数字签名。processTransaction()函数说明这个新的交易是否被创建。processTransaction()中对应功能如下:1、验证签名,不一样就停止函数。2、收集输入集中的每一个输出id,而且每一个输入是一个UTXO。3、检查交易是否有效。4、创建交易输出,扣除钱,产生一个交易,将交易金额发给收款人,剩余金额返回给付款人,将输出增加到未使用的输出列表中,把已经使用的交易从UTXOS中移除。
一般是在wallet中发起一个交易,Transation中的processTransaction来判断这个交易是否被创建,如果没有创建,则进行创建,并交易。
TransactionInput.java:
package My_Block;
//交易输入类
//比特币规定每一笔新的交易的输入必须是某笔交易未花费的输出
public class TransactionInput {
public String transactionOutputId;//指向交易输出类->transactionId
public TransactionOutput UTXO;//包含所有未使用的交易输出
public TransactionInput(String transactionOutputId) {
this.transactionOutputId=transactionOutputId;
}
}
TransactionInput类中,每一笔新的交易的输入必须是某笔交易未花费的输出,所以可以看到,输入存储了输出的ID和UTXO。UTXO指的就是未使用的交易输出。一个TransactionInput()类对应一个未进行使用的输出,这样的一个类列表组成一个输入
TransactionOutput.java:
package My_Block;
//增加一个交易输出类
//一个交易输出被创建用来显示一个比特币发送给你的地址
//你账户的余额是所有指向你的未使用交易的输出。
import java.security.*;
public class TransactionOutput {
public String id;//这个交易输出的id
public PublicKey reciepient;//持有者的公钥
public float value;//持有者的金额
public String parentTransactionId;//交易编号
//构造函数
public TransactionOutput(PublicKey reciepient,float value,String parentTransactionId) {
this.reciepient=reciepient;
this.value=value;
this.parentTransactionId=parentTransactionId;
this.id=StringUtil.applySha256(StringUtil.getStringFromKey(reciepient)+Float.toString(value)+parentTransactionId);
}
//用来验证是否属于你
public boolean isMine(PublicKey publicKey) {
return (publicKey==reciepient);
}
}
你账户的余额是所有指向你的未使用交易的输出。所以这个类存储了一个交易输出的id,这个交易持有者的公钥reciepient,持有者只有这个交易输出的金额value,和parentTransactionId是交易编号。
Wallet.java:
package My_Block;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Wallet {
public PrivateKey privateKey;//为了对你的交易进行签名,别人没有这个私钥是不能进行交易的
public PublicKey publicKey;//你的地址,用于分享你的公钥给别人来获得付款,别人用公钥来验证你的签名是否有效
//钱包自身的UTXOs,用来存储没有交易的输出
public HashMap<String,TransactionOutput> UTXOs=new HashMap<String,TransactionOutput>();
/*
* 即私钥用来签名不想被篡改的数据,而公钥是用来验证签名的,签名=私钥+(from+to+value)
* 我们通过密钥对的方式来创建一对公钥和私钥,密钥对采用的是椭圆曲线加密算法(ECDSA)
*/
public Wallet() {
generateKeyPair();
}
//generateKeyPair函数生成一对私钥和公钥
public void generateKeyPair() {
try {
KeyPairGenerator keyGen=KeyPairGenerator.getInstance("ECDSA","BC");
SecureRandom random=SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec=new ECGenParameterSpec("prime192v1");
keyGen.initialize(ecSpec,random);
KeyPair keyPiar=keyGen.generateKeyPair();
privateKey=keyPiar.getPrivate();
publicKey=keyPiar.getPublic();
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
//返回余额并保存钱包自身的UTXO
public float getBalance() {
float total=0;
for(Map.Entry<String, TransactionOutput>item: NoobChain.UTXOs.entrySet()) {
TransactionOutput UTXO=item.getValue();
if(UTXO.isMine(publicKey)) {//查看输出是否属于我(货币是不是我的)
UTXOs.put(UTXO.id,UTXO);//添加到未使用的交易列表中,确保这个输出列表中的输出全部属于我
total+=UTXO.value;
}
}
return total;
}
//创建并返回属于这个钱包的新交易
public Transaction sendFunds(PublicKey _recipient,float value) {
if(getBalance()<value) {//搜集余额检查金额
System.out.println("#Not Enough funds to send transaction.Transaction Discarded");
return null;
}
//创建输入列表
ArrayList<TransactionInput> inputs=new ArrayList<TransactionInput>();
float total=0;
//entrySet()的返回值是返回一个Set集合,也是键值对对应,此集合的类型为Map.Entry
//HashMap不支持Iterator所以要通过其他方式迭代Map中的Key和Value
//Map.Entry<>方法表示Map中的一个实体(一个key-一个value对,是Set<Map.Entry<K,V>>
for(Map.Entry<String, TransactionOutput> item:UTXOs.entrySet()) {
TransactionOutput UTXO=item.getValue();
total+=UTXO.value;
inputs.add(new TransactionInput(UTXO.id));
if(total>value) //将拿出的钱相加,发现大于需要支付的钱的时候,停止拿出钱,将那些拿出的钱(未交易的输出)的id记录下来,当作输入列表
break;
}
Transaction newTransaction=new Transaction(publicKey,_recipient,value,inputs);
newTransaction.generateSignature(privateKey);//用你的私钥对你不想被改变的数据加密,生成一个数字签名
for(TransactionInput input:inputs) {
UTXOs.remove(input.transactionOutputId);//将这个输出从你的钱包中去除
}
return newTransaction;//返回一个新交易
}
}
Wallet.java中getBalance()返回钱包余额。sendFunds()创建一个新的交易(再此过程中,检查是否有足够的钱,如果是则继续,否就return)。创建一个列表,列表元素为未交易的输出对应的id。拿出所有家当进入交易,当打出的钱大于需要支付的时候,将这些钱放入一个tarnsactioninput列表。用Transaction创建一个交易。将你想要加密的数据进行私钥加密。
2、接下来是完成一个交易的流程:
在NoobChain文件中,首先创建了一个叫做blockchain的ArryList区块链,其中的元素是Block对象。挖取区块难度为6,这个难度可以调整,创建两个钱包walletA和walletB,还有一个创世纪钱包coinbase。
然后创建创世纪交易,将100个货币发给walletA,步骤为:先实例化Transaction类的一个对象,创建出创世纪交易genesisTransaction,然后用创世纪钱包的私钥对交易中的一些数据进行加密,使用Transaction中的generateSignature()方法实现,接下来设置创世纪交易的ID为0,因为每个交易都有一个很特殊的hash值,所以0是为了用于区分它和之后的交易。因为创世纪钱包的特殊性,我们利用交易genesisTransation中的信息手动创建一个输出,即TransactionOutput名为的对象,将这个对象添加到genesisTransaction的outputs列表中作为输出列表。保存这个交易到UTXOs中,创建第一个区块,区块传入的previoushash值为0(默认第一块区块的previoushash为0),然后调用genesis中的addTransaction()方法,因为previoushash为0,所以就不通过transaction.processTransaction()方法来创建交易输出,而在上面选择手动创建输出,这个区块名叫genesis。接下来就是对区块的挖掘,使用addBlock()方法调用区块的mineBlock()方法,进行挖取,如果挖取成功,则将这个区块加入区块链。
现在walletA中就有了100块钱,walletB中还是0块钱,接下来我们创建walletA和walletB之间的交易。
先实例化一个Block,名为block1,传入的previoushash为genesis的hash值,然后使用Wallet中的sendFunds()方法来创建一个交易,比如:walletA.sendFunds(walletB.publicKey,40f)
就是创建了一个walletA转40元到walletB的交易,为Transaction类的对象,然后调用block1中的addTransaction()方法,这个方法中又调用了transaction.processTransaction()方法,在这个方法中完成各种交易,当返回true时表示完成交易,将这次交易信息添加到block1的交易列表transactions中,并完成这次交易。使用addBlock()方法进行区块block1的挖掘,当计算成功(挖掘成功)后将这个区块添加到blockchain中。
之后的交易都是这样进行的,完成一次交易,然后将这次交易的信息存入一个区块中,然后进行区块的挖掘,当计算机经过复杂的计算挖掘到这个区块时,将这个区块加入区块链中,这样就构成了一个区块链。