-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Victor Marin
committed
Feb 17, 2021
0 parents
commit c407ee9
Showing
14 changed files
with
1,067 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
target | ||
localchain.db | ||
localchain.iml | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + | ||
'}'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
Oops, something went wrong.