dirauth.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. #!/usr/bin/env python3
  2. import random # For simulation, not cryptography!
  3. import bisect
  4. import math
  5. import logging
  6. import nacl.encoding
  7. import nacl.signing
  8. import merklelib
  9. import hashlib
  10. import network
  11. # A relay descriptor is a dict containing:
  12. # epoch: epoch id
  13. # idkey: a public identity key
  14. # onionkey: a public onion key
  15. # addr: a network address
  16. # bw: bandwidth
  17. # flags: relay flags
  18. # pathselkey: a path selection public key (Single-Pass Walking Onions only)
  19. # vrfkey: a VRF public key (Single-Pass Walking Onions only)
  20. # sig: a signature over the above by the idkey
  21. class RelayDescriptor:
  22. def __init__(self, descdict):
  23. self.descdict = descdict
  24. def __str__(self, withsig = True):
  25. res = "RelayDesc [\n"
  26. for k in ["epoch", "idkey", "onionkey", "pathselkey", "addr",
  27. "bw", "flags", "vrfkey", "sig"]:
  28. if k in self.descdict:
  29. if k == "idkey" or k == "onionkey" or k == "pathselkey":
  30. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.descdict[k]).decode("ascii") + "\n"
  31. elif k == "sig":
  32. if withsig:
  33. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.descdict[k]).decode("ascii") + "\n"
  34. else:
  35. res += " " + k + ": " + str(self.descdict[k]) + "\n"
  36. res += "]\n"
  37. return res
  38. def sign(self, signingkey, perfstats):
  39. serialized = self.__str__(False)
  40. signed = signingkey.sign(serialized.encode("ascii"))
  41. perfstats.sigs += 1
  42. self.descdict["sig"] = signed.signature
  43. @staticmethod
  44. def verify(desc, perfstats):
  45. assert(type(desc) is RelayDescriptor)
  46. serialized = desc.__str__(False)
  47. perfstats.verifs += 1
  48. idkey = nacl.signing.VerifyKey(desc.descdict["idkey"])
  49. idkey.verify(serialized.encode("ascii"), desc.descdict["sig"])
  50. # A SNIP is a dict containing:
  51. # epoch: epoch id
  52. # idkey: a public identity key
  53. # onionkey: a public onion key
  54. # addr: a network address
  55. # flags: relay flags
  56. # pathselkey: a path selection public key (Single-Pass Walking Onions only)
  57. # range: the (lo,hi) values for the index range (lo is inclusive, hi is
  58. # exclusive; that is, x is in the range if lo <= x < hi).
  59. # lo=hi denotes an empty range.
  60. # auth: either a signature from the authorities over the above
  61. # (Threshold signature case) or a Merkle path to the root
  62. # contained in the consensus (Merkle tree case)
  63. #
  64. # Note that the fields of the SNIP are the same as those of the
  65. # RelayDescriptor, except bw and sig are removed, and range and auth are
  66. # added.
  67. class SNIP:
  68. def __init__(self, snipdict):
  69. self.snipdict = snipdict
  70. def __str__(self, withauth = True):
  71. res = "SNIP [\n"
  72. for k in ["epoch", "idkey", "onionkey", "pathselkey", "addr",
  73. "flags", "range", "auth"]:
  74. if k in self.snipdict:
  75. if k == "idkey" or k == "onionkey" or k == "pathselkey":
  76. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.snipdict[k]).decode("ascii") + "\n"
  77. elif k == "auth":
  78. if withauth:
  79. if network.thenetwork.snipauthmode == \
  80. network.SNIPAuthMode.THRESHSIG:
  81. res += " " + k + ": " + nacl.encoding.HexEncoder.encode(self.snipdict[k]).decode("ascii") + "\n"
  82. else:
  83. res += " " + k + ": " + str(self.snipdict[k])
  84. else:
  85. res += " " + k + ": " + str(self.snipdict[k]) + "\n"
  86. res += "]\n"
  87. return res
  88. def auth(self, signingkey, perfstats):
  89. if network.thenetwork.snipauthmode == network.SNIPAuthMode.THRESHSIG:
  90. serialized = self.__str__(False)
  91. signed = signingkey.sign(serialized.encode("ascii"))
  92. perfstats.sigs += 1
  93. self.snipdict["auth"] = signed.signature
  94. else:
  95. raise ValueError("Merkle auth not valid for SNIP.auth")
  96. @staticmethod
  97. def verify(snip, consensus, verifykey, perfstats):
  98. if network.thenetwork.snipauthmode == network.SNIPAuthMode.THRESHSIG:
  99. assert(type(snip) is SNIP and type(consensus) is Consensus)
  100. assert(consensus.consdict["epoch"] == snip.snipdict["epoch"])
  101. serialized = snip.__str__(False)
  102. perfstats.verifs += 1
  103. verifykey.verify(serialized.encode("ascii"),
  104. snip.snipdict["auth"])
  105. else:
  106. assert(merklelib.verify_leaf_inclusion(
  107. snip.__str__(False),
  108. [merklelib.AuditNode(p[0], p[1])
  109. for p in snip.snipdict["auth"]],
  110. merklelib.Hasher(), consensus.consdict["merkleroot"]))
  111. # A consensus is a dict containing:
  112. # epoch: epoch id
  113. # numrelays: total number of relays
  114. # totbw: total bandwidth of relays
  115. # merkleroot: the root of the SNIP Merkle tree (Merkle tree auth only)
  116. # relays: list of relay descriptors (Vanilla Onion Routing only)
  117. # sigs: list of signatures from the dirauths
  118. class Consensus:
  119. def __init__(self, epoch, relays):
  120. relays = [ d for d in relays if d.descdict['epoch'] == epoch ]
  121. self.consdict = dict()
  122. self.consdict['epoch'] = epoch
  123. self.consdict['numrelays'] = len(relays)
  124. if network.thenetwork.womode == network.WOMode.VANILLA:
  125. self.consdict['totbw'] = sum([ d.descdict['bw'] for d in relays ])
  126. self.consdict['relays'] = relays
  127. else:
  128. self.consdict['totbw'] = 1<<32
  129. def __str__(self, withsigs = True):
  130. res = "Consensus [\n"
  131. for k in ["epoch", "numrelays", "totbw"]:
  132. if k in self.consdict:
  133. res += " " + k + ": " + str(self.consdict[k]) + "\n"
  134. if network.thenetwork.womode == network.WOMode.VANILLA:
  135. for r in self.consdict['relays']:
  136. res += str(r)
  137. if network.thenetwork.snipauthmode == network.SNIPAuthMode.MERKLE:
  138. for k in ["merkleroot"]:
  139. if k in self.consdict:
  140. res += " " + k + ": " + str(self.consdict[k]) + "\n"
  141. if withsigs and ('sigs' in self.consdict):
  142. for s in self.consdict['sigs']:
  143. res += " sig: " + nacl.encoding.HexEncoder.encode(s).decode("ascii") + "\n"
  144. res += "]\n"
  145. return res
  146. def sign(self, signingkey, index, perfstats):
  147. """Use the given signing key to sign the consensus, storing the
  148. result in the sigs list at the given index."""
  149. serialized = self.__str__(False)
  150. signed = signingkey.sign(serialized.encode("ascii"))
  151. perfstats.sigs += 1
  152. if 'sigs' not in self.consdict:
  153. self.consdict['sigs'] = []
  154. if index >= len(self.consdict['sigs']):
  155. self.consdict['sigs'].extend([None] * (index+1-len(self.consdict['sigs'])))
  156. self.consdict['sigs'][index] = signed.signature
  157. @staticmethod
  158. def verify(consensus, verifkeylist, perfstats):
  159. """Use the given list of verification keys to check the
  160. signatures on the consensus. Return the RelayPicker if
  161. successful, or raise an exception otherwise."""
  162. assert(type(consensus) is Consensus)
  163. serialized = consensus.__str__(False)
  164. for i, vk in enumerate(verifkeylist):
  165. perfstats.verifs += 1
  166. vk.verify(serialized.encode("ascii"), consensus.consdict['sigs'][i])
  167. # If we got this far, all is well. Return the RelayPicker.
  168. return RelayPicker.get(consensus)
  169. # An ENDIVE is a dict containing:
  170. # epoch: epoch id
  171. # snips: list of SNIPS (in THRESHSIG mode, these include the auth
  172. # signatures; in MERKLE mode, these do _not_ include auth)
  173. # sigs: list of signatures from the dirauths
  174. class ENDIVE:
  175. def __init__(self, epoch, snips):
  176. snips = [ s for s in snips if s.snipdict['epoch'] == epoch ]
  177. self.enddict = dict()
  178. self.enddict['epoch'] = epoch
  179. self.enddict['snips'] = snips
  180. def __str__(self, withsigs = True):
  181. res = "ENDIVE [\n"
  182. for k in ["epoch"]:
  183. if k in self.enddict:
  184. res += " " + k + ": " + str(self.enddict[k]) + "\n"
  185. for s in self.enddict['snips']:
  186. res += str(s)
  187. if withsigs and ('sigs' in self.enddict):
  188. for s in self.enddict['sigs']:
  189. res += " sig: " + nacl.encoding.HexEncoder.encode(s).decode("ascii") + "\n"
  190. res += "]\n"
  191. return res
  192. def sign(self, signingkey, index, perfstats):
  193. """Use the given signing key to sign the ENDIVE, storing the
  194. result in the sigs list at the given index."""
  195. serialized = self.__str__(False)
  196. signed = signingkey.sign(serialized.encode("ascii"))
  197. perfstats.sigs += 1
  198. if 'sigs' not in self.enddict:
  199. self.enddict['sigs'] = []
  200. if index >= len(self.enddict['sigs']):
  201. self.enddict['sigs'].extend([None] * (index+1-len(self.enddict['sigs'])))
  202. self.enddict['sigs'][index] = signed.signature
  203. @staticmethod
  204. def verify(endive, consensus, verifkeylist, perfstats):
  205. """Use the given list of verification keys to check the
  206. signatures on the ENDIVE and consensus. Return the RelayPicker
  207. if successful, or raise an exception otherwise."""
  208. assert(type(endive) is ENDIVE and type(consensus) is Consensus)
  209. serializedcons = consensus.__str__(False)
  210. for i, vk in enumerate(verifkeylist):
  211. perfstats.verifs += 1
  212. vk.verify(serializedcons.encode("ascii"), consensus.consdict['sigs'][i])
  213. serializedend = endive.__str__(False)
  214. for i, vk in enumerate(verifkeylist):
  215. perfstats.verifs += 1
  216. vk.verify(serializedend.encode("ascii"), endive.enddict['sigs'][i])
  217. # If we got this far, all is well. Return the RelayPicker.
  218. return RelayPicker.get(consensus, endive)
  219. class RelayPicker:
  220. """An instance of this class (which may be a singleton in the
  221. simulation) is returned by the Consensus.verify() and
  222. ENDIVE.verify() methods. It does any necessary precomputation
  223. and/or caching, and exposes a method to select a random bw-weighted
  224. relay, either explicitly specifying a uniform random value, or
  225. letting the choice be done internally."""
  226. # The singleton instance
  227. relaypicker = None
  228. def __init__(self, consensus, endive = None):
  229. self.epoch = consensus.consdict["epoch"]
  230. self.totbw = consensus.consdict["totbw"]
  231. self.consensus = consensus
  232. self.endive = endive
  233. assert(endive is None or endive.enddict["epoch"] == self.epoch)
  234. if network.thenetwork.womode == network.WOMode.VANILLA:
  235. # Create the array of cumulative bandwidth values from a
  236. # consensus. The array (cdf) will have the same length as
  237. # the number of relays in the consensus. cdf[0] = 0, and
  238. # cdf[i] = cdf[i-1] + relay[i-1].bw.
  239. self.cdf = [0]
  240. for r in consensus.consdict['relays']:
  241. self.cdf.append(self.cdf[-1]+r.descdict['bw'])
  242. # Remove the last item, which should be the sum of all the bws
  243. self.cdf.pop()
  244. logging.debug('cdf=%s', self.cdf)
  245. else:
  246. # Note that clients will call this with endive = None
  247. if self.endive is not None:
  248. self.cdf = [ s.snipdict['range'][0] \
  249. for s in self.endive.enddict['snips'] ]
  250. if network.thenetwork.snipauthmode == \
  251. network.SNIPAuthMode.MERKLE:
  252. # Construct the Merkle tree of SNIPs and check the
  253. # root matches the one in the consensus
  254. self.merkletree = merklelib.MerkleTree(
  255. [snip.__str__(False) \
  256. for snip in DirAuth.endive.enddict['snips']],
  257. merklelib.Hasher())
  258. assert(self.consensus.consdict["merkleroot"] == \
  259. self.merkletree.merkle_root)
  260. else:
  261. self.cdf = None
  262. logging.debug('cdf=%s', self.cdf)
  263. @staticmethod
  264. def get(consensus, endive = None):
  265. # Return the singleton instance, if it exists for this epoch
  266. # However, don't use the cached instance if that one has
  267. # endive=None, but we were passed a real ENDIVE
  268. if RelayPicker.relaypicker is not None and \
  269. (RelayPicker.relaypicker.endive is not None or \
  270. endive is None) and \
  271. RelayPicker.relaypicker.epoch == consensus.consdict["epoch"]:
  272. return RelayPicker.relaypicker
  273. # Create it otherwise, storing the result as the singleton
  274. RelayPicker.relaypicker = RelayPicker(consensus, endive)
  275. return RelayPicker.relaypicker
  276. def pick_relay_by_uniform_index(self, idx):
  277. """Pass in a uniform random index random(0,totbw-1) to get a
  278. relay's descriptor or snip (depending on the network mode) selected weighted by bw."""
  279. if network.thenetwork.womode == network.WOMode.VANILLA:
  280. relays = self.consensus.consdict['relays']
  281. else:
  282. relays = self.endive.enddict['snips']
  283. # Find the rightmost entry less than or equal to idx
  284. i = bisect.bisect_right(self.cdf, idx)
  285. r = relays[i-1]
  286. if network.thenetwork.snipauthmode == \
  287. network.SNIPAuthMode.MERKLE:
  288. # If we haven't yet computed the Merkle path for this SNIP,
  289. # do it now, and store it in the SNIP so that the client
  290. # will get it.
  291. if "auth" not in r.snipdict:
  292. r.snipdict["auth"] = [ (p.hash, p.type) for p in \
  293. self.merkletree.get_proof(r.__str__(False))._nodes]
  294. return r
  295. def pick_weighted_relay(self):
  296. """Select a random relay with probability proportional to its bw
  297. weight."""
  298. idx = self.pick_weighted_relay_index()
  299. return self.pick_relay_by_uniform_index(idx)
  300. def pick_weighted_relay_index(self):
  301. """Select a random relay index (for use in Walking Onions)
  302. uniformly, which will results in picking a relay with
  303. probability proportional to its bw weight."""
  304. totbw = self.totbw
  305. if totbw < 1:
  306. raise ValueError("No relays to choose from")
  307. return random.randint(0, totbw-1)
  308. class DirAuthNetMsg(network.NetMsg):
  309. """The subclass of NetMsg for messages to and from directory
  310. authorities."""
  311. class DirAuthUploadDescMsg(DirAuthNetMsg):
  312. """The subclass of DirAuthNetMsg for uploading a relay
  313. descriptor."""
  314. def __init__(self, desc):
  315. self.desc = desc
  316. class DirAuthDelDescMsg(DirAuthNetMsg):
  317. """The subclass of DirAuthNetMsg for deleting a relay
  318. descriptor."""
  319. def __init__(self, desc):
  320. self.desc = desc
  321. class DirAuthGetConsensusMsg(DirAuthNetMsg):
  322. """The subclass of DirAuthNetMsg for fetching the consensus."""
  323. class DirAuthConsensusMsg(DirAuthNetMsg):
  324. """The subclass of DirAuthNetMsg for returning the consensus."""
  325. def __init__(self, consensus):
  326. self.consensus = consensus
  327. class DirAuthGetConsensusDiffMsg(DirAuthNetMsg):
  328. """The subclass of DirAuthNetMsg for fetching the consensus, if the
  329. requestor already has the previous consensus."""
  330. class DirAuthConsensusDiffMsg(DirAuthNetMsg):
  331. """The subclass of DirAuthNetMsg for returning the consensus, if the
  332. requestor already has the previous consensus. We don't _actually_
  333. produce the diff at this time; we just charge fewer bytes for this
  334. message."""
  335. def __init__(self, consensus):
  336. self.consensus = consensus
  337. def size(self):
  338. if network.symbolic_byte_counters:
  339. return super().size()
  340. return math.ceil(DirAuthConsensusMsg(self.consensus).size() \
  341. * network.P_Delta)
  342. class DirAuthGetENDIVEMsg(DirAuthNetMsg):
  343. """The subclass of DirAuthNetMsg for fetching the ENDIVE."""
  344. class DirAuthENDIVEMsg(DirAuthNetMsg):
  345. """The subclass of DirAuthNetMsg for returning the ENDIVE."""
  346. def __init__(self, endive):
  347. self.endive = endive
  348. class DirAuthGetENDIVEDiffMsg(DirAuthNetMsg):
  349. """The subclass of DirAuthNetMsg for fetching the ENDIVE, if the
  350. requestor already has the previous ENDIVE."""
  351. class DirAuthENDIVEDiffMsg(DirAuthNetMsg):
  352. """The subclass of DirAuthNetMsg for returning the ENDIVE, if the
  353. requestor already has the previous consensus. We don't _actually_
  354. produce the diff at this time; we just charge fewer bytes for this
  355. message in Merkle mode. In threshold signature mode, we would still
  356. need to download at least the new signatures for every SNIP in the
  357. ENDIVE, so for now, just assume there's no gain from ENDIVE diffs in
  358. threshold signature mode."""
  359. def __init__(self, endive):
  360. self.endive = endive
  361. def size(self):
  362. if network.symbolic_byte_counters:
  363. return super().size()
  364. if network.thenetwork.snipauthmode == \
  365. network.SNIPAuthMode.THRESHSIG:
  366. return DirAuthENDIVEMsg(self.endive).size()
  367. return math.ceil(DirAuthENDIVEMsg(self.endive).size() \
  368. * network.P_Delta)
  369. class DirAuthConnection(network.ClientConnection):
  370. """The subclass of Connection for connections to directory
  371. authorities."""
  372. def __init__(self, peer):
  373. super().__init__(peer)
  374. def uploaddesc(self, desc):
  375. """Upload our RelayDescriptor to the DirAuth."""
  376. self.sendmsg(DirAuthUploadDescMeg(desc))
  377. def getconsensus(self):
  378. self.consensus = None
  379. self.sendmsg(DirAuthGetConsensusMsg())
  380. return self.consensus
  381. def getconsensusdiff(self):
  382. self.consensus = None
  383. self.sendmsg(DirAuthGetConsensusDiffMsg())
  384. return self.consensus
  385. def getendive(self):
  386. self.endive = None
  387. self.sendmsg(DirAuthGetENDIVEMsg())
  388. return self.endive
  389. def getendivediff(self):
  390. self.endive = None
  391. self.sendmsg(DirAuthGetENDIVEDiffMsg())
  392. return self.endive
  393. def receivedfromserver(self, msg):
  394. if isinstance(msg, DirAuthConsensusMsg):
  395. self.consensus = msg.consensus
  396. elif isinstance(msg, DirAuthConsensusDiffMsg):
  397. self.consensus = msg.consensus
  398. elif isinstance(msg, DirAuthENDIVEMsg):
  399. self.endive = msg.endive
  400. elif isinstance(msg, DirAuthENDIVEDiffMsg):
  401. self.endive = msg.endive
  402. else:
  403. raise TypeError('Not a server-originating DirAuthNetMsg', msg)
  404. class DirAuth(network.Server):
  405. """The class representing directory authorities."""
  406. # We simulate the act of computing the consensus by keeping a
  407. # class-static dict that's accessible to all of the dirauths
  408. # This dict is indexed by epoch, and the value is itself a dict
  409. # indexed by the stringified descriptor, with value a pair of (the
  410. # number of dirauths that saw that descriptor, the descriptor
  411. # itself).
  412. uploadeddescs = dict()
  413. consensus = None
  414. endive = None
  415. def __init__(self, me, tot):
  416. """Create a new directory authority. me is the index of which
  417. dirauth this one is (starting from 0), and tot is the total
  418. number of dirauths."""
  419. self.me = me
  420. self.tot = tot
  421. self.name = "Dirauth %d of %d" % (me+1, tot)
  422. self.perfstats = network.PerfStats(network.EntType.DIRAUTH)
  423. self.perfstats.is_bootstrapping = True
  424. # Create the dirauth signature keypair
  425. self.sigkey = nacl.signing.SigningKey.generate()
  426. self.perfstats.keygens += 1
  427. self.netaddr = network.thenetwork.bind(self)
  428. self.perfstats.name = "DirAuth at %s" % self.netaddr
  429. network.thenetwork.setdirauthkey(me, self.sigkey.verify_key)
  430. network.thenetwork.wantepochticks(self, True, True, True)
  431. def connected(self, client):
  432. """Callback invoked when a client connects to us. This callback
  433. creates the DirAuthConnection that will be passed to the
  434. client."""
  435. # We don't actually need to keep per-connection state at
  436. # dirauths, even in long-lived connections, so this is
  437. # particularly simple.
  438. return DirAuthConnection(self)
  439. def generate_consensus(self, epoch):
  440. """Generate the consensus (and ENDIVE, if using Walking Onions)
  441. for the given epoch, which should be the one after the one
  442. that's currently about to end."""
  443. threshold = int(self.tot/2)+1
  444. consensusdescs = []
  445. for numseen, desc in DirAuth.uploadeddescs[epoch].values():
  446. if numseen >= threshold:
  447. consensusdescs.append(desc)
  448. DirAuth.consensus = Consensus(epoch, consensusdescs)
  449. if network.thenetwork.womode != network.WOMode.VANILLA:
  450. totbw = sum([ d.descdict["bw"] for d in consensusdescs ])
  451. hi = 0
  452. cumbw = 0
  453. snips = []
  454. for d in consensusdescs:
  455. cumbw += d.descdict["bw"]
  456. lo = hi
  457. hi = int((cumbw<<32)/totbw)
  458. snipdict = dict(d.descdict)
  459. del snipdict["bw"]
  460. snipdict["range"] = (lo,hi)
  461. snips.append(SNIP(snipdict))
  462. DirAuth.endive = ENDIVE(epoch, snips)
  463. def epoch_ending(self, epoch):
  464. # Only dirauth 0 actually needs to generate the consensus
  465. # because of the shared class-static state, but everyone has to
  466. # sign it. Note that this code relies on dirauth 0's
  467. # epoch_ending callback being called before any of the other
  468. # dirauths'.
  469. if (epoch+1) not in DirAuth.uploadeddescs:
  470. DirAuth.uploadeddescs[epoch+1] = dict()
  471. if self.me == 0:
  472. self.generate_consensus(epoch+1)
  473. del DirAuth.uploadeddescs[epoch+1]
  474. if network.thenetwork.snipauthmode == \
  475. network.SNIPAuthMode.THRESHSIG:
  476. for s in DirAuth.endive.enddict['snips']:
  477. s.auth(self.sigkey, self.perfstats)
  478. elif network.thenetwork.snipauthmode == \
  479. network.SNIPAuthMode.MERKLE:
  480. # Construct the Merkle tree of the SNIPs in the ENDIVE
  481. # and put the root in the consensus
  482. tree = merklelib.MerkleTree(
  483. [snip.__str__(False) \
  484. for snip in DirAuth.endive.enddict['snips']],
  485. merklelib.Hasher())
  486. DirAuth.consensus.consdict["merkleroot"] = tree.merkle_root
  487. else:
  488. if network.thenetwork.snipauthmode == \
  489. network.SNIPAuthMode.THRESHSIG:
  490. for s in DirAuth.endive.enddict['snips']:
  491. # We're just simulating threshold sigs by having
  492. # only the first dirauth sign, but in reality each
  493. # dirauth would contribute to the signature (at the
  494. # same cost as each one signing), so we'll charge
  495. # their perfstats as well
  496. self.perfstats.sigs += 1
  497. DirAuth.consensus.sign(self.sigkey, self.me, self.perfstats)
  498. if network.thenetwork.womode != network.WOMode.VANILLA:
  499. DirAuth.endive.sign(self.sigkey, self.me, self.perfstats)
  500. def received(self, client, msg):
  501. self.perfstats.bytes_received += msg.size()
  502. if isinstance(msg, DirAuthUploadDescMsg):
  503. # Check the uploaded descriptor for sanity
  504. epoch = msg.desc.descdict['epoch']
  505. if epoch != network.thenetwork.getepoch() + 1:
  506. return
  507. # Store it in the class-static dict
  508. if epoch not in DirAuth.uploadeddescs:
  509. DirAuth.uploadeddescs[epoch] = dict()
  510. descstr = str(msg.desc)
  511. if descstr not in DirAuth.uploadeddescs[epoch]:
  512. DirAuth.uploadeddescs[epoch][descstr] = (1, msg.desc)
  513. else:
  514. DirAuth.uploadeddescs[epoch][descstr] = \
  515. (DirAuth.uploadeddescs[epoch][descstr][0]+1,
  516. DirAuth.uploadeddescs[epoch][descstr][1])
  517. elif isinstance(msg, DirAuthDelDescMsg):
  518. # Check the uploaded descriptor for sanity
  519. epoch = msg.desc.descdict['epoch']
  520. if epoch != network.thenetwork.getepoch() + 1:
  521. return
  522. # Remove it from the class-static dict
  523. if epoch not in DirAuth.uploadeddescs:
  524. return
  525. descstr = str(msg.desc)
  526. if descstr not in DirAuth.uploadeddescs[epoch]:
  527. return
  528. elif DirAuth.uploadeddescs[epoch][descstr][0] == 1:
  529. del DirAuth.uploadeddescs[epoch][descstr]
  530. else:
  531. DirAuth.uploadeddescs[epoch][descstr] = \
  532. (DirAuth.uploadeddescs[epoch][descstr][0]-1,
  533. DirAuth.uploadeddescs[epoch][descstr][1])
  534. elif isinstance(msg, DirAuthGetConsensusMsg):
  535. replymsg = DirAuthConsensusMsg(DirAuth.consensus)
  536. msgsize = replymsg.size()
  537. self.perfstats.bytes_sent += msgsize
  538. client.reply(replymsg)
  539. elif isinstance(msg, DirAuthGetConsensusDiffMsg):
  540. replymsg = DirAuthConsensusDiffMsg(DirAuth.consensus)
  541. msgsize = replymsg.size()
  542. self.perfstats.bytes_sent += msgsize
  543. client.reply(replymsg)
  544. elif isinstance(msg, DirAuthGetENDIVEMsg):
  545. replymsg = DirAuthENDIVEMsg(DirAuth.endive)
  546. msgsize = replymsg.size()
  547. self.perfstats.bytes_sent += msgsize
  548. client.reply(replymsg)
  549. elif isinstance(msg, DirAuthGetENDIVEDiffMsg):
  550. replymsg = DirAuthENDIVEDiffMsg(DirAuth.endive)
  551. msgsize = replymsg.size()
  552. self.perfstats.bytes_sent += msgsize
  553. client.reply(replymsg)
  554. else:
  555. raise TypeError('Not a client-originating DirAuthNetMsg', msg)
  556. def closed(self):
  557. pass
  558. if __name__ == '__main__':
  559. # Start some dirauths
  560. numdirauths = 9
  561. dirauthaddrs = []
  562. for i in range(numdirauths):
  563. dirauth = DirAuth(i, numdirauths)
  564. dirauthaddrs.append(dirauth.netaddr)
  565. for a in dirauthaddrs:
  566. print(a,end=' ')
  567. print()
  568. network.thenetwork.nextepoch()