From 78f2f3be13a4116372575fae65492af8fd19e1c3 Mon Sep 17 00:00:00 2001 From: mohab Date: Sun, 30 Jan 2022 21:29:06 +0200 Subject: [PATCH] separate ouroboros from streamlet --- script/research/PoS-blockchain/__init__.py | 0 .../{streamlet => PoS-blockchain}/node.py | 19 ++--- .../PoS-blockchain/ouroboros/__init__.py | 1 + .../PoS-blockchain/ouroboros/utils.py | 40 ++++++++++ .../research/PoS-blockchain/ouroboros/vrf.py | 59 ++++++++++++++ script/research/PoS-blockchain/protocol.py | 48 ++++++++++++ .../2-execution-model-and-definitions.py | 0 .../streamlet/3.2-blocks-and-blockchain.py | 0 .../streamlet/3.3-votes-and-notarization.py | 0 .../streamlet/3.4-protocol.py | 24 ++---- .../PoS-blockchain/streamlet/__init__.py | 6 ++ .../{ => PoS-blockchain}/streamlet/block.py | 2 +- .../streamlet/blockchain.py | 0 .../{ => PoS-blockchain}/streamlet/clock.py | 0 .../{ => PoS-blockchain}/streamlet/logger.py | 0 .../research/PoS-blockchain/streamlet/node.py | 78 +++++++++++++++++++ .../{ => PoS-blockchain}/streamlet/utils.py | 0 .../{ => PoS-blockchain}/streamlet/vote.py | 0 .../{ => PoS-blockchain}/streamlet/vrf.py | 0 19 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 script/research/PoS-blockchain/__init__.py rename script/research/{streamlet => PoS-blockchain}/node.py (86%) create mode 100644 script/research/PoS-blockchain/ouroboros/__init__.py create mode 100644 script/research/PoS-blockchain/ouroboros/utils.py create mode 100644 script/research/PoS-blockchain/ouroboros/vrf.py create mode 100644 script/research/PoS-blockchain/protocol.py rename script/research/{ => PoS-blockchain}/streamlet/2-execution-model-and-definitions.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/3.2-blocks-and-blockchain.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/3.3-votes-and-notarization.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/3.4-protocol.py (84%) create mode 100644 script/research/PoS-blockchain/streamlet/__init__.py rename script/research/{ => PoS-blockchain}/streamlet/block.py (97%) rename script/research/{ => PoS-blockchain}/streamlet/blockchain.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/clock.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/logger.py (100%) create mode 100644 script/research/PoS-blockchain/streamlet/node.py rename script/research/{ => PoS-blockchain}/streamlet/utils.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/vote.py (100%) rename script/research/{ => PoS-blockchain}/streamlet/vrf.py (100%) diff --git a/script/research/PoS-blockchain/__init__.py b/script/research/PoS-blockchain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/script/research/streamlet/node.py b/script/research/PoS-blockchain/node.py similarity index 86% rename from script/research/streamlet/node.py rename to script/research/PoS-blockchain/node.py index 06c413294..106654496 100644 --- a/script/research/streamlet/node.py +++ b/script/research/PoS-blockchain/node.py @@ -1,9 +1,6 @@ -import copy, utils -from block import Block -from blockchain import Blockchain -from vote import Vote -from vrf import VRF -from logger import Logger +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. @@ -15,7 +12,7 @@ class Node: self.id = id self.clock = clock # Clock syncronization to be implemented. self.password = password - self.private_key, self.public_key = utils.generate_keys(self.password) + self.private_key, self.public_key = generate_keys(self.password) self.blockchain = Blockchain(init_block) self.unconfirmed_transactions = [] self.log = Logger(self) @@ -37,12 +34,12 @@ class Node: 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 = utils.sign_message(self.password, self.private_key, proposed_block) + 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 utils.verify_signature(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 #TODO alert that is insecure, e should be set by the ticing clock @@ -61,7 +58,7 @@ class Node: 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 = utils.sign_message(self.password, self.private_key, self.round_block) + 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) @@ -70,7 +67,7 @@ class Node: # 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(utils.verify_signature(node_public_key, vote.block, vote.vote)) + 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) diff --git a/script/research/PoS-blockchain/ouroboros/__init__.py b/script/research/PoS-blockchain/ouroboros/__init__.py new file mode 100644 index 000000000..1a5fc042b --- /dev/null +++ b/script/research/PoS-blockchain/ouroboros/__init__.py @@ -0,0 +1 @@ +from ouroboros.vrf import VRF diff --git a/script/research/PoS-blockchain/ouroboros/utils.py b/script/research/PoS-blockchain/ouroboros/utils.py new file mode 100644 index 000000000..09bf76e85 --- /dev/null +++ b/script/research/PoS-blockchain/ouroboros/utils.py @@ -0,0 +1,40 @@ +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 diff --git a/script/research/PoS-blockchain/ouroboros/vrf.py b/script/research/PoS-blockchain/ouroboros/vrf.py new file mode 100644 index 000000000..5485b980b --- /dev/null +++ b/script/research/PoS-blockchain/ouroboros/vrf.py @@ -0,0 +1,59 @@ +from streamlet.logger import Logger +import random as rnd +from tate_bilinear_pairing import eta, ecc +from ouroboros.utils import inverse_of +eta.init(369) + +class VRF(object): + ''' + verifiable random function implementation + ''' + 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 diff --git a/script/research/PoS-blockchain/protocol.py b/script/research/PoS-blockchain/protocol.py new file mode 100644 index 000000000..5134f743d --- /dev/null +++ b/script/research/PoS-blockchain/protocol.py @@ -0,0 +1,48 @@ +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()) diff --git a/script/research/streamlet/2-execution-model-and-definitions.py b/script/research/PoS-blockchain/streamlet/2-execution-model-and-definitions.py similarity index 100% rename from script/research/streamlet/2-execution-model-and-definitions.py rename to script/research/PoS-blockchain/streamlet/2-execution-model-and-definitions.py diff --git a/script/research/streamlet/3.2-blocks-and-blockchain.py b/script/research/PoS-blockchain/streamlet/3.2-blocks-and-blockchain.py similarity index 100% rename from script/research/streamlet/3.2-blocks-and-blockchain.py rename to script/research/PoS-blockchain/streamlet/3.2-blocks-and-blockchain.py diff --git a/script/research/streamlet/3.3-votes-and-notarization.py b/script/research/PoS-blockchain/streamlet/3.3-votes-and-notarization.py similarity index 100% rename from script/research/streamlet/3.3-votes-and-notarization.py rename to script/research/PoS-blockchain/streamlet/3.3-votes-and-notarization.py diff --git a/script/research/streamlet/3.4-protocol.py b/script/research/PoS-blockchain/streamlet/3.4-protocol.py similarity index 84% rename from script/research/streamlet/3.4-protocol.py rename to script/research/PoS-blockchain/streamlet/3.4-protocol.py index ec31ec847..9e2c3de5e 100644 --- a/script/research/streamlet/3.4-protocol.py +++ b/script/research/PoS-blockchain/streamlet/3.4-protocol.py @@ -2,9 +2,6 @@ from block import Block from node import Node -from vrf import VRF -import math -import numpy as np # Genesis block is generated. genesis_block = Block("⊥", 0, '⊥') @@ -34,17 +31,11 @@ node1.broadcast_transaction([node0, node2, node3, node4, node5], "tx2") node4.receive_transaction("tx3") node4.broadcast_transaction([node0, node1, node2, node3, node5], "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)] +leader = nodes[hash(str(epoch))%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) +leader.propose_block(1, nodes) # Nodes vote on the block and broadcast their vote to rest nodes. for node in nodes: @@ -70,17 +61,13 @@ node6.receive_transaction("tx6") node6.broadcast_transaction([node0, node1, node2, node3, node4, node5], "tx6") x = epoch -vrf = VRF() -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)] +leader = nodes[hash(str(epoch))%len(nodes)] # A random leader is selected. -print(f"epoch number in protocol: {epoch}") # Leader forms a block and broadcasts it. -leader.propose_block(epoch, y, pi, vrf.pk, g, nodes) +leader.propose_block(epoch, nodes) # Nodes vote on the block and broadcast their vote to rest nodes. for node in nodes: @@ -92,4 +79,3 @@ assert(node0.output() == node1.output() == node2.output() == node3.output() == n # 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) -print('finished...') \ No newline at end of file diff --git a/script/research/PoS-blockchain/streamlet/__init__.py b/script/research/PoS-blockchain/streamlet/__init__.py new file mode 100644 index 000000000..895ea8ad3 --- /dev/null +++ b/script/research/PoS-blockchain/streamlet/__init__.py @@ -0,0 +1,6 @@ +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 diff --git a/script/research/streamlet/block.py b/script/research/PoS-blockchain/streamlet/block.py similarity index 97% rename from script/research/streamlet/block.py rename to script/research/PoS-blockchain/streamlet/block.py index 3b79207f7..1f7a2a89f 100644 --- a/script/research/streamlet/block.py +++ b/script/research/PoS-blockchain/streamlet/block.py @@ -1,4 +1,4 @@ -class Block: +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): diff --git a/script/research/streamlet/blockchain.py b/script/research/PoS-blockchain/streamlet/blockchain.py similarity index 100% rename from script/research/streamlet/blockchain.py rename to script/research/PoS-blockchain/streamlet/blockchain.py diff --git a/script/research/streamlet/clock.py b/script/research/PoS-blockchain/streamlet/clock.py similarity index 100% rename from script/research/streamlet/clock.py rename to script/research/PoS-blockchain/streamlet/clock.py diff --git a/script/research/streamlet/logger.py b/script/research/PoS-blockchain/streamlet/logger.py similarity index 100% rename from script/research/streamlet/logger.py rename to script/research/PoS-blockchain/streamlet/logger.py diff --git a/script/research/PoS-blockchain/streamlet/node.py b/script/research/PoS-blockchain/streamlet/node.py new file mode 100644 index 000000000..bc29cc034 --- /dev/null +++ b/script/research/PoS-blockchain/streamlet/node.py @@ -0,0 +1,78 @@ +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) + + \ No newline at end of file diff --git a/script/research/streamlet/utils.py b/script/research/PoS-blockchain/streamlet/utils.py similarity index 100% rename from script/research/streamlet/utils.py rename to script/research/PoS-blockchain/streamlet/utils.py diff --git a/script/research/streamlet/vote.py b/script/research/PoS-blockchain/streamlet/vote.py similarity index 100% rename from script/research/streamlet/vote.py rename to script/research/PoS-blockchain/streamlet/vote.py diff --git a/script/research/streamlet/vrf.py b/script/research/PoS-blockchain/streamlet/vrf.py similarity index 100% rename from script/research/streamlet/vrf.py rename to script/research/PoS-blockchain/streamlet/vrf.py