aggstam 4 жил өмнө
parent
commit
59ac25a54e

+ 117 - 0
script/research/streamlet/2-execution-model-and-definitions.py

@@ -0,0 +1,117 @@
+# Section 2 from "Streamlet: Textbook Streamlined Blockchains"
+
+class Node:
+	''' This class represents a simplyfied protocol node.
+		Each node is numbered and has a secret-public keys pair, to sign messages. 
+		Modes receive inputs (transactions) and maintain an ordered log (blockchain), 
+		containing a sequense of strings (blocks). '''
+		
+	def __init__(self, id, secret_key, public_key):
+		self.id = id
+		self.secret_key = secret_key
+		self.public_key = public_key
+		self.blockchain = Blockchain()
+		self.inputs = []
+	
+	def __repr__(self):
+		return "Node=[id={0}, secret_key={1}, public_key={2}, blockchain={3}, inputs={4}".format(self.id, self.secret_key, self.public_key, self.blockchain, self.inputs)
+		
+	def receive_input(self, input):
+		# Additional validity rules must be defined by the protocol for its blockchain data structure.
+		self.inputs.append(input)
+	
+	def output(self):
+		return self.blockchain
+	
+	def broadcast(self, nodes, input):
+		for node in nodes:
+			node.receive_input(input)
+			
+	def finalize_block(self):
+		block = Block(self.inputs)
+		self.blockchain.add_block(block) # Block is appended to nodes blockchain
+		self.inputs = []
+		
+class Block:
+	''' This class represents a simplyfied block structure. '''
+	
+	def __init__(self, transactions):
+		self.transactions = transactions
+	
+	def __repr__(self):
+		return "Block=[transactions={0}]".format(self.transactions)
+	
+	def __eq__(self, other):
+		return self.transactions == other.transactions
+		
+class Blockchain:
+	''' This class represents a simplyfied blockchain structure. '''
+	
+	def __init__(self):
+		self.blocks = []
+	
+	def __repr__(self):
+		return "Blockchain=[blocks={0}]".format(self.blocks)
+	
+	def __eq__(self, other):
+		return self.blocks == other.blocks
+		
+	def __len__(self):
+		return len(self.blocks)
+		
+	def __getitem__(self, index):
+		  return self.blocks[index]
+	
+	def add_block(self, block):
+		self.blocks.append(block)
+
+# There are in total n nodes numbered.
+node0 = Node(0, "dummy_secret_key0", "dummy_public_key0")
+node1 = Node(1, "dummy_secret_key1", "dummy_public_key1")
+
+# Advesary chooses last node to corrupt(static corruption).
+corruptedNode = Node(2, "dummy_secret_key2", "dummy_public_key2")
+
+# We simulate some rounds to test consistency.
+
+# Round 0 synchronization period.
+# node0 receives input and broadcasts it to rest nodes.
+node0.receive_input("tx0")
+node0.broadcast([node1, corruptedNode], "tx0")
+
+# node1 receives input and broadcasts it to rest nodes.
+node1.receive_input("tx1")
+node1.broadcast([node0, corruptedNode], "tx1")
+
+# corruptedNode receives input but doesn't broadcast to rest nodes.
+corruptedNode.receive_input("tx2")
+
+# We assume nodes finalize blocks(append to blockchain) at the end of each round.
+node0.finalize_block()
+node1.finalize_block()
+corruptedNode.finalize_block()
+
+# In round 1, a new node joins.
+node3 = Node(3, "dummy_secret_key3", "dummy_public_key3")
+
+# node3 receives input and broadcasts it to rest nodes.
+node3.receive_input("tx3")
+node3.broadcast([node0, node1, corruptedNode], "tx3")
+
+# Nodes finalize blocks.
+node0.finalize_block()
+node1.finalize_block()
+corruptedNode.finalize_block()
+node3.finalize_block()
+
+# Consistency testing.
+# node0 and node1 remained honest, therefore their outputs must be the same.
+assert(node0.output() == node1.output())
+
+# Since node3 joined later, node0 and node1 outputs are a prefix or equal to node3 output.
+# Based on that, node3 output is a suffix of node0 and node1 outputs.
+assert(node0.output()[-len(node3.output()):] == node3.output().blocks)
+assert(node1.output()[-len(node3.output()):] == node3.output().blocks)
+
+# Below assertion will fail, as corrupt node deviated from the protocol.
+# assert(node0.output() == corruptedNode.output())

