commit 8666ad8752bd6afb083e163b7a537541bd423d40 Author: Aelita4 Date: Wed Jan 4 22:15:07 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c68bbbb --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/DzieCoin.iml b/DzieCoin.iml new file mode 100644 index 0000000..b1c395b --- /dev/null +++ b/DzieCoin.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fe91a01 --- /dev/null +++ b/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + groupId + DzieCoin + 1.0-SNAPSHOT + + + 19 + 19 + UTF-8 + + \ No newline at end of file diff --git a/src/main/java/pl/mikorosa/dziecoin/Block.java b/src/main/java/pl/mikorosa/dziecoin/Block.java new file mode 100644 index 0000000..d84ffd2 --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/Block.java @@ -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 transactions; + private List nfts; + private int nonce; + + public Block() { + this.transactions = new ArrayList<>(); + this.nfts = new ArrayList<>(); + } + + public Block(String prevHash, int height, List transactions) { + this.prevHash = prevHash; + this.height = height; + this.transactions = transactions; + this.nfts = new ArrayList<>(); + } + + public Block(String hash, String prevHash, int height, List transactions, List 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 getTransactions() { + return transactions; + } + + public int getHeight() { + return height; + } + + public List getNFTs() { + return nfts; + } + + public String getHash() { + return hash; + } + + public String getPrevHash() { + return prevHash; + } + + public int getNonce() { + return nonce; + } +} diff --git a/src/main/java/pl/mikorosa/dziecoin/Blockchain.java b/src/main/java/pl/mikorosa/dziecoin/Blockchain.java new file mode 100644 index 0000000..ab21fb0 --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/Blockchain.java @@ -0,0 +1,86 @@ +package pl.mikorosa.dziecoin; + +import java.util.ArrayList; +import java.util.List; + +public class Blockchain { + private List blocks; + private int length; + + public Blockchain(String genesisBlockRecipient) { + blocks = new ArrayList<>(); + length = blocks.size(); + + Transaction firstTransaction = new Transaction("0", genesisBlockRecipient, 10); + List 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 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; + } +} diff --git a/src/main/java/pl/mikorosa/dziecoin/BlockchainIntegrityException.java b/src/main/java/pl/mikorosa/dziecoin/BlockchainIntegrityException.java new file mode 100644 index 0000000..466dba1 --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/BlockchainIntegrityException.java @@ -0,0 +1,8 @@ +package pl.mikorosa.dziecoin; + +public class BlockchainIntegrityException extends Exception { + public BlockchainIntegrityException(String message) { + super(message); + } +} + diff --git a/src/main/java/pl/mikorosa/dziecoin/CalculateHash.java b/src/main/java/pl/mikorosa/dziecoin/CalculateHash.java new file mode 100644 index 0000000..4f2be3a --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/CalculateHash.java @@ -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(); + } +} diff --git a/src/main/java/pl/mikorosa/dziecoin/Main.java b/src/main/java/pl/mikorosa/dziecoin/Main.java new file mode 100644 index 0000000..7b59f35 --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/Main.java @@ -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 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 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())); + } +} \ No newline at end of file diff --git a/src/main/java/pl/mikorosa/dziecoin/NFT.java b/src/main/java/pl/mikorosa/dziecoin/NFT.java new file mode 100644 index 0000000..83cfaf6 --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/NFT.java @@ -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 + '\'' + + '}'; + } +} diff --git a/src/main/java/pl/mikorosa/dziecoin/Transaction.java b/src/main/java/pl/mikorosa/dziecoin/Transaction.java new file mode 100644 index 0000000..19ac6ea --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/Transaction.java @@ -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 + + '}'; + } +} diff --git a/src/main/java/pl/mikorosa/dziecoin/Wallet.java b/src/main/java/pl/mikorosa/dziecoin/Wallet.java new file mode 100644 index 0000000..3c8286a --- /dev/null +++ b/src/main/java/pl/mikorosa/dziecoin/Wallet.java @@ -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; + } +}