Blockchain!
This commit is contained in:
61
src/main/java/tech/nevets/nevcoin/Block.java
Normal file
61
src/main/java/tech/nevets/nevcoin/Block.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package tech.nevets.nevcoin;
|
||||
|
||||
import tech.nevets.nevcoin.transactions.Transaction;
|
||||
import tech.nevets.nevcoin.util.StringUtil;
|
||||
|
||||
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.data = data;
|
||||
this.previousHash = previousHash;
|
||||
this.timeStamp = new Date().getTime();
|
||||
this.hash = calculateHash();
|
||||
}
|
||||
|
||||
public String calculateHash() {
|
||||
String calculatedHash = StringUtil.applySha256(
|
||||
previousHash
|
||||
+ Long.toString(timeStamp)
|
||||
+ Integer.toString(nonce)
|
||||
+ merkleRoot
|
||||
//+ data
|
||||
);
|
||||
return calculatedHash;
|
||||
}
|
||||
|
||||
public void mineBlock(int difficulty) {
|
||||
merkleRoot = StringUtil.getMerkleRoot(transactions);
|
||||
String target = StringUtil.getDifficultyString(difficulty);
|
||||
|
||||
while (!hash.substring(0, difficulty).equals(target)) {
|
||||
nonce ++;
|
||||
hash = calculateHash();
|
||||
}
|
||||
System.out.println("Block mined: " + hash);
|
||||
}
|
||||
|
||||
public boolean addTransaction(Transaction transaction) {
|
||||
if (transaction == null) return false;
|
||||
if (!"0".equals(previousHash)) {
|
||||
if (transaction.processTransaction() != true) {
|
||||
System.out.println("Transaction failed to process. Discarded.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
transactions.add(transaction);
|
||||
System.out.println("Transaction Successfully added to Block");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
143
src/main/java/tech/nevets/nevcoin/NevCoin.java
Normal file
143
src/main/java/tech/nevets/nevcoin/NevCoin.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package tech.nevets.nevcoin;
|
||||
|
||||
import tech.nevets.nevcoin.transactions.Transaction;
|
||||
import tech.nevets.nevcoin.transactions.TransactionInput;
|
||||
import tech.nevets.nevcoin.transactions.TransactionOutput;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class NevCoin {
|
||||
|
||||
public static ArrayList<Block> blockChain = new ArrayList<Block>();
|
||||
public static HashMap<String, TransactionOutput> UTXOs = new HashMap<String, TransactionOutput>();
|
||||
|
||||
public static int difficulty = 10;
|
||||
public static float minimumTransaction = 0.1f;
|
||||
public static Wallet walletA;
|
||||
public static Wallet walletB;
|
||||
public static Transaction genesisTransaction;
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||
|
||||
walletA = new Wallet();
|
||||
walletB = new Wallet();
|
||||
Wallet coinbase = new Wallet();
|
||||
|
||||
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey, 100f, null);
|
||||
genesisTransaction.generateSignature(coinbase.privateKey);
|
||||
genesisTransaction.transactionId = "0";
|
||||
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.recipient, genesisTransaction.value, genesisTransaction.transactionId));
|
||||
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
|
||||
|
||||
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));
|
||||
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;
|
||||
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));
|
||||
|
||||
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 Transaction(" + 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).recipient != currentTransaction.recipient) {
|
||||
System.out.println("#Transaction(" + t + ") output recipient is not who it should be");
|
||||
return false;
|
||||
}
|
||||
if (currentTransaction.outputs.get(1).recipient != currentTransaction.sender) {
|
||||
System.out.println("#Transaction(" + t + ") output 'change' is not sender.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void addBlock(Block newBlock) {
|
||||
newBlock.mineBlock(difficulty);
|
||||
blockChain.add(newBlock);
|
||||
}
|
||||
}
|
||||
75
src/main/java/tech/nevets/nevcoin/Wallet.java
Normal file
75
src/main/java/tech/nevets/nevcoin/Wallet.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package tech.nevets.nevcoin;
|
||||
|
||||
import tech.nevets.nevcoin.transactions.Transaction;
|
||||
import tech.nevets.nevcoin.transactions.TransactionInput;
|
||||
import tech.nevets.nevcoin.transactions.TransactionOutput;
|
||||
|
||||
import java.security.*;
|
||||
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;
|
||||
|
||||
public HashMap<String, TransactionOutput> UTXOs = new HashMap<String, TransactionOutput>();
|
||||
|
||||
public Wallet() {
|
||||
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 keyPair = keyGen.generateKeyPair();
|
||||
|
||||
privateKey = keyPair.getPrivate();
|
||||
publicKey = keyPair.getPublic();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public float getBalance() {
|
||||
float total = 0;
|
||||
for (Map.Entry<String, TransactionOutput> item: NevCoin.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;
|
||||
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);
|
||||
|
||||
for (TransactionInput input: inputs) {
|
||||
UTXOs.remove(input.transactionOutputId);
|
||||
}
|
||||
|
||||
return newTransaction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package tech.nevets.nevcoin.transactions;
|
||||
|
||||
import tech.nevets.nevcoin.NevCoin;
|
||||
import tech.nevets.nevcoin.util.StringUtil;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Transaction {
|
||||
public String transactionId;
|
||||
public PublicKey sender;
|
||||
public PublicKey recipient;
|
||||
public float value;
|
||||
public byte[] signature;
|
||||
|
||||
public ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>();
|
||||
public ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>();
|
||||
|
||||
private static int sequence = 0;
|
||||
|
||||
public Transaction(PublicKey from, PublicKey to, float value, ArrayList<TransactionInput> inputs) {
|
||||
this.sender = from;
|
||||
this.recipient = to;
|
||||
this.value = value;
|
||||
this.inputs = inputs;
|
||||
}
|
||||
|
||||
public boolean processTransaction() {
|
||||
if (verifySignature() == false) {
|
||||
System.out.println("#Transaction Signature failed to verify");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (TransactionInput i : inputs) {
|
||||
i.UTXO = NevCoin.UTXOs.get(i.transactionOutputId);
|
||||
}
|
||||
|
||||
if (getInputsValue() < NevCoin.minimumTransaction) {
|
||||
System.out.println("#Transaction Inputs to small: " + getInputsValue());
|
||||
return false;
|
||||
}
|
||||
|
||||
float leftOver = getInputsValue() - value;
|
||||
transactionId = calculateHash();
|
||||
outputs.add(new TransactionOutput( this.recipient, value, transactionId));
|
||||
outputs.add(new TransactionOutput( this.sender, leftOver, transactionId));
|
||||
|
||||
for (TransactionOutput o : outputs) {
|
||||
NevCoin.UTXOs.put(o.id, o);
|
||||
}
|
||||
|
||||
for (TransactionInput i : inputs) {
|
||||
if (i.UTXO == null) continue; //if Transaction can't be found skip it
|
||||
NevCoin.UTXOs.remove(i.UTXO.id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public float getInputsValue() {
|
||||
float total = 0;
|
||||
for (TransactionInput i : inputs) {
|
||||
if (i.UTXO == null) continue; //if Transaction can't be found skip it
|
||||
total += i.UTXO.value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public void generateSignature(PrivateKey privateKey) {
|
||||
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + Float.toString(value) ;
|
||||
signature = StringUtil.applyECDSASig(privateKey, data);
|
||||
}
|
||||
|
||||
public boolean verifySignature() {
|
||||
String data = StringUtil.getStringFromKey(sender) + StringUtil.getStringFromKey(recipient) + Float.toString(value) ;
|
||||
return StringUtil.verifyECDSASig(sender, data, signature);
|
||||
}
|
||||
|
||||
public float getOutputsValue() {
|
||||
float total = 0;
|
||||
for(TransactionOutput o : outputs) {
|
||||
total += o.value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private String calculateHash() {
|
||||
sequence++;
|
||||
return StringUtil.applySha256(
|
||||
StringUtil.getStringFromKey(sender)
|
||||
+ StringUtil.getStringFromKey(recipient)
|
||||
+ Float.toString(value) + sequence
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package tech.nevets.nevcoin.transactions;
|
||||
|
||||
public class TransactionInput {
|
||||
|
||||
public String transactionOutputId;
|
||||
public TransactionOutput UTXO;
|
||||
|
||||
public TransactionInput(String transactionOutputId) {
|
||||
this.transactionOutputId = transactionOutputId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package tech.nevets.nevcoin.transactions;
|
||||
|
||||
import tech.nevets.nevcoin.util.StringUtil;
|
||||
import java.security.PublicKey;
|
||||
|
||||
public class TransactionOutput {
|
||||
public String id;
|
||||
public PublicKey recipient;
|
||||
public float value;
|
||||
public String parentTransactionId;
|
||||
|
||||
public TransactionOutput(PublicKey recipient, float value, String parentTransactionId) {
|
||||
this.recipient = recipient;
|
||||
this.value = value;
|
||||
this.parentTransactionId = parentTransactionId;
|
||||
this.id = StringUtil.applySha256(StringUtil.getStringFromKey(recipient) + Float.toString(value)+parentTransactionId);
|
||||
}
|
||||
|
||||
public boolean isMine(PublicKey publicKey) {
|
||||
return (publicKey == recipient);
|
||||
}
|
||||
}
|
||||
92
src/main/java/tech/nevets/nevcoin/util/StringUtil.java
Normal file
92
src/main/java/tech/nevets/nevcoin/util/StringUtil.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package tech.nevets.nevcoin.util;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import tech.nevets.nevcoin.transactions.Transaction;
|
||||
|
||||
import java.security.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getJson(Object o) {
|
||||
return new GsonBuilder().setPrettyPrinting().create().toJson(o);
|
||||
}
|
||||
|
||||
public static String getDifficultyString(int difficulty) {
|
||||
return new String(new char[difficulty]).replace('\0', '0');
|
||||
}
|
||||
|
||||
public static String getStringFromKey(Key key) {
|
||||
return Base64.getEncoder().encodeToString(key.getEncoded());
|
||||
}
|
||||
|
||||
public static String getMerkleRoot(ArrayList<Transaction> transactions) {
|
||||
int count = transactions.size();
|
||||
|
||||
List<String> previousTreeLayer = new ArrayList<String>();
|
||||
for (Transaction transaction : transactions) {
|
||||
previousTreeLayer.add(transaction.transactionId);
|
||||
}
|
||||
List<String> treeLayer = previousTreeLayer;
|
||||
|
||||
while (count > 1) {
|
||||
treeLayer = new ArrayList<String>();
|
||||
for(int i=1; i < previousTreeLayer.size(); i += 2) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user