+ 66 - 0
script/research/streamlet/3.2-blocks-and-blockchain.py

@@ -0,0 +1,66 @@
+# Section 3.2 from "Streamlet: Textbook Streamlined Blockchains"
+
+class Block:
+	''' This class represents a tuple of the form (h, e, txs).
+		Each blocks parent hash h may be computed simply as a hash of the parent block. '''
+		
+	def __init__(self, h, e, txs):
+		self.h = h # parent hash
+		self.e = e # epoch number
+		self.txs = txs # transactions payload
+	
+	def __repr__(self):
+		return "Block=[h={0}, e={1}, txs={2}]".format(self.h, self.e, self.txs)
+	
+	def __hash__(self):
+		return hash((self.h, self.e, self.txs)) # Python hash is used for demostranation porpuses only.
+
+class Blockchain:
+	''' This class represents a sequence of blocks starting with the genesis block. '''
+	
+	def __init__(self, genesis_block):
+		self.chain = [genesis_block]
+	
+	def __repr__(self):
+		return "Blockchain=[chain={0}]".format(self.chain)
+	
+	def check_block_validity(self, block, previous_block):
+		''' A block is considered valid when its parent hash is equal to the hash of the 
+			previous block and their epochs are incremental, exluding genesis. '''
+		
+		assert(block.h != '⊥') # genesis block check
+		assert(block.h == hash(previous_block))
+		assert(block.e > previous_block.e)
+
+	def check_chain_validity(self):
+		''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
+		
+		for index, block in enumerate(self.chain[1:]):
+			self.check_block_validity(block, self.chain[index])
+	
+	def add_block(self, block):		
+		''' Insertion of a valid block. '''	
+		self.check_block_validity(block, self.chain[-1])
+		self.chain.append(block)
+
+# We generate a genesis block and a blockchain.
+genesis_block = Block("⊥", 0, '⊥')
+chain = Blockchain(genesis_block)
+
+# A new block is generated and appended to the blockchain, since its valid.
+block1 = Block(hash(genesis_block), 1, "tx1, tx2, tx3")
+chain.add_block(block1)
+
+# A new block is generated and appended to the blockchain, since its valid.
+block2 = Block(hash(block1), 2, "tx4, tx5, tx6")
+chain.add_block(block2)
+
+# We check entire blockchain validity.
+chain.check_chain_validity()
+
+# Following code examples will fail, due to block validity checks:
+# wrong_block = Block(hash(block1), 3, "tx4,tx5,tx6") # Previous block not last.
+# chain.add_block(wrong_block)
+
+# wrong_block = Block(hash(block2), 1, "tx4,tx5,tx6") # Epoch not incremental.
+# chain.add_block(wrong_block)

+ 62 - 0
script/research/streamlet/3.3-votes-and-notarization.py

@@ -0,0 +1,62 @@
+# Section 3.3 from "Streamlet: Textbook Streamlined Blockchains"
+
+from cryptography.hazmat.primitives import serialization, hashes
+from cryptography.hazmat.primitives.asymmetric import rsa, padding
+from cryptography.hazmat.backends import default_backend
+from cryptography.exceptions import InvalidSignature
+
+# Cryptographic algorithm used is for demostranation porpuses only.
+# Generating the keys pair. 
+def generate_keys(private_key_password):
+	private_key = rsa.generate_private_key(
+		public_exponent=65537,
+		key_size=2048
+	)	
+	encrypted_pem_private_key = private_key.private_bytes(
+		encoding=serialization.Encoding.PEM,
+		format=serialization.PrivateFormat.PKCS8,
+		encryption_algorithm=serialization.BestAvailableEncryption(private_key_password.encode())
+	)
+	pem_public_key = private_key.public_key().public_bytes(
+	  encoding=serialization.Encoding.PEM,
+	  format=serialization.PublicFormat.SubjectPublicKeyInfo
+	)
+	
+	return encrypted_pem_private_key, pem_public_key
+
+# Signs a message using private_key.
+def sign_message(password, private_key, message):
+	privkey = serialization.load_pem_private_key(private_key, password=password.encode(), backend=default_backend())
+	signed_message = privkey.sign(
+		message.encode(),
+		padding.PSS(
+			mgf=padding.MGF1(hashes.SHA256()),
+			salt_length=padding.PSS.MAX_LENGTH),
+		hashes.SHA256()
+	)
+	return signed_message
+	
+# Verifies a message against a public key.
+def verify_signature(public_key, message, signed_message):
+	pubkey = serialization.load_pem_public_key(public_key, backend=default_backend())
+	try:
+		pubkey.verify(
+			signed_message,
+			message.encode(),
+			padding.PSS(
+				mgf=padding.MGF1(hashes.SHA256()),
+				salt_length=padding.PSS.MAX_LENGTH),
+			hashes.SHA256())
+		return True
+	except InvalidSignature:
+		return False
+
+# When a node votes on a block, it simply signs it with the private key, and broadcasts the message to rest nodes.	
+message = "block"
+node_password = "node_password"	
+node_private_key, node_public_key = generate_keys(node_password)
+signed_message = sign_message(node_password, node_private_key, message)
+
+# When nodes receive votes, they verify them against nodes public key.
+assert(verify_signature(node_public_key, message, signed_message))
+# If votes for that specific block are >=2n/3, node marks block as notarized.

