[research/ouroboros] added Input endorser

This commit is contained in:
mohab
2022-02-12 13:02:12 +02:00
parent 237b71353d
commit d3b1dcebb3
8 changed files with 160 additions and 73 deletions

View File

@@ -1,8 +1,7 @@
import threading
from ouroboros.clock import SynchedNTPClock
from ouroboros.vrf import VRF
from ouroboros.logger import Logger
import threading
import time
'''
\class TrustedBeacon
@@ -51,6 +50,8 @@ class TrustedBeacon(SynchedNTPClock, threading.Thread):
else:
sigmas = []
proofs = []
#TODO since it's expensive, but to generate single (y,pi) pair as seed
# and use random hash function to generate the rest randomly.
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)

View File

@@ -1,6 +1,7 @@
import json
from ouroboros.utils import encode_genesis_data, decode_gensis_data, state_hash
from ouroboros.consts import *
from ouroboros.logger import Logger
'''
single block B_i for slot i in the system live time L,
@@ -16,7 +17,7 @@ class Block(object):
it's one-based
@param gensis: boolean, True for gensis block
'''
def __init__(self, previous_block, data, slot_uid, genesis=False):
def __init__(self, previous_block, data, slot_uid, genesis_time, genesis=False):
# state is hte hash of the previous block in the blockchain
self.state=''
if slot_uid>1:
@@ -24,6 +25,8 @@ class Block(object):
self.tx = data
self.sl = slot_uid
self.is_genesis=genesis
self.endorsed=False
self.log = Logger(genesis_time)
def __repr__(self):
if self.is_genesis:
@@ -52,6 +55,9 @@ class Block(object):
'sl': self.sl}
return json.encoder(d)
def set_endorsed(self):
self.endorsed=True
@property
def data(self):
@@ -103,5 +109,5 @@ lead by offline leader
is an empty Block
'''
class EmptyBlock(Block):
def __init__(self):
Block.__init__(self, '', -1, False)
def __init__(self, genesis_time):
Block.__init__(self, '', -1, genesis_time, False)

View File

@@ -1,8 +1,8 @@
from ouroboros.logger import Logger
import time
'''
Non-forkable Blockchain for simplicity
#TODO consider forkable property
#TODO implement forkable chain
'''
class Blockchain(object):
def __init__(self, R, genesis_time):
@@ -37,6 +37,3 @@ class Blockchain(object):
self.__add_block(block)
else:
self.log.warn(f"an empty block at index of index: {block.index},\nrelative slot:{idx}\nabsolute slot: {self.length*idx+block.slot}")

View File

