Quellcode durchsuchen

basic mining and sending money seems to work now

Malte Kraus vor 8 Jahren
Ursprung
Commit
9985148595
19 geänderte Dateien mit 219 neuen und 81 gelöschten Zeilen
  1. 2 0
      .gitignore
  2. 0 0
      __init__.py
  3. 0 20
      crypto.py
  4. 0 13
      mining_strategy.py
  5. 0 0
      src/__init__.py
  6. 30 7
      src/block.py
  7. 20 8
      src/blockchain.py
  8. 13 9
      src/chainbuilder.py
  9. 41 0
      src/crypto.py
  10. 2 1
      src/merkle.py
  11. 14 7
      src/mining.py
  12. 23 0
      src/mining_strategy.py
  13. 5 5
      src/proof_of_work.py
  14. 0 0
      src/protocol.py
  15. 24 11
      src/transaction.py
  16. 0 0
      tests/__init__.py
  17. 17 0
      tests/mock_protocol.py
  18. 0 0
      tests/test_merkle.py
  19. 28 0
      tests/test_mining.py

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+__pycache__
+.venv

+ 0 - 0
__init__.py


+ 0 - 20
crypto.py

@@ -1,20 +0,0 @@
-""" Cryptographic primitives. """
-
-from Crypto.Signature import PKCS1_v1_5
-from Crypto.Hash import SHA512
-
-def get_hasher():
-    """ Returns a object that you can use for hashing. Currently SHA512, swap it out for something if you feel like it! """
-    return SHA512.new()
-
-MAX_HASH = (1 << 512) - 1
-
-def verify_sign(hashed_value, signature, pub_key):
-    """ Verify a signature for a already hashed value and a public key. """
-    ver = PKCS1_v1_5.new(pub_key)
-    return ver.verify(hashed_value, signature)
-
-def sign(hashed_value, priv_key):
-    """ Sign a hashed value with a private key. """
-    signer = PKCS1_v1_5.new(priv_key)
-    return signer.sign(hashed_value)

+ 0 - 13
mining_strategy.py

@@ -1,13 +0,0 @@
-from block import Block
-from datetime import datetime
-from merkle import merkle_tree
-
-def create_block(blockchain, unconfirmed_transactions):
-    """
-    Creates a new block that can be mined.
-    """
-    tree = merkle_tree(unconfirmed_transactions)
-    head = blockchain.head
-    difficulty = blockchain.compute_difficulty()
-    return Block(None, head.hash, datetime.now(), 0, head.height + difficulty,
-                 None, difficulty, tree.get_hash(), unconfirmed_transactions)

+ 0 - 0
src/__init__.py


+ 30 - 7
block.py → src/block.py

@@ -1,7 +1,9 @@
-from merkle import merkle_tree
-from crypto import get_hasher
-from proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY
 from datetime import datetime
 from datetime import datetime
+from binascii import hexlify
+
+from .merkle import merkle_tree
+from .crypto import get_hasher
+from .proof_of_work import verify_proof_of_work, GENESIS_DIFFICULTY
 
 
 class Block:
 class Block:
     """ A block. """
     """ A block. """
@@ -17,6 +19,17 @@ class Block:
         self.difficulty = difficulty
         self.difficulty = difficulty
         self.transactions = transactions
         self.transactions = transactions
 
 
+    def __str__(self):
+        return """Block {}
+    Previous Block: {}
+    Merkle Hash: {}
+    Time: {}
+    Nonce: {:x}
+    Height: {}
+    Received Time: {}
+    Difficulty: {:x}""".format(hexlify(self.hash), hexlify(self.prev_block_hash), hexlify(self.merkle_root_hash), self.time, self.nonce, self.height, self.received_time, self.difficulty)
+
+
     def verify_merkle(self):
     def verify_merkle(self):
         """ Verify that the merkle root hash is correct for the transactions in this block. """
         """ Verify that the merkle root hash is correct for the transactions in this block. """
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
         return merkle_tree(self.transactions).get_hash() == self.merkle_root_hash
@@ -25,14 +38,14 @@ class Block:
         hasher = get_hasher()
         hasher = get_hasher()
         hasher.update(self.prev_block_hash)
         hasher.update(self.prev_block_hash)
         hasher.update(self.merkle_root_hash)
         hasher.update(self.merkle_root_hash)