+ 106 - 0
script/research/streamlet/3.4-protocol.py

@@ -0,0 +1,106 @@
+# Section 3.4 from "Streamlet: Textbook Streamlined Blockchains"
+
+from block import Block
+from node import Node
+
+# Genesis block is generated.
+genesis_block = Block("⊥", 0, '⊥')
+genesis_block.notarized = True
+genesis_block.finalized = True
+
+# We create some nodes to participate in the Protocol.
+# There are in total n nodes numbered.
+node0 = Node(0, "clock", "node_password0", genesis_block)
+node1 = Node(1, "clock", "node_password1", genesis_block)
+node2 = Node(2, "clock", "node_password2", genesis_block)
+node3 = Node(3, "clock", "node_password3", genesis_block)
+node4 = Node(4, "clock", "node_password4", genesis_block)
+node5 = Node(5, "clock", "node_password5", genesis_block)
+
+nodes = [node0, node1, node2, node3, node4, node5]
+
+# We simulate some rounds to test consistency.
+epoch = 1
+
+# Nodes receive transactions and broacasts them between them.
+# node0 receives input and broadcasts it to rest nodes.
+node0.receive_transaction("tx0")
+node0.broadcast_transaction([node1, node2, node3, node4, node5], "tx0")
+# node1 receives input and broadcasts it to rest nodes.
+node1.receive_transaction("tx2")
+node1.broadcast_transaction([node0, node2, node3, node4, node5], "tx2")
+# node4 receives input and broadcasts it to rest nodes.
+node4.receive_transaction("tx3")
+node4.broadcast_transaction([node0, node1, node2, node3, node5], "tx3")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())
+
+epoch = 2
+
+# node3 receives input and broadcasts it to rest nodes.
+node3.receive_transaction("tx4")
+node3.broadcast_transaction([node0, node1, node2, node4, node5], "tx4")
+# node5 receives input and broadcasts it to rest nodes.
+node5.receive_transaction("tx5")
+node5.broadcast_transaction([node0, node1, node2, node3, node4], "tx5")
+# node2 receives input and broadcasts it to rest nodes.
+node2.receive_transaction("tx6")
+node2.broadcast_transaction([node0, node1, node3, node4, node5], "tx6")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())
+
+epoch = 3
+
+# node3 receives input and broadcasts it to rest nodes.
+node3.receive_transaction("tx7")
+node3.broadcast_transaction([node0, node1, node2, node4, node5], "tx7")
+# node5 receives input and broadcasts it to rest nodes.
+node5.receive_transaction("tx8")
+node5.broadcast_transaction([node0, node1, node2, node3, node4], "tx8")
+# node2 receives input and broadcasts it to rest nodes.
+node2.receive_transaction("tx9")
+node2.broadcast_transaction([node0, node1, node3, node4, node5], "tx9")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())
+
+epoch = 4
+
+# node3 receives input and broadcasts it to rest nodes.
+node3.receive_transaction("tx19")
+node3.broadcast_transaction([node0, node1, node2, node4, node5], "tx10")
+# node5 receives input and broadcasts it to rest nodes.
+node5.receive_transaction("tx11")
+node5.broadcast_transaction([node0, node1, node2, node3, node4], "tx11")
+# node2 receives input and broadcasts it to rest nodes.
+node2.receive_transaction("tx12")
+node2.broadcast_transaction([node0, node1, node3, node4, node5], "tx12")
+
+# A random leader is selected.
+leader = nodes[hash(str(epoch))%len(nodes)]
+
+# Leader forms a block and broadcasts it.
+leader.propose_block(epoch, nodes)
+
+# We verify that all nodes have the same blockchain on round end.
+assert(node0.output() == node1.output() == node2.output() == node3.output() == node4.output() == node5.output())

