2023-05-18 16:29:28 +00:00
|
|
|
# typing imports
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from random import randint
|
|
|
|
from typing import TypeAlias
|
|
|
|
|
|
|
|
# carnot imports
|
|
|
|
# lib imports
|
2023-05-25 08:26:35 +00:00
|
|
|
from blspy import PrivateKey, Util, BasicSchemeMPL, G2Element, G1Element
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
# stdlib imports
|
|
|
|
from hashlib import sha256
|
|
|
|
|
|
|
|
View: TypeAlias = int
|
2023-05-25 08:26:35 +00:00
|
|
|
Sig: TypeAlias = bytes
|
|
|
|
Entropy: TypeAlias = bytes
|
|
|
|
PublicKey: TypeAlias = G1Element
|
|
|
|
VERSION = 0
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
def generate_random_sk() -> PrivateKey:
|
|
|
|
seed = bytes([randint(0, 255) for _ in range(32)])
|
2023-05-25 08:26:35 +00:00
|
|
|
return BasicSchemeMPL.key_gen(seed)
|
|
|
|
|
2023-05-18 16:29:28 +00:00
|
|
|
|
2023-05-25 08:26:35 +00:00
|
|
|
def view_to_bytes(view: View) -> bytes:
|
|
|
|
return view.to_bytes((view.bit_length() + 7) // 8, byteorder='little', signed=True)
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class RandomBeacon:
|
|
|
|
version: int
|
2023-05-25 08:26:35 +00:00
|
|
|
sig: Sig
|
|
|
|
|
|
|
|
def entropy(self) -> Entropy:
|
|
|
|
return self.sig
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NormalMode:
|
|
|
|
|
|
|
|
@staticmethod
|
2023-05-25 08:26:35 +00:00
|
|
|
def verify(beacon: RandomBeacon, pk: PublicKey, view: View) -> bool:
|
2023-05-18 16:29:28 +00:00
|
|
|
"""
|
2023-05-25 08:26:35 +00:00
|
|
|
:param beacon: the provided beacon
|
|
|
|
:param view: view to verify beacon upon
|
|
|
|
:param pk: public key of the issuer of the beacon
|
2023-05-18 16:29:28 +00:00
|
|
|
:return:
|
|
|
|
"""
|
2023-05-25 08:26:35 +00:00
|
|
|
sig = G2Element.from_bytes(beacon.sig)
|
|
|
|
return BasicSchemeMPL.verify(pk, view_to_bytes(view), sig)
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2023-05-25 08:26:35 +00:00
|
|
|
def generate_beacon(private_key: PrivateKey, view: View) -> RandomBeacon:
|
|
|
|
return RandomBeacon(VERSION, bytes(BasicSchemeMPL.sign(private_key, view_to_bytes(view))))
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RecoveryMode:
|
|
|
|
|
|
|
|
@staticmethod
|
2023-05-25 08:26:35 +00:00
|
|
|
def verify(last_beacon: RandomBeacon, beacon: RandomBeacon, view: View) -> bool:
|
2023-05-18 16:29:28 +00:00
|
|
|
"""
|
2023-05-25 08:26:35 +00:00
|
|
|
:param last_beacon: beacon for view - 1
|
|
|
|
:param beacon: beacon for view
|
|
|
|
:param view: the view to verify beacon upon
|
2023-05-18 16:29:28 +00:00
|
|
|
:return:
|
|
|
|
"""
|
2023-05-25 08:26:35 +00:00
|
|
|
b = sha256(last_beacon.entropy() + view_to_bytes(view)).digest()
|
|
|
|
return b == beacon.entropy()
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2023-05-25 08:26:35 +00:00
|
|
|
def generate_beacon(last_beacon_entropy: Entropy, view: View) -> RandomBeacon:
|
|
|
|
return RandomBeacon(VERSION, sha256(last_beacon_entropy + view_to_bytes(view)).digest())
|
2023-05-18 16:29:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RandomBeaconHandler:
|
|
|
|
def __init__(self, beacon: RandomBeacon):
|
|
|
|
"""
|
|
|
|
:param beacon: Beacon should be initialized with either the last known working beacon from recovery.
|
|
|
|
Or the hash of the genesis block in case of first consensus round.
|
|
|
|
:return: Self
|
|
|
|
"""
|
|
|
|
self.last_beacon: RandomBeacon = beacon
|
|
|
|
|
2023-05-25 08:26:35 +00:00
|
|
|
def verify_happy(self, new_beacon: RandomBeacon, pk: PublicKey, view: View) -> bool:
|
|
|
|
if NormalMode.verify(new_beacon, pk, view):
|
2023-05-18 16:29:28 +00:00
|
|
|
self.last_beacon = new_beacon
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2023-05-25 08:26:35 +00:00
|
|
|
def verify_unhappy(self, new_beacon: RandomBeacon, view: View) -> bool:
|
|
|
|
if RecoveryMode.verify(self.last_beacon, new_beacon, view):
|
2023-05-18 16:29:28 +00:00
|
|
|
self.last_beacon = new_beacon
|
|
|
|
return True
|
|
|
|
return False
|