Просмотр исходного кода

[research/ouroboros] implemented leader selection with vrf

mohab 4 лет назад
Родитель
Сommit
405cf940c2
36 измененных файлов с 451 добавлено и 1022 удалено
  1. 0 83
      script/research/PoS-blockchain/node.py
  2. 0 1
      script/research/PoS-blockchain/ouroboros/__init__.py
  3. 0 45
      script/research/PoS-blockchain/ouroboros/beacon.py
  4. 0 68
      script/research/PoS-blockchain/ouroboros/environment.py
  5. 0 13
      script/research/PoS-blockchain/ouroboros/logger.py
  6. 0 70
      script/research/PoS-blockchain/ouroboros/stakeholder.py
  7. 0 48
      script/research/PoS-blockchain/protocol.py
  8. 0 114
      script/research/PoS-blockchain/streamlet/2-execution-model-and-definitions.py
  9. 0 63
      script/research/PoS-blockchain/streamlet/3.2-blocks-and-blockchain.py
  10. 0 62
      script/research/PoS-blockchain/streamlet/3.3-votes-and-notarization.py
  11. 0 81
      script/research/PoS-blockchain/streamlet/3.4-protocol.py
  12. 0 6
      script/research/PoS-blockchain/streamlet/__init__.py
  13. 0 22
      script/research/PoS-blockchain/streamlet/block.py
  14. 0 34
      script/research/PoS-blockchain/streamlet/blockchain.py
  15. 0 54
      script/research/PoS-blockchain/streamlet/clock.py
  16. 0 10
      script/research/PoS-blockchain/streamlet/logger.py
  17. 0 78
      script/research/PoS-blockchain/streamlet/node.py
  18. 0 50
      script/research/PoS-blockchain/streamlet/utils.py
  19. 0 9
      script/research/PoS-blockchain/streamlet/vote.py
  20. 0 97
      script/research/PoS-blockchain/streamlet/vrf.py
  21. 1 0
      script/research/dpos/__init__.py
  22. 0 0
      script/research/dpos/ouroboros-test/__init__.py
  23. 12 0
      script/research/dpos/ouroboros-test/stakeholder_test.py
  24. 8 0
      script/research/dpos/ouroboros/__init__.py
  25. 61 0
      script/research/dpos/ouroboros/beacon.py
  26. 3 6
      script/research/dpos/ouroboros/block.py
  27. 1 1
      script/research/dpos/ouroboros/blockchain.py
  28. 5 3
      script/research/dpos/ouroboros/clock.py
  29. 128 0
      script/research/dpos/ouroboros/environment.py
  30. 1 1
      script/research/dpos/ouroboros/epoch.py
  31. 0 0
      script/research/dpos/ouroboros/kes.py
  32. 13 0
      script/research/dpos/ouroboros/logger.py
  33. 144 0
      script/research/dpos/ouroboros/stakeholder.py
  34. 55 0
      script/research/dpos/ouroboros/utils.py
  35. 4 3
      script/research/dpos/ouroboros/vrf.py
  36. 15 0
      script/research/dpos/simulation.py

+ 0 - 83
script/research/PoS-blockchain/node.py

@@ -1,83 +0,0 @@
-import copy
-from streamlet import Block, Blockchain, Vote, Logger, generate_keys, sign_message, verify_signature
-from ouroboros import VRF
-
-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 = generate_keys(self.password)
-		self.blockchain = Blockchain(init_block)
-		self.unconfirmed_transactions = []
-		self.log = Logger(self)
-		self.current_epoch=None #this need to be set by the clock tics
-	
-	def __repr__(self):
-		return "Node=[id={0}, clock={1}, password={2}, private_key={3}, public_key={4}, blockchain={5}, unconfirmed_transactions={6}".format(self.id, self.clock, self.password, self.private_key, self.public_key, self.blockchain, self.unconfirmed_transactions)
-	
-	def output(self):
-		return self.blockchain
-	
-	def receive_transaction(self, transaction):
-		# 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):
-		for node in nodes:
-			node.receive_transaction(transaction)
-			
-	def propose_block(self, epoch, y, pi, vrf_pk, g, nodes):
-		proposed_block = Block(hash(self.blockchain.blocks[-1]), epoch, self.unconfirmed_transactions)
-		signed_proposed_block = sign_message(self.password, self.private_key, proposed_block)
-		for node in nodes:
-			node.receive_proposed_block(self.public_key, y, pi, vrf_pk, g, copy.deepcopy(proposed_block), copy.deepcopy(signed_proposed_block))
-	
-	def receive_proposed_block(self, leader_pubkey, y, pi, vrf_pk, g, round_block, signed_round_block):
-		if not verify_signature(leader_pubkey, round_block, signed_round_block):
-			self.log.warn("the signature of the proposed block dosn't match")
-			return
-		#TODO alert that is insecure, e should be set by the ticing clock
-		x = round_block.e
-		#TODO pass and verify the proposed leader id
-		print(f"epoch number in verification {round_block.e}")
-		print(f"verifying {x}, {y}, {pi}, {vrf_pk}, {g}")
-		if not VRF.verify(x, y, pi, vrf_pk, g):
-			self.log.warn("failed verifying choosing leader")
-			return
-		self.round_block = round_block
-		
-	def vote_on_round_block(self, nodes):
-		# Node verifies proposed block extends from one of the longest notarized chains that node has seen at the time.
-		# Already notarized check.
-		if self.round_block != self.blockchain.blocks[-1]:
-			self.blockchain.check_block_validity(self.round_block, self.blockchain.blocks[-1])
-		#TODO implement: at this point we need to verify the unconfirmed transactions
-		signed_block = sign_message(self.password, self.private_key, self.round_block)
-		vote = Vote(signed_block, self.round_block, self.id)
-		for node in nodes:
-			node.receive_vote(self.public_key, vote, nodes)
-
-	def receive_vote(self, node_public_key, vote, nodes):
-		# We verify we haven't received a vote from that node again.
-		assert(vote not in self.round_block.votes)
-		# When nodes receive votes, they verify them against nodes public key.
-		assert(verify_signature(node_public_key, vote.block, vote.vote))
-		assert(self.round_block == vote.block)
-		# Additional rules must be defined by the protocol for its voting system.
-		self.round_block.votes.append(vote)
-		# When a node sees 2n/3 votes for a block it notarizes it
-		if (self.round_block != self.blockchain.blocks[-1] and len(self.round_block.votes) > (2 * len(nodes) / 3)):
-			notarized_block = copy.deepcopy(self.round_block)
-			notarized_block.notarized = True
-			self.blockchain.add_block(notarized_block)
-			# Node removes block transactions from unconfirmed_transactions array
-			#for transaction in notarized_block.txs:
-			#	self.unconfirmed_transactions.remove(transaction)
-			
-			