+ 25 - 0
script/research/streamlet/block.py

@@ -0,0 +1,25 @@
+class Block:
+	''' This class represents a tuple of the form (h, e, txs).
+		Each blocks parent hash h may be computed simply as a hash of the parent block. '''
+
+	def __init__(self, h, e, txs):
+		self.h = h	# parent hash
+		self.e = e	# epoch number
+		self.txs = txs	# transactions payload
+		self.votes = []	 # Epoch votes
+		self.notarized = False	# block notarization flag
+		self.finalized = False	# block finalization flag
+
+	def __repr__(self):
+		return "Block=[h={0}, e={1}, txs={2}, notarized={3}, finalized={4}]".format(
+			self.h, self.e, self.txs, self.notarized, self.finalized)
+
+	def __hash__(self):
+		# python hash is used for demostranation porpuses only.
+		return hash((self.h, self.e, str(self.txs)))
+
+	def __eq__(self, other):
+		return self.h == other.h and self.e == other.e and self.txs == other.txs
+
+	def encode(self):
+		return(("{0},{1},{2}".format(self.h, self.e, self.txs)).encode())

+ 46 - 0
script/research/streamlet/blockchain.py

@@ -0,0 +1,46 @@
+class Blockchain:
+	''' This class represents a sequence of blocks starting with the genesis block. '''
+
+	def __init__(self, intial_block):
+		self.blocks = [intial_block]
+
+	def __repr__(self):
+		return "Blockchain=[blocks={0}]".format(self.blocks)
+
+	def __eq__(self, other):
+		return self.blocks == other.blocks
+
+	def __len__(self):
+		return len(self.blocks)
+
+	def __getitem__(self, index):
+		return self.blocks[index]
+
+	def check_block_validity(self, block, previous_block):
+		''' A block is considered valid when its parent hash is equal to the hash of the
+			previous block and their epochs are incremental, exluding genesis.
+			Aadditional validity rules can be applied. '''
+
+		assert(block.h != '⊥')	# genesis block check
+		assert(block.h == hash(previous_block))
+		assert(block.e > previous_block.e)
+
+	def check_chain_validity(self):
+		''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
+		
+		for index, block in enumerate(self.blocks[1:]):
+			self.check_block_validity(block, self.blocks[index])
+
+	def add_block(self, block):
+		''' Insertion of a valid block. '''
+		
+		self.check_block_validity(block, self.blocks[-1])
+		self.blocks.append(block)
+
+	def is_notarized(self):
+		''' Blockchain notarization check. '''
+		
+		for block in self.blocks:
+			if not block.notarized:
+				return False
+		return True

+ 184 - 0
script/research/streamlet/node.py

