diff --git a/.idea/misc.xml b/.idea/misc.xml index a165cb3..df7e8eb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ - + + + + \ No newline at end of file diff --git a/.idea/project.iml b/.idea/project.iml index 47baa8c..a241e87 100644 --- a/.idea/project.iml +++ b/.idea/project.iml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/src/blockchain/Block.java b/src/blockchain/Block.java new file mode 100644 index 0000000..fec0bfc --- /dev/null +++ b/src/blockchain/Block.java @@ -0,0 +1,132 @@ +package blockchain; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + + + +class Block implements Serializable{ + + private List messagesStack = new LinkedList<>(); + private String prevHash; + private String hash; + private int id; + private long timeStamp; + private int magicNumber; + private long maxMsgID; + + public Block(Block prevBlock, int difficulty){ + + if(prevBlock == null) { + this.prevHash = "0"; + this.id = 1; + } + else { + this.prevHash = prevBlock.getHash(); + this.id = prevBlock.getId() + 1; + } + String zeros = ""; + for (int i = 0; i < difficulty; i++){ + zeros += "0"; + } + + do { + setMagicNumber(); + this.hash = applySha256(this.prevHash + this.magicNumber); + }while(!this.hash.startsWith(zeros)); + setMaxMsgID(); + this.timeStamp = new Date().getTime(); + } + + public void printOutResults() throws UnsupportedEncodingException { + System.out.println("Id: " + this.getId()); + System.out.println("Timestamp: " + this.getTimeStamp()); + System.out.println("Magic number: " + this.getMagicNumber()); + System.out.println("Hash of the previous block:"); + System.out.println(this.getPrevHash()); + System.out.println("Hash of the block:"); + System.out.println(this.getHash()); + if (this.id == 1 || messagesStack.isEmpty()){ + System.out.println("Block data: no messages"); + }else{ + System.out.println("Block data: "); + for(Message s: messagesStack) { + String str = new String(s.getList().get(0), "UTF-8"); + + System.out.println(str); + } + } + } + + + private void setMagicNumber() { + this.magicNumber = new Random().nextInt(Integer.MAX_VALUE); + } + + public int getId() { + return id; + } + + public String getHash() { + return hash; + } + + public String getPrevHash() { + return prevHash; + } + + public long getTimeStamp(){ + return timeStamp; + } + + + public String applySha256(String input){ + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + /* Applies sha256 to our input */ + byte[] hash = digest.digest(input.getBytes("UTF-8")); + StringBuilder hexString = new StringBuilder(); + for (byte i : hash) { + String hex = Integer.toHexString(0xff & i); + if(hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } + catch(Exception e) { + throw new RuntimeException(e); + } + } + + public int getMagicNumber(){ + return this.magicNumber; + } + + public void writeMessagesStack(LinkedList messagesStack){ + this.messagesStack.addAll(messagesStack); + } + + public LinkedList getMessageStack(){ + return (LinkedList) this.messagesStack; + } + + private void setMaxMsgID(){ + long max = 0; + for (Message aMessagesStack : this.messagesStack) { + if (max < aMessagesStack.getId()) { + max = aMessagesStack.getId(); + } + } + this.maxMsgID = max; + } + + public long getMaxMsgID(){ + return this.maxMsgID; + } + +} diff --git a/src/blockchain/Blockchain.java b/src/blockchain/Blockchain.java new file mode 100644 index 0000000..76538c5 --- /dev/null +++ b/src/blockchain/Blockchain.java @@ -0,0 +1,167 @@ +package blockchain; + +import java.io.*; +import java.util.LinkedList; + + +class Blockchain { + private LinkedList messages = new LinkedList<>(); + private volatile LinkedList blockchain = null; + private boolean newFile = false; + private int timeSpent; + private int difficulty = 0; + private static long ids = 1; + + + public Blockchain(String filePath) { + File file = new File(filePath);//"D:\\hyperskillGit\\blockchain\\MyBlockchain"); + readBlockchainFromFile(file); + } + + public void readBlockchainFromFile(File file) { + try { + newFile = file.createNewFile(); + if (newFile) { + System.out.println("The new blockchain file was successfully created."); + this.blockchain = new LinkedList<>(); + this.difficulty = 0; + Blockchain.ids = 1; + } else { + FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis); + this.blockchain = (LinkedList) ois.readObject(); + System.out.println("The Blockchain was read from the file"); + ois.close(); + fis.close(); + if(blockchain.peekLast().getMessageStack().peekLast() != null) { + Blockchain.ids = blockchain.peekLast().getMessageStack().peekLast().getId() + 1L; + } + this.difficulty = getDifficultyFromFile(this.blockchain.peekLast().getHash()); + } + } catch (IOException e) { + System.out.println("Cannot create the file: " + file.getPath()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public void writeBlockchainToFile(String filePath) { + File file = new File(filePath); + try { + FileOutputStream fos = new FileOutputStream(file); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(blockchain); + oos.close(); + fos.close(); + System.out.println("The Blockchain was successfully written to the file"); + System.out.println(""); + } catch (FileNotFoundException e) { + System.out.println("File not found"); + } catch (IOException e) { + System.out.println("Cannot write to the file"); + } + } + + public void setDifficulty() { + Block b = this.blockchain.peekLast(); + if (b == null) { + this.difficulty = 0; + } else { + if (getTimeSpent() < 10) { + this.difficulty += 1; + System.out.println("N was increased to " + this.difficulty); + } else if (getTimeSpent() >= 60) { + this.difficulty -= 1; + System.out.println("N was decreased to " + this.difficulty); + } else { + System.out.println("N stays the same"); + } + } + } + + public synchronized int getDifficulty() { + return this.difficulty; + } + + public synchronized LinkedList getBlockchain() { + return this.blockchain; + } + + public synchronized boolean addBlock(Block block) { + Block blockPrev = this.blockchain.peekLast(); + if (block != null) { + if (blockPrev == null && block.getPrevHash().equals("0")) { //first block + this.blockchain.add(block); + return true; + } else if (blockPrev.getHash().equals(block.getPrevHash())) { //block is valid + this.blockchain.add(block); + return true; + } else { + return false; + } + } else { + return false; + } + } + + public synchronized boolean isBlockchainValid() { + int cnt = 0; + if (!this.newFile) { + for (int i = 0; i < this.blockchain.size() - 1; i++) { + if (!this.blockchain.get(i).getHash().equals(this.blockchain.get(i + 1).getPrevHash())) { + cnt++; + } + } + //check that every message has an identifier greater than the maximum identifier of the previous block + for(int i = this.blockchain.size() - 1; i > 0; i--){ + for(int j = 0; j < this.blockchain.get(i).getMessageStack().size(); j++) { + if (!(this.blockchain.get(i-1).getMaxMsgID() < this.blockchain.get(i).getMessageStack().get(j).getId())) + { + cnt++; + } + } + } + } + return cnt == 0; + } + + private int getDifficultyFromFile(String hash){ + int i = 0; + while (hash.charAt(i) == '0'){ + i++; + } + return i; + } + + public void addMessagesToTheBlockchain(Message msg) throws Exception { + if(msg.getId() < Blockchain.ids) { + if(msg.verifySignature(msg.getList().get(0),msg.getList().get(1))) { + this.messages.add(msg); + } + else { + System.out.println("Signature is wrong"); + } + }else{ + System.out.println("Messaage rejected"); + } + } + + public void addMessagesToTheBlock(){ + this.blockchain.peekLast().writeMessagesStack(this.messages); + this.messages.clear(); + } + + public void setTimeSpent(int timeSpent){ + this.timeSpent = timeSpent; + } + + public int getTimeSpent() { + return timeSpent; + } + + public long setMsgID(){ + return ids++; + } + + +} diff --git a/src/blockchain/GenerateKeys.java b/src/blockchain/GenerateKeys.java new file mode 100644 index 0000000..abf980d --- /dev/null +++ b/src/blockchain/GenerateKeys.java @@ -0,0 +1,33 @@ +package blockchain; + +import java.security.*; + + +class GenerateKeys { + + private KeyPairGenerator keyGen; + + private PrivateKey privateKey; + private PublicKey publicKey; + + public GenerateKeys(int keylenth) throws NoSuchAlgorithmException, NoSuchProviderException { + this.keyGen = KeyPairGenerator.getInstance("RSA"); + this.keyGen.initialize(keylenth); + } + + public void createKeys(){ + KeyPair pair; + pair = this.keyGen.generateKeyPair(); + this.privateKey = pair.getPrivate(); + this.publicKey = pair.getPublic(); + } + + public PrivateKey getPrivateKey(){ + return this.privateKey; + } + + public PublicKey getPublicKey() { + return this.publicKey; + } + +} diff --git a/src/blockchain/Main.java b/src/blockchain/Main.java index adf1d02..ee3ee75 100644 --- a/src/blockchain/Main.java +++ b/src/blockchain/Main.java @@ -1,7 +1,49 @@ package blockchain; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + public class Main { - public static void main(String[] args) { - System.out.print("Hello world!"); + public static Blockchain blockchain; + public static List users = new ArrayList<>(); + public static List miners = new ArrayList<>(); + public static GenerateKeys gk; + + + public static void main(String[] args) throws InterruptedException, NoSuchProviderException, NoSuchAlgorithmException { + Scanner in = new Scanner(System.in); + String filePath = in.nextLine();//"D:\\hyperskillGit\\blockchain\\MyBlockchain"; + blockchain = new Blockchain(filePath); + gk = new GenerateKeys(1024); + gk.createKeys(); + + + if (blockchain.isBlockchainValid()) { + users.add(new User("Tom")); + users.add(new User("Sara")); + users.add(new User("John")); + + for (int i = 0; i < 7; i++) { + miners.add(new Miner(i + 1)); + } + miners.forEach(Thread::start); + + users.forEach(Thread::start); + + for (Thread miner : miners) { + miner.join(); + } + + blockchain.writeBlockchainToFile(filePath); + System.exit(0); + + } else { + System.out.println("Blockchain loaded from file is not valid."); + } + } } \ No newline at end of file diff --git a/src/blockchain/Message.java b/src/blockchain/Message.java new file mode 100644 index 0000000..35a3236 --- /dev/null +++ b/src/blockchain/Message.java @@ -0,0 +1,46 @@ +package blockchain; + +import java.io.Serializable; +import java.security.Signature; +import java.util.ArrayList; +import java.util.List; + + +class Message implements Serializable{ + private List list; + private long id; + + //The constructor of Message class builds the list that will be written to the file. + //The list consists of the message and the signature. + public Message(String data, long id) throws Exception { + list = new ArrayList<>(); + list.add(data.getBytes()); + list.add(sign(data)); + this.id = id; + } + + //The method that signs the data using the private key that is stored in keyFile path + public byte[] sign(String data) throws Exception{ + Signature rsa = Signature.getInstance("SHA1withRSA"); + rsa.initSign(Main.gk.getPrivateKey()); + rsa.update(data.getBytes()); + return rsa.sign(); + } + + //Method for signature verification that initializes with the Public Key, + //updates the data to be verified and then verifies them using the signature + public boolean verifySignature(byte[] data, byte[] signature) throws Exception { + Signature sig = Signature.getInstance("SHA1withRSA"); + sig.initVerify(Main.gk.getPublicKey()); + sig.update(data); + return sig.verify(signature); + } + + public List getList() { + return list; + } + + public long getId(){ + return this.id; + } +} diff --git a/src/blockchain/Miner.java b/src/blockchain/Miner.java new file mode 100644 index 0000000..e340889 --- /dev/null +++ b/src/blockchain/Miner.java @@ -0,0 +1,57 @@ +package blockchain; + + +import java.io.UnsupportedEncodingException; +import java.time.LocalTime; + +class Miner extends Thread { + private int minerNumber; + private int VC = 0; + + + public Miner(int minerNumber) { + this.minerNumber = minerNumber; + this.VC = 0; + } + + + @Override + public void run() { + LocalTime start = LocalTime.now(); + while (!Main.blockchain.addBlock(new Block(Main.blockchain.getBlockchain().peekLast(), Main.blockchain.getDifficulty()))) { } + Main.blockchain.addMessagesToTheBlock(); + LocalTime finish = LocalTime.now(); + Main.blockchain.setTimeSpent(finish.toSecondOfDay() - start.toSecondOfDay()); + System.out.println(""); + System.out.println("Block:"); + System.out.println("Created by miner # " + this.minerNumber); + addVC(100); + System.out.println(this.minerNumber + " gets " + 100 + "VC"); + try { + Main.blockchain.getBlockchain().peekLast().printOutResults(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + System.out.println("Block was generating for " + Main.blockchain.getTimeSpent() + " seconds"); + Main.blockchain.setDifficulty(); + } + + public void addVC(int add){ + this.VC += add; + } + + public void sendVC(int sub){ + if(this.VC < sub){ + System.out.println("Not enough VC, send transaction suspend"); + }else{ + this.VC -= sub; + } + } + + public int getVC() { + return this.VC; + } + +} + + diff --git a/src/blockchain/User.java b/src/blockchain/User.java new file mode 100644 index 0000000..53d8a4c --- /dev/null +++ b/src/blockchain/User.java @@ -0,0 +1,64 @@ +package blockchain; + + +import java.util.Scanner; + + +class User extends Thread { + private String name; + private Message message; + Scanner in = new Scanner(System.in); + private int VC = 100; + + public User(String name){ + this.name = name; + } + + @Override + public void run() { + while (!this.isInterrupted()) { + newMaessage(); + try { + Main.blockchain.addMessagesToTheBlockchain(getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private synchronized void newMaessage(){ + if(!this.isInterrupted()) { + synchronized (User.class) { + System.out.print(this.name + ": "); + String data = in.nextLine(); + data = this.name + ": " + data; + try { + this.message = new Message(data,Main.blockchain.setMsgID()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public Message getMessage(){ + return this.message; + } + + public void addVC(int add){ + this.VC += add; + } + + public void sendVC(int sub){ + if(this.VC < sub){ + System.out.println("Not enough VC, send transaction suspend"); + }else{ + this.VC -= sub; + } + } + + public int getVC() { + return this.VC; + } + +}