+ 0 - 1
script/research/PoS-blockchain/ouroboros/__init__.py

@@ -1 +0,0 @@
-from ouroboros.vrf import VRF

+ 0 - 45
script/research/PoS-blockchain/ouroboros/beacon.py

@@ -1,45 +0,0 @@
-from clock import SynchedNTPClock
-from vrf import VRF
-import threading
-import time 
-
-'''
-\class TrustedBeacon
-
-the trusted beacon is decentralized, such that at the onset of the Epoch,
-the leader of the first slot generated the signed seed, and release the signature, 
-proof, and base to the genesis block.
-
-#TODO implement trustedbeacon as a node
-'''
-class TrustedBeacon(SynchedNTPClock, threading.Thread):
-    def __init__(self, node, epoch_length):
-        SynchedNTPClock.__init__(self.epoch_length)
-        threading.Thread.__init__(self)
-        self.daemon=True
-        self.epoch_length=epoch_length # how many slots in a a block
-        self.node = node #stakeholder
-        self.vrf = VRF(self.node.vrf_pk, self.node.vrf_sk, self.node.vrk_base)
-        self.current_slot = self.slot
-
-    def run(self):
-        self.__background()
-
-    def __background(self):
-        current_epoch = self.slot
-        while True:
-            if self.slot != current_epoch:
-                current_epoch = self.slot
-                self.__callback()
-
-    def __callback(self):
-        self.current_slot = self.slot
-        sigma, proof = self.vrf.sign(self.current_slot)
-        if self.slot%self.epoch_length==0:
-            self.node.new_slot(self.current_slot, sigma, proof)
-        else:
-            self.node.new_slot(self.current_slot, sigma, proof, True)
-
-    def verify(self, y, pi, pk_raw, g):
-        return VRF.verify(self.current_slot, y, pi, pk_raw, g)
-    

+ 0 - 68
script/research/PoS-blockchain/ouroboros/environment.py

@@ -1,68 +0,0 @@
-import numpy as np
-import math
-import random
-'''
-\class Z is the environment
-'''
-class Z(object):
-    def __init__(self, stakeholdes, epoch_length=100):
-        self.epoch_length=epoch_length
-        self.stakeholders = np.array(stakeholdes)
-        self.adversary_mask=np.array([True]*len(stakeholdes))
-    '''
-        return genesis data of the current epoch
-    '''
-    def get_genesis_data(self):
-        #TODO implement    
-        pass
-    
-    @property
-    def current_leader_vrf_pk(self):
-        #TODO implement
-        pass
-    
-    @property
-    def current_leader_vrf_g(self):
-        #TODO implement
-        pass
-
-    #TODO complete
-    def obfuscate_idx(self, i):
-        return i
-
-    #TODO complete
-    def deobfuscate_idx(self, i):
-        return i
-
-    def corrupt(self, i):
-        if i<0 or i>len(self.adversary_mask):
-            return False
-        self.adversary_mask[self.deobfuscate_idx(i)]=False
-        return True
-    
-    '''
-    return the length of all parties
-    '''
-    def __len__(self):
-        return len(self.stakeholders)
-
-    @property
-    def length(self):
-        return len(self.stakeholders)
-    @property
-    def honest(self):
-        return len(self.stakeholders[self.adversary_mask])
-
-    def select_epoch_leaders(self, sigma):
-        def leader_selection_hash(sigma):
-            Y = np.array(sigma)
-            y_hypotenuse2 = math.ceil(np.sum(Y[1]**2+Y[2]**2))
-            return y_hypotenuse2
-        seed = leader_selection_hash(sigma)
-        random.seed(seed)
-        leader_idx=seed%self.length
-        leader = self.stakeholders[leader_idx]
-        while not self.adversary_mask[leader_idx]:
-            leader_idx=random.randint(0,self.length)
-        #TODO select the following leader for this epoch, note, 
-        # under a single condition that no one is able to predict who is next

+ 0 - 13
script/research/PoS-blockchain/ouroboros/logger.py

@@ -1,13 +0,0 @@
-class Logger(object):
-    def __init__(self, obj):
-        self.obj = obj
-
-    def info(self, payload):
-        print(f"\t[{self.obj}]:\n{payload}")
-    
-    def warn(self, payload):
-        print(f"\t[{self.obj}]:\n{payload}")
-    
-    def error(self, pyaload):
-        print(f"\t[{self.obj}]:\n{payload}")
-        exit()

+ 0 - 70
script/research/PoS-blockchain/ouroboros/stakeholder.py

