|
@@ -18,7 +18,7 @@ class RelayDescriptor:
|
|
|
self.descdict = descdict
|
|
|
|
|
|
def __str__(self, withsig = True):
|
|
|
- res = "RelayDesc[\n"
|
|
|
+ res = "RelayDesc [\n"
|
|
|
for k in ["epoch", "idkey", "onionkey", "addr", "bw", "flags",
|
|
|
"vrfkey", "sig"]:
|
|
|
if k in self.descdict:
|
|
@@ -41,6 +41,52 @@ class RelayDescriptor:
|
|
|
serialized = self.__str__(False)
|
|
|
self.descdict["idkey"].verify(serialized.encode("ascii"), self.descdict["sig"])
|
|
|
|
|
|
+# A consensus is a dict containing:
|
|
|
+# epoch: epoch id
|
|
|
+# numrelays: total number of relays
|
|
|
+# totbw: total bandwidth of relays
|
|
|
+# relays: list of relay descriptors (Vanilla Onion Routing only)
|
|
|
+# sigs: list of signatures from the dirauths
|
|
|
+class Consensus:
|
|
|
+ def __init__(self, epoch, relays):
|
|
|
+ relays = [ d for d in relays if d.descdict['epoch'] == epoch ]
|
|
|
+ self.consdict = dict()
|
|
|
+ self.consdict['epoch'] = epoch
|
|
|
+ self.consdict['numrelays'] = len(relays)
|
|
|
+ self.consdict['totbw'] = sum([ d.descdict['bw'] for d in relays ])
|
|
|
+ self.consdict['relays'] = relays
|
|
|
+
|
|
|
+ def __str__(self, withsigs = True):
|
|
|
+ res = "Consensus [\n"
|
|
|
+ for k in ["epoch", "numrelays", "totbw"]:
|
|
|
+ if k in self.consdict:
|
|
|
+ res += " " + k + ": " + str(self.consdict[k]) + "\n"
|
|
|
+ for r in self.consdict['relays']:
|
|
|
+ res += str(r)
|
|
|
+ if withsigs and ('sigs' in self.consdict):
|
|
|
+ for s in self.consdict['sigs']:
|
|
|
+ res += " sig: " + nacl.encoding.HexEncoder.encode(s).decode("ascii") + "\n"
|
|
|
+ res += "]\n"
|
|
|
+ return res
|
|
|
+
|
|
|
+ def sign(self, signingkey, index):
|
|
|
+ """Use the given signing key to sign the consensus, storing the
|
|
|
+ result in the sigs list at the given index."""
|
|
|
+ serialized = self.__str__(False)
|
|
|
+ signed = signingkey.sign(serialized.encode("ascii"))
|
|
|
+ if 'sigs' not in self.consdict:
|
|
|
+ self.consdict['sigs'] = []
|
|
|
+ if index >= len(self.consdict['sigs']):
|
|
|
+ self.consdict['sigs'].extend([None] * (index+1-len(self.consdict['sigs'])))
|
|
|
+ self.consdict['sigs'][index] = signed.signature
|
|
|
+
|
|
|
+ def verify(self, verifkeylist):
|
|
|
+ """Use the given list of verification keys to check the
|
|
|
+ signatures on the consensus."""
|
|
|
+ serialized = self.__str__(False)
|
|
|
+ for i, vk in enumerate(verifkeylist):
|
|
|
+ vk.verify(serialized.encode("ascii"), self.consdict['sigs'][i])
|
|
|
+
|
|
|
class DirAuthNetMsg(network.NetMsg):
|
|
|
"""The subclass of NetMsg for messages to and from directory
|
|
|
authorities."""
|
|
@@ -105,9 +151,12 @@ class DirAuth(network.Server):
|
|
|
# We simulate the act of computing the consensus by keeping a
|
|
|
# class-static dict that's accessible to all of the dirauths
|
|
|
# This dict is indexed by epoch, and the value is itself a dict
|
|
|
- # indexed by the stringified descriptor, with value of the number of
|
|
|
- # dirauths that saw that descriptor.
|
|
|
+ # indexed by the stringified descriptor, with value a pair of (the
|
|
|
+ # number of dirauths that saw that descriptor, the descriptor
|
|
|
+ # itself).
|
|
|
uploadeddescs = dict()
|
|
|
+ consensus = None
|
|
|
+ endive = None
|
|
|
|
|
|
def __init__(self, me, tot):
|
|
|
"""Create a new directory authority. me is the index of which
|
|
@@ -116,9 +165,12 @@ class DirAuth(network.Server):
|
|
|
self.me = me
|
|
|
self.tot = tot
|
|
|
self.name = "Dirauth %d of %d" % (me+1, tot)
|
|
|
- self.consensus = None
|
|
|
- self.endive = None
|
|
|
- network.thenetwork.wantepochticks(self, True)
|
|
|
+
|
|
|
+ # Create the dirauth signature keypair
|
|
|
+ self.sigkey = nacl.signing.SigningKey.generate()
|
|
|
+
|
|
|
+ network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
|
|
|
+ network.thenetwork.wantepochticks(self, True, True)
|
|
|
|
|
|
def connected(self, client):
|
|
|
"""Callback invoked when a client connects to us. This callback
|
|
@@ -130,8 +182,29 @@ class DirAuth(network.Server):
|
|
|
# particularly simple.
|
|
|
return DirAuthConnection(self)
|
|
|
|
|
|
- def newepoch(self, epoch):
|
|
|
- print('New epoch', epoch, 'for', self)
|
|
|
+ def generate_consensus(self, epoch):
|
|
|
+ """Generate the consensus (and ENDIVE, if using Walking Onions)
|
|
|
+ for the given epoch, which should be the one after the one
|
|
|
+ that's currently about to end."""
|
|
|
+ threshold = int(self.tot/2)+1
|
|
|
+ consensusdescs = []
|
|
|
+ for numseen, desc in DirAuth.uploadeddescs[epoch].values():
|
|
|
+ if numseen >= threshold:
|
|
|
+ consensusdescs.append(desc)
|
|
|
+ DirAuth.consensus = Consensus(epoch, consensusdescs)
|
|
|
+
|
|
|
+
|
|
|
+ def epoch_ending(self, epoch):
|
|
|
+ # Only dirauth 0 actually needs to generate the consensus
|
|
|
+ # because of the shared class-static state, but everyone has to
|
|
|
+ # sign it. Note that this code relies on dirauth 0's
|
|
|
+ # epoch_ending callback being called before any of the other
|
|
|
+ # dirauths'.
|
|
|
+ if self.me == 0:
|
|
|
+ self.generate_consensus(epoch+1)
|
|
|
+ del DirAuth.uploadeddescs[epoch+1]
|
|
|
+ DirAuth.consensus.sign(self.sigkey, self.me)
|
|
|
+ print(DirAuth.consensus)
|
|
|
|
|
|
def received(self, client, msg):
|
|
|
if isinstance(msg, DirAuthUploadDescMsg):
|
|
@@ -144,13 +217,15 @@ class DirAuth(network.Server):
|
|
|
DirAuth.uploadeddescs[epoch] = dict()
|
|
|
descstr = str(msg.desc)
|
|
|
if descstr not in DirAuth.uploadeddescs[epoch]:
|
|
|
- DirAuth.uploadeddescs[epoch][descstr] = 1
|
|
|
+ DirAuth.uploadeddescs[epoch][descstr] = (1, msg.desc)
|
|
|
else:
|
|
|
- DirAuth.uploadeddescs[epoch][descstr] += 1
|
|
|
+ DirAuth.uploadeddescs[epoch][descstr] = \
|
|
|
+ (DirAuth.uploadeddescs[epoch][descstr][0]+1,
|
|
|
+ DirAuth.uploadeddescs[epoch][descstr][1])
|
|
|
elif isinstance(msg, DirAuthGetConsensusMsg):
|
|
|
- client.sendmsg(DirAuthConsensusMsg(self.consensus))
|
|
|
+ client.sendmsg(DirAuthConsensusMsg(DirAuth.consensus))
|
|
|
elif isinstance(msg, DirAuthGetENDIVEMsg):
|
|
|
- client.sendmsg(DirAuthENDIVEMsg(self.endive))
|
|
|
+ client.sendmsg(DirAuthENDIVEMsg(DirAuth.endive))
|
|
|
else:
|
|
|
raise TypeError('Not a client-originating DirAuthNetMsg', msg)
|
|
|
|