-        hasher.update(self.time)
-        hasher.update(self.difficulty)
+        hasher.update(str(self.time.timestamp()).encode())
+        hasher.update(str(self.difficulty).encode())
         return hasher
         return hasher
 
 
     def get_hash(self):
     def get_hash(self):
         """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
         """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
         hasher = self.get_partial_hash()
         hasher = self.get_partial_hash()
-        hasher.update(self.nonce) # for mining we want to get a copy of hasher here
+        hasher.update(str(self.nonce).encode()) # for mining we want to get a copy of hasher here
         return hasher.digest()
         return hasher.digest()
 
 
     def verify_difficulty(self):
     def verify_difficulty(self):
@@ -47,10 +60,20 @@ class Block:
         return chain.get_block_by_hash(chain) is not None
         return chain.get_block_by_hash(chain) is not None
 
 
     def verify_transactions(self, chain):
     def verify_transactions(self, chain):
-        """ Verify all transaction are valid in the given block chain. """
+        """ Verify all transaction in this block are valid in the given block chain. """
+        mining_reward = None
+        # TODO: mining fees and variable block rewards
         for t in self.transactions:
         for t in self.transactions:
+            if not t.inputs:
+                if mining_reward is not None:
+                    return False
+                mining_reward = t
+
             if not t.verify(chain):
             if not t.verify(chain):
                 return False
                 return False
+        if mining_reward is not None:
+            if sum(map(lambda t: t.amount, mining_reward.targets)) > chain.compute_blockreward(chain.get_block_by_hash(self.prev_block_hash)):
+                return False
         return True
         return True
 
 
     def verify(self, chain):
     def verify(self, chain):

+ 20 - 8
blockchain.py → src/blockchain.py

@@ -3,13 +3,14 @@ class Blockchain:
     def __init__(self, blocks):
     def __init__(self, blocks):
         self.blocks = blocks
         self.blocks = blocks
         assert self.blocks[0].height == 0
         assert self.blocks[0].height == 0
-        self.blocks_by_hash = {block.hash: block for block in blocks}
+        self.block_indices = {block.hash: i for (i, block) in enumerate(blocks)}
 
 
     def get_transaction_by_hash(self, hash_val):
     def get_transaction_by_hash(self, hash_val):
         """
         """
         Returns a transaction from its hash, or None.
         Returns a transaction from its hash, or None.
         """
         """
-        for block in self.blocks[prev_block.height::-1]:
+        # TODO: build a hash table with this info
+        for block in self.blocks[::-1]:
             for trans in block.transactions:
             for trans in block.transactions:
                 if trans.get_hash() == hash_val:
                 if trans.get_hash() == hash_val:
                     return trans
                     return trans
@@ -24,8 +25,9 @@ class Blockchain:
         if prev_block is None:
         if prev_block is None:
             prev_block = self.head
             prev_block = self.head
 
 
-        assert self.blocks[prev_block.height] is prev_block
-        for block in self.blocks[prev_block.height::-1]:
+        idx = self.block_indices[prev_block.hash]
+        assert self.blocks[idx] is prev_block
+        for block in self.blocks[idx::-1]:
             for trans in block.transactions:
             for trans in block.transactions:
                 if transaction_input in trans.inputs:
                 if transaction_input in trans.inputs:
                     return False
                     return False
@@ -35,17 +37,18 @@ class Blockchain:
         """
         """
         Returns a block by its hash value, or None if it cannot be found.
         Returns a block by its hash value, or None if it cannot be found.
         """
         """
-        return self.blocks_by_hash.get(hash_val)
+        return self.blocks[self.block_indices.get(hash_val)]
 
 
     def verify_all_transactions(self):
     def verify_all_transactions(self):
         """
         """
         Verify the transactions in all blocks in this chain.
         Verify the transactions in all blocks in this chain.
         """
         """
         for block in self.blocks:
         for block in self.blocks:
-            block.verify_transactions(self)
-
+            if not block.verify_transactions(self):
+                return False
+        return True
 
 
-    @getter
+    @property
     def head(self):
     def head(self):
         """ The head of this block chain. """
         """ The head of this block chain. """
         return self.blocks[-1]
         return self.blocks[-1]
