Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# crypto-project
python crypto course final project
# Crypto-Project
Final project in Crypto course using Python
39 changes: 32 additions & 7 deletions classes/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,29 @@ class Block:
transactions: List[Transaction] # list of transactions
timestamp: datetime = dc.field(init=False) # time the block got mined
previous_hash: str # hash from the previous block
nonce: int = dc.field(init=False) # A counter used for the proof-of-work algorithm (number used to generate hash)
# proof: bool = dc.field(default=False) # TODO: search for 'proof-of-work' algorithm
miner_address: str = dc.field(default=None)
nonce: int = dc.field(init=False) # A counter used for the proof-of-work algorithm
proof: bool = dc.field(default=False) # proof of mining the block
miner_address: str = dc.field(default=None) # miner public key address

TOKEN_PRIZE: ClassVar[float] = dc.field(default=.1) # reword to miner

def __post_init__(self):
"""
initialize the timestamp and the nonce, called right after the object receives values for its fields
:return: None
"""
self.timestamp = datetime.now()
self.nonce = 1 # TODO: method
self.nonce = 1

# Returns str of all data members -> called by compute_block_hash()
def compute_block_header(self) -> bytes:
"""
Returns str of all data members -> called by compute_block_hash() method
:return: all data members in bytes
"""
block_str = \
get_fields_str(self.previous_hash, self.index, self.timestamp, self.nonce)
return block_str.encode()

