首先定义 Block Class:
import javax.xml.bind.DatatypeConverter;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Block Class
* @Author: Lisa Ding
*/
class Block {
private int index = 0;
private Timestamp timestamp = null;
private String data = "";
private String previousHash = "";
private BigInteger nonce = new BigInteger("0");
private int difficulty = 0;
/**
* the Block constructor.
* @param index This is the position within the chain. Genesis is at 0.
* @param timestamp This is the time this block was added.
* @param data This is the transaction to be included on the blockchain.
* @param difficulty This is the number of leftmost nibbles that need to be 0.
*/
public Block(int index, Timestamp timestamp, String data, int difficulty) {
this.index = index;
this.timestamp = timestamp;
this.data = data;
this.difficulty = difficulty;
}
/**
* This method computes a hash of the concatenation of the index, timestamp, data, previousHash, nonce, and difficulty.
* @return a String holding Hexadecimal characters
*/
public String calculateHash() {
Date date = new Date();
date.setTime(this.timestamp.getTime());
String concat = Integer.toString(this.index) // index
+ new SimpleDateFormat("yyyymmdd").format(date)// timestamp
+ this.data // data
+ this.previousHash // previousHash
+ new String(this.nonce.toByteArray()) // nonce
+ this.difficulty; // difficulty
MessageDigest md;
String hex = "";
try {
md = MessageDigest.getInstance("SHA-256");
md.update(concat.getBytes());
byte[] digest = md.digest();
hex = DatatypeConverter.printHexBinary(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return hex;
}
/**
* The proof of work methods finds a good hash. It increments the nonce until it produces a good hash. This method
* calls calculateHash() to compute a hash of the concatenation of the index, timestamp, data, previousHash, nonce,
* and difficulty. If the hash has the appropriate number of leading hex zeroes, it is done and returns that proper
* hash. If the hash does not have the appropriate number of leading hex zeroes, it increments the nonce by 1 and tries
* again. It continues this process, burning electricity and CPU cycles, until it gets lucky and finds a good hash.
* @param difficulty determines how much work is required to produce a proper hash. This is the number of leftmost
* nibbles that need to be 0.
* @return a String with a hash that has the appropriate number of leading hex zeroes.
*/
public String proofOfWork(int difficulty) {
// Initially set nonce to zero.
this.nonce = new BigInteger("0");
String hash = "";
int proofOfWork = 0;
do {
this.nonce = this.nonce.add(new BigInteger("1"));
hash = calculateHash();
proofOfWork = getProofOfWork(hash);
} while (proofOfWork!= difficulty);
return hash;
}
/**
* Helper method to find the number of leftmost 0's (representing proof of work).
* @param hash the hash string
* @return the number of leftmost 0's (representing proof of work)
*/
public static int getProofOfWork(String hash) {
int proofOfWork = 0;
for (int i = 0; i < hash.length(); i++) {
char c = hash.toCharArray()[i];
if (c == '0') {
proofOfWork++;
} else {
break;
}
}
return proofOfWork;
}
/**
* Simple getter method
* @return difficulty
*/
public int getDifficulty() {
return difficulty;
}
/**
* Simple setter method
* @param difficulty determines how much work is required to produce a proper hash
*/
public void setDifficulty(int difficulty) {
this.difficulty = difficulty;
}
/**
* Override Java's toString method
* @return A JSON representation of all of this block's data is returned.
*/
@Override
public String toString() {
return "[" + this.index + ","
+ this.timestamp + ","
+ this.data + ","
+ this.previousHash + ","
+ this.nonce + ","
+ this.difficulty + "]\n";
}
/**
* Simple getter method
* @return previous hash
*/
public String getPreviousHash() {
return previousHash;
}
/**
* Simple setter method
* @param previousHash a hashpointer to this block's parent
*/
public void setPreviousHash(String previousHash) {
this.previousHash = previousHash;
}
/**
* Simple getter method
* @return index of block
*/
public int getIndex() {
return index;
}
/**
* Simple setter method
* @param index the index of this block in the chain
*/
public void setIndex(int index) {
this.index = index;
}
/**
* Simple getter method
* @return timestamp of this block
*/
public Timestamp getTimestamp() {
return timestamp;
}
/**
* Simple setter method
* @param timestamp of when this block was created
*/
public void setTimestamp(Timestamp timestamp) {
this.timestamp = timestamp;
}
/**
* Simple getter method
* @return this block's transaction
*/
public String getData() {
return data;
}
/**
* Simple setter method
* @param data represents the transaction held by this block
*/
public void setData(String data) {
this.data = data;
}
/**
* Simple getter method
* @return this block's transaction
*/
public BigInteger getNonce() {
return nonce;
}
/**
* Simple setter method
* @param nonce represents a BigInteger value determined by a proof of work routine.
*/
public void setNonce(BigInteger nonce) {
this.nonce = nonce;
}
}
然后实现 BlockChain Class:
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Scanner;
/**
* Block Chain Class
* @Author: Lisa Ding
*/
public class BlockChain {
/**
* an ArrayList to hold Blocks
*/
private static ArrayList<Block> chain;
/**
* a chain hash to hold a SHA256 hash of the most recently added Block.
*/
private static String chainHash;
/**
* BlockChain default constructor.
*/
public BlockChain() {
// Initialize the block chain.
chain = new ArrayList<>();
// Create a genesis block with an empty string as the previous hash and a difficulty of 2.
Block block = new Block(0, new Timestamp(System.currentTimeMillis()), "{Genesis}", 2);
// Add the genesis block to block chain.
chain.add(block);
// Compute current chain hash
chainHash = block.proofOfWork(2);
}
/**
* Get system time
* @return the current system time
*/
public Timestamp getTime() {
return new Timestamp(System.currentTimeMillis());
}
/**
* Retrieve the latest block in the block chain
* @return a reference to the most recently added Block.
*/
public Block getLatestBlock() {
return chain.get(chain.size() - 1);
}
/**
* Add a block to the block chain
* @prerequisite: the block is already constructed
* Time complexity: With the prerequisite, it takes O(1) time to add block in block chain
* @param newBlock is added to the BlockChain as the most recent block
*/
public void addBlock(Block newBlock) {
chain.add(newBlock);
}
/**
* This method uses the toString method defined on each individual block.
* @return a json String representation of the entire chain is returned.
*/
@Override
public String toString() {
String entireChain = "{";
for (Block chains : chain) {
entireChain = entireChain + chains.toString();
}
return entireChain + "}";
}
/**
* If the chain only contains one block, the genesis block at position 0, this routine computes the hash of the block
* and checks that the hash has the requisite number of leftmost 0's (proof of work) as specified in the difficulty
* field. It also checks that the chain hash is equal to this computed hash. If either check fails, return false.
* Otherwise, return true. If the chain has more blocks than one, begin checking from block one. Continue checking
* until you have validated the entire chain. The first check will involve a computation of a hash in Block 0 and a
* comparison with the hash pointer in Block 1. If they match and if the proof of work is correct, go and visit the
* next block in the chain. At the end, check that the chain hash is also correct.
*
* To analyze time complexity:
* First, it will take O(N) time to loop from the front to end of the block in the block chain.
* In the loop, there's a static get ProofOfWork method to obtain the proof of work in the block.
* For difficulty = 4, on average the total time complexity should be O(4 * N)
* For difficulty = 5, on average the total time complecity should be O(5 * N)
*
* @return true if and only if the chain is valid
*/
public boolean isChainValid() {
// Condition 1: If the chain only contains one block:
if (chain.size() == 1) {
// Check 1: if the proof of work is correct (it computes the hash of the block and checks that the hash has
// the requisite number of leftmost 0's (proof of work) as specified in the difficulty field.)
Block block = chain.get(0);
int difficulty = block.getDifficulty();
int proofOfWork = Block.getProofOfWork(block.calculateHash());
if (proofOfWork != difficulty) {
System.out.println("the hash of the block does not has the requisite number of leftmost 0's (proof of work)" +
" as specified in the difficulty field." +
"number of leftmost 0's (proof of work): " + proofOfWork + "\n" +
"difficulty: " + difficulty);
return false;
}
// Check 2: if the chain hash is equal to this computed hash.
if (! chainHash.equals(block.calculateHash())) {
System.out.println("the chain hash is not equal to the computed hash.\n" +
"the chain hash: " + Integer.toString(block.hashCode()) + "\n" +
"the computed hash: " + block.calculateHash());
return false;
}
// Condition 2: If the chain has more blocks than one:
} else {
// begin checking from block one
for (int i = 1; i < chain.size(); i++) {
Block prevBlock = chain.get(i - 1);
Block curBlock = chain.get(i);
// Check 1: if a computation of a hash in previous block is equal to the hash pointer in current block.
if (!prevBlock.calculateHash().equals(curBlock.getPreviousHash())) {
System.out.println("The hash in previous block is not equal to the hash pointer in current block " +
"at chain block " + i + ".\n" +
"hash in previous block: " + prevBlock.calculateHash() + "\n" +
"hash pointer in current block: " + curBlock.getPreviousHash());
return false;
}
// Check 2: if the proof of work is correct
Block block = chain.get(i);
int difficulty = block.getDifficulty();
int proofOfWork = Block.getProofOfWork(block.calculateHash());
if (proofOfWork < difficulty) {
System.out.println("the hash of the block does not has the requisite number of leftmost 0's (proof of work)" +
" as specified in the difficulty field." +
"number of leftmost 0's (proof of work): " + proofOfWork + "\n" +
"difficulty: " + difficulty);
return false;
}
}
}
// Return true it the chain passes all checks.
return true;
}
最后为 BlockChain Class 添加 main method:
/**
* Test driver for Blockchain Class.
* @param args is unused
*/
public static void main(String[] args) {
// Create a BlockChain object
// The blockchain is initialized with a Genesis block when calling the constrictor.
BlockChain blockChain = new BlockChain();
System.out.println("Block Chain Menu\n" +
"1. Add a transaction to the blockchain.\n" +
"2. Verify the blockchain.\n" +
"3. View the blockchain.\n" +
"4. Exit");
String input = "";
do {
System.out.println("\nPlease enter an option:");
Scanner scan = new Scanner(System.in);
input = scan.nextLine();
// Menu Option 1: Add a transaction to the blockchain.
switch (input) {
case "1":
System.out.println("Enter difficulty");
int difficulty = Integer.parseInt(scan.nextLine());
System.out.println("Enter transaction");
String transaction = scan.nextLine();
// Create a new block according to difficulty and transaction
Block newBlock = new Block(chain.size(), blockChain.getTime(), transaction, difficulty);
// Set its previous hash
newBlock.setPreviousHash(chainHash);
// compute nonce value by calling proofOfWork, and set current hash to chainHash;
chainHash = newBlock.proofOfWork(difficulty);
// Add this block to block chain. This operation takes O(1) time
blockChain.addBlock(newBlock);
System.out.println("The block has been added to the block chain.");
/*
Analysis with blocks of difficulty 4 and blocks with difficulty 5:
I use a stop watch to record how much time it takes to add block with difficulty 4 and 5.
For difficulty 4, it takes around 4 - 5 seconds to add a block
For difficulty 5, it takes 6 - 7 seconds to add a block.
The more the difficulty, the more time it takes to add the block.
*/
break;
// Menu Option 2: Verify the block chain.
case "2":
System.out.println("Verifying");
System.out.println("Chain verification: " + (blockChain.isChainValid() ? "true" : "false"));
break;
// Menu Option 3: View the block chain
case "3":
System.out.println("chain:" + blockChain);
System.out.println("chainHash == \n" + chainHash);
break;
}
} while (!input.equals("4"));
}
}