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

[research/ourobors] fixed bugs in vrf, blockchain

mohab 4 лет назад
Родитель
Сommit
4eee40e873

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

@@ -1 +1,10 @@
-#dynamic proof of stake blockchain
+#dynamic proof of stake blockchain
+from dpos import ouroboros
+from dpos.ouroboros.vrf import VRF
+from dpos.ouroboros.environment import Z
+from dpos.ouroboros.stakeholder import Stakeholder
+from dpos.ouroboros.clock import SynchedNTPClock
+from dpos.ouroboros.block import Block, EmptyBlock, GensisBlock
+from dpos.ouroboros.blockchain import Blockchain
+from dpos.ouroboros.epoch import Epoch
+from dpos.ouroboros.utils import *

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


+ 18 - 16
script/research/dpos/ouroboros/beacon.py

@@ -14,27 +14,28 @@ 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, genesis_time):
+    def __init__(self, node, vrf, epoch_length, genesis_time):
         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.vrf = vrf
         self.current_slot = self.slot
         self.log = Logger(self, genesis_time)
-        self.log.info(f"[TrustedBeacon] constructed for node {str(node)}")
+        self.log.info(f"constructed for node {str(node)}")
 
     def __repr__(self):
         return f"trustedbeacon"
 
     def run(self):
-        self.log.info("[TrustedBeacon] thread [start]")
+        self.log.highlight("thread [start]")
         self.__background()
-        self.log.info("[TrustedBeacon] thread [end]")
+        self.log.info("thread [end]")
 
     def __background(self):
         current_epoch = self.slot
+        self.log.info('background waiting for the onset of next synched epoch...')
         while True:
             if self.slot != current_epoch:
                 current_epoch = self.slot
@@ -42,19 +43,20 @@ class TrustedBeacon(SynchedNTPClock, threading.Thread):
 
     def __callback(self):
         self.current_slot = self.slot
-        sigmas = []
-        proofs = []
-        for i in range(self.epoch_length):
-            self.log.info(f"[TrustedBeacon] callback: new slot of idx: {self.current_slot}, epoch slot {i}")
+        if self.current_slot%self.epoch_length!=0:
+            self.log.info(f"callback: new slot of idx: {self.current_slot}")
             y, pi = self.vrf.sign(self.current_slot)
-            self.log.info(f"[TrustedBeacon] callback: signature calculated for {str(self.node)}")
-            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])
+            self.log.info(f"callback: signature calculated for {str(self.node)}")
+            self.node.new_slot(self.current_slot, y, pi)
         else:
-            self.log.info([f"[TrustedBeacon] new epoch with simgas of size:{len(sigmas)}, proofs: {len(proofs)}"])
+            sigmas = []
+            proofs = []
+            for i in range(self.epoch_length):
+                self.log.info(f"callback: new slot of idx: {self.current_slot}, epoch slot {i}")
+                y, pi = self.vrf.sign(self.current_slot)
+                self.log.info(f"callback: signature calculated for {str(self.node)}")
+                sigmas.append(y)
+                proofs.append(pi)
             self.node.new_epoch(self.current_slot, sigmas, proofs)
 
     def verify(self, y, pi, pk_raw, g):

+ 20 - 4
script/research/dpos/ouroboros/block.py

@@ -1,5 +1,6 @@
 import json
 from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
+from ouroboros.consts import *
 
 '''
 single block B_i for slot i in the system live time L,
@@ -30,6 +31,16 @@ class Block(object):
                 decode_gensis_data(self.data)
         return "Block at {slot:"+self.sl+",data:"+self.tx+",state:"+self.state+"}"
     
+    def __hash__(self):
+        if type(self.tx)==str:
+            return hash((self.state, self.tx, self.sl))
+        elif type(self.tx)==dict:
+            #TODO include distribution
+            return hash((self.state, self.tx[SEED], self.tx[TX]))
+        else: 
+            #TODO (fix) shouldn't reach here
+            return 0
+
     def __eq__(self, block):
         return self.state==block.state and \
             self.tx == block.tx and \
@@ -62,10 +73,15 @@ class GensisBlock(Block):
     '''
     def __init__(self, previous_block, data, slot_uid):
         # stakeholders is list of tuple (pk_i, s_i) for the ith stakeholder