@@ -1,70 +0,0 @@
-from block import Block, GensisBlock, EmptyBlock
-from blockchain import Blockchain
-from epoch import Epoch
-from beacon import TrustedBeacon
-from vrf import generate_vrf_keys
-from utils import *
-import numpy as np
-import math
-
-'''
-\class Stakeholder
-'''
-class Stakeholder(object):
-    def __init__(self, env, epoch_length=100, passwd='password'):
-        self.passwd=passwd
-        self.epoch_length=epoch_length
-        self.blockchain = Blockchain(self.epoch_length)
-        self.beacon = TrustedBeacon(self, self.epoch_length)
-        self.beacon.start()
-        pk, sk, g = generate_vrf_keys(self.passwd)
-        self.vrf_pk = pk
-        self.vrf_sk = sk
-        self.vrf_base = g
-        self.current_block = None
-        self.uncommited_tx=''
-        self.tx=''
-        self.current_slot_uid = self.beacon.slot
-        self.current_epoch = None
-        self.env = env
-        
-    @property
-    def epoch_index(self):
-        return round(self.current_slot_uid/self.epoch_length)
-
-    '''
-    it's a callback function, and called by the diffuser
-    '''
-    def new_slot(self, slot, sigma, proof, new_epoch=False):
-        '''
-        #TODO implement praos
-        for this implementation we assume synchrony,
-        and at this point, and no delay is considered (for simplicity)
-        '''
-
-        if not self.beacon.verify(sigma, proof, self.env.current_leader_vrf_pk, self.env.current_leader_vrf_g):
-            #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
-            #in this case this slot is empty
-            self.current_block=EmptyBlock() 
-            self.current_epoch.add_block(self.current_block)
-            return
-        self.current_slot_uid = slot
-        if new_epoch:
-            # add epoch to the ledger
-            if self.current_slot_uid > 1:
-                self.blockchain.add_epoch(self.current_epoch)
-            #kickoff gensis block
-            self.tx = self.env.get_genesis_data()
-            self.current_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid)
-            self.current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch_index)
-            #TODO elect leaders
-            self.select_leader(slot, sigma, proof)
-
-        else:
-            self.current_block=Block(self.current_block, self.tx, self.current_slot_uid)
-            self.current_epoch.add_block(self.current_block)
-
-
-    def select_leader(self, slot, sigma, proof):
-        #TODO implement
-        pass

+ 0 - 48
script/research/PoS-blockchain/protocol.py

@@ -1,48 +0,0 @@
-from streamlet import Block
-from ouroboros import VRF
-from node import Node
-import math
-import numpy as np
-
-# Genesis block is generated.
-genesis_block = Block("⊥", 0, '⊥')
-
-# 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)
-node4 = Node(4, "clock", "node_password4", genesis_block)
-
-nodes = [node0, node1, node4]
-# 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, node4], "tx0")
-# node1 receives input and broadcasts it to rest nodes.
-node1.receive_transaction("tx2")
-node1.broadcast_transaction([node0, node4], "tx2")
-# node4 receives input and broadcasts it to rest nodes.
-node4.receive_transaction("tx3")
-node4.broadcast_transaction([node0, node1], "tx3")
-
-vrf = VRF()
-x = epoch
-y, pi, g = vrf.sign(x)
-Y = np.array(y)
-y_hypotenuse2 = np.sum(Y[1]**2+Y[2]**2)
-# A random leader is selected.
-leader = nodes[math.ceil(y_hypotenuse2)%len(nodes)]
-
-print(f"proposed {x}, {y}, {pi}, {vrf.pk}, {g}")
-# Leader forms a block and broadcasts it.
-leader.propose_block(1, y, pi, vrf.pk, g, nodes)
-
-# Nodes vote on the block and broadcast their vote to rest nodes.
-for node in nodes:
-	node.vote_on_round_block(nodes)
-
-# We verify that all nodes have the same blockchain on round end.
-assert(node0.output() == node1.output() == node4.output())

+ 0 - 114
script/research/PoS-blockchain/streamlet/2-execution-model-and-definitions.py

@@ -1,114 +0,0 @@
-# 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())

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

@@ -1,63 +0,0 @@
-# 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)
-	
-	''' 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. '''
-	def check_block_validity(self, block, previous_block):
-		assert(block.h != '⊥') # genesis block check
-		assert(block.h == hash(previous_block))
-		assert(block.e > previous_block.e)
-
-	''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
-	def check_chain_validity(self):
-		for index, block in enumerate(self.chain[1:]):
-			self.check_block_validity(block, self.chain[index])
-	
-	''' Insertion of a valid block. '''	
-	def add_block(self, 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)

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

@@ -1,62 +0,0 @@
-# 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.

+ 0 - 81
script/research/PoS-blockchain/streamlet/3.4-protocol.py