@@ -55,3 +58,12 @@ class Blockchain:
         # TODO: dynamic calculation
         # TODO: dynamic calculation
         # TODO: verify difficulty in new blocks
         # TODO: verify difficulty in new blocks
         return self.head.difficulty
         return self.head.difficulty
+
+    def compute_blockreward(self, prev_block):
+        assert prev_block is not None
+        reward = 1000
+        l = self.block_indices[prev_block.hash]
+        while l > 0:
+            l = l - 10000
+            reward = reward // 2
+        return reward

+ 13 - 9
chainbuilder.py → src/chainbuilder.py

@@ -1,5 +1,5 @@
-from block import GENESIS_BLOCK, GENESIS_BLOCK_HASH
-from blockchain import Blockchain
+from .block import GENESIS_BLOCK, GENESIS_BLOCK_HASH
+from .blockchain import Blockchain
 
 
 class ChainBuilder:
 class ChainBuilder:
     """
     """
@@ -7,7 +7,7 @@ class ChainBuilder:
     chain that it attempts to download and verify.
     chain that it attempts to download and verify.
     """
     """
 
 
-    def __init__(self):
+    def __init__(self, protocol):
         self.primary_block_chain = Blockchain([GENESIS_BLOCK])
         self.primary_block_chain = Blockchain([GENESIS_BLOCK])
         self.unconfirmed_block_chain = []
         self.unconfirmed_block_chain = []
 
 
@@ -16,18 +16,21 @@ class ChainBuilder:
 
 
         self.chain_change_handlers = []
         self.chain_change_handlers = []
 
 
+        protocol.block_receive_handlers.append(self.new_block_received)
+        protocol.trans_receive_handlers.append(self.new_transaction_received)
+
     def new_transaction_received(self, transaction):
     def new_transaction_received(self, transaction):
         """ Event handler that is called by the network layer when a transaction is received. """
         """ Event handler that is called by the network layer when a transaction is received. """
         hash_val = transaction.get_hash()
         hash_val = transaction.get_hash()
-        if block_chain.get_transaction_by_hash(hash_val) is None:
-           unconfirmed_transactions[hash_val] = transaction
+        if self.primary_block_chain.get_transaction_by_hash(hash_val) is None:
+           self.unconfirmed_transactions[hash_val] = transaction
 
 
     def _new_primary_block_chain(self, chain):
     def _new_primary_block_chain(self, chain):
         """
         """
         Does all the housekeeping that needs to be done when a new longest chain is found.
         Does all the housekeeping that needs to be done when a new longest chain is found.
         """
         """
         self.primary_block_chain = chain
         self.primary_block_chain = chain
-        for (hash_val, trans) in self.unconfirmed_transactions:
+        for (hash_val, trans) in self.unconfirmed_transactions.items():
             if not trans.verify(chain):
             if not trans.verify(chain):
                 del self.unconfirmed_transactions[hash_val]
                 del self.unconfirmed_transactions[hash_val]
 
 
@@ -43,6 +46,7 @@ class ChainBuilder:
         while unc[-1].prev_block_hash in self.block_cache:
         while unc[-1].prev_block_hash in self.block_cache:
             unc.append(self.block_cache[unc[-1].prev_block_hash])
             unc.append(self.block_cache[unc[-1].prev_block_hash])
 
 
+
         if unc[-1].height == 0:
         if unc[-1].height == 0:
             chain = Blockchain(unc[::-1])
             chain = Blockchain(unc[::-1])
             if chain.verify_all_transactions():
             if chain.verify_all_transactions():
@@ -62,13 +66,13 @@ class ChainBuilder:
             if self.unconfirmed_block_chain[-1].prev_block_hash == block.hash:
             if self.unconfirmed_block_chain[-1].prev_block_hash == block.hash:
                 self.unconfirmed_block_chain.append(block)
                 self.unconfirmed_block_chain.append(block)
                 self.get_next_unconfirmed_block()
                 self.get_next_unconfirmed_block()