# Returns a hashed str of all data members
def compute_block_hash(self) -> str:
"""
computes the hash of the block using SHA-256 algorithm
Expand All @@ -38,8 +44,27 @@ def compute_block_hash(self) -> str:
block_hash = hashlib.sha256(self.compute_block_header()).hexdigest()
return block_hash

# TODO: to see if necessary 'validate_function'
def validate_block(self, validate_function: Callable[[any], bool]) -> bool:
"""
validate a block by a given callback function
:param validate_function:
:return: if the block is valid
"""
return validate_function(self)


def proof_of_work(self, prev_nonce):
"""
checks the miner's solution to the block's problem,
by checking the first 4 characters of the computed hash.
:param prev_nonce: the nonce of the previous block in the chain
:return: the current nonce
"""
while self.proof is False:
compute_hash = hashlib.sha256(str(self.nonce**2 - prev_nonce**2).encode()).hexdigest()
if compute_hash[:4] == '0000': # more zeros more difficult it will be to mine the block
self.proof = True
else:
self.nonce += 1
return self.nonce

50 changes: 38 additions & 12 deletions classes/block_chain.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,65 @@
from dataclasses import dataclass
from typing import List
from transaction import Transaction
from exceptions import TransactionException
from exceptions import TransactionException, BlockChainException
from block import Block


@dataclass
class BlockChain:
unverified_transactions: List[Transaction]
chain: List[Block]
unverified_transactions: List[Transaction] # temporary list of unverified transactions
chain: List[Block] # the chain of blocks

def __post_init__(self):
genesis_block = Block(1,[],"0") # GENESIS_BLOCK is the first block in the chain
"""
creates the GENESIS_BLOCK, this is the first block in the chain,
called right after the object receives values for its fields
:return: None
"""
genesis_block = Block(1,[],"0")
self.chain.append(genesis_block)

# adds block to chain
def add_block(self, block, miner):
is_valid = block.validate_block(lambda block2: True) # TODO: explain
"""
checks if the block is valid and adds the block to chain
:param block: new block
:param miner: the current miner who mined the block
:return: None
"""
is_valid = block.validate_block(lambda v: self.chain[-1].compute_block_hash() == block.compute_block_hash())
if is_valid:
self.create_block(miner)

# adds temporary transactions to a new block
def create_block(self, miner):
"""
creates a new block and adds the unverified transactions list to this new block,
checks the proof of work and mines the to block to miner,
and sends reward to miner.
:param miner: the current miner
:return: None
"""
new_block = Block(
self.chain[-1].index+1,
self.unverified_transactions,
self.chain[-1].compute_block_hash(),
# True, # TODO: this is for updating 'proof' attribute in Block class
miner.address
False,
""
)
miner.set_tokens(new_block.TOKEN_PRIZE)
self.unverified_transactions.clear()
new_block.proof_of_work(self.chain[-1].nonce)
if new_block.proof:
new_block.miner_address = miner.address
miner.set_tokens(new_block.TOKEN_PRIZE)
self.unverified_transactions.clear()
else:
raise BlockChainException("The block have not been mined")

def add_transaction_to_queue(self,transaction):
"""
checks if transaction is valid and checks if it's possible to make the transaction -
if the sender has the amount of tokens that needed
:param transaction:
:return: None
"""
if isinstance(transaction, Transaction):
if len(self.unverified_transactions) > 0:
transaction.link_transactions(self.unverified_transactions[-1])
Expand All @@ -47,4 +74,3 @@ def add_transaction_to_queue(self,transaction):
raise TransactionException("you don't have enough tokens")



22 changes: 21 additions & 1 deletion classes/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,40 @@

@dataclass
class Client:
_tokens: float
_tokens: float = dc.field(init=False)
address: rsa.PublicKey = dc.field(init=False)
_private_key: rsa.PrivateKey = dc.field(init=False)

def __post_init__(self):
"""
initialize client's keys, called right after the object receives values for its fields
:return: None
"""
self.address, self._private_key = rsa.newkeys(512)
self._tokens = 0

def add_tokens(self, value):
"""
adds tokens to client's tokens
:param value: amount of tokens
:return: None
"""
self._tokens += value

def subtract_tokens(self, value):
"""
subtracts tokens from client's tokens
:param value: amount of tokens
:return: None
"""
self._tokens -= value

def validate_enough_tokens(self, value):
"""
validates if client has enough tokens
:param value: the amount of tokens to validate
:return: True or False
"""
if value < self._tokens:
return True
return False
Expand Down
16 changes: 14 additions & 2 deletions classes/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@ class Message:
message_signature:bytes = dc.field(init=False)

def __post_init__(self):
"""
initialize the current time, called right after the object receives values for its fields
:return: None
"""
self.timestamp = datetime.now()

def message_as_bytes(self) -> bytes:
"""
:return: message details in bytes
"""
message_str = get_fields_str(self.timestamp, self.amount, self.sender_addr, self.receiver_addr)
return message_str.encode()

def sign_message(self,sender_priv_key:rsa.PrivateKey,hash_algo:str) -> None:
"""
signs the message by a given hash algorithm
:param sender_priv_key: private key of sender
:param hash_algo: hash algorithm
:return: None
"""
known_hashes = ['MD5', 'SHA-1','SHA-224', 'SHA-256', 'SHA-384','SHA-512']
if hash_algo not in known_hashes:
raise exceptions.TransactionException("Hash method is not valid")
Expand All @@ -35,8 +48,7 @@ def verify_message(self) -> str:
"""
This method validates the integrity of the signed message using the following:
the data used for signing, the signature itself and the public key
:param self:
:return:None or raise VerificationError when the signature doesn't match the message.
:return: None or raise VerificationError when the signature doesn't match the message.
"""
try:
hash_method_name = rsa.verify(self.message_as_bytes(),self.message_signature,self.sender_addr)
Expand Down
22 changes: 15 additions & 7 deletions classes/miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@

@dataclass
class Miner:
_tokens: float
_tokens: float = dc.field(init=False)
address: rsa.PublicKey = dc.field(init=False)
_private_key: rsa.PrivateKey = dc.field(init=False)

def __post_init__(self):
"""
initialize miner's keys, called right after the object receives values for its fields
:return: None
"""
self.address, self._private_key = rsa.newkeys(512)
self._tokens = 0

def address_as_str(self):
"""
:return: the address(public key) as string
"""
return str(self.address)

def set_tokens(self, value):
"""
adds tokens to miner's tokens
:param value: amount of tokens
:return: None
"""
self._tokens += value

# TODO: mining a new block ?
def mine_block(self):
pass

# if __name__ == '__main__':
# m = Miner(5.0)
# print(m)

28 changes: 20 additions & 8 deletions classes/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,27 @@ class Transaction:
trans_hash: str = dc.field(init=False)

def __post_init__(self):
"""
initialize the current time of the transaction and computes the hash,
called right after the object receives values for its fields
:return: None
"""
self.timestamp = datetime.now()
self.payload_hash = self.compute_payload_hash()
self.trans_hash = self.__compute_trans_hash()

def seal(self):
"""
checks the seal of the transaction by the hash
:return: None
"""
self.trans_hash = self.__compute_trans_hash()

def compute_payload_hash(self):
# member_str = str(self.timestamp) + str(self.amount) + self.sender + self.receiver
# members_bytearray = bytearray(member_str, encoding="utf-8")
"""
computes the hash of message details
:return: hash of message details
"""
members_bytearray = self.message.message_as_bytes()
return hashlib.sha256(members_bytearray).hexdigest()

Expand All @@ -51,20 +62,21 @@ def validate_integrity(self):
if self.payload_hash != self.compute_payload_hash() or self.trans_hash != self.__compute_trans_hash():
raise exceptions.TransactionException("Tempered transaction number = " + str(self))

# TODO: validate that there is no double spending
def double_spending(self):
pass

def link_transactions(self,prev_trans):
"""
links the current transaction to the previous transaction
:param prev_trans: from block
:return: None
"""
if isinstance(prev_trans,Transaction):
self.prev_trans_hash = prev_trans.trans_hash
else:
raise ValueError("prev_trans argument is not of type Transaction")

def __repr__(self):
"""
:return: a string representation of the Transaction's fields
:return: a string representation some of the Transaction's fields
"""
return "Transaction{\n"+"timestamp=" + str(self.timestamp) + ",\namount=" + str(self.amount) +\
return "Transaction{\n"+"timestamp=" + str(self.timestamp) + ",\namount=" + str(self.message.amount) +\
",\npayload_hash="+str(self.payload_hash)+"\n}"

Empty file removed main.py
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.