@@ -1,81 +0,0 @@
-# Section 3.4 from "Streamlet: Textbook Streamlined Blockchains"
-
-from block import Block
-from node import Node
-
-# Genesis block is generated.
-genesis_block = Block("⊥", 0, '⊥')
-
-# 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(1, nodes)
-
-# Nodes vote on the block and broadcast their vote to rest nodes.
-for node in nodes:
-	node.vote_on_round_block(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
-
-# We introduce a new node. Assumption: no history sync, a Node starts participating in next epoch.
-node6 = Node(6, "clock", "node_password5", node0.output()[-1])
-nodes.append(node6)
-
-# node3 receives input and broadcasts it to rest nodes.
-node3.receive_transaction("tx4")
-node3.broadcast_transaction([node0, node1, node2, node4, node5, node6], "tx4")
-# node5 receives input and broadcasts it to rest nodes.
-node5.receive_transaction("tx5")
-node5.broadcast_transaction([node0, node1, node2, node3, node4, node6], "tx5")
-# node6 receives input and broadcasts it to rest nodes.
-node6.receive_transaction("tx6")
-node6.broadcast_transaction([node0, node1, node2, node3, node4, node5], "tx6")
-
-x = epoch
-
-# A random leader is selected.
-leader = nodes[hash(str(epoch))%len(nodes)]
-# A random leader is selected.
-
-# Leader forms a block and broadcasts it.
-leader.propose_block(epoch, nodes)
-
-# Nodes vote on the block and broadcast their vote to rest nodes.
-for node in nodes:
-	node.vote_on_round_block(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())
-
-# Since node6 joined later, node0 output is a prefix or equal to node6 output.
-# Based on that, node6 output is a suffix of node0 output.
-assert(node0.output().blocks[-len(node6.output()):] == node6.output().blocks)

+ 0 - 6
script/research/PoS-blockchain/streamlet/__init__.py

@@ -1,6 +0,0 @@
-from streamlet.block import Block
-from streamlet.blockchain import Blockchain
-from streamlet.clock import Clock
-from streamlet.vote import Vote
-from streamlet.utils import *
-from streamlet.logger import Logger

+ 0 - 22
script/research/PoS-blockchain/streamlet/block.py

@@ -1,22 +0,0 @@
-class Block(object):
-	''' 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):
-		return hash((self.h, self.e, str(self.txs))) # python hash is used for demostranation porpuses only.
-		
-	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())

+ 0 - 34
script/research/PoS-blockchain/streamlet/blockchain.py

@@ -1,34 +0,0 @@
-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]
-	
-	''' 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. '''
-	def check_block_validity(self, block, previous_block):
-		assert(block.h != '⊥') # genesis block check
-		assert(block.h == hash(previous_block))
-		assert(block.e > previous_block.e)
-
-	''' A blockchain is considered valid, when every block is valid, based on check_block_validity method. '''
-	def check_chain_validity(self):
-		for index, block in enumerate(self.blocks[1:]):
-			self.check_block_validity(block, self.blocks[index])
-	
-	''' Insertion of a valid block. '''	
-	def add_block(self, block):		
-		self.check_block_validity(block, self.blocks[-1])
-		self.blocks.append(block)

+ 0 - 54
script/research/PoS-blockchain/streamlet/clock.py

@@ -1,54 +0,0 @@
-'''
-synchronized clock
-'''
-
-import ntplib
-from time import ctime
-import math
-
-class Clock(object):
-    def __init__(self, epoch_length=180, ntp_server='europe.pool.ntp.org'):
-        self.epoch_length=epoch_length #2 minutes
-        self.ntp_server = ntp_server
-        self.ntp_client = ntplib.NTPClient()
-        #TODO validate the server
-        # when was darkfi birthday? as seconds since the epoch 
-        self.darkfi_epoch=0
-        self.observers = []
-    def __repr__(self):
-        return 'darkfi time: '+ ctime(self.darkfi_time) + ', current synched time: ' + ctime(self.synched_time)
-
-    def __get_time_stat(self):
-        response=None
-        success=True
-        while not success:
-            try:
-                response = self.ntp_client.request(self.ntp_server, version=3)
-                success=True
-            except ntplib.NTPException as e:
-                 print("connection failed: {}".format(e.what()))
-        return response
-    @property
-    def synched_time(self):
-        state = self.__get_time_stat()
-        synched_time = state.tx_time
-        return synched_time
-
-    @property
-    def darkfi_time(self):
-        return self.synched_time - self.darkfi_epoch
-
-    @property
-    def epoch(self):   
-        return math.floor(self.darkfi_time/self.epoch_length)
-
-    def bind(self, callback):
-        self.observers.append((callback))
-
-    def background(self):
-        current_epoch = self.epoch
-        while True:
-            if self.epoch !=current_epoch:
-                current_epoch = self.epoch
-                for obs in self.observers:
-                    obs(current_epoch)

+ 0 - 10
script/research/PoS-blockchain/streamlet/logger.py

@@ -1,10 +0,0 @@
-class Logger(object):
-    def __init__(self, obj):
-        self.obj = obj
-    def info(self, payload):
-        print(f"[{self.obj}]: {payload}")
-    def warn(self, payload):
-        print(f"[{self.obj}]: {payload}")
-    def error(self, pyaload):
-        print(f"[{self.obj}]: {payload}")
-        exit()

+ 0 - 78
script/research/PoS-blockchain/streamlet/node.py

@@ -1,78 +0,0 @@
-import copy
-from block import Block
-from utils import *
-from blockchain import Blockchain
-from vote import Vote
-from logger import Logger
-
-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 = generate_keys(self.password)
-		self.blockchain = Blockchain(init_block)
-		self.unconfirmed_transactions = []
-		self.log = Logger(self)
-		self.current_epoch=None #this need to be set by the clock tics
-	
-	def __repr__(self):
-		return "Node=[id={0}, clock={1}, password={2}, private_key={3}, public_key={4}, blockchain={5}, unconfirmed_transactions={6}".format(self.id, self.clock, self.password, self.private_key, self.public_key, self.blockchain, self.unconfirmed_transactions)
-	
-	def output(self):
-		return self.blockchain
-	
-	def receive_transaction(self, transaction):
-		# 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):
-		for node in nodes:
-			node.receive_transaction(transaction)
-			
-	def propose_block(self, epoch, nodes):
-		proposed_block = Block(hash(self.blockchain.blocks[-1]), epoch, self.unconfirmed_transactions)
-		signed_proposed_block = 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))
-	
-	def receive_proposed_block(self, leader_pubkey, round_block, signed_round_block):
-		if not verify_signature(leader_pubkey, round_block, signed_round_block):
-			self.log.warn("the signature of the proposed block dosn't match")
-			return
-		self.round_block = round_block
-		
-	def vote_on_round_block(self, nodes):
-		# Node verifies proposed block extends from one of the longest notarized chains that node has seen at the time.
-		# Already notarized check.
-		if self.round_block != self.blockchain.blocks[-1]:
-			self.blockchain.check_block_validity(self.round_block, self.blockchain.blocks[-1])
-		#TODO implement: at this point we need to verify the unconfirmed transactions
-		signed_block = sign_message(self.password, self.private_key, self.round_block)
-		vote = Vote(signed_block, self.round_block, self.id)
-		for node in nodes:
-			node.receive_vote(self.public_key, vote, nodes)
-
-	def receive_vote(self, node_public_key, vote, nodes):
-		# We verify we haven't received a vote from that node again.
-		assert(vote not in self.round_block.votes)
-		# When nodes receive votes, they verify them against nodes public key.
-		assert(verify_signature(node_public_key, vote.block, vote.vote))
-		assert(self.round_block == vote.block)
-		# Additional rules must be defined by the protocol for its voting system.
-		self.round_block.votes.append(vote)
-		# When a node sees 2n/3 votes for a block it notarizes it
-		if (self.round_block != self.blockchain.blocks[-1] and len(self.round_block.votes) > (2 * len(nodes) / 3)):
-			notarized_block = copy.deepcopy(self.round_block)
-			notarized_block.notarized = True
-			self.blockchain.add_block(notarized_block)
-			# Node removes block transactions from unconfirmed_transactions array
-			#for transaction in notarized_block.txs:
-			#	self.unconfirmed_transactions.remove(transaction)
-			
-			

+ 0 - 50
script/research/PoS-blockchain/streamlet/utils.py

@@ -1,50 +0,0 @@
-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

+ 0 - 9
script/research/PoS-blockchain/streamlet/vote.py

@@ -1,9 +0,0 @@
-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)

+ 0 - 97
script/research/PoS-blockchain/streamlet/vrf.py

@@ -1,97 +0,0 @@
-from logger import Logger
-import random as rnd
-from  tate_bilinear_pairing import eta, ecc
-eta.init(369)
-
-def extended_euclidean_algorithm(a, b):
-    """
-    Returns a three-tuple (gcd, x, y) such that
-    a * x + b * y == gcd, where gcd is the greatest
-    common divisor of a and b.
-
-    This function implements the extended Euclidean
-    algorithm and runs in O(log b) in the worst case.
-    """
-    s, old_s = 0, 1
-    t, old_t = 1, 0
-    r, old_r = b, a
-
-    while r != 0:
-        quotient = old_r // r
-        old_r, r = r, old_r - quotient * r
-        old_s, s = s, old_s - quotient * s
-        old_t, t = t, old_t - quotient * t
-
-    return old_r, old_s, old_t
-
-
-def inverse_of(n, p):
-    """
-    Returns the multiplicative inverse of
-    n modulo p.
-
-    This function returns an integer m such that
-    (n * m) % p == 1.
-    """
-    gcd, x, y = extended_euclidean_algorithm(n, p)
-    assert (n * x + p * y) % p == gcd
-
-    if gcd != 1:
-        # Either n is 0, or p is not a prime number.
-        raise ValueError(
-            '{} has no multiplicative inverse '
-            'modulo {}'.format(n, p))
-    else:
-        return x % p
-
-
-class VRF(object):
-    def __init__(self):
-        self.pk = None
-        self.sk = None
-        self.log = Logger(self)
-        #TODO (res) adhoc temporary
-        self.g = ecc.gen()
-        self.__gen()
-        self.order = ecc.order()
-
-    def __gen(self):
-        '''
-        generate pk/sk
-        '''
-        # TODO implement that is simple sk choosing mechanism for poc; 
-        self.sk = rnd.randint(0,1000)
-        self.pk = ecc.scalar_mult(self.sk, self.g)
-
-    '''
-    short signature without random oracle
-    @param x: message to be signed
-    '''
-    def sign(self, x):
-        pi = ecc.scalar_mult(inverse_of(x+self.sk, self.order), self.g)
-        y = eta.pairing(*self.g[1:], *pi[1:])
-        return (y, pi, self.g)
-    
-    '''
-    verify signature
-    @param x: signed messaged
-    @param y: signature
-    @param pi: [inf, x, y] proof components
-    @param pk: [inf, x, y] public key components of the prover 
-    @param g: group base
-    '''
-    def verify(x, y, pi, pk_raw, g):
-        gx = ecc.scalar_mult(x, g)
-        #pk = ecc.scalar_mult(1, pk_raw)
-        rhs = eta.pairing(*ecc.scalar_mult(1,g)[1:], *pi[1:])
-        if not y == rhs:
-            print(f"y: {y}, rhs: {rhs}")
-            return False
-        gxs = ecc.add(gx, pk_raw)
-        lhs = eta.pairing(*gxs[1:], *pi[1:])
-        rhs = eta.pairing(*ecc.scalar_mult(1, g)[1:], *ecc.scalar_mult(1, g)[1:])
-        if not lhs==rhs:
-            print(f"proposed {x}, {y}, {pi}, {pk_raw}, {g}")
-            print(f"lhs: {lhs},\nrhs: {rhs}")
-            return False
-        return True

+ 1 - 0
script/research/dpos/__init__.py

@@ -0,0 +1 @@
+#dynamic proof of stake blockchain

+ 0 - 0
script/research/PoS-blockchain/__init__.py → script/research/dpos/ouroboros-test/__init__.py


+ 12 - 0
script/research/dpos/ouroboros-test/stakeholder_test.py

@@ -0,0 +1,12 @@
+from dpos.ouroboros import Stakeholder
+from dpos.ouroboros import Z
+import random
+
+EPOCH_LENGTH = 7
+stakeholders = []
+for i in range(3):
+    stakeholders.append(Stakeholder)(EPOCH_LENGTH)
+
+environment = Z(stakeholders, EPOCH_LENGTH)
+
+environment.start()

+ 8 - 0
script/research/dpos/ouroboros/__init__.py

@@ -0,0 +1,8 @@
+from ouroboros.vrf import VRF
+from ouroboros.environment import Z
+from ouroboros.stakeholder import Stakeholder
+from ouroboros.clock import SynchedNTPClock
+from ouroboros.block import Block, EmptyBlock, GensisBlock
+from ouroboros.blockchain import Blockchain
+from ouroboros.epoch import Epoch
+from ouroboros.utils import *

+ 61 - 0
script/research/dpos/ouroboros/beacon.py

@@ -0,0 +1,61 @@
+from ouroboros.clock import SynchedNTPClock
+from ouroboros.vrf import VRF
+from ouroboros.logger import Logger
+import threading
+import time 
+
+'''
+\class TrustedBeacon
+
+the trusted beacon is decentralized, such that at the onset of the Epoch,
+the leader of the first slot generated the signed seed, and release the signature, 
+proof, and base to the genesis block.
+
+#TODO implement trustedbeacon as a node
+'''
+class TrustedBeacon(SynchedNTPClock, threading.Thread):
+    def __init__(self, node, vrf_sk, epoch_length):
+        self.epoch_length=epoch_length # how many slots in a a block
+        SynchedNTPClock.__init__(self)
+        threading.Thread.__init__(self)
+        self.daemon=True
+        self.node = node #stakeholder
+        self.vrf = VRF(self.node.vrf_pk, vrf_sk, self.node.vrf_base)
+        self.current_slot = self.slot
+        self.log = Logger(self)
+        self.log.info("[TrustedBeacon]")
+
+    def __repr__(self):
+        return f"trustedbeadon\n"
+
+    def run(self):
+        self.log.info("[TrustedBeacon] thread [start]")
+        self.__background()
+        self.log.info("[TrustedBeacon] thread [end]")
+
+    def __background(self):
+        current_epoch = self.slot
+        while True:
+            if self.slot != current_epoch:
+                current_epoch = self.slot
+                self.__callback()
+
+    def __callback(self):
+        self.current_slot = self.slot
+        sigmas = []
+        proofs = []
+        self.log.info(f"[TrustedBeacon] new slot of idx: {self.current_slot}")
+        for i in range(self.epoch_length):
+            y, pi = self.vrf.sign(self.current_slot)
+            sigmas.append(y)
+            proofs.append(pi)
+        if self.current_slot%self.epoch_length==0:
+            self.log.info(["[TrustedBeacon] new slot"])
+            self.node.new_slot(self.current_slot, sigmas[0], proofs[0])
+        else:
+            self.log.info([f"[TrustedBeacon] new epoch with simgas of size:{len(sigmas)}, proofs: {len(proofs)}"])
+            self.node.new_epoch(self.current_slot, sigmas, proofs)
+
+    def verify(self, y, pi, pk_raw, g):
+        return VRF.verify(self.current_slot, y, pi, pk_raw, g)
+    

+ 3 - 6
script/research/PoS-blockchain/ouroboros/block.py → script/research/dpos/ouroboros/block.py

@@ -1,5 +1,5 @@
 import json
-from utils import encode_genesis_data, decode_gensis_data, state_hash
+from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
 
 '''
 single block B_i for slot i in the system live time L,
@@ -41,9 +41,6 @@ class Block(object):
             'sl': self.sl}
         return json.encoder(d)
 