@@ -0,0 +1,184 @@
+import copy
+import utils
+from block import Block
+from blockchain import Blockchain
+from vote import Vote
+
+class Node:
+	''' This class represents a protocol node.
+		Each node is numbered and has a secret-public keys pair, to sign messages.
+		Nodes hold a set of Blockchains(some of which are not notarized)
+		and a set of unconfirmed pending transactions.
+		All nodes have syncronized clocks, using GST approach. '''
+
+	def __init__(self, id, clock, password, init_block):
+		self.id = id
+		self.clock = clock	# Clock syncronization to be implemented.
+		self.password = password
+		self.private_key, self.public_key = utils.generate_keys(self.password)
+		self.canonical_blockchain = Blockchain(init_block)
+		self.node_blockchains = []
+		self.unconfirmed_transactions = []
+
+	def __repr__(self):
+		return "Node=[id={0}]".format(self.id)
+
+	def output(self):
+		''' A nodes output is the finalized (canonical) blockchain they hold. '''
+	
+		return self.canonical_blockchain
+
+	def receive_transaction(self, transaction):
+		''' Node retreives a transaction and append it to the unconfirmed transactions list.
+			Additional validity rules must be defined by the protocol for its blockchain data structure. '''
+
+		self.unconfirmed_transactions.append(transaction)
+
+	def broadcast_transaction(self, nodes, transaction):
+		''' Node broadcast a transaction to provided nodes list. '''
+		
+		for node in nodes:
+			node.receive_transaction(transaction)
+
+	def find_longest_notarized_chain(self):
+		''' Finds the longest fully notarized blockchain the node holds.'''
+	
+		longest_notarized_chain = self.canonical_blockchain
+		length = 0
+		for blockchain in self.node_blockchains:
+			if blockchain.is_notarized() and len(blockchain.blocks) > length:
+				longest_notarized_chain = blockchain
+				length = len(blockchain.blocks)
+		return longest_notarized_chain
+
+	def propose_block(self, epoch, nodes):
+		''' Node generates a block for that epoch, containing all uncorfirmed transactions.
+			Block extends the longest notarized blockchain the node holds.
+			Node signs the block, and broadcasts it to rest nodes. '''
+	
+		longest_notarized_chain = self.find_longest_notarized_chain()
+		proposed_block = copy.deepcopy(Block(
+			hash(longest_notarized_chain.blocks[-1]), epoch, self.unconfirmed_transactions))
+		signed_proposed_block = copy.deepcopy(
+			utils.sign_message(
+				self.password,
+				self.private_key,
+				proposed_block))
+		for node in nodes:
+			node.receive_proposed_block(self.public_key, copy.deepcopy(
+				proposed_block), copy.deepcopy(signed_proposed_block), nodes)
+
+	def find_extended_blockchain(self, block):
+		''' For a provided block, node searches for any blockchain that it extends.
+			If a fork blockchain is not found, block is tested against the canonical blockchain. '''
+	
+		for blockchain in self.node_blockchains:
+			if block.h == hash(
+					blockchain.blocks[-1]) and block.e > blockchain.blocks[-1].e:
+				return blockchain
+		if block.h == hash(
+				self.canonical_blockchain.blocks[-1]) and block.e > self.canonical_blockchain.blocks[-1].e:
+			return self.canonical_blockchain
+		return None
+
+	def find_block(self, vote_block):
+		''' Node searches it the blockchains it holds for provided block. '''
+	
+		for blockchain in self.node_blockchains:
+			for block in reversed(blockchain.blocks):
+				if vote_block == block:
+					return block
+		for block in reversed(self.canonical_blockchain.blocks):
+			if vote_block == block:
+				return block
+		return None
+
+	def extends_notarized_blockchain(self, blockchain):
+		''' Node verifies if provided blockchain is notarized excluding the last block. '''
+		
+		for block in blockchain.blocks[:-1]:
+			if not block.notarized:
+				return False
+		return True
+
+	def vote_block(self, block, nodes):
+		''' Given a block, node finds which blockchain it extends.
+			If block extends the canonical blockchain, a new fork blockchain is created.
+			Node votes on the block, only if it extends the longest notarized chain it has seen. '''
+	
+		blockchain = self.find_extended_blockchain(block)
+		if not blockchain or blockchain is self.canonical_blockchain:
+			blockchain = Blockchain(copy.deepcopy(block))
+			self.node_blockchains.append(blockchain)
+		else:
+			blockchain.add_block(copy.deepcopy(block))
+
+		if self.extends_notarized_blockchain(blockchain):
+			signed_block = utils.sign_message(
+				self.password, self.private_key, block)
+			vote = Vote(signed_block, block, self.id)
+			for node in nodes:
+				node.receive_vote(self.public_key, vote, nodes)
+
+	def receive_proposed_block(
+			self,
+			leader_public_key,
+			round_block,
+			signed_round_block,
+			nodes):
+		''' Node receives the proposed block, verifies its sender(epoch leader), and proceeds with voting on it. '''
+		
+		assert(
+			utils.verify_signature(
+				leader_public_key,
+				round_block,
+				signed_round_block))
+		self.vote_block(round_block, nodes)
+
+	def check_blockchain_finalization(self, block):
+		''' For the provided block, node checks if the blockchain it extends can be finalized.
+			Consensus finalization logic: If node has observed the notarization of 3 consecutive
+			blocks in a fork chain, it finalizes (appends to canonical blockchain) all blocks up to the middle block.
+			When fork chain blocks are finalized, rest fork chains not starting by those blocks are removed. '''
+		
+		if block in self.canonical_blockchain.blocks:
+			blockchain = self.canonical_blockchain
+		else:
+			for node_blockchain in self.node_blockchains:
+				if block in node_blockchain.blocks:
+					blockchain = node_blockchain
+		if blockchain and len(blockchain) > 2:
+			if blockchain.blocks[-3].notarized and blockchain.blocks[-2].notarized:
+				for block in blockchain.blocks[:-1]:
+					block.finalized = True
+					self.canonical_blockchain.blocks.append(block)
+				for node_blockchain in self.node_blockchains:
+					if node_blockchain.blocks[-len(blockchain.blocks[:-1]):] != blockchain.blocks[:-1]:
+						self.node_blockchains.remove(node_blockchain)
+					else:
+						del node_blockchain[-len(blockchain.blocks[:-1]):]
+
+	def receive_vote(self, node_public_key, vote, nodes):
+		''' Node receives a vote for a block.
+			First, sender is verified using their public key.
+			Block is searched in nodes blockchains.
+			If the vote wasn't received before, it is appended to block votes list.
+			When a node sees 2n/3 votes for a block it notarizes it.
+			When a block gets notarized, the transactions it contains are removed from
+			nodes unconfirmed transactions list.
+			Finally, we check if the notarization of the block can finalize parent blocks
+			in its blockchain. '''
+	
+		assert(utils.verify_signature(node_public_key, vote.block, vote.vote))
+		vote_block = self.find_block(vote.block)
+		if not vote_block:
+			self.vote_block(copy.deepcopy(vote.block), nodes)
+			return
+		if vote not in vote_block.votes:
+			vote_block.votes.append(vote)
+		if not vote_block.notarized and len(vote_block.votes) > (2 * len(nodes) / 3):
+			vote_block.notarized = True
+			for transaction in vote_block.txs:
+				if transaction in self.unconfirmed_transactions:
+					self.unconfirmed_transactions.remove(transaction)
+			self.check_blockchain_finalization(vote_block)