-            else if self.unconfirmed_block_chain[0].hash == block.prev_block_hash:
+            elif self.unconfirmed_block_chain[0].hash == block.prev_block_hash:
                 self.unconfirmed_block_chain.insert(0, block)
                 self.unconfirmed_block_chain.insert(0, block)
 
 
-            if block.height > self.unconfirmed_block_chain[0].height and block.height > self.primary_block_chain.get_height():
+            if block.height > self.unconfirmed_block_chain[0].height and block.height > self.primary_block_chain.head.height:
                 self.unconfirmed_block_chain = [block]
                 self.unconfirmed_block_chain = [block]
                 self.get_next_unconfirmed_block()
                 self.get_next_unconfirmed_block()
-        elif block.height > self.primary_block_chain.get_height():
+        elif block.height > self.primary_block_chain.head.height:
             self.unconfirmed_block_chain = [block]
             self.unconfirmed_block_chain = [block]
             self.get_next_unconfirmed_block()
             self.get_next_unconfirmed_block()
 
 

+ 41 - 0
src/crypto.py

@@ -0,0 +1,41 @@
+""" Cryptographic primitives. """
+
+from Crypto.Signature import PKCS1_v1_5
+from Crypto.Hash import SHA512
+from Crypto.PublicKey import RSA
+
+
+def get_hasher():
+    """ Returns a object that you can use for hashing. Currently SHA512, swap it out for something if you feel like it! """
+    return SHA512.new()
+
+MAX_HASH = (1 << 512) - 1
+
+class Signing:
+    def __init__(self, byte_repr):
+        self.rsa = RSA.importKey(byte_repr)
+
+    def verify_sign(self, hashed_value, signature):
+        """ Verify a signature for a already hashed value and a public key. """
+        ver = PKCS1_v1_5.new(self.rsa)
+        h = get_hasher()
+        h.update(hashed_value)
+        return ver.verify(h, signature)
+
+    def sign(self, hashed_value):
+        """ Sign a hashed value with a private key. """
+        signer = PKCS1_v1_5.new(self.rsa)
+        h = get_hasher()
+        h.update(hashed_value)
+        return signer.sign(h)
+
+    @classmethod
+    def generatePrivateKey(cls):
+        return Signing(RSA.generate(3072).exportKey())
+
+    def as_bytes(self, include_priv=False):
+        """ bytes representation of this key. """
+        if include_priv:
+            return self.rsa.exportKey()
+        else:
+            return self.rsa.publickey().exportKey()

+ 2 - 1
merkle.py → src/merkle.py

@@ -1,7 +1,8 @@
-from crypto import get_hasher
 import json
 import json
 from treelib import Node, Tree
 from treelib import Node, Tree
 
 
+from .crypto import get_hasher
+
 class MerkleNode:
 class MerkleNode:
     """ A hash tree node, pointing to a leaf value or another node. """
     """ A hash tree node, pointing to a leaf value or another node. """
 
 

+ 14 - 7
mining.py → src/mining.py

@@ -1,15 +1,19 @@
-from proof_of_work import ProofOfWork
-from chainbuilder import ChainBuilder
+from .proof_of_work import ProofOfWork
+from .chainbuilder import ChainBuilder
+from . import mining_strategy
+from .protocol import Protocol
+
 from threading import Thread
 from threading import Thread
-import mining_strategy
 from time import sleep
 from time import sleep
 
 
 class Miner:
 class Miner:
-    def __init__(self):
-        self.chainbuilder = ChainBuilder()
+    def __init__(self, proto, reward_pubkey):
+        self.proto = proto
+        self.chainbuilder = ChainBuilder(proto)
         self.chainbuilder.chain_change_handlers.append(self.chain_changed)
         self.chainbuilder.chain_change_handlers.append(self.chain_changed)
         self.is_mining = False
         self.is_mining = False
         self.cur_miner = None
         self.cur_miner = None
+        self.reward_pubkey = reward_pubkey
         Thread(target=self._miner_thread, daemon=True).start()
         Thread(target=self._miner_thread, daemon=True).start()
 
 
     def _miner_thread(self):
     def _miner_thread(self):
@@ -18,7 +22,10 @@ class Miner:
             if miner is None:
             if miner is None:
                 sleep(1)
                 sleep(1)
             else:
             else:
-                success = miner.run()
+                block = miner.run()
+                self.cur_miner = None
+                if block is not None:
+                    self.proto.broadcast_mined_block(block)
 
 
     def start_mining(self, block):
     def start_mining(self, block):
         """ Start mining on a new block. """
         """ Start mining on a new block. """
@@ -38,5 +45,5 @@ class Miner:
         primary chain changes.
         primary chain changes.
         """
         """
         chain = self.chainbuilder.primary_block_chain
         chain = self.chainbuilder.primary_block_chain
-        block = mining_strategy.create_block(chain, self.chainbuilder.unconfirmed_transactions)
+        block = mining_strategy.create_block(chain, self.chainbuilder.unconfirmed_transactions.values(), self.reward_pubkey)
         self.start_mining(block)
         self.start_mining(block)

+ 23 - 0
src/mining_strategy.py

@@ -0,0 +1,23 @@
+from datetime import datetime
+
+from .block import Block
+from .merkle import merkle_tree
+from .transaction import Transaction, TransactionTarget
+
+from Crypto.PublicKey import RSA
+
+def create_block(blockchain, unconfirmed_transactions, reward_pubkey):
+    """
+    Creates a new block that can be mined.
+    """
+    head = blockchain.head
+
+    transactions = list(unconfirmed_transactions)
+    reward = blockchain.compute_blockreward(head)
+    trans = Transaction([], [TransactionTarget(reward_pubkey, reward)], [], iv=head.hash)
+    transactions.append(trans)
+
+    tree = merkle_tree(transactions)
+    difficulty = blockchain.compute_difficulty()
+    return Block(None, head.hash, datetime.now(), 0, head.height + difficulty,
+                 None, difficulty, tree.get_hash(), transactions)

+ 5 - 5
proof_of_work.py → src/proof_of_work.py

@@ -1,4 +1,4 @@
-from crypto import MAX_HASH
+from .crypto import MAX_HASH
 
 
 def verify_proof_of_work(block):
 def verify_proof_of_work(block):
     """ Verify the proof of work on a block. """
     """ Verify the proof of work on a block. """
@@ -12,7 +12,7 @@ class ProofOfWork:
         self.block = block
         self.block = block
         self.success = False
         self.success = False
 
 
-    def abort(self)
+    def abort(self):
         self.stopped = True
         self.stopped = True
 
 
     def run(self):
     def run(self):
@@ -23,9 +23,9 @@ class ProofOfWork:
         while not self.stopped:
         while not self.stopped:
             for _ in range(1000):
             for _ in range(1000):
                 h = hasher.copy()
                 h = hasher.copy()
-                h.update(self.block.nonce)
+                h.update(str(self.block.nonce).encode())
                 self.block.hash = h.digest()
                 self.block.hash = h.digest()
                 if verify_proof_of_work(self.block):
                 if verify_proof_of_work(self.block):
-                    return True
+                    return self.block
                 self.block.nonce += 1
                 self.block.nonce += 1
-        return False
+        return None

+ 0 - 0
protocol.py → src/protocol.py


+ 24 - 11
transaction.py → src/transaction.py

@@ -1,5 +1,6 @@
 from collections import namedtuple
 from collections import namedtuple
-from crypto import get_hasher, sign, verify_sign
+
+from .crypto import get_hasher, Signing
 
 
 """ The recipient of a transaction ('coin'). """
 """ The recipient of a transaction ('coin'). """
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
@@ -10,28 +11,40 @@ TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_i
 
 
 class Transaction:
 class Transaction:
 
 
-    def __init__(self, inputs, targets, signatures=None):
+    def __init__(self, inputs, targets, signatures=None, iv=None):
         self.inputs = inputs
         self.inputs = inputs
         self.targets = targets
         self.targets = targets
         self.signatures = signatures or []
         self.signatures = signatures or []
+        # The IV is used to differentiate block reward transactions.
+        # These have no inputs and therefore would otherwise hash to the
+        # same value, when the target is identical.
+        # Reuse of IVs leads to inaccessible coins.
+        self.iv = iv
 
 
     def get_hash(self):
     def get_hash(self):
         """ Hash this transaction. Returns raw bytes. """
         """ Hash this transaction. Returns raw bytes. """
         h = get_hasher()
         h = get_hasher()