-    @property
-    def state(self):
-        return self.st
     
     @property
     def data(self):
@@ -68,7 +65,7 @@ class GensisBlock(Block):
         self.stakeholders = data['stakeholders']
         self.seed = data['seed']
         data = encode_genesis_data(self.stakeholders, self.seed)
-        super.__init__(previous_block, data, slot_uid, True)
+        Block.__init__(self, previous_block, data, slot_uid, True)
     '''
     @return: the number of participating stakeholders in the blockchain
     '''
@@ -88,4 +85,4 @@ is an empty Block
 '''
 class EmptyBlock(Block):
     def __init__(self):
-        super.__init__(None, '', -1, False)
+        Block.__init__(self, '', -1, False)

+ 1 - 1
script/research/PoS-blockchain/ouroboros/blockchain.py → script/research/dpos/ouroboros/blockchain.py

@@ -1,4 +1,4 @@
-from logger import Logger
+from ouroboros.logger import Logger
 '''
 Non-forkable Blockchain for simplicity
 #TODO consider forkable property

+ 5 - 3
script/research/PoS-blockchain/ouroboros/clock.py → script/research/dpos/ouroboros/clock.py

@@ -22,13 +22,15 @@ class SynchedNTPClock(object):
 
     def __get_time_stat(self):
         response=None
