[research/ourobors] fixed bugs in vrf, blockchain

This commit is contained in:
mohab
2022-02-11 19:22:03 +02:00
parent 4545cf2a1c
commit 4eee40e873
15 changed files with 166 additions and 87 deletions

View File

@@ -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 *

View File

@@ -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):

View File

@@ -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
'''

View File

@@ -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)

View File

@@ -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

View File

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

View File

@@ -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))

View File

@@ -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

View File

@@ -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()

View File

@@ -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:

View File

@@ -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}")

View File

@@ -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:

View File

@@ -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'