Initial commit
This commit is contained in:
commit
8666ad8752
|
@ -0,0 +1,31 @@
|
|||
### IntelliJ IDEA ###
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
target/
|
||||
.idea/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_19">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,16 @@
|
|||
<?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>DzieCoin</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>19</maven.compiler.source>
|
||||
<maven.compiler.target>19</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,118 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Block {
|
||||
private String hash;
|
||||
private String prevHash;
|
||||
private int height;
|
||||
private List<Transaction> transactions;
|
||||
private List<NFT> nfts;
|
||||
private int nonce;
|
||||
|
||||
public Block() {
|
||||
this.transactions = new ArrayList<>();
|
||||
this.nfts = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Block(String prevHash, int height, List<Transaction> transactions) {
|
||||
this.prevHash = prevHash;
|
||||
this.height = height;
|
||||
this.transactions = transactions;
|
||||
this.nfts = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Block(String hash, String prevHash, int height, List<Transaction> transactions, List<NFT> nfts, int nonce) {
|
||||
this.hash = hash;
|
||||
this.prevHash = prevHash;
|
||||
this.height = height;
|
||||
this.transactions = transactions;
|
||||
this.nfts = nfts;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public void mineBlock() {
|
||||
String blockStruct = this.prevHash + this.height;
|
||||
for (Transaction transaction : this.transactions) {
|
||||
blockStruct += transaction.toString();
|
||||
}
|
||||
|
||||
for (NFT nft : nfts) {
|
||||
blockStruct += nft.toString();
|
||||
}
|
||||
|
||||
int nonce = 0;
|
||||
|
||||
String hash;
|
||||
|
||||
System.out.println();
|
||||
while(true) {
|
||||
hash = CalculateHash.sha256sum(blockStruct + nonce);
|
||||
if(hash.startsWith("0".repeat(Main.difficulty))) break;
|
||||
nonce++;
|
||||
System.out.print("\rMining block " + height + "... " + nonce);
|
||||
}
|
||||
|
||||
System.out.println("\nSuccessfully mined block " + height + " (nonce: " + nonce + ")");
|
||||
|
||||
this.hash = hash;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public void addTransaction(Transaction transaction) {
|
||||
transactions.add(transaction);
|
||||
}
|
||||
|
||||
public void addNFT(NFT nft) {
|
||||
nfts.add(nft);
|
||||
}
|
||||
|
||||
public enum VerifyCodes {
|
||||
SUCCESS,
|
||||
INVALID_POW_SIGNATURE,
|
||||
HASH_MISMATCH,
|
||||
INVALID_BLOCK_HASH
|
||||
}
|
||||
|
||||
public VerifyCodes verifyBlock() {
|
||||
String blockStruct = this.prevHash + this.height;
|
||||
for (Transaction transaction : this.transactions) {
|
||||
blockStruct += transaction.toString();
|
||||
}
|
||||
for (NFT nft : nfts) {
|
||||
blockStruct += nft.toString();
|
||||
}
|
||||
blockStruct += this.nonce;
|
||||
|
||||
String calculatedHash = CalculateHash.sha256sum(blockStruct);
|
||||
|
||||
if(!calculatedHash.equals(this.hash)) return VerifyCodes.INVALID_BLOCK_HASH;
|
||||
if(!calculatedHash.startsWith("0".repeat(Main.difficulty))) return VerifyCodes.INVALID_POW_SIGNATURE;
|
||||
return VerifyCodes.SUCCESS;
|
||||
}
|
||||
|
||||
public List<Transaction> getTransactions() {
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public List<NFT> getNFTs() {
|
||||
return nfts;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getPrevHash() {
|
||||
return prevHash;
|
||||
}
|
||||
|
||||
public int getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Blockchain {
|
||||
private List<Block> blocks;
|
||||
private int length;
|
||||
|
||||
public Blockchain(String genesisBlockRecipient) {
|
||||
blocks = new ArrayList<>();
|
||||
length = blocks.size();
|
||||
|
||||
Transaction firstTransaction = new Transaction("0", genesisBlockRecipient, 10);
|
||||
List<Transaction> transactions = new ArrayList<>();
|
||||
transactions.add(firstTransaction);
|
||||
|
||||
Block genesisBlock = new Block("0", 1, transactions);
|
||||
|
||||
genesisBlock.mineBlock();
|
||||
if(genesisBlock.verifyBlock() != Block.VerifyCodes.SUCCESS) throw new IllegalStateException("Genesis block is invalid");
|
||||
|
||||
try {
|
||||
addBlock(genesisBlock);
|
||||
} catch (BlockchainIntegrityException e) {
|
||||
System.out.println("Blockchain Integrity Exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public List<Block> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public Block getLatestBlock() {
|
||||
return blocks.get(length - 1);
|
||||
}
|
||||
|
||||
public Block.VerifyCodes validateChain() {
|
||||
for (int i = 1; i < (length - 1); i++) {
|
||||
if(blocks.get(i).verifyBlock() != Block.VerifyCodes.SUCCESS) return blocks.get(i).verifyBlock();
|
||||
if(!blocks.get(i).getHash().equals(blocks.get(i + 1).getPrevHash())) return Block.VerifyCodes.HASH_MISMATCH;
|
||||
}
|
||||
|
||||
for (Block block : blocks) {
|
||||
if(!block.getHash().startsWith("0".repeat(Main.difficulty))) return Block.VerifyCodes.INVALID_POW_SIGNATURE;
|
||||
}
|
||||
|
||||
return Block.VerifyCodes.SUCCESS;
|
||||
}
|
||||
|
||||
public void addBlock(Block block) throws BlockchainIntegrityException {
|
||||
Block.VerifyCodes status = block.verifyBlock();
|
||||
|
||||
if(status == Block.VerifyCodes.HASH_MISMATCH) throw new BlockchainIntegrityException("Block " + block.getHeight() + " calculated hash and actual hash does not match");
|
||||
if(status == Block.VerifyCodes.INVALID_POW_SIGNATURE) throw new BlockchainIntegrityException("Block " + block.getHeight() + " contains invalid proof of work signature");
|
||||
if(status == Block.VerifyCodes.INVALID_BLOCK_HASH) throw new BlockchainIntegrityException("Block " + block.getHeight() + " contains invalid hash");
|
||||
|
||||
if(length != 0) { // don't check for very first block
|
||||
if (!getLatestBlock().getHash().equals(block.getPrevHash())) throw new BlockchainIntegrityException("Block " + block.getHeight() + " previous hash does not match");
|
||||
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
if(this.getAddressBalance(transaction.getSender()) < transaction.getAmount())
|
||||
throw new BlockchainIntegrityException("Wallet " + transaction.getSender() + " at block " + block.getHeight() + " tried to spend too much\nBalance: " + this.getAddressBalance(transaction.getSender()) + "\nTried to spend: " + transaction.getAmount());
|
||||
}
|
||||
}
|
||||
blocks.add(block);
|
||||
length++;
|
||||
}
|
||||
|
||||
public int getAddressBalance(String walletAddress) {
|
||||
int amount = 0;
|
||||
|
||||
for (Block block : blocks) {
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
if(transaction.getSender().equals(walletAddress)) amount -= transaction.getAmount();
|
||||
if(transaction.getRecipient().equals(walletAddress)) amount += transaction.getAmount();
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
public class BlockchainIntegrityException extends Exception {
|
||||
public BlockchainIntegrityException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public abstract class CalculateHash {
|
||||
public static String sha256sum(String input) {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch(NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
byte[] sha256sum = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b : sha256sum) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String md5sum(String input) {
|
||||
MessageDigest md;
|
||||
|
||||
try {
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
} catch(NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
byte[] md5sum = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b : md5sum) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static int difficulty;
|
||||
public static Blockchain blockchain;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Wallet w1 = new Wallet();
|
||||
Wallet w2 = new Wallet();
|
||||
Wallet w3 = new Wallet();
|
||||
|
||||
difficulty = 4;
|
||||
blockchain = new Blockchain(w1.getAddress());
|
||||
|
||||
NFT n1 = new NFT(w1.getAddress(), "licencja na wozek widlowy");
|
||||
|
||||
List<Transaction> t1 = new ArrayList<>();
|
||||
t1.add(new Transaction(w1.getAddress(), w2.getAddress(), 5));
|
||||
|
||||
Block b1 = new Block(blockchain.getLatestBlock().getHash(), blockchain.getLength() + 1, t1);
|
||||
|
||||
b1.addNFT(n1);
|
||||
|
||||
b1.mineBlock();
|
||||
|
||||
try {
|
||||
blockchain.addBlock(b1);
|
||||
} catch (BlockchainIntegrityException e) {
|
||||
System.out.println("Blockchain Integrity Exception: " + e.getMessage());
|
||||
}
|
||||
|
||||
List<Transaction> t2 = new ArrayList<>();
|
||||
t2.add(new Transaction(w1.getAddress(), w3.getAddress(), 2));
|
||||
t2.add(new Transaction(w2.getAddress(), w3.getAddress(), 3));
|
||||
|
||||
Block b2 = new Block(blockchain.getLatestBlock().getHash(), blockchain.getLength() + 1, t2);
|
||||
b2.mineBlock();
|
||||
|
||||
try {
|
||||
blockchain.addBlock(b2);
|
||||
} catch (BlockchainIntegrityException e) {
|
||||
System.out.println("Blockchain Integrity Exception: " + e.getMessage());
|
||||
}
|
||||
|
||||
for (Block block : blockchain.getBlocks()) {
|
||||
System.out.println("Block " + block.getHeight() + ": " + block.getHash());
|
||||
for (Transaction transaction : block.getTransactions()) {
|
||||
System.out.println(" " + transaction.getSender() + " => " + transaction.getRecipient() + " - " + transaction.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Balances:");
|
||||
System.out.println("Wallet 1: " + blockchain.getAddressBalance(w1.getAddress()));
|
||||
System.out.println("Wallet 2: " + blockchain.getAddressBalance(w2.getAddress()));
|
||||
System.out.println("Wallet 3: " + blockchain.getAddressBalance(w3.getAddress()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
public class NFT {
|
||||
private String contract;
|
||||
private String owner;
|
||||
private String data;
|
||||
|
||||
public NFT(String owner, String data) {
|
||||
this.owner = owner;
|
||||
this.data = data;
|
||||
this.contract = CalculateHash.md5sum(owner + data);
|
||||
}
|
||||
|
||||
public NFT(String contract, String owner, String data) {
|
||||
String testHash = CalculateHash.md5sum(owner + data);
|
||||
if(!testHash.equals(contract)) throw new IllegalStateException("NFT contract and data does not match");
|
||||
this.owner = owner;
|
||||
this.data = data;
|
||||
this.contract = contract;
|
||||
};
|
||||
|
||||
public String getContract() {
|
||||
return contract;
|
||||
}
|
||||
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NFT{" +
|
||||
"contract='" + contract + '\'' +
|
||||
", owner='" + owner + '\'' +
|
||||
", data='" + data + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
public class Transaction {
|
||||
private String sender;
|
||||
private String recipient;
|
||||
private int amount;
|
||||
|
||||
public Transaction(String sender, String recipient, int amount) {
|
||||
this.sender = sender;
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Transaction{" +
|
||||
"sender='" + sender + '\'' +
|
||||
", recipient='" + recipient + '\'' +
|
||||
", amount=" + amount +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package pl.mikorosa.dziecoin;
|
||||
|
||||
import java.security.*;
|
||||
|
||||
public class Wallet {
|
||||
private String address;
|
||||
private PublicKey publicKey;
|
||||
private PrivateKey privateKey;
|
||||
|
||||
Wallet() {
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(1024);
|
||||
KeyPair keyPair = keyGen.genKeyPair();
|
||||
privateKey = keyPair.getPrivate();
|
||||
publicKey = keyPair.getPublic();
|
||||
address = CalculateHash.md5sum(publicKey.getEncoded().toString());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
System.out.println("No such algo: " + e);
|
||||
} catch (ProviderException e) {
|
||||
System.out.println("Provider exception: " + e);
|
||||
} catch (Exception e) {
|
||||
System.out.println("General exception: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue