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