123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- #!/usr/bin/env python3
- import nacl.encoding
- import nacl.signing
- import network
- # A relay descriptor is a dict containing:
- # epoch: epoch id
- # idkey: a public identity key
- # onionkey: a public onion key
- # addr: a network address
- # bw: bandwidth
- # flags: relay flags
- # vrfkey: a VRF public key (Single-Pass Walking Onions only)
- # sig: a signature over the above by the idkey
- class RelayDescriptor:
- def __init__(self, descdict):
- self.descdict = descdict
- def __str__(self, withsig = True):
- res = "RelayDesc [\n"
- for k in ["epoch", "idkey", "onionkey", "addr", "bw", "flags",
- "vrfkey", "sig"]:
- if k in self.descdict:
- if k == "idkey" or k == "onionkey":
- res += " " + k + ": " + self.descdict[k].encode(encoder=nacl.encoding.HexEncoder).decode("ascii") + "\n"
- elif k == "sig":
- if withsig:
- res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.descdict[k]).decode("ascii") + "\n"
- else:
- res += " " + k + ": " + str(self.descdict[k]) + "\n"
- res += "]\n"
- return res
- def sign(self, signingkey):
- serialized = self.__str__(False)
- signed = signingkey.sign(serialized.encode("ascii"))
- self.descdict["sig"] = signed.signature
- def verify(self):
- 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."""
- class DirAuthUploadDescMsg(DirAuthNetMsg):
- """The subclass of DirAuthNetMsg for uploading a relay
- descriptor."""
- def __init__(self, desc):
- self.desc = desc
- class DirAuthGetConsensusMsg(DirAuthNetMsg):
- """The subclass of DirAuthNetMsg for fetching the consensus."""
- class DirAuthConsensusMsg(DirAuthNetMsg):
- """The subclass of DirAuthNetMsg for returning the consensus."""
- def __init__(self, consensus):
- self.consensus = consensus
- class DirAuthGetENDIVEMsg(DirAuthNetMsg):
- """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
- class DirAuthENDIVEMsg(DirAuthNetMsg):
- """The subclass of DirAuthNetMsg for returning the ENDIVE."""
- def __init__(self, endive):
- self.endive = endive
- class DirAuthConnection(network.ClientConnection):
- """The subclass of Connection for connections to directory
- authorities."""
- def __init__(self, peer = None):
- super().__init__(peer)
- def uploaddesc(self, desc):
- """Upload our RelayDescriptor to the DirAuth."""
- self.sendmsg(DirAuthUploadDescMeg(desc))
- def getconsensus(self):
- self.consensus = None
- self.sendmsg(DirAuthGetConsensusMsg())
- return self.consensus
- def getENDIVE(self):
- self.endive = None
- self.sendmsg(DirAuthGetENDIVEMsg())
- return self.endive
- def receivedfromserver(self, msg):
- if isinstance(msg, DirAuthConsensusMsg):
- self.consensus = msg.consensus
- elif isinstance(msg, DirAuthENDIVEMsg):
- self.endive = msg.endive
- else:
- raise TypeError('Not a server-originating DirAuthNetMsg', msg)
-
- class DirAuth(network.Server):
- """The class representing directory authorities."""
- # 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 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
- dirauth this one is (starting from 0), and tot is the total
- number of dirauths."""
- self.me = me
- self.tot = tot
- self.name = "Dirauth %d of %d" % (me+1, tot)
- # 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
- creates the DirAuthConnection that will be passed to the
- client."""
- # We don't actually need to keep per-connection state at
- # dirauths, even in long-lived connections, so this is
- # particularly simple.
- return DirAuthConnection(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)
- def received(self, client, msg):
- if isinstance(msg, DirAuthUploadDescMsg):
- # Check the uploaded descriptor for sanity
- epoch = msg.desc.descdict['epoch']
- if epoch != network.thenetwork.getepoch() + 1:
- return
- # Store it in the class-static dict
- if epoch not in DirAuth.uploadeddescs:
- DirAuth.uploadeddescs[epoch] = dict()
- descstr = str(msg.desc)
- if descstr not in DirAuth.uploadeddescs[epoch]:
- DirAuth.uploadeddescs[epoch][descstr] = (1, msg.desc)
- else:
- DirAuth.uploadeddescs[epoch][descstr] = \
- (DirAuth.uploadeddescs[epoch][descstr][0]+1,
- DirAuth.uploadeddescs[epoch][descstr][1])
- elif isinstance(msg, DirAuthGetConsensusMsg):
- client.reply(DirAuthConsensusMsg(DirAuth.consensus))
- elif isinstance(msg, DirAuthGetENDIVEMsg):
- client.reply(DirAuthENDIVEMsg(DirAuth.endive))
- else:
- raise TypeError('Not a client-originating DirAuthNetMsg', msg)
- def closed(self):
- pass
- if __name__ == '__main__':
- # Start some dirauths
- numdirauths = 9
- dirauthaddrs = []
- for i in range(numdirauths):
- dirauth = DirAuth(i, numdirauths)
- dirauthaddrs.append(network.thenetwork.bind(dirauth))
- for a in dirauthaddrs:
- print(a,end=' ')
- print()
- network.thenetwork.nextepoch()
|