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