-        self.stakeholders = data['stakeholders']
-        self.seed = data['seed']
-        data = encode_genesis_data(self.stakeholders, self.seed)
-        Block.__init__(self, previous_block, data, slot_uid, True)
+        self.stakeholders = data[STAKEHOLDERS]
+        self.distribution = data[STAKEHOLDERS_DISTRIBUTIONS]
+        self.seed = data.get(SEED, '') #needed for pvss
+        shd_buff = ''
+        for shd in self.distribution:
+            shd_buff +=str(shd)
+        #data = encode_genesis_data(shd_buff)
+        data_dict = {'seed':self.seed, 'distribution':shd_buff}
+        Block.__init__(self, previous_block, str(data_dict), slot_uid, True)
     '''
     @return: the number of participating stakeholders in the blockchain
     '''

+ 3 - 1
script/research/dpos/ouroboros/blockchain.py

@@ -17,7 +17,7 @@ class Blockchain(object):
     def __repr__(self):
         buff=''
         for i in range(len(self.blocks)):
-            buff+=self.blocks[i]
+            buff+=str(self.blocks[i])
         return buff
     
     def __getitem__(self, i):
@@ -30,6 +30,8 @@ class Blockchain(object):
         self.blocks.append(block)
 
     def add_epoch(self, epoch):
+        assert epoch!=None, 'epoch cant be None'
+        assert len(epoch)>0 , 'epoch cant be zero-sized'
         for idx, block in enumerate(epoch):
             if not block.empty:
                 self.__add_block(block)

+ 1 - 1
script/research/dpos/ouroboros/clock.py

@@ -8,7 +8,7 @@ import math
 
 class SynchedNTPClock(object):
 
-    def __init__(self, slot_length=180, ntp_server='europe.pool.ntp.org'):
+    def __init__(self, slot_length=1, ntp_server='europe.pool.ntp.org'):
         #TODO how long should be the slot length
         self.slot_length=slot_length
         self.ntp_server = ntp_server

+ 4 - 0
script/research/dpos/ouroboros/consts.py

@@ -0,0 +1,4 @@
+STAKEHOLDERS_DISTRIBUTIONS='distributions'
+STAKEHOLDERS='stakeholders'
+SEED='seed'
+TX='tx'

+ 8 - 7
script/research/dpos/ouroboros/environment.py

@@ -2,6 +2,7 @@ import numpy as np
 import math
 import random
 from ouroboros.logger import Logger
+from ouroboros.consts import *
 import time
 '''
 \class Z is the environment
@@ -27,8 +28,11 @@ class Z(object):
         return genesis data of the current epoch
     '''
     def get_genesis_data(self):
-        #TODO implement dynaming staking 
-        return ''
+        #TODO implement dynaming staking
+        genesis_data = {STAKEHOLDERS: self.stakeholders,
+        STAKEHOLDERS_DISTRIBUTIONS:[],
+            SEED: ''}
+        return genesis_data
     
     @property
     def current_leader_id(self):
@@ -74,8 +78,7 @@ class Z(object):
         return len(self.stakeholders[self.adversary_mask])
 
     def select_epoch_leaders(self, sigmas, proofs):
-        if len(sigmas)==self.epoch_length or len(proofs)==self.epoch_length:
-            self.log.error(f"size mismatch between sigmas: {len(sigmas)}, proofs: {len(proofs)}, and epoch_length: {self.epoch_length}")
+        assert len(sigmas)==self.epoch_length and len(proofs)==self.epoch_length, self.log.error(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]
@@ -120,11 +123,9 @@ class Z(object):
     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))
+        self.log.info(f"<blockchain>  {len(bc)} blocks: "+str(bc))

+ 17 - 8
script/research/dpos/ouroboros/epoch.py

@@ -1,17 +1,20 @@
 from ouroboros.utils import state_hash
+from ouroboros.logger import Logger
 
-class Epoch(object):
+class Epoch(list):
     '''
     epoch spans R slots, 
     maximum number of block in Epoch is R
     epoch must start with gensis block B0
     '''
-    def __init__(self, gensis_block, R, epoch_idx):
+    def __init__(self, gensis_block, R, epoch_idx, genesis_time):
         self.gensis_block=gensis_block
         self.blocks = []
         self.R = R #maximum epoch legnth, and it's a fixed property of the system
         self.e = epoch_idx
-        self.index=0
+        self.n=0
+        self.log = Logger(genesis_time)
+
     @property
     def slot(self):
         return self.gensis_block.sl
