Procházet zdrojové kódy

basic mining and sending money seems to work now

Malte Kraus před 8 roky
rodič
revize
9985148595

+ 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 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:
     """ A block. """
@@ -17,6 +19,17 @@ class Block:
         self.difficulty = difficulty
         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):
         """ 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
@@ -25,14 +38,14 @@ class Block:
         hasher = get_hasher()
         hasher.update(self.prev_block_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
 
     def get_hash(self):
         """ Compute the hash of the header data. This is not necessarily the received hash value for this block! """
         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()
 
     def verify_difficulty(self):
@@ -47,10 +60,20 @@ class Block:
         return chain.get_block_by_hash(chain) is not None
 
     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:
+            if not t.inputs:
+                if mining_reward is not None:
+                    return False
+                mining_reward = t
+
             if not t.verify(chain):
                 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
 
     def verify(self, chain):

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

@@ -3,13 +3,14 @@ class Blockchain:
     def __init__(self, blocks):
         self.blocks = blocks
         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):
         """
         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:
                 if trans.get_hash() == hash_val:
                     return trans
@@ -24,8 +25,9 @@ class Blockchain:
         if prev_block is None:
             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:
                 if transaction_input in trans.inputs:
                     return False
@@ -35,17 +37,18 @@ class Blockchain:
         """
         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):
         """
         Verify the transactions in all blocks in this chain.
         """
         for block in self.blocks:
-            block.verify_transactions(self)
-
+            if not block.verify_transactions(self):
+                return False
+        return True
 
-    @getter
+    @property
     def head(self):
         """ The head of this block chain. """
         return self.blocks[-1]
@@ -55,3 +58,12 @@ class Blockchain:
         # TODO: dynamic calculation
         # TODO: verify difficulty in new blocks
         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:
     """
@@ -7,7 +7,7 @@ class ChainBuilder:
     chain that it attempts to download and verify.
     """
 
-    def __init__(self):
+    def __init__(self, protocol):
         self.primary_block_chain = Blockchain([GENESIS_BLOCK])
         self.unconfirmed_block_chain = []
 
@@ -16,18 +16,21 @@ class ChainBuilder:
 
         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):
         """ Event handler that is called by the network layer when a transaction is received. """
         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):
         """
         Does all the housekeeping that needs to be done when a new longest chain is found.
         """
         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):
                 del self.unconfirmed_transactions[hash_val]
 
@@ -43,6 +46,7 @@ class ChainBuilder:
         while unc[-1].prev_block_hash in self.block_cache:
             unc.append(self.block_cache[unc[-1].prev_block_hash])
 
+
         if unc[-1].height == 0:
             chain = Blockchain(unc[::-1])
             if chain.verify_all_transactions():
@@ -62,13 +66,13 @@ class ChainBuilder:
             if self.unconfirmed_block_chain[-1].prev_block_hash == block.hash:
                 self.unconfirmed_block_chain.append(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)
 
-            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.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.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
 from treelib import Node, Tree
 
+from .crypto import get_hasher
+
 class MerkleNode:
     """ 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
-import mining_strategy
 from time import sleep
 
 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.is_mining = False
         self.cur_miner = None
+        self.reward_pubkey = reward_pubkey
         Thread(target=self._miner_thread, daemon=True).start()
 
     def _miner_thread(self):
@@ -18,7 +22,10 @@ class Miner:
             if miner is None:
                 sleep(1)
             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):
         """ Start mining on a new block. """
@@ -38,5 +45,5 @@ class Miner:
         primary chain changes.
         """
         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)

+ 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):
     """ Verify the proof of work on a block. """
@@ -12,7 +12,7 @@ class ProofOfWork:
         self.block = block
         self.success = False
 
-    def abort(self)
+    def abort(self):
         self.stopped = True
 
     def run(self):
@@ -23,9 +23,9 @@ class ProofOfWork:
         while not self.stopped:
             for _ in range(1000):
                 h = hasher.copy()
-                h.update(self.block.nonce)
+                h.update(str(self.block.nonce).encode())
                 self.block.hash = h.digest()
                 if verify_proof_of_work(self.block):
-                    return True
+                    return self.block
                 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 crypto import get_hasher, sign, verify_sign
+
+from .crypto import get_hasher, Signing
 
 """ The recipient of a transaction ('coin'). """
 TransactionTarget = namedtuple("TransactionTarget", ["recipient_pk", "amount"])
@@ -10,28 +11,40 @@ TransactionInput = namedtuple("TransactionInput", ["transaction_hash", "output_i
 
 class Transaction:
 
-    def __init__(self, inputs, targets, signatures=None):
+    def __init__(self, inputs, targets, signatures=None, iv=None):
         self.inputs = inputs
         self.targets = targets
         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):
         """ Hash this transaction. Returns raw bytes. """
         h = get_hasher()
+        if self.iv is not None:
+            h.update(self.iv)
         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:
-            h.update(inp)
+            h.update(inp.transaction_hash)
+            h.update(str(inp.output_idx).encode())
         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):
         """ 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
 
         for (s, i) in zip(self.signatures, self.inputs):
@@ -42,8 +55,8 @@ class Transaction:
     def _verify_single_sig(self, sig, inp, chain):
         """ Verifies the signature on a single input. """
         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):

+ 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))