Skip to content

Commit

Permalink
localchain functional
Browse files Browse the repository at this point in the history
  • Loading branch information
Victor Marin committed Feb 17, 2021
0 parents commit c407ee9
Show file tree
Hide file tree
Showing 14 changed files with 1,067 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
localchain.db
localchain.iml
.idea
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
to install:
mvn clean compile assembly:single

Simple, hacky toy blockchain implementation.

Thank you @CryptoKass for the knowledge and code bits - https://medium.com/programmers-blockchain/blockchain-development-mega-guide-5a316e6d10df#bba8 / https://github.com/CryptoKass/NoobChain-Tutorial-Part-2

You compile it and run multiple instances of the program in your console.
Follow terminal text to figure out things.

First instance run will start the network itself.
Consecutive instances will be ask you for input on what you want to add, be it a miner of a trader.

Adding more miners won't do much since all miners have the same processing power and start with nonce = 0;
So the first miner will always be the top one with the longest chain. (Might be different if two miners are started at roughly the same time)

Each trader will have its wallet PublicKey posted on the terminal. To send LocalCoins from one Trader to another, you copy the receivers Public key and paste it in the terminal where it asks for it

For now, there is no miner reward for mining a block, neither is there a reward based on transaction fees. Miners do the work out of the goodness of their hearts.

Coins are generated once, in a Genesis Transaction that happens when the first Trader joins the network. When that happens, 1000LC is being transfered to this trader.
54 changes: 54 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>groupId</groupId>
<artifactId>localchain</artifactId>
<version>1.0</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.16.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<!-- Build an executable JAR -->
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.LocalChain.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
49 changes: 49 additions & 0 deletions src/main/java/com/LocalChain/Block.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.LocalChain;

import java.util.ArrayList;
import java.util.Date;

public class Block {

public String hash;
public String previousBlockHash;
public String merkleRoot = "emptyString";
public ArrayList<TxRecord> transactions = new ArrayList<>();
public ArrayList<String> transactionIds = new ArrayList<>();
public int nonce = 0;
public long timestamp;

public Block(String previousBlockHash) {
this.hash = null;
this.previousBlockHash = previousBlockHash;
this.timestamp = new Date().getTime();
}

public boolean isMined() {
return hash != null;
}

public boolean addTx(TxRecord transaction) {
if(transaction == null) return false;
if((!transaction.processTx())) {
System.out.println("Transaction failed to process. Discarded.");
return false;
}

transactions.add(transaction);
transactionIds.add(transaction.txId);
System.out.println("Transaction Successfully added to Block");
return true;
}

@Override
public String toString() {
return "Block{" +
"hash='" + hash + '\'' +
", previousBlockHash='" + previousBlockHash + '\'' +
", merkleRoot='" + merkleRoot + '\'' +
", nonce=" + nonce +
", timestamp=" + timestamp +
'}';
}
}
115 changes: 115 additions & 0 deletions src/main/java/com/LocalChain/CryptoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.LocalChain;

import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

public class CryptoService {
//Thanks CryptoKass

//Applies Sha256 to a string and returns the result.
public static String applySha256(String input){

try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");

//Applies sha256 to our input,
byte[] hash = digest.digest(input.getBytes("UTF-8"));

StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
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);
}
}

//Applies ECDSA Signature and returns the result ( as bytes ).
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;
}

//Verifies a String signature
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 getStringFromKey(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}

public static String getMerkleRoot(ArrayList<TxRecord> transactions) {
int count = transactions.size();

List<String> previousTreeLayer = new ArrayList<String>();
for(TxRecord transaction : transactions) {
previousTreeLayer.add(transaction.txId);
}
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;
}

public static PublicKey getPublicKeyFromString(String publicKey) {
try {
EncodedKeySpec publicKeySpace = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
return keyFactory.generatePublic(publicKeySpace);
} catch (Exception e) {
System.out.println("ERR in getting PublicK from DB string");
}

return null;
}

public static PrivateKey getPrivateKeyFromString(String privateKey) {
try {
EncodedKeySpec privateKeySpace = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
return keyFactory.generatePrivate(privateKeySpace);
} catch (Exception e) {
System.out.println("ERR in getting PrivateK from DB string");
}

return null;
}
}

70 changes: 70 additions & 0 deletions src/main/java/com/LocalChain/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.LocalChain;

import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.Scanner;

public class Main {

public static void main(String[] args) {
Network network;
Scanner userInput = new Scanner(System.in);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

File f = new File(System.getProperty("user.dir") + "/localchain.db");
if (!f.exists()) {
System.out.println("Starting the network!");
network = new Network();
network.startNetwork();

System.out.println("\n\nPrint the current longest chain? (just press whatever, doesn't matter)");
String answer = userInput.next();

while (answer != null) {
System.out.println("CHAIN:");
Network.printLatestChain();
System.out.println("USER COUNT:");
System.out.println(Network.getUserCount());
System.out.println("\n\n");

System.out.println("\n\nPrint the current longest chain? (just press whatever, doesn't matter)");
answer = userInput.next();
}
} else {
System.out.println("What do you wish to be?");
System.out.println("(1) Miner");
System.out.println("(2) Trader");

int answer = userInput.nextInt();
userInput.nextLine();

System.out.println("Give a name to your user...");
String userName = userInput.nextLine();

switch (answer) {
case 1:
System.out.println("Joining network as a miner!");
Miner miner = new Miner(userName);
miner.startMining();
break;
case 2:
System.out.println("Joining network as a normal trading user!");
User trader = new User(userName);

if (Network.noTransactionsRecorded()) {
PublicKey pubKey = Network.getNetworkPublicKey();
PrivateKey privKey = Network.getNetworkPrivateKey();

TxRecord newTransaction = new TxRecord(pubKey, trader.wallet.publicKey, 1000, null);
newTransaction.generateSignature(privKey);
Network.addUncofirmedTxRecord(newTransaction);
}

trader.startTrading(userInput);
break;
}
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/LocalChain/MineUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.LocalChain;

public class MineUtils {

public Block mine(Block block, int difficulty) {
System.out.println("Starting mining:\n");
int nonce = 0;

String hash;
String target = getDificultyString(difficulty);
if (block.hash == null) {
block.nonce = nonce;
hash = calculateHash(block);
block.hash = hash;
}

while(!block.hash.substring( 0, difficulty).equals(target)) {
nonce++;
block.nonce = nonce;
hash = calculateHash(block);
block.hash = hash;
}

System.out.println("Block Mined!!!: " + block.hash + " NONCE:" + block.nonce);
return block;
}

private String calculateHash(Block block) {
return CryptoService.applySha256(
block.previousBlockHash +
Long.toString(block.timestamp) +
block.merkleRoot +
Integer.toString(block.nonce)
);
}

private String getDificultyString(int difficulty) {
return new String(new char[difficulty]).replace('\0', '0');
}
}
Loading

0 comments on commit c407ee9

Please sign in to comment.