@@ -30,24 +33,30 @@ class Epoch(object):
             return None
         return self.blocks[0]
 
+    def __len__(self):
+        return self.length
+
     def add_block(self, block):
-        if len(self.blocks)>0 and not block.state==state_hash(self.block[-1]):
+        if len(self.blocks)>0 and not block.state==state_hash(self.blocks[-1]):
             #TODO we dealing with corrupt stakeholder,
             # action to be taken
             # the caller of the add_block should execute (corrupt,U)
             pass
+
         if self.length==self.R:
-            raise f"can't exceed Epoch's length: {self.length}"
+            self.log.error(f"epoch length: {self.length} can't exceed Epoch's length: {self.R}")
         self.blocks.append(block)
     
     def __iter__(self):
+        self.n=0
         return self
     
     def __next__(self):
+        blk=None
         for i in range(self.length):
             try:
-                res=self.blocks[self.index]
+                blk=self.blocks[self.n]
             except IndexError:
                 raise StopIteration
-            self.index+=1
-            return res
+            self.n+=1
+            return blk

+ 6 - 3
script/research/dpos/ouroboros/logger.py

@@ -14,11 +14,14 @@ class Logger(object):
         return round(d,1)
 
     def info(self, payload):
-        print("\033[32m", f"[{self.diff}] - [{self.obj}]:\n\t{payload}\n", "\033[0m")
+        print("\033[32m", f"[{self.diff}] - [{type(self.obj).__name__}] {self.obj}:\n\t{payload}\n", "\033[0m")
+    
+    def highlight(self, payload):
+        print("\033[35m", f"[{self.diff}] - [{type(self.obj).__name__}] {self.obj}:\n\t{payload}\n", "\033[0m")
     
     def warn(self, payload):
-        print("\033[33m", f"[{self.diff}] - [{self.obj}]:\n\t{payload}\n", "\033[0m")
+        print("\033[33m", f"[{self.diff}] - [{type(self.obj).__name__}] {self.obj}:\n\t{payload}\n", "\033[0m")
     
     def error(self, payload):
-        print("\033[31m", f"[{self.diff}] - [{self.obj}]:\n\t{payload}\n", "\033[0m")
+        print("\033[31m", f"[{self.diff}] - [{type(self.obj).__name__}] {self.obj}:\n\t{payload}\n", "\033[0m")
         exit()

+ 32 - 22
script/research/dpos/ouroboros/stakeholder.py

@@ -3,24 +3,25 @@ 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.vrf import verify, VRF
 from ouroboros.utils import *
 from ouroboros.logger import Logger
+from ouroboros.consts import *
 import time
 '''
 \class Stakeholder
 '''
 class Stakeholder(object):
-    def __init__(self, epoch_length=100, passwd='password'):
+    def __init__(self, epoch_length=2, 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 = VRF(self.passwd)
         #verification keys
-        self.__vrf_pk = pk
-        self.__vrf_sk = sk
-        self.__vrf_base = g
+        self.__vrf_pk = self.vrf.pk
+        self.__vrf_sk = self.vrf.sk
+        self.__vrf_base = self.vrf.g
         #signature keys
         sig_sk, sig_pk = generate_sig_keys(self.passwd)
         self.sig_sk = sig_sk
@@ -56,19 +57,27 @@ class Stakeholder(object):
         self.env=env
         self.log = Logger(self, self.env.genesis_time)
         self.blockchain = Blockchain(self.epoch_length, self.env.genesis_time)
-        self.beacon = TrustedBeacon(self,  self.__vrf_sk, self.epoch_length, self.env.genesis_time)
+        self.beacon = TrustedBeacon(self,  self.vrf, self.epoch_length, self.env.genesis_time)
         self.current_slot_uid = self.beacon.slot
 
 
     def start(self):
-        self.log.info("Stakeholder.start [started]")
+        self.log.info("start [started]")
         self.beacon.start()
-        self.log.info("Stakeholder.start [ended]")
+        self.log.info("start [ended]")
 
     @property
     def epoch_index(self):
         return round(self.current_slot_uid/self.epoch_length)
 
+    def __gen_genesis_epoch(self):
+        '''
+        '''
+        self.tx = self.env.get_genesis_data()
+        self.tx[TX]=self.uncommited_tx
+        self.uncommited_tx=''
+        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, self.env.genesis_time)
     '''
     it's a callback function, and called by the diffuser
     '''