@@ -1,9 +1,11 @@
from debugpy import configure
import numpy as np
import math
import random
import time
from ouroboros.logger import Logger
from ouroboros.consts import *
import time
'''
\class Z is the environment
'''
@@ -15,8 +17,10 @@ class Z(object):
self.stakeholders = np.array(stakeholdes)
self.adversary_mask=np.array([True]*len(stakeholdes))
self.current_epoch_leaders=[-1]*self.epoch_length
self.current_epoch_endorsers=[-1]*self.epoch_length
self.current_slot=0
self.log.info("Z initialized")
self.current_blk_endorser_sig=None
def __repr__(self):
buff= f"envirnment of {self.length} stakholders"
@@ -35,13 +39,27 @@ class Z(object):
return genesis_data
@property
def current_leader_id(self):
def epoch_slot(self):
return self.current_slot%self.epoch_length
@property
def current_leader_id(self):
return self.current_epoch_leaders[self.epoch_slot]
@property
def current_stakeholder(self):
self.log.info(f"getting leader of id{self.current_leader_id} of size {len(self.stakeholders)}")
self.log.info(f"getting leader of id: {self.current_leader_id}")
return self.stakeholders[self.current_leader_id]
@property
def current_endorser_id(self):
return self.current_endorser_id[self.epoch_slot]
@property
def current_endorser(self):
self.log.info(f"getting endorser of id: {self.current_leader_id}")
return self.stakeholders[self.current_endorser_id]
@property
def current_leader_vrf_pk(self):
return self.stakeholders[self.current_leader_id].vrf_pk
@@ -54,6 +72,9 @@ class Z(object):
def current_leader_sig_pk(self):
return self.stakeholders[self.current_leader_id].sig_pk
@property
def current_endorser_sig_pk(self):
return self.stakeholders[self.current_endorser_id].sig_pk
#TODO complete
def obfuscate_idx(self, i):
@@ -78,6 +99,7 @@ class Z(object):
@property
def length(self):
return len(self.stakeholders)
@property
def honest(self):
return len(self.stakeholders[self.adversary_mask])
@@ -95,13 +117,17 @@ class Z(object):
seed = leader_selection_hash(sigma)
random.seed(seed)
leader_idx=seed%self.length
endorser_idx=random.randint(0,self.length)
# only select an honest leaders
while not self.adversary_mask[leader_idx]:
while leader_idx==endorser_idx or not self.adversary_mask[leader_idx] or not self.adversary_mask[endorser_idx]:
leader_idx=random.randint(0,self.length)
enderser_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
self.current_epoch_endorsers[i]=endorser_idx
return self.current_epoch_leaders, self.current_epoch_endorsers
def new_slot(self, slot, sigma, proof):
self.current_slot=slot
@@ -111,21 +137,27 @@ class Z(object):
if current_leader.is_leader:
#pass leadership to the current slot leader from the epoch leader
self.stakeholders[self.current_epoch_leaders[self.current_leader_id]].set_leader()
self.stakeholders[self.current_epoch_endorsers[self.current_endorser_id]].set_endorser()
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)
leaders, endorsers = self.select_epoch_leaders(sigmas, proofs)
return leaders, endorsers
def broadcast_block(self, signed_block, slot_uid):
for stakeholder in self.stakeholders:
if not stakeholder.is_leader:
stakeholder.receive_block(signed_block, slot_uid)
stakeholder.receive_block(signed_block, self.current_blk_endorser_sig, slot_uid)
self.print_blockchain()
def encorse_block(self, sig, slot_uid):
#TODO commit this step to handshake phases
self.current_blk_endorser_sig=sig
confirmed = self.stakeholders[self.current_leader_id].confirm_endorsing(sig, slot_uid)
if confirmed:
self.current_blk_endorser_sig=sig
def start(self):
for sh in self.stakeholders:
sh(self)
@@ -134,4 +166,19 @@ class Z(object):
def print_blockchain(self):
for sh in self.stakeholders:
bc = sh.blockchain
self.log.highlight(f"<blockchain> {len(bc)} blocks: "+str(bc))
self.log.highlight(f"<blockchain> {len(bc)} blocks: "+str(bc))
def confirm_endorsing(self, sig, blk_uid):
if blk_uid==self.current_slot:
self.current_blk_endorser_sig = sig
def corrupt_leader(self):
self.corrupt(self.current_leader_id)
def corrupt_endorse(self):
self.corrupt(self.current_endorser_id)
def corrupt_blk(self):
self.log.warn(f"<corrupt_blk> at slot: {self.current_slot}")
self.corrupt_leader()
self.corrupt_endorse()

View File