-        success=True
+        success=False
         while not success:
             try:
                 response = self.ntp_client.request(self.ntp_server, version=3)
                 success=True
-            except ntplib.NTPException as e:
-                 print("connection failed: {}".format(e.what()))
+            #except ntplib.NTPException as e:
+            except:
+                pass
+                 #print("connection failed: {}".format(e.what()))
         return response
 
     @property

+ 128 - 0
script/research/dpos/ouroboros/environment.py

@@ -0,0 +1,128 @@
+import numpy as np
+import math
+import random
+from ouroboros.logger import Logger
+'''
+\class Z is the environment
+'''
+class Z(object):
+    def __init__(self, stakeholdes, epoch_length):
+        self.log = Logger(self)
+        self.epoch_length=epoch_length
+        self.stakeholders = np.array(stakeholdes)
+        self.adversary_mask=np.array([True]*len(stakeholdes))
+        self.current_epoch_leaders=[-1]*self.epoch_length
+        self.current_slot=0
+        self.log.info("Z initialized")
+    
+    def __repr__(self):
+        buff= f"envirnment of {self.length} stakholders"
+        for sh in self.stakeholders:
+            buff+=str(sh)+"\n"
+        return buff
+
+    '''
+        return genesis data of the current epoch
+    '''
+    def get_genesis_data(self):
+        #TODO implement dynaming staking 
+        return ''
+    
+    @property
+    def current_leader_id(self):
+        return self.current_slot%self.epoch_length
+    @property
+    def current_stakeholder(self):
+        self.log.info(f"getting leader of id{self.current_leader_id} of size {len(self.stakeholders)}")
+        return self.stakeholders[self.current_leader_id]
+
+    @property
+    def current_leader_vrf_pk(self):
+        return self.stakeholders[self.current_leader_id].vrf_pk
+    
+    @property
+    def current_leader_vrf_g(self):
+        return self.stakeholders[self.current_leader_id].vrf_base
+
+    #TODO complete
+    def obfuscate_idx(self, i):
+        return i
+
+    #TODO complete
+    def deobfuscate_idx(self, i):
+        return i
+
+    def corrupt(self, i):
+        if i<0 or i>len(self.adversary_mask):
+            return False
+        self.adversary_mask[self.deobfuscate_idx(i)]=False
+        return True
+    
+    '''
+    return the length of all parties
+    '''
+    def __len__(self):
+        return len(self.stakeholders)
+
+    @property
+    def length(self):
+        return len(self.stakeholders)
+    @property
+    def honest(self):
+        return len(self.stakeholders[self.adversary_mask])
+
+    def select_epoch_leaders(self, sigmas, proofs):
+        assert(len(sigmas)==self.epoch_length and len(proofs)==self.epoch_length, \
+            f"size mismatch between sigmas: {len(sigmas)}, proofs: {len(proofs)}, and epoch_length: {self.epoch_length}")
+        for i in range(self.epoch_length):
+            self.log.info(f"current sigma of index {i} of total {len(sigmas)}, epoch_length: {self.epoch_length}")
+            sigma = sigmas[i]
+            assert (sigma!=None, 'proof cant be None')
+            def leader_selection_hash(sigma):
+                Y = np.array(sigma)
+                y_hypotenuse2 = math.ceil(np.sum(Y[1]**2+Y[2]**2))
+                return y_hypotenuse2
+            seed = leader_selection_hash(sigma)
+            random.seed(seed)
+            leader_idx=seed%self.length
+            # only select an honest leaders
+            while not self.adversary_mask[leader_idx]:
+                leader_idx=random.randint(0,self.length)
+            #TODO select the following leader for this epoch, note, 
+            # under a single condition that no one is able to predict who is next
+            self.current_epoch_leaders[i]=leader_idx
+        return self.current_epoch_leaders
+
+    def new_slot(self, slot, sigma, proof):
+        self.current_slot=slot
+        self.log.info(f"stakeholders: {self.stakeholders}")
+        current_leader = self.stakeholders[self.current_leader_id]
+        assert(current_leader!=None, "current leader cant be None")
+        if current_leader.is_leader:
+            #pass leadership to the current slot leader from the epoch leader
+            self.stakeholders[self.current_epoch_leaders[slot%self.epoch_length]].set_leader()
+    
+    def new_epoch(self, slot, sigmas, proofs):
+        self.current_slot=slot
+        #self.log.info(f"stakeholders: {self.stakeholders}")
+        #current_leader = self.stakeholders[self.current_leader_id]
+        #assert(current_leader!=None, 'current leader cant be none')
+        #assert(current_leader.is_leader)
+        self.select_epoch_leaders(sigmas, proofs)
+
+    def broadcast_block(self, signed_block):
+        for stakeholder in self.stakeholders:
+            if not stakeholder.is_leader:
+                self.stakeholders.receive_block(signed_block)
+
+    def start(self):
+        for sh in self.stakeholders:
+            sh(self)
+        self.log.info("Z.start [started]")
+        for sh in self.stakeholders:
+            sh.start()
+        self.log.info("Z.start [ended]")
+
+    def print_blockchain(self):
+        bc = self.stakeholders[0].blockchain
+        self.log.info(f"blockchain of {len(bc)} blocks: "+str(bc))