@@ -78,17 +87,15 @@ class Stakeholder(object):
         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.log.highlight("<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)
+        # add old epoch to the ledger
+        if self.current_slot_uid > 1 and self.current_epoch!=None and len(self.current_epoch)>0:
+            self.blockchain.add_epoch(self.current_epoch) 
         #if leader, you need to broadcast the block
+        self.__gen_genesis_epoch() 
         if self.am_current_leader:
             self.broadcast_block()
 
@@ -101,26 +108,27 @@ class Stakeholder(object):
         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.log.highlight("<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) :
+        if not 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.log.warn(f"<new_slot> current_epoch is None!")
+                return
+        if self.current_epoch==None:
+            self.log.warn(f"<new_slot> current_epoch is None!")
+            self.__gen_genesis_epoch()
         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()
 
@@ -134,6 +142,7 @@ class Stakeholder(object):
         self.am_corrupt=False
 
     def broadcast_block(self):
+        self.log.highlight("broadcasting block")
         assert(self.am_current_leader)
         signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
         self.env.broadcast_block(signed_block)
@@ -141,6 +150,7 @@ class Stakeholder(object):
 
 
     def receive_block(self, received_block):
+        self.log.highlight("receiving block")
         if verify_signature(self.env.current_leader_sig_pk, self.current_block, received_block):
             pass
         else:

+ 34 - 21
script/research/dpos/ouroboros/vrf.py

@@ -5,30 +5,23 @@ from ouroboros.utils import inverse_of
 from ouroboros.utils import vrf_hash
 eta.init(369)
 
-'''
-gernate vrf keys for stakeholder
-@param sk_seed: this is suppoed to be the password of the stakeholder
-'''
-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)
-    base = ecc.gen()
-    pk = ecc.scalar_mult(sk, base)
-    return (pk, sk, base)
 
 class VRF(object):
     '''
     verifiable random function implementation
     '''
-    def __init__(self, pk, sk, base):
-        self.pk = pk
-        self.sk = sk
-        self.g=base
+    def __init__(self, seed):
         self.log = Logger(self)
         self.order = ecc.order()
+        #TODO use ecc to gen sk
+        sk = vrf_hash(seed) % self.order
+        g = ecc.gen()
+        pk = ecc.scalar_mult(sk, g)
+        #
+        self.pk = pk
+        self.sk = sk
+        self.g=g
+        
 
     '''
     short signature without random oracle
@@ -50,12 +43,32 @@ class VRF(object):
     @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):
+    def verify(self, x, y, pi):
+        gx = ecc.scalar_mult(x, self.g)
+        rhs = eta.pairing(*ecc.scalar_mult(1,self.g)[1:], *pi[1:])
+        if not y == rhs:
+            print(f"y: {y}, rhs: {rhs}")
+            return False
+        gxs = ecc.add(gx, self.pk)
+        lhs = eta.pairing(*gxs[1:], *pi[1:])
+        rhs = eta.pairing(*ecc.scalar_mult(1, self.g)[1:], *ecc.scalar_mult(1, self.g)[1:])
+        if not lhs==rhs:
+            print(f"proposed {x}, {y}, {pi}, {self.pk}, {self.g}")
+            print(f"lhs: {lhs},\nrhs: {rhs}")
+            return False
+        return True
+
+'''
+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}")

+ 3 - 3
script/research/dpos/simulation.py

@@ -2,13 +2,13 @@ from ouroboros import Stakeholder
 from ouroboros import Z
 import time
 
-EPOCH_LENGTH = 3
+EPOCH_LENGTH = 2
 stakeholders = []
-for i in range(3):
+
+for i in range(2):
     stakeholders.append(Stakeholder(EPOCH_LENGTH))
 
 environment = Z(stakeholders, EPOCH_LENGTH, genesis_time=time.time())
-
 environment.start()
 
 for sh in environment.stakeholders:

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


+ 10 - 0
script/research/dpos/vrf_test.py

@@ -0,0 +1,10 @@
+from ouroboros.vrf import VRF, verify
+
+seed='seed'
+m=234234234
+
+vrf = VRF(seed)
+y, pi  = vrf.sign(m)
+
+assert vrf.verify(m, y, pi), 'verification failed'
+assert verify(m, y, pi, vrf.pk, vrf.g), 'verification failed'