+        if self.iv is not None:
+            h.update(self.iv)
         for target in self.targets:
         for target in self.targets:
-            h.update(target.amount)
-            h.update(target.recipient_pk)
+            h.update(str(target.amount).encode())
+            h.update(target.recipient_pk.as_bytes())
         for inp in self.inputs:
         for inp in self.inputs:
-            h.update(inp)
+            h.update(inp.transaction_hash)
+            h.update(str(inp.output_idx).encode())
         return h.digest()
         return h.digest()
 
 
-    def sign(self, private_key):
-        """ Sign this transaction with a private key. You need to call this in the same order as the inputs. """
-        self.signatures.append(sign(self.get_hash(), private_key))
+    def sign(self, private_keys):
+        """
+        Sign this transaction with the given private keys. The private keys need
+        to be in the same order as the inputs.
+        """
+        for private_key in private_keys:
+            self.signatures.append(private_key.sign(self.get_hash()))
 
 
     def _verify_signatures(self, chain):
     def _verify_signatures(self, chain):
         """ Verify that all inputs are signed and the signatures are valid. """
         """ Verify that all inputs are signed and the signatures are valid. """
-        if len(self.signatures) != len(self.inputs)
+        if len(self.signatures) != len(self.inputs):
             return False
             return False
 
 
         for (s, i) in zip(self.signatures, self.inputs):
         for (s, i) in zip(self.signatures, self.inputs):
@@ -42,8 +55,8 @@ class Transaction:
     def _verify_single_sig(self, sig, inp, chain):
     def _verify_single_sig(self, sig, inp, chain):
         """ Verifies the signature on a single input. """
         """ Verifies the signature on a single input. """
         trans = chain.get_transaction_by_hash(inp.transaction_hash)
         trans = chain.get_transaction_by_hash(inp.transaction_hash)
-        sender_pk = trans.targets[inp.output_idx]
-        return verify_sign(self.get_hash(), sig)
+        sender_pk = trans.targets[inp.output_idx].recipient_pk
+        return sender_pk.verify_sign(self.get_hash(), sig)
 
 
 
 
     def _verify_single_spend(self, chain, prev_block):
     def _verify_single_spend(self, chain, prev_block):

+ 0 - 0
tests/__init__.py


+ 17 - 0
tests/mock_protocol.py

@@ -0,0 +1,17 @@
+from src.mining import Miner
+
+class MockProtocol:
+    def __init__(self):
+        self.block_receive_handlers = []
+        self.trans_receive_handlers = []
+
+    def fake_block_received(self, block):
+        for handler in self.block_receive_handlers:
+            handler(block)
+
+    def fake_trans_received(self, trans):
+        for handler in self.trans_receive_handlers:
+            handler(trans)
+
+    def broadcast_mined_block(self, block):
+        self.fake_block_received(block)

+ 0 - 0
test_merkle.py → tests/test_merkle.py


+ 28 - 0
tests/test_mining.py

@@ -0,0 +1,28 @@
+from .mock_protocol import MockProtocol
+from src.mining import Miner
+from src.block import GENESIS_BLOCK
+from src.crypto import Signing
+from src.transaction import Transaction, TransactionInput, TransactionTarget
+
+from time import sleep
+
+reward_key = Signing.generatePrivateKey()
+
+proto = MockProtocol()
+miner1 = Miner(proto, reward_key)
+miner2 = Miner(proto, reward_key)
+miner2.chain_changed()
+
+
+
+sleep(5)
+#proto.fake_block_received(GENESIS_BLOCK)
+strans1 = miner2.chainbuilder.primary_block_chain.head.transactions[0]
+strans1 = TransactionInput(strans1.get_hash(), 0)
+strans2 = miner2.chainbuilder.primary_block_chain.head.transactions[0]
+strans2 = TransactionInput(strans2.get_hash(), 0)
+trans = Transaction([strans1, strans2], [])
+trans.sign([reward_key, reward_key])
+miner2.chainbuilder.new_transaction_received(trans)
+sleep(5)
+print(len(miner2.chainbuilder.primary_block_chain.blocks))