+ 1 - 1
script/research/PoS-blockchain/ouroboros/epoch.py → script/research/dpos/ouroboros/epoch.py

@@ -1,4 +1,4 @@
-from utils import state_hash
+from ouroboros.utils import state_hash
 
 class Epoch(object):
     '''

+ 0 - 0
script/research/PoS-blockchain/ouroboros/kes.py → script/research/dpos/ouroboros/kes.py


+ 13 - 0
script/research/dpos/ouroboros/logger.py

@@ -0,0 +1,13 @@
+class Logger(object):
+    def __init__(self, obj):
+        self.obj = obj
+
+    def info(self, payload):
+        print(f"\t[{self.obj}]:\n{payload}\n")
+    
+    def warn(self, payload):
+        print(f"\t[{self.obj}]:\n{payload}\n")
+    
+    def error(self, payload):
+        print(f"\t[{self.obj}]:\n{payload}\n")
+        exit()

+ 144 - 0
script/research/dpos/ouroboros/stakeholder.py

@@ -0,0 +1,144 @@
+#from asyncio.log import logger
+from ouroboros.block import Block, GensisBlock, EmptyBlock
+from ouroboros.blockchain import Blockchain
+from ouroboros.epoch import Epoch
+from ouroboros.beacon import TrustedBeacon
+from ouroboros.vrf import generate_vrf_keys, VRF
+from ouroboros.utils import *
+from ouroboros.logger import Logger
+
+'''
+\class Stakeholder
+'''
+class Stakeholder(object):
+    def __init__(self, epoch_length=100, passwd='password'):
+        #TODO (fix) remove redundant variables reley on environment
+        self.passwd=passwd
+        self.stake=0
+        self.epoch_length=epoch_length
+        pk, sk, g = generate_vrf_keys(self.passwd)
+        self.__vrf_pk = pk
+        self.__vrf_sk = sk
+        self.__vrf_base = g
+
+        self.blockchain = Blockchain(self.epoch_length)
+        self.beacon = TrustedBeacon(self, self.epoch_length, self.__vrf_sk)
+        sig_sk, sig_pk = generate_sig_keys(self.passwd)
+        self.sig_sk = sig_sk
+        self.sig_pk = sig_pk
+        self.current_block = None
+        self.uncommited_tx=''
+        self.tx=''
+        self.current_slot_uid = self.beacon.slot
+        self.current_epoch = None
+        self.am_current_leader=False
+        self.am_current_endorder=False
+        self.am_corrupt=False
+        self.log = Logger(self)
+
+    @property
+    def is_leader(self):
+        return self.am_current_leader
+
+    
+    @property
+    def vrf_pk(self):
+        return self.__vrf_pk
+
+    @property
+    def vrf_base(self):
+        return self.__vrf_base
+    
+    def __repr__(self):
+        buff = f"\tstakeholder with stake:{self.stake}\t"
+        return buff
+
+    def __call__(self, env):
+        self.env=env
+
+    def start(self):
+        self.log.info("Stakeholder.start [started]")
+        self.beacon.start()
+        self.log.info("Stakeholder.start [ended]")
+
+    @property
+    def epoch_index(self):
+        return round(self.current_slot_uid/self.epoch_length)
+
+    '''
+    it's a callback function, and called by the diffuser
+    '''
+    def new_epoch(self, slot, sigmas, proofs):
+        '''
+        #TODO implement praos
+        for this implementation we assume synchrony,
+        and at this point, and no delay is considered (for simplicity)
+        '''
+        self.log.info("[stakeholder.new_epoch] start")
+        self.env.new_epoch(slot, sigmas, proofs)
+        self.current_slot_uid = slot
+            # add epoch to the ledger
+        if self.current_slot_uid > 1:
+            self.blockchain.add_epoch(self.current_epoch)   
+        #kickoff gensis block
+        self.tx = self.env.get_genesis_data()
+        self.current_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid)
+        self.current_epoch=Epoch(self.current_block, self.epoch_length, self.epoch_index)
+        #if leader, you need to broadcast the block
+        if self.am_current_leader:
+            self.broadcast_block()
+
+    '''
+    it's a callback function, and called by the diffuser
+    '''
+    def new_slot(self, slot, sigma, proof):
+        '''
+        #TODO implement praos
+        for this implementation we assume synchrony,
+        and at this point, and no delay is considered (for simplicity)
+        '''
+        self.log.info("[stakeholder.new_slot] start")
+        self.env.new_slot(slot, sigma, proof)
+        vrf_pk = self.env.current_leader_vrf_pk
+        vrf_g = self.env.current_leader_vrf_g
+        assert(vrf_pk!=None)
+        assert(vrf_g!=None)
+        if not VRF.verify(slot, sigma, proof, vrf_pk,vrf_g) :
+            #TODO the leader is corrupted, action to be taken against the corrupt stakeholder
+            #in this case this slot is empty
+            self.current_block=EmptyBlock() 
+            if self.current_epoch!=None:
+                self.current_epoch.add_block(self.current_block)
+            else:
+                #TODO (fix) this shouldn't happen!
+                self.log.info(f"[Stakeholder] new_slot, current_epoch is None!")
+            return
+        self.current_slot_uid = slot
+        self.current_block=Block(self.current_block, self.tx, self.current_slot_uid)
+        self.current_epoch.add_block(self.current_block)
+        #TODO if leader you need to broadcast the block
+        if self.am_current_leader:
+            self.broadcast_block()
+
+    def set_leader(self):
+        self.am_current_leader=True
+
+    def set_endorser(self):
+        self.am_endorser=True
+
+    def set_corrupt(self):
+        self.am_corrupt=False
+
+    def broadcast_block(self):
+        assert(self.am_current_leader)
+        signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
+        self.env.broadcast_block(signed_block)
+        self.env.print_blockchain()
+
+
+    def receive_block(self, received_block):
+        if verify_signature(self.env.current_leader_sig_pk, self.current_block, received_block):
+            pass
+        else:
+            self.env.corrupt(self.env.current_leader_id)
+        self.env.print_blockchain()

+ 55 - 0
script/research/PoS-blockchain/ouroboros/utils.py → script/research/dpos/ouroboros/utils.py

@@ -1,3 +1,7 @@
+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
 import random
 import joblib
 import pickle
@@ -94,3 +98,54 @@ just in case two stakeholders started with the same seed
 '''
 def vrf_hash(seed):
     return hash(seed)
