Advertisement
Guest User

Untitled

a guest
Jul 20th, 2019
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.38 KB | None | 0 0
  1. # Use DEVp2p to build a mesh.
  2. # TODO: setup Kivy properly, test apks and executables.
  3. import random
  4. from kivy import app as kivy_app
  5. from kivy.uix.label import Label
  6. from os import urandom
  7. import salsa20
  8. import sys
  9. from devp2p.app import BaseApp
  10. from devp2p.protocol import BaseProtocol
  11. from devp2p.service import WiredService
  12. import devp2p.crypto
  13.  
  14. sha3 = devp2p.crypto.sha3
  15. from devp2p.utils import colors, COLOR_END
  16. from devp2p import app_helper
  17. import rlp
  18.  
  19. # --TODO--add functions for mesh connection to Sediment app/service---------------------
  20. nonce_empty_bytes = 0
  21.  
  22.  
  23. class Message(rlp.Serializable):
  24. fields = [
  25. ('data', rlp.sedes.binary),
  26. ('sender', rlp.sedes.binary),
  27. ('recipient', rlp.sedes.binary)
  28. ]
  29.  
  30. def __init__(self, data=b'', sender=b'', recipient=b''):
  31. assert isinstance(data, bytes)
  32. assert isinstance(sender, bytes)
  33. assert isinstance(recipient, bytes)
  34. super(Message, self).__init__(data, sender, recipient)
  35.  
  36. @property
  37. def hash(self):
  38. return sha3(rlp.encode(self))
  39.  
  40. def __repr__(self):
  41. try:
  42. return '<%s(data=%d hash=%s)>' % (self.__class__.__name__, self.fields["data"],
  43. self.hash.encode('hex')[:4])
  44. except:
  45. return '<%s>' % self.__class__.__name__
  46.  
  47.  
  48. class BloomMessage(rlp.Serializable):
  49. # messages using bloom filters instead of keys for sender and recipient, for some degree of privacy.
  50. fields = [
  51. ('data', rlp.sedes.binary),
  52. ('backfilter', rlp.sedes.binary),
  53. ('nextfilter', rlp.sedes.binary)
  54. ]
  55.  
  56. def __init__(self, data=b'', backfilter=b'', nextfilter=b''):
  57. assert isinstance(data, bytes)
  58. assert isinstance(backfilter, bytes)
  59. assert isinstance(nextfilter, bytes)
  60. super(BloomMessage, self).__init__(data, backfilter, nextfilter)
  61.  
  62. @property
  63. def hash(self):
  64. return sha3(rlp.encode(self))
  65.  
  66. def __repr__(self):
  67. try:
  68. return '<%s(data=%d hash=%s)>' % (self.__class__.__name__, self.fields["data"],
  69. self.hash.encode('hex')[:4])
  70. except:
  71. return '<%s>' % self.__class__.__name__
  72.  
  73.  
  74. class SedimentProtocol(BaseProtocol):
  75. protocol_id = 1
  76. network_id = 0
  77. max_cmd_id = 7
  78. name = 'sediment'
  79. version = 1
  80.  
  81. def __init__(self, peer, service):
  82. # required by P2PProtocol
  83. self.config = peer.config
  84. BaseProtocol.__init__(self, peer, service)
  85.  
  86. class endorsement(BaseProtocol.command):
  87. """
  88. plaintext message of one user endorsing another into the network, data = signed pubk + pubk + endorsed name.
  89. """
  90. cmd_id = 7
  91.  
  92. structure = [
  93. ('endorsement', Message)
  94. ]
  95.  
  96. class sm(BaseProtocol.command): # secret message (encrypted message with some privacy of destination/source)
  97.  
  98. """
  99. Message with dark-routing
  100. """
  101. cmd_id = 6
  102.  
  103. structure = [
  104. ('sm', BloomMessage)
  105. ]
  106.  
  107. class bye(BaseProtocol.command):
  108. """
  109. LEAVE the network! (data = random nonce)
  110. """
  111. cmd_id = 5
  112.  
  113. structure = [
  114. ('bye', Message)
  115. ]
  116.  
  117. class hello(BaseProtocol.command):
  118. """
  119. Join the network! (data = random nonce)
  120. """
  121. cmd_id = 4
  122.  
  123. structure = [
  124. ('hello', Message)
  125. ]
  126.  
  127. class pe(BaseProtocol.command): # (encrypted) password exchange
  128.  
  129. """
  130. encrypted message sending data required to connect to this node via the mesh.
  131. same data should be used as secret key for sm. like a session-key.
  132. """
  133. cmd_id = 3
  134.  
  135. structure = [
  136. ('pe', Message)
  137. ]
  138.  
  139. class em(BaseProtocol.command): # encrypted message
  140.  
  141. """
  142. message in encrypted plaintext, for future use and extensibility
  143. """
  144. cmd_id = 2
  145.  
  146. structure = [
  147. ('em', Message)
  148. ]
  149.  
  150. class um(BaseProtocol.command): # unencrypted message
  151.  
  152. """
  153. message in unencrypted plaintext, for future use and extensibility
  154. """
  155. cmd_id = 1
  156.  
  157. structure = [
  158. ('um', Message)
  159. ]
  160.  
  161.  
  162. class DuplicatesFilter(object):
  163. def __init__(self, max_items=1024):
  164. self.max_items = max_items
  165. self.filter = list()
  166.  
  167. def update(self, data):
  168. """returns True if unknown"""
  169. if data not in self.filter:
  170. self.filter.append(data)
  171. if len(self.filter) > self.max_items:
  172. self.filter.pop(0)
  173. return True
  174. else:
  175. self.filter.append(self.filter.pop(0))
  176. return False
  177.  
  178. def __contains__(self, v):
  179. return v in self.filter
  180.  
  181.  
  182. class SedimentService(WiredService):
  183. # required by BaseService
  184. name = 'sediment'
  185. default_config = {'sediment': dict(num_participants=1)}
  186.  
  187. # required by WiredService
  188. wire_protocol = SedimentProtocol # create for each peer
  189.  
  190. def __init__(self, app):
  191. self.config = app.config
  192. raw_privkey = bytes.fromhex(self.config['node']['privkey_hex'])
  193. self.crypto = devp2p.crypto.ECCx(raw_privkey=raw_privkey)
  194. self.address = self.crypto.raw_pubkey
  195. super(SedimentService, self).__init__(app)
  196.  
  197. def start(self):
  198. super(SedimentService, self).start()
  199.  
  200. def log(self, text, **kargs):
  201. node_num = self.config['node_num']
  202. msg = ' '.join([
  203. colors[node_num % len(colors)],
  204. "NODE%d" % node_num,
  205. text,
  206. (' %r' % kargs if kargs else ''),
  207. COLOR_END])
  208. print(msg)
  209.  
  210. def nearest_peer_from_pubkey(self, pubkey):
  211. """
  212. attempts to retrieve a peer that owns the provided pubkey. If no such peer exists, returns the numerically
  213. closest peer.
  214. :param pubkey: public key of peer you are attempting to retrieve
  215. :return: peer, either requested, or closest to.
  216. """
  217. for now_peer in self.app.services.peermanager.peers:
  218. if pubkey == now_peer.remote_pubkey():
  219. return now_peer
  220. return min(self.app.services.peermanager.peers, key=lambda x: abs(
  221. int(x.remote_pubkey(), base=16) - int(pubkey, base=16)))
  222.  
  223. def broadcast(self, obj, command_type, origin=None):
  224. self.log('broadcasting', obj=obj)
  225. bcast = self.app.services.peermanager.broadcast
  226. bcast(SedimentProtocol, command_type, dargs=(obj,),
  227. exclude_peers=[origin.peer] if origin else [])
  228.  
  229. def narrowcast(self, obj, command_type, origin=None, target=None, fork=False):
  230. """
  231. probabilistically route a message towards the target
  232. :param obj: Message object to route
  233. :param command_type: command type string
  234. :param origin: sender to prevent routing backwards
  235. :param target: forward route.
  236. :param fork: fork message to random peer for more guarantee of arrival.
  237. :return:
  238. """
  239. all_peers = self.app.services.peermanager.peers
  240. bcast = self.app.services.peermanager.broadcast
  241. if len(all_peers) < 2: # special behaviour to prevent dead-ends.
  242. bcast(SedimentProtocol, command_type, args=(obj,),
  243. exclude_peers=[])
  244. return
  245. excluded_peers = all_peers
  246. self.log('sending', obj=obj)
  247.  
  248. if target.peer: # attempt to send to nearest peer (ideally, destination)
  249. excluded_peers.remove(target.peer)
  250. if fork: # optionally fork to a random peer
  251. while len(excluded_peers) >= len(all_peers):
  252. excluded_peers.remove(random.choice(excluded_peers)) # and one "random" peer, to avoid dead-ends
  253. else:
  254. while len(excluded_peers) + 1 >= len(all_peers): # if target is unknown, pick two random peers.
  255. excluded_peers.remove(random.choice(excluded_peers))
  256. excluded_peers.extend([origin.peer] if origin else [])
  257. bcast(SedimentProtocol, command_type, args=(obj,),
  258. exclude_peers=excluded_peers)
  259.  
  260. def on_wire_protocol_stop(self, proto):
  261. assert isinstance(proto, self.wire_protocol)
  262. self.log('----------------------------------')
  263. self.log('on_wire_protocol_stop', proto=proto)
  264. self.send_bye() # die gracefully.
  265.  
  266. # application logic
  267.  
  268. def on_wire_protocol_start(self, proto):
  269. self.log('----------------------------------')
  270. self.log('on_wire_protocol_start', proto=proto, peers=self.app.services.peermanager.peers)
  271. assert isinstance(proto, self.wire_protocol)
  272. # register callbacks
  273. proto.receive_endorsement_callbacks.append(self.on_receive_endorsement)
  274. proto.receive_em_callbacks.append(self.on_receive_em)
  275. proto.receive_pe_callbacks.append(self.on_receive_pe)
  276. proto.receive_um_callbacks.append(self.on_receive_um)
  277. proto.receive_bye_callbacks.append(self.on_receive_bye)
  278. proto.receive_hello_callbacks.append(self.on_receive_hello)
  279. proto.receive_sm_callbacks.append(self.on_receive_sm)
  280. self.send_hello()
  281.  
  282. # TODO below v
  283.  
  284. @staticmethod
  285. def hide(data, noise=-1):
  286. if noise == -1:
  287. noise = len(data) - 3
  288. assert noise < len(data)
  289. newdata = data[:len(data) - noise] + urandom(noise)
  290. assert len(newdata) == len(data)
  291. return newdata
  292.  
  293. def send_em(self, data, remote):
  294. new_em = Message(data=self.encrypt_to_remote(data, remote), sender=self.address, recipient=remote)
  295. self.narrowcast(new_em, 'em', target=self.nearest_peer_from_pubkey(remote), fork=True)
  296.  
  297. def send_sm(self, data, remote):
  298. nextfilter = self.hide(remote)
  299. backfilter = self.hide(self.address)
  300. new_sm = BloomMessage(data=self.encrypt_to_remote(data, remote), backfilter=backfilter, nextfilter=nextfilter)
  301. self.narrowcast(new_sm, 'sm', target=self.nearest_peer_from_pubkey(remote), fork=True)
  302.  
  303. def send_pe(self, remote):
  304. # send a pe message, preferably narrowly.
  305. # (effectively the same as em)
  306. pass
  307.  
  308. def send_um(self, data, remote):
  309. new_um = Message(data=data, sender=self.address, recipient=remote)
  310. self.narrowcast(new_um, 'um', target=self.nearest_peer_from_pubkey(remote), fork=True)
  311.  
  312. def send_bye(self):
  313. new_bye = Message(data=urandom(2), sender=self.address)
  314. self.broadcast(new_bye, 'bye')
  315.  
  316. def send_hello(self):
  317. new_hello = Message(data=urandom(2), sender=self.address)
  318. self.broadcast(new_hello, 'hello')
  319.  
  320. def send_endorsement(self, endorsee, friendly_name):
  321. endorsee_bytes = bytes.fromhex(endorsee)
  322. friendly_name_bytes = bytes(friendly_name, "utf8")
  323. new_endorsement = Message(
  324. data=self.crypto.sign(endorsee_bytes + friendly_name_bytes) + endorsee_bytes + friendly_name_bytes,
  325. sender=self.address)
  326. self.broadcast(new_endorsement, 'endorsement')
  327.  
  328. endorsement_hist = DuplicatesFilter()
  329. endorsed_name = {} # remote_pubkey:{endorser/sender:endorsed_name}
  330.  
  331. def on_receive_endorsement(self, proto, endorsement):
  332. if self.endorsement_hist.update(endorsement):
  333. endorsee_bytes = endorsement.fields["data"][65:128]
  334. sender = endorsement.fields["sender"]
  335. if devp2p.crypto.ecdsa_verify(
  336. sender,
  337. endorsement.fields["data"][:64],
  338. endorsement.fields["data"][65:]):
  339. endorsement_existing = self.endorsed_name.get(endorsee_bytes)
  340. if endorsement_existing:
  341. self.endorsed_name[endorsee_bytes][sender] = endorsement.fields["data"][129:]
  342. else:
  343. self.endorsed_name[endorsee_bytes] = {sender: endorsement.fields["data"][129:]}
  344. self.broadcast(endorsement, 'endorsement')
  345. else:
  346. pass
  347. else:
  348. pass
  349.  
  350. bye_hist = DuplicatesFilter()
  351.  
  352. def on_receive_bye(self, proto, bye):
  353. if self.bye_hist.update(bye):
  354. assert isinstance(bye, Message)
  355. assert isinstance(proto, self.wire_protocol)
  356. local_peer = self.nearest_peer_from_pubkey(bye.fields["sender"])
  357. if local_peer == bye.fields["sender"]:
  358. # if bye is fom direct peer
  359. peer_connection = local_peer.connection
  360. # TODO disconnect from the CONNECTION associated to the NODE that sent this bye
  361. self.broadcast(bye, 'bye')
  362. # broadcast bye to your peers.
  363. else:
  364. pass
  365. # do not rebroadcast
  366. # TODO handle disconnection of remote peer.
  367. else:
  368. pass
  369.  
  370. hello_hist = DuplicatesFilter()
  371.  
  372. def on_receive_hello(self, proto, hello):
  373. if self.hello_hist.update(hello):
  374. assert isinstance(hello, Message)
  375. assert isinstance(proto, self.wire_protocol)
  376. if not self.is_meshed(hello.fields["sender"]):
  377. self.send_pe(hello.fields["sender"])
  378. else:
  379. pass
  380. else:
  381. pass
  382.  
  383. em_hist = DuplicatesFilter()
  384.  
  385. def on_receive_em(self, proto, em):
  386. if self.em_hist.update(em):
  387. assert isinstance(em, Message)
  388. assert isinstance(proto, self.wire_protocol)
  389. if self.address == em.fields["recipient"]:
  390. message_plaintext = self.decrypt_from_remote(em.fields["data"], em.fields["sender"])
  391. self.incoming_message_display(em.fields["sender"], message_plaintext)
  392. else:
  393. origin = self.nearest_peer_from_pubkey(em.fields["sender"])
  394. target = self.nearest_peer_from_pubkey(em.fields["recipient"])
  395. if target.remote_pubkey == em.fields["recipient"]:
  396. fork = False
  397. else:
  398. fork = True
  399. self.narrowcast(em, 'em', origin=origin, target=target, fork=fork)
  400. else:
  401. pass
  402.  
  403. sm_hist = DuplicatesFilter()
  404.  
  405. def on_receive_sm(self, proto, sm): # TODO
  406. if self.sm_hist.update(sm):
  407. assert isinstance(sm, BloomMessage)
  408. assert isinstance(proto, self.wire_protocol)
  409. if self.catch_bloom(sm.fields["nextfilter"]):
  410. pass # process message if you are the recipient, use em for forward-compatibility.
  411. # Forward message even if you are the recipient in case anyone is watching.
  412. origin = self.nearest_peer_from_pubkey(sm.fields["backfilter"]) # should already work with bloom filter
  413. target = self.nearest_peer_from_pubkey(sm.fields["nextfilter"])
  414. self.narrowcast(sm, 'sm', origin=origin, target=target, fork=True)
  415. else:
  416. pass
  417.  
  418. pe_hist = DuplicatesFilter()
  419.  
  420. def on_receive_pe(self, proto, pe):
  421. if self.pe_hist.update(pe):
  422. assert isinstance(pe, Message)
  423. assert isinstance(proto, self.wire_protocol)
  424. if self.address == pe.fields["recipient"]:
  425. if not self.is_meshed(pe.fields["sender"]):
  426. # attempt to connect to mesh using info provided in this pe message
  427. # if successful, update peer data and save connection data for this peer
  428. # if unsuccessful, do nothing
  429. pass
  430. else:
  431. origin = self.nearest_peer_from_pubkey(pe.fields["sender"])
  432. target = self.nearest_peer_from_pubkey(pe.fields["recipient"])
  433. if target.remote_pubkey == pe.fields["recipient"]:
  434. fork = False
  435. else:
  436. fork = True
  437. self.narrowcast(pe, 'pe', origin=origin, target=target, fork=fork)
  438. else:
  439. origin = self.nearest_peer_from_pubkey(pe.fields["sender"])
  440. target = self.nearest_peer_from_pubkey(pe.fields["recipient"])
  441. if target.remote_pubkey == pe.fields["recipient"]:
  442. fork = False
  443. else:
  444. fork = True
  445. self.narrowcast(pe, 'pe', origin=origin, target=target, fork=fork)
  446. else:
  447. pass
  448.  
  449. um_hist = DuplicatesFilter()
  450.  
  451. def on_receive_um(self, proto, um):
  452. if self.um_hist.update(um):
  453. assert isinstance(um, Message)
  454. assert isinstance(proto, self.wire_protocol)
  455. fields_sender_ = um.fields["sender"]
  456. origin = self.nearest_peer_from_pubkey(fields_sender_)
  457. target = self.nearest_peer_from_pubkey(um.fields["recipient"])
  458. if um.fields["recipient"] == self.address:
  459. plaintext_data = um.fields["data"]
  460. self.incoming_message_display(fields_sender_, plaintext_data)
  461. else:
  462. self.narrowcast(um, 'um', origin=origin, target=target)
  463. else:
  464. pass
  465.  
  466. def endorsed_nodes(self, remote_key):
  467. friendly_names = self.endorsed_name.get(remote_key)
  468. if friendly_names:
  469. for key in friendly_names:
  470. if key in self.endorsed_name:
  471. return friendly_names.get(key)
  472. return remote_key # TODO, return endorsed name of "most trusted" endorsement.
  473.  
  474. def incoming_message_display(self, fields_sender_, plaintext_data):
  475. print(self.endorsed_nodes(fields_sender_) + ": " + plaintext_data) # todo change when UI is done.
  476.  
  477. def encrypt_to_remote(self, data, remote):
  478. pubkey = devp2p.crypto.PublicKey(pubkey=remote)
  479. shared_raw_secret = self.crypto.get_ecdh_key(pubkey.public_key)
  480. shared_secret = sha3(shared_raw_secret)
  481. return self.encrypt(shared_secret, data)
  482.  
  483. def decrypt_from_remote(self, data, remote):
  484. pubkey = devp2p.crypto.PublicKey(pubkey=remote)
  485. shared_raw_secret = self.crypto.get_ecdh_key(pubkey.public_key)
  486. shared_secret = sha3(shared_raw_secret)
  487. return self.decrypt(shared_secret, data)
  488.  
  489. def attempt_decrypt_secret(self, data, remote): # decrypt data from closest endorsed peer.
  490. return data # TODO
  491.  
  492. @staticmethod
  493. def encrypt(remote_secret, data_in):
  494. # encrypt with random nonce
  495. nonce = urandom(8)
  496. cipher_text = salsa20.Salsa20_xor(data_in, nonce, remote_secret)
  497. return nonce + cipher_text
  498.  
  499. @staticmethod
  500. def decrypt(remote_secret, data_in):
  501. return salsa20.Salsa20_xor(
  502. data_in[8:],
  503. data_in[:7],
  504. remote_secret
  505. )
  506.  
  507. def is_meshed(self, remote):
  508. return False # TODO
  509.  
  510. def catch_bloom(self, incoming_filter):
  511. if incoming_filter[:2] == self.address.hex()[:2]: # TODO, do something better.
  512. return True
  513. else:
  514. return False
  515.  
  516. def closest_endorsed_peer(self, remote):
  517. pass
  518.  
  519.  
  520. class SedimentApp(BaseApp):
  521. client_name = 'sediment'
  522. version = '0.1'
  523. client_version = '%s/%s/%s' % (version, sys.platform,
  524. 'py%d.%d.%d' % sys.version_info[:3])
  525. client_version_string = '%s/v%s' % (client_name, client_version)
  526. default_config = dict(BaseApp.default_config)
  527. default_config['client_version_string'] = client_version_string
  528. default_config['post_app_start_callback'] = None
  529.  
  530.  
  531. class ChatApp(kivy_app.App):
  532. """
  533. TODO
  534. UI/frontend and device setup code goes here
  535. """
  536.  
  537. def build(self):
  538. import _thread
  539. _thread.start_new_thread(app_helper.run, (SedimentApp, SedimentService)) # do this better maybe?
  540. return Label(text="Hello World")
  541.  
  542.  
  543. if __name__ == '__main__':
  544. ChatApp().run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement