Blockchain!

This commit is contained in:
2021-11-21 20:35:10 -05:00
commit 763c968a72
20 changed files with 1015 additions and 0 deletions

View 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;
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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
);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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;
}
}