+
+
+def generate_sig_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

+ 4 - 3
script/research/PoS-blockchain/ouroboros/vrf.py → script/research/dpos/ouroboros/vrf.py

@@ -1,8 +1,8 @@
-from streamlet.logger import Logger
+from ouroboros.logger import Logger
 import random as rnd
 from  tate_bilinear_pairing import eta, ecc
 from ouroboros.utils import inverse_of
-from utils import vrf_hash
+from ouroboros.utils import vrf_hash
 eta.init(369)
 
 '''
@@ -14,7 +14,8 @@ def generate_vrf_keys(sk_seed):
         generate pk/sk
     return: list of pk (public key), sk(secret key), base(field base)
     '''
-    sk = vrf_hash(sk_seed)
+    #sk = vrf_hash(sk_seed)
+    sk=2
     base = ecc.gen()
     pk = ecc.scalar_mult(sk, base)
     return (pk, sk, base)

+ 15 - 0
script/research/dpos/simulation.py

@@ -0,0 +1,15 @@
+from ouroboros import Stakeholder
+from ouroboros import Z
+import random
+
+EPOCH_LENGTH = 3
+stakeholders = []
+for i in range(3):
+    stakeholders.append(Stakeholder(EPOCH_LENGTH))
+
+environment = Z(stakeholders, EPOCH_LENGTH)
+
+environment.start()
+
+for sh in environment.stakeholders:
+    sh.beacon.join()