+ 54 - 0
script/research/streamlet/utils.py

@@ -0,0 +1,54 @@
+from cryptography.hazmat.primitives import serialization, hashes
+from cryptography.hazmat.primitives.asymmetric import rsa, padding
+from cryptography.hazmat.backends import default_backend
+from cryptography.exceptions import InvalidSignature
+
+def generate_keys(private_key_password):
+	''' Generating the keys pair. Cryptographic algorithm used is for demostranation porpuses only. '''
+	
+	private_key = rsa.generate_private_key(
+		public_exponent=65537,
+		key_size=2048
+	)
+	encrypted_pem_private_key = private_key.private_bytes(
+		encoding=serialization.Encoding.PEM,
+		format=serialization.PrivateFormat.PKCS8,
+		encryption_algorithm=serialization.BestAvailableEncryption(
+			private_key_password.encode()))
+	pem_public_key = private_key.public_key().public_bytes(
+		encoding=serialization.Encoding.PEM,
+		format=serialization.PublicFormat.SubjectPublicKeyInfo
+	)
+
+	return encrypted_pem_private_key, pem_public_key
+
+def sign_message(password, private_key, message):
+	''' Signs a message using private_key. '''
+	
+	privkey = serialization.load_pem_private_key(
+		private_key, password=password.encode(), backend=default_backend())
+	signed_message = privkey.sign(
+		message.encode(),
+		padding.PSS(
+			mgf=padding.MGF1(hashes.SHA256()),
+			salt_length=padding.PSS.MAX_LENGTH),
+		hashes.SHA256()
+	)
+	return signed_message
+
+def verify_signature(public_key, message, signed_message):
+	''' Verifies a message against a public key. '''
+
+	pubkey = serialization.load_pem_public_key(
+		public_key, backend=default_backend())
+	try:
+		pubkey.verify(
+			signed_message,
+			message.encode(),
+			padding.PSS(
+				mgf=padding.MGF1(hashes.SHA256()),
+				salt_length=padding.PSS.MAX_LENGTH),
+			hashes.SHA256())
+		return True
+	except InvalidSignature:
+		return False

+ 11 - 0
script/research/streamlet/vote.py

@@ -0,0 +1,11 @@
+class Vote:
+	''' This class represents a tuple of the form (vote, B, id). '''
+
+	def __init__(self, vote, block, id):
+		self.vote = vote  # signed block
+		self.block = block	# epoch number
+		self.id = id  # node id
+
+	def __repr__(self):
+		return "Vote=[vote={0}, block={1}, id={2}]".format(
+			self.vote, self.block, self.id)