@@ -1,4 +1,3 @@
#from asyncio.log import logger
from ouroboros.block import Block, GensisBlock, EmptyBlock
from ouroboros.blockchain import Blockchain
from ouroboros.epoch import Epoch
@@ -7,13 +6,12 @@ from ouroboros.vrf import verify, VRF
from ouroboros.utils import *
from ouroboros.logger import Logger
from ouroboros.consts import *
from copy import deepcopy
import time
'''
\class Stakeholder
'''
class Stakeholder(object):
def __init__(self, epoch_length=2, passwd='password'):
#TODO (fix) remove redundant variables reley on environment
self.passwd=passwd
@@ -38,6 +36,7 @@ class Stakeholder(object):
self.am_corrupt=False
#
self.blockchain=None
self.current_blk_endorser_sig = None
@property
def is_leader(self):
@@ -78,8 +77,9 @@ class Stakeholder(object):
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_block=GensisBlock(self.current_block, self.tx, self.current_slot_uid, self.env.genesis_time)
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
'''
@@ -100,6 +100,7 @@ class Stakeholder(object):
self.__gen_genesis_epoch()
if self.am_current_leader:
self.broadcast_block()
self.am_current_leader=False
'''
it's a callback function, and called by the diffuser
@@ -117,7 +118,7 @@ class Stakeholder(object):
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()
self.current_block=EmptyBlock(self.env.genesis_time)
if self.current_epoch==None:
self.log.warn(f"<new_slot> leader verification fails, current_epoch is None!")
self.__gen_genesis_epoch()
@@ -127,11 +128,24 @@ class Stakeholder(object):
self.log.warn(f"<new_slot> current_epoch is None!")
self.__gen_genesis_epoch()
self.current_slot_uid = slot
prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock()
self.current_block=Block(prev_blk, self.tx, self.current_slot_uid)
prev_blk = self.blockchain[-1] if len(self.blockchain)>0 else EmptyBlock(self.env.genesis_time)
self.current_block=Block(prev_blk, self.tx, self.current_slot_uid, self.env.genesis_time)
self.current_epoch.add_block(self.current_block)
if self.am_current_leader:
self.broadcast_block()
self.end_leadership()
elif self.am_current_endorder:
self.endorse_block()
self.end_endorsing()
def end_leadership(self):
self.log(f"stakeholder:{str(self)} ending leadership for slot{self.current_slot_uid}")
self.am_current_leader=False
def end_endorsing(self):
self.log(f"stakeholder:{str(self)} ending endorsing for slot{self.current_slot_uid}")
self.am_current_endorder=False
def set_leader(self):
self.am_current_leader=True
@@ -145,11 +159,22 @@ class Stakeholder(object):
def broadcast_block(self):
self.log.highlight("broadcasting block")
assert(self.am_current_leader and self.current_block is not None)
signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
signed_block=None
#TODO should wait for l slot until block is endorsed
if not self.current_block.endorsed:
self.current_block = EmptyBlock(self.env.genesis_time)
else:
signed_block = sign_message(self.passwd, self.sig_sk, self.current_block)
self.env.broadcast_block(signed_block, self.current_slot_uid)
self.current_block=None
def endorse_block(self):
if not self.am_current_endorder:
return
sig = sign_message(self.passwd, self.sig_sk, self.current_block)
self.env.endorse_block(sig, self.current_slot_uid)
def receive_block(self, signed_block, blk_uid):
self.log.highlight("receiving block")
def __get_blk(self, blk_uid):
cur_blk = None
assert(blk_uid>0)
stashed=True
@@ -159,9 +184,34 @@ class Stakeholder(object):
#TODO this assumes synced blockchain
cur_blk = self.blockchain[blk_uid]
stashed=False
return cur_blk, stashed
def receive_block(self, signed_block, endorser_sig, blk_uid):
self.log.highlight("receiving block")
cur_blk, stashed = self.__get_blk(blk_uid)
#TODO to consider deley should retrive leader_pk of corresponding blk_uid
if verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block):
if verify_signature(self.env.current_leader_sig_pk, cur_blk, signed_block) \
and verify_signature(self.env.current_endorser_sig_pk, cur_blk, endorser_sig):
if stashed:
self.current_epoch.add_block(cur_blk)
else:
self.env.corrupt(self.env.current_leader_id)
self.env.corrupt_blk()
def confirm_endorsing(self, signed_endorsed_block, blk_uid):
confirmed=False
if self.am_current_leader:
cur_blk, _ = self.__get_blk(blk_uid)
if verify_signature(self.env.current_endorser_sig_pk, cur_blk, signed_endorsed_block):
if blk_uid==self.current_slot_uid:
self.current_block.set_endorsed()
else:
self.blockchain[blk_uid].set_endorsed()
confirmed=True
#self.env.confirm_endorsing(signed_endorsed_block, blk_uid)
else:
confirmed=False
#self.env.confirm_endorsing(None, blk_uid)
self.log.info(f"<confirm_endorsing enderser signature: {self.current_blk_endorser_sig}")
return confirmed

View File

@@ -5,7 +5,29 @@ from ouroboros.utils import inverse_of
from ouroboros.utils import vrf_hash
eta.init(369)
'''
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)
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
class VRF(object):
'''
verifiable random function implementation
@@ -21,7 +43,6 @@ class VRF(object):
self.pk = pk
self.sk = sk
self.g=g
'''
short signature without random oracle
@@ -58,26 +79,3 @@ class VRF(object):
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)
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

View File

@@ -1,6 +1,6 @@
import time
from ouroboros import Stakeholder
from ouroboros import Z
import time
EPOCH_LENGTH = 2
stakeholders = []

View File

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