前面的文章,笔者花了不少的文字来介绍区块链的基础概念,以太坊的概念和开发,大家是否感觉区块链开发还是挺复杂的呢。但其实区块链技术本质就是一个分布式账本,在技术上本质就是一个链表。链表里面有一个个的区块,每个区块有自己的数字签名(涉及到加密技术)和交易数据和一些其他的数据,然后把一堆区块串起来就是区块链,同时把区块数据能够进行广播,基本上就完成一个最简单的区块链系统。
那么今天,笔者用java开发语言来简单实现一个简易版的区块链。
首先,定义好区块的结构,我们先回顾下一个区块有哪些信息:
- 区块hash
- 上一个区块hash
- 时间戳
- 交易信息列表
其中交易信息(以UTXO模型为例)包括以下信息:
- 交易hash
- 交易发送者
- 交易接受者
- 时间戳
- 交易额
- 签名
- 交易输入
- 交易输出
OK,回顾完了区块的数据内容,我们用Java代码编写出来数据结构,很明显我们需要定义两个Java对象:交易和区块。
看下面的代码
交易类:Transaction
public class Transaction {
public String transactionHash; //交易hash
public PublicKey sender; //发送这地址
public PublicKey receiver; //接受者地址
public double value; //交易额
public byte[] signature; //签名数据
public long timeStamp; //时间戳
public ArrayList inputs = new ArrayList(); //交易输入
public ArrayList outputs = new ArrayList(); //交易输出
}
区块类:Block
public class Block {
public String hash;
public String previousHash;
public List data; //交易列表
public long timeStamp; //时间戳
}
交易输出类:定义UTXO中的交易输出
public class TransactionOutput {
public String id;
public PublicKey receiver; //
public double value; //接受者的拥有的余额
public String parentTransactionHash; //产生该交易输出的交易hash
public TransactionOutput(PublicKey receiver, float value, String parentTransactionHash) {
this.receiver = receiver;
this.value = value;
this.parentTransactionHash = parentTransactionHash;
this.id = StringUtil.applySha256(StringUtil.getStringFromKey(receiver)+Float.toString(value)+parentTransactionHash);
}
交易输入类:
public class TransactionInput {
public String transactionOutputId; //交易输出的id
public TransactionOutput UTXO; //Contains the Unspent transaction output
public TransactionInput(String transactionOutputId) {
this.transactionOutputId = transactionOutputId;
}
}
钱包类:
钱包包括公钥和私钥,已经余额
public class Wallet {
public PrivateKey privateKey;
public PublicKey publicKey;
public HashMap UTXOs = new HashMap();
public Wallet() {
generateKeyPair();
}
public void generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ECDSA","BC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime192v1");
// Initialize the key generator and generate a KeyPair
keyGen.initialize(ecSpec, random); //256
KeyPair keyPair = keyGen.generateKeyPair();
// Set the public and private keys from the keyPair
privateKey = keyPair.getPrivate();
publicKey = keyPair.getPublic();
}catch(Exception e) {
throw new RuntimeException(e);
}
}
public float getBalance() {
float total = 0;
for (Map.Entry item: NoobChain.UTXOs.entrySet()){
TransactionOutput UTXO = item.getValue();
if(UTXO.isMine(publicKey)) { //if output belongs to me ( if coins belong to me )
UTXOs.put(UTXO.id,UTXO); //add it to our list of unspent transactions.
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 inputs = new ArrayList();
float total = 0;
for (Map.Entry 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);
for(TransactionInput input: inputs){
UTXOs.remove(input.transactionOutputId);
}
return newTransaction;
}
}
加密工具类:
public class StringUtil {
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);
}
}
//Applies ECDSA Signature and returns the result ( as bytes ).
public static byte[] applyECDSASig(PrivateKey privateKey, String input) {
Signature dsa;
byte[] output = new byte[0];
try {
dsa = Signature.getInstance("ECDSA", "BC");
dsa.initSign(privateKey);
byte[] strByte = input.getBytes();
dsa.update(strByte);
byte[] realSig = dsa.sign();
output = realSig;
} catch (Exception e) {
throw new RuntimeException(e);
}
return output;
}
//Verifies a String signature
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。
定义完成了基本的数据结果,我们编写一个main函数跑起来。
public class NoobChain {
public static ArrayList blockchain = new ArrayList();
public static HashMap UTXOs = new HashMap();
public static int difficulty = 3;
public static float minimumTransaction = 0.1f;
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction;
public static void main(String[] args) {
//add our blocks to the blockchain ArrayList:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); //Setup Bouncey castle as a Security Provider
//Create wallets:
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();
//create genesis transaction, which sends 100 NoobCoin to walletA:
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
genesisTransaction.generateSignature(coinbase.privateKey); //manually sign the genesis transaction
genesisTransaction.transactionId = "0"; //manually set the transaction id
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.reciepient, genesisTransaction.value, genesisTransaction.transactionId)); //manually add the Transactions Output
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0)); //its important to store our first transaction in the UTXOs list.
System.out.println("Creating and Mining Genesis block... ");
Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis);
//testing
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));
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();
}
}