dirauth.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env python3
  2. import nacl.encoding
  3. import nacl.signing
  4. import network
  5. # A relay descriptor is a dict containing:
  6. # epoch: epoch id
  7. # idkey: a public identity key
  8. # onionkey: a public onion key
  9. # addr: a network address
  10. # bw: bandwidth
  11. # flags: relay flags
  12. # vrfkey: a VRF public key (Single-Pass Walking Onions only)
  13. # sig: a signature over the above by the idkey
  14. class RelayDescriptor:
  15. def __init__(self, descdict):
  16. self.descdict = descdict
  17. def __str__(self, withsig = True):
  18. res = "RelayDesc[\n"
  19. for k in ["epoch", "idkey", "onionkey", "addr", "bw", "flags",
  20. "vrfkey", "sig"]:
  21. if k in self.descdict:
  22. if k == "idkey" or k == "onionkey":
  23. res += " " + k + ": " + self.descdict[k].encode(encoder=nacl.encoding.HexEncoder).decode("ascii") + "\n"
  24. elif k == "sig":
  25. if withsig:
  26. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.descdict[k]).decode("ascii") + "\n"
  27. else:
  28. res += " " + k + ": " + str(self.descdict[k]) + "\n"
  29. res += "]\n"
  30. return res
  31. def sign(self, signingkey):
  32. serialized = self.__str__(False)
  33. signed = signingkey.sign(serialized.encode("ascii"))
  34. self.descdict["sig"] = signed.signature
  35. def verify(self):
  36. serialized = self.__str__(False)
  37. self.descdict["idkey"].verify(serialized.encode("ascii"), self.descdict["sig"])
  38. class DirAuthNetMsg(network.NetMsg):
  39. """The subclass of NetMsg for messages to and from directory
  40. authorities."""
  41. class DirAuthUploadDescMsg(DirAuthNetMsg):
  42. """The subclass of DirAuthNetMsg for uploading a relay
  43. descriptor."""
  44. def __init__(self, desc):
  45. self.desc = desc
  46. class DirAuthGetConsensusMsg(DirAuthNetMsg):
  47. """The subclass of DirAuthNetMsg for fetching the consensus."""
  48. class DirAuthConsensusMsg(DirAuthNetMsg):
  49. """The subclass of DirAuthNetMsg for returning the consensus."""
  50. def __init__(self, consensus):
  51. self.consensus = consensus
  52. class DirAuthGetENDIVEMsg(DirAuthNetMsg):
  53. """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
  54. class DirAuthENDIVEMsg(DirAuthNetMsg):
  55. """The subclass of DirAuthNetMsg for returning the ENDIVE."""
  56. def __init__(self, endive):
  57. self.endive = endive
  58. class DirAuthConnection(network.ClientConnection):
  59. """The subclass of Connection for connections to directory
  60. authorities."""
  61. def __init__(self, peer = None):
  62. super().__init__(peer)
  63. def uploaddesc(self, desc):
  64. """Upload our RelayDescriptor to the DirAuth."""
  65. self.sendmsg(DirAuthUploadDescMeg(desc))
  66. def getconsensus(self):
  67. self.consensus = None
  68. self.sendmsg(DirAuthGetConsensusMsg())
  69. return self.consensus
  70. def getENDIVE(self):
  71. self.endive = None
  72. self.sendmsg(DirAuthGetENDIVEMsg())
  73. return self.endive
  74. def receivedfromserver(self, msg):
  75. if isinstance(msg, DirAuthConsensusMsg):
  76. self.consensus = msg.consensus
  77. elif isinstance(msg, DirAuthENDIVEMsg):
  78. self.endive = msg.endive
  79. else:
  80. raise TypeError('Not a server-originating DirAuthNetMsg', msg)
  81. class DirAuth(network.Server):
  82. """The class representing directory authorities."""
  83. # We simulate the act of computing the consensus by keeping a
  84. # class-static dict that's accessible to all of the dirauths
  85. # This dict is indexed by epoch, and the value is itself a dict
  86. # indexed by the stringified descriptor, with value of the number of
  87. # dirauths that saw that descriptor.
  88. uploadeddescs = dict()
  89. def __init__(self, me, tot):
  90. """Create a new directory authority. me is the index of which
  91. dirauth this one is (starting from 0), and tot is the total
  92. number of dirauths."""
  93. self.me = me
  94. self.tot = tot
  95. self.name = "Dirauth %d of %d" % (me+1, tot)
  96. self.consensus = None
  97. self.endive = None
  98. network.thenetwork.wantepochticks(self, True)
  99. def connected(self, client):
  100. """Callback invoked when a client connects to us. This callback
  101. creates the DirAuthConnection that will be passed to the
  102. client."""
  103. # We don't actually need to keep per-connection state at
  104. # dirauths, even in long-lived connections, so this is
  105. # particularly simple.
  106. return DirAuthConnection(self)
  107. def newepoch(self, epoch):
  108. print('New epoch', epoch, 'for', self)
  109. def received(self, client, msg):
  110. if isinstance(msg, DirAuthUploadDescMsg):
  111. # Check the uploaded descriptor for sanity
  112. epoch = msg.desc.descdict['epoch']
  113. if epoch != network.thenetwork.getepoch() + 1:
  114. return
  115. # Store it in the class-static dict
  116. if epoch not in DirAuth.uploadeddescs:
  117. DirAuth.uploadeddescs[epoch] = dict()
  118. descstr = str(msg.desc)
  119. if descstr not in DirAuth.uploadeddescs[epoch]:
  120. DirAuth.uploadeddescs[epoch][descstr] = 1
  121. else:
  122. DirAuth.uploadeddescs[epoch][descstr] += 1
  123. elif isinstance(msg, DirAuthGetConsensusMsg):
  124. client.sendmsg(DirAuthConsensusMsg(self.consensus))
  125. elif isinstance(msg, DirAuthGetENDIVEMsg):
  126. client.sendmsg(DirAuthENDIVEMsg(self.endive))
  127. else:
  128. raise TypeError('Not a client-originating DirAuthNetMsg', msg)
  129. def closed(self):
  130. pass
  131. if __name__ == '__main__':
  132. # Start some dirauths
  133. numdirauths = 9
  134. dirauthaddrs = []
  135. for i in range(numdirauths):
  136. dirauth = DirAuth(i, numdirauths)
  137. dirauthaddrs.append(network.thenetwork.bind(dirauth))
  138. for a in dirauthaddrs:
  139. print(a,end=' ')
  140. print()
  141. network.thenetwork.nextepoch()