Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Use DEVp2p to build a mesh.
- # TODO: setup Kivy properly, test apks and executables.
- import random
- from kivy import app as kivy_app
- from kivy.uix.label import Label
- from os import urandom
- import salsa20
- import sys
- from devp2p.app import BaseApp
- from devp2p.protocol import BaseProtocol
- from devp2p.service import WiredService
- import devp2p.crypto
- sha3 = devp2p.crypto.sha3
- from devp2p.utils import colors, COLOR_END
- from devp2p import app_helper
- import rlp
- # --TODO--add functions for mesh connection to Sediment app/service---------------------
- nonce_empty_bytes = 0
- class Message(rlp.Serializable):
- fields = [
- ('data', rlp.sedes.binary),
- ('sender', rlp.sedes.binary),
- ('recipient', rlp.sedes.binary)
- ]
- def __init__(self, data=b'', sender=b'', recipient=b''):
- assert isinstance(data, bytes)
- assert isinstance(sender, bytes)
- assert isinstance(recipient, bytes)
- super(Message, self).__init__(data, sender, recipient)
- @property
- def hash(self):
- return sha3(rlp.encode(self))
- def __repr__(self):
- try:
- return '<%s(data=%d hash=%s)>' % (self.__class__.__name__, self.fields["data"],
- self.hash.encode('hex')[:4])
- except:
- return '<%s>' % self.__class__.__name__
- class BloomMessage(rlp.Serializable):
- # messages using bloom filters instead of keys for sender and recipient, for some degree of privacy.
- fields = [
- ('data', rlp.sedes.binary),
- ('backfilter', rlp.sedes.binary),
- ('nextfilter', rlp.sedes.binary)
- ]
- def __init__(self, data=b'', backfilter=b'', nextfilter=b''):
- assert isinstance(data, bytes)
- assert isinstance(backfilter, bytes)
- assert isinstance(nextfilter, bytes)
- super(BloomMessage, self).__init__(data, backfilter, nextfilter)
- @property
- def hash(self):
- return sha3(rlp.encode(self))
- def __repr__(self):
- try:
- return '<%s(data=%d hash=%s)>' % (self.__class__.__name__, self.fields["data"],
- self.hash.encode('hex')[:4])
- except:
- return '<%s>' % self.__class__.__name__
- class SedimentProtocol(BaseProtocol):
- protocol_id = 1
- network_id = 0
- max_cmd_id = 7
- name = 'sediment'
- version = 1
- def __init__(self, peer, service):
- # required by P2PProtocol
- self.config = peer.config
- BaseProtocol.__init__(self, peer, service)
- class endorsement(BaseProtocol.command):
- """
- plaintext message of one user endorsing another into the network, data = signed pubk + pubk + endorsed name.
- """
- cmd_id = 7
- structure = [
- ('endorsement', Message)
- ]
- class sm(BaseProtocol.command): # secret message (encrypted message with some privacy of destination/source)
- """
- Message with dark-routing
- """
- cmd_id = 6
- structure = [
- ('sm', BloomMessage)
- ]
- class bye(BaseProtocol.command):
- """
- LEAVE the network! (data = random nonce)
- """
- cmd_id = 5
- structure = [
- ('bye', Message)
- ]
- class hello(BaseProtocol.command):
- """
- Join the network! (data = random nonce)
- """
- cmd_id = 4
- structure = [
- ('hello', Message)
- ]
- class pe(BaseProtocol.command): # (encrypted) password exchange
- """
- encrypted message sending data required to connect to this node via the mesh.
- same data should be used as secret key for sm. like a session-key.
- """
- cmd_id = 3
- structure = [
- ('pe', Message)
- ]
- class em(BaseProtocol.command): # encrypted message
- """
- message in encrypted plaintext, for future use and extensibility
- """
- cmd_id = 2
- structure = [
- ('em', Message)
- ]
- class um(BaseProtocol.command): # unencrypted message
- """
- message in unencrypted plaintext, for future use and extensibility
- """
- cmd_id = 1
- structure = [
- ('um', Message)
- ]
- class DuplicatesFilter(object):
- def __init__(self, max_items=1024):
- self.max_items = max_items
- self.filter = list()
- def update(self, data):
- """returns True if unknown"""
- if data not in self.filter:
- self.filter.append(data)
- if len(self.filter) > self.max_items:
- self.filter.pop(0)
- return True
- else:
- self.filter.append(self.filter.pop(0))
- return False
- def __contains__(self, v):
- return v in self.filter
- class SedimentService(WiredService):
- # required by BaseService
- name = 'sediment'
- default_config = {'sediment': dict(num_participants=1)}
- # required by WiredService
- wire_protocol = SedimentProtocol # create for each peer
- def __init__(self, app):
- self.config = app.config
- raw_privkey = bytes.fromhex(self.config['node']['privkey_hex'])
- self.crypto = devp2p.crypto.ECCx(raw_privkey=raw_privkey)
- self.address = self.crypto.raw_pubkey
- super(SedimentService, self).__init__(app)
- def start(self):
- super(SedimentService, self).start()
- def log(self, text, **kargs):
- node_num = self.config['node_num']
- msg = ' '.join([
- colors[node_num % len(colors)],
- "NODE%d" % node_num,
- text,
- (' %r' % kargs if kargs else ''),
- COLOR_END])
- print(msg)
- def nearest_peer_from_pubkey(self, pubkey):
- """
- attempts to retrieve a peer that owns the provided pubkey. If no such peer exists, returns the numerically
- closest peer.
- :param pubkey: public key of peer you are attempting to retrieve
- :return: peer, either requested, or closest to.
- """
- for now_peer in self.app.services.peermanager.peers:
- if pubkey == now_peer.remote_pubkey():
- return now_peer
- return min(self.app.services.peermanager.peers, key=lambda x: abs(
- int(x.remote_pubkey(), base=16) - int(pubkey, base=16)))
- def broadcast(self, obj, command_type, origin=None):
- self.log('broadcasting', obj=obj)
- bcast = self.app.services.peermanager.broadcast
- bcast(SedimentProtocol, command_type, dargs=(obj,),
- exclude_peers=[origin.peer] if origin else [])
- def narrowcast(self, obj, command_type, origin=None, target=None, fork=False):
- """
- probabilistically route a message towards the target
- :param obj: Message object to route
- :param command_type: command type string
- :param origin: sender to prevent routing backwards
- :param target: forward route.
- :param fork: fork message to random peer for more guarantee of arrival.
- :return:
- """
- all_peers = self.app.services.peermanager.peers
- bcast = self.app.services.peermanager.broadcast
- if len(all_peers) < 2: # special behaviour to prevent dead-ends.
- bcast(SedimentProtocol, command_type, args=(obj,),
- exclude_peers=[])
- return
- excluded_peers = all_peers
- self.log('sending', obj=obj)
- if target.peer: # attempt to send to nearest peer (ideally, destination)
- excluded_peers.remove(target.peer)
- if fork: # optionally fork to a random peer
- while len(excluded_peers) >= len(all_peers):
- excluded_peers.remove(random.choice(excluded_peers)) # and one "random" peer, to avoid dead-ends
- else:
- while len(excluded_peers) + 1 >= len(all_peers): # if target is unknown, pick two random peers.
- excluded_peers.remove(random.choice(excluded_peers))
- excluded_peers.extend([origin.peer] if origin else [])
- bcast(SedimentProtocol, command_type, args=(obj,),
- exclude_peers=excluded_peers)
- def on_wire_protocol_stop(self, proto):
- assert isinstance(proto, self.wire_protocol)
- self.log('----------------------------------')
- self.log('on_wire_protocol_stop', proto=proto)
- self.send_bye() # die gracefully.
- # application logic
- def on_wire_protocol_start(self, proto):
- self.log('----------------------------------')
- self.log('on_wire_protocol_start', proto=proto, peers=self.app.services.peermanager.peers)
- assert isinstance(proto, self.wire_protocol)
- # register callbacks
- proto.receive_endorsement_callbacks.append(self.on_receive_endorsement)
- proto.receive_em_callbacks.append(self.on_receive_em)
- proto.receive_pe_callbacks.append(self.on_receive_pe)
- proto.receive_um_callbacks.append(self.on_receive_um)
- proto.receive_bye_callbacks.append(self.on_receive_bye)
- proto.receive_hello_callbacks.append(self.on_receive_hello)
- proto.receive_sm_callbacks.append(self.on_receive_sm)
- self.send_hello()
- # TODO below v
- @staticmethod
- def hide(data, noise=-1):
- if noise == -1:
- noise = len(data) - 3
- assert noise < len(data)
- newdata = data[:len(data) - noise] + urandom(noise)
- assert len(newdata) == len(data)
- return newdata
- def send_em(self, data, remote):
- new_em = Message(data=self.encrypt_to_remote(data, remote), sender=self.address, recipient=remote)
- self.narrowcast(new_em, 'em', target=self.nearest_peer_from_pubkey(remote), fork=True)
- def send_sm(self, data, remote):
- nextfilter = self.hide(remote)
- backfilter = self.hide(self.address)
- new_sm = BloomMessage(data=self.encrypt_to_remote(data, remote), backfilter=backfilter, nextfilter=nextfilter)
- self.narrowcast(new_sm, 'sm', target=self.nearest_peer_from_pubkey(remote), fork=True)
- def send_pe(self, remote):
- # send a pe message, preferably narrowly.
- # (effectively the same as em)
- pass
- def send_um(self, data, remote):
- new_um = Message(data=data, sender=self.address, recipient=remote)
- self.narrowcast(new_um, 'um', target=self.nearest_peer_from_pubkey(remote), fork=True)
- def send_bye(self):
- new_bye = Message(data=urandom(2), sender=self.address)
- self.broadcast(new_bye, 'bye')
- def send_hello(self):
- new_hello = Message(data=urandom(2), sender=self.address)
- self.broadcast(new_hello, 'hello')
- def send_endorsement(self, endorsee, friendly_name):
- endorsee_bytes = bytes.fromhex(endorsee)
- friendly_name_bytes = bytes(friendly_name, "utf8")
- new_endorsement = Message(
- data=self.crypto.sign(endorsee_bytes + friendly_name_bytes) + endorsee_bytes + friendly_name_bytes,
- sender=self.address)
- self.broadcast(new_endorsement, 'endorsement')
- endorsement_hist = DuplicatesFilter()
- endorsed_name = {} # remote_pubkey:{endorser/sender:endorsed_name}
- def on_receive_endorsement(self, proto, endorsement):
- if self.endorsement_hist.update(endorsement):
- endorsee_bytes = endorsement.fields["data"][65:128]
- sender = endorsement.fields["sender"]
- if devp2p.crypto.ecdsa_verify(
- sender,
- endorsement.fields["data"][:64],
- endorsement.fields["data"][65:]):
- endorsement_existing = self.endorsed_name.get(endorsee_bytes)
- if endorsement_existing:
- self.endorsed_name[endorsee_bytes][sender] = endorsement.fields["data"][129:]
- else:
- self.endorsed_name[endorsee_bytes] = {sender: endorsement.fields["data"][129:]}
- self.broadcast(endorsement, 'endorsement')
- else:
- pass
- else:
- pass
- bye_hist = DuplicatesFilter()
- def on_receive_bye(self, proto, bye):
- if self.bye_hist.update(bye):
- assert isinstance(bye, Message)
- assert isinstance(proto, self.wire_protocol)
- local_peer = self.nearest_peer_from_pubkey(bye.fields["sender"])
- if local_peer == bye.fields["sender"]:
- # if bye is fom direct peer
- peer_connection = local_peer.connection
- # TODO disconnect from the CONNECTION associated to the NODE that sent this bye
- self.broadcast(bye, 'bye')
- # broadcast bye to your peers.
- else:
- pass
- # do not rebroadcast
- # TODO handle disconnection of remote peer.
- else:
- pass
- hello_hist = DuplicatesFilter()
- def on_receive_hello(self, proto, hello):
- if self.hello_hist.update(hello):
- assert isinstance(hello, Message)
- assert isinstance(proto, self.wire_protocol)
- if not self.is_meshed(hello.fields["sender"]):
- self.send_pe(hello.fields["sender"])
- else:
- pass
- else:
- pass
- em_hist = DuplicatesFilter()
- def on_receive_em(self, proto, em):
- if self.em_hist.update(em):
- assert isinstance(em, Message)
- assert isinstance(proto, self.wire_protocol)
- if self.address == em.fields["recipient"]:
- message_plaintext = self.decrypt_from_remote(em.fields["data"], em.fields["sender"])
- self.incoming_message_display(em.fields["sender"], message_plaintext)
- else:
- origin = self.nearest_peer_from_pubkey(em.fields["sender"])
- target = self.nearest_peer_from_pubkey(em.fields["recipient"])
- if target.remote_pubkey == em.fields["recipient"]:
- fork = False
- else:
- fork = True
- self.narrowcast(em, 'em', origin=origin, target=target, fork=fork)
- else:
- pass
- sm_hist = DuplicatesFilter()
- def on_receive_sm(self, proto, sm): # TODO
- if self.sm_hist.update(sm):
- assert isinstance(sm, BloomMessage)
- assert isinstance(proto, self.wire_protocol)
- if self.catch_bloom(sm.fields["nextfilter"]):
- pass # process message if you are the recipient, use em for forward-compatibility.
- # Forward message even if you are the recipient in case anyone is watching.
- origin = self.nearest_peer_from_pubkey(sm.fields["backfilter"]) # should already work with bloom filter
- target = self.nearest_peer_from_pubkey(sm.fields["nextfilter"])
- self.narrowcast(sm, 'sm', origin=origin, target=target, fork=True)
- else:
- pass
- pe_hist = DuplicatesFilter()
- def on_receive_pe(self, proto, pe):
- if self.pe_hist.update(pe):
- assert isinstance(pe, Message)
- assert isinstance(proto, self.wire_protocol)
- if self.address == pe.fields["recipient"]:
- if not self.is_meshed(pe.fields["sender"]):
- # attempt to connect to mesh using info provided in this pe message
- # if successful, update peer data and save connection data for this peer
- # if unsuccessful, do nothing
- pass
- else:
- origin = self.nearest_peer_from_pubkey(pe.fields["sender"])
- target = self.nearest_peer_from_pubkey(pe.fields["recipient"])
- if target.remote_pubkey == pe.fields["recipient"]:
- fork = False
- else:
- fork = True
- self.narrowcast(pe, 'pe', origin=origin, target=target, fork=fork)
- else:
- origin = self.nearest_peer_from_pubkey(pe.fields["sender"])
- target = self.nearest_peer_from_pubkey(pe.fields["recipient"])
- if target.remote_pubkey == pe.fields["recipient"]:
- fork = False
- else:
- fork = True
- self.narrowcast(pe, 'pe', origin=origin, target=target, fork=fork)
- else:
- pass
- um_hist = DuplicatesFilter()
- def on_receive_um(self, proto, um):
- if self.um_hist.update(um):
- assert isinstance(um, Message)
- assert isinstance(proto, self.wire_protocol)
- fields_sender_ = um.fields["sender"]
- origin = self.nearest_peer_from_pubkey(fields_sender_)
- target = self.nearest_peer_from_pubkey(um.fields["recipient"])
- if um.fields["recipient"] == self.address:
- plaintext_data = um.fields["data"]
- self.incoming_message_display(fields_sender_, plaintext_data)
- else:
- self.narrowcast(um, 'um', origin=origin, target=target)
- else:
- pass
- def endorsed_nodes(self, remote_key):
- friendly_names = self.endorsed_name.get(remote_key)
- if friendly_names:
- for key in friendly_names:
- if key in self.endorsed_name:
- return friendly_names.get(key)
- return remote_key # TODO, return endorsed name of "most trusted" endorsement.
- def incoming_message_display(self, fields_sender_, plaintext_data):
- print(self.endorsed_nodes(fields_sender_) + ": " + plaintext_data) # todo change when UI is done.
- def encrypt_to_remote(self, data, remote):
- pubkey = devp2p.crypto.PublicKey(pubkey=remote)
- shared_raw_secret = self.crypto.get_ecdh_key(pubkey.public_key)
- shared_secret = sha3(shared_raw_secret)
- return self.encrypt(shared_secret, data)
- def decrypt_from_remote(self, data, remote):
- pubkey = devp2p.crypto.PublicKey(pubkey=remote)
- shared_raw_secret = self.crypto.get_ecdh_key(pubkey.public_key)
- shared_secret = sha3(shared_raw_secret)
- return self.decrypt(shared_secret, data)
- def attempt_decrypt_secret(self, data, remote): # decrypt data from closest endorsed peer.
- return data # TODO
- @staticmethod
- def encrypt(remote_secret, data_in):
- # encrypt with random nonce
- nonce = urandom(8)
- cipher_text = salsa20.Salsa20_xor(data_in, nonce, remote_secret)
- return nonce + cipher_text
- @staticmethod
- def decrypt(remote_secret, data_in):
- return salsa20.Salsa20_xor(
- data_in[8:],
- data_in[:7],
- remote_secret
- )
- def is_meshed(self, remote):
- return False # TODO
- def catch_bloom(self, incoming_filter):
- if incoming_filter[:2] == self.address.hex()[:2]: # TODO, do something better.
- return True
- else:
- return False
- def closest_endorsed_peer(self, remote):
- pass
- class SedimentApp(BaseApp):
- client_name = 'sediment'
- version = '0.1'
- client_version = '%s/%s/%s' % (version, sys.platform,
- 'py%d.%d.%d' % sys.version_info[:3])
- client_version_string = '%s/v%s' % (client_name, client_version)
- default_config = dict(BaseApp.default_config)
- default_config['client_version_string'] = client_version_string
- default_config['post_app_start_callback'] = None
- class ChatApp(kivy_app.App):
- """
- TODO
- UI/frontend and device setup code goes here
- """
- def build(self):
- import _thread
- _thread.start_new_thread(app_helper.run, (SedimentApp, SedimentService)) # do this better maybe?
- return Label(text="Hello World")
- if __name__ == '__main__':
- ChatApp().run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement