import fcp n = fcp.node.FCPNode() # for debugging add verbosity=5 import uuid import random try: import chatterbot # let's get a real conversation :) # https://github.com/guntherc/ChatterBot/wiki/Quick-Start # get with `pip install --user chatterbot` irc_loguri = "USK@Dtz9FjDPmOxiT54Wjt7JwMJKWaqSOS-UGw4miINEvtg,cuIx2THw7G7cVyh9PuvNiHa1e9BvNmmfTcbQ7llXh2Q,AQACAAE/irclogs/1337/" print "Getting the latest IRC log as base for the chatterbot" IRC_LOGLINES = n.get(uri=irc_loguri, realtime=True, priority=1, followRedirect=True)[1].splitlines() import re # what follows is an evil hack, but what the heck :) p = re.compile(r'<.*?>') q = re.compile(r'&.*?;') IRC_LOGLINES = [q.sub('', p.sub('', str(unicode(i.strip(), errors="ignore")))) for i in IRC_LOGLINES] IRC_LOGLINES = [i[:-5] for i in IRC_LOGLINES # skip the time (last 5 letters) if (i[:-5] and # skip empty not "spam" in i # do not trigger spam-marking )][7:] # skip header except ImportError: chatterbot = None class Person(object): def __init__(self, myname, mymessage): self.name = myname self.message = mymessage self.introduced = False self.public_key, self.private_key = n.genkey() print self.name, "uses key", self.public_key # we need a list of versions for the different keys self.versions = {} for name in ["messages", "riddles", "known", "spammers"]: self.versions[name] = -1 # does not exist yet # and sets of answers, watched riddle-answer keys, known people and spammers. # We use sets for these, because we only need membership-tests and iteration. # The answers contain KSKs, the others the raw SSK of the person. # watched contains all persons whose messages we read. self.lists = {} for name in ["answers", "watched", "known", "spammers", "knowntocheck"]: self.lists[name] = set() # running requests per name, used for making all persons update asynchronously self.jobs = {} # and just for fun: get real conversations. Needs chatterbot and IRC_LOGLINES. # this is a bit slow to start, but fun. try: self.chatbot = chatterbot.ChatBot(self.name) self.chatbot.train(IRC_LOGLINES) except: self.chatbot = None def public_usk(self, name, version=0): """Get the public usk of type name.""" return get_usk_namespace(self.public_key, name, version) def private_usk(self, name, version=0): """Get the private usk of type name.""" return get_usk_namespace(self.private_key, name, version) def put(self, key, data): """Insert the data asynchronously to the key. This is just a helper to avoid typing the realtime arguments over and over again. :returns: a job object. To get the public key, use job.wait(60).""" return n.put(uri=key, data=data, async=True, Global=True, persistence="forever", realtime=True, priority=1, IgnoreUSKDatehints="true") def get(self, key): """Retrieve the data asynchronously to the key. This is just a helper to avoid typing the realtime arguments over and over again. :returns: a job object. To get the public key, use job.wait(60).""" return n.get(uri=key, async=True, realtime=True, priority=1, IgnoreUSKDatehints="true", followRedirect=True) def introduce_to_start(self, other_public_key): """Introduce self to the other by solving a riddle and uploading the messages USK.""" riddlekey = get_usk_namespace(other_public_key, "riddles", "-1") # -1 means the latest version try: self.jobs["getriddle"].append(self.get(riddlekey)) except KeyError: self.jobs["getriddle"] = [self.get(riddlekey)] def introduce_start(self): """Select a person and start a job to get a riddle.""" known = list(self.lists["known"]) if known: # introduce to a random person to minimize # the chance of collisions k = random.choice(known) self.introduce_to_start(k) def introduce_process(self): """Get and process the riddle data.""" for job in self.jobs.get("getriddle", [])[:]: if job.isComplete(): try: riddle = job.wait()[1] except Exception as e: # try again next time print self.name, "getting the riddle from", job.uri, "failed with", e return self.jobs["getriddle"].remove(job) answerkey = self.solve_riddle(riddle) messagekey = self.public_usk("messages") try: self.jobs["answerriddle"].append(self.put(answerkey, messagekey)) except KeyError: self.jobs["answerriddle"] = [self.put(answerkey, messagekey)] def introduce_finalize(self): """Check whether the riddle answer was inserted successfully.""" for job in self.jobs.get("answerriddle", [])[:]: if job.isComplete(): try: job.wait() self.jobs["answerriddle"].remove(job) self.introduced = True except Exception as e: # try again next time print self.name, "inserting the riddle-answer failed with", e return def new_riddle(self): """Create and upload a new riddle.""" answerkey = "KSK@" + str(uuid.uuid1()) + "-answered" self.lists["answers"].add(answerkey) self.versions["riddles"] += 1 next_riddle_key = self.private_usk("riddles", self.versions["riddles"]) self.put(next_riddle_key, answerkey) def solve_riddle(self, riddle): """Get the key for the given riddle. In this example we make it easy: The riddle is the key. For a real system, this needs user interaction. """ return riddle def update_info(self): for name in ["known", "spammers"]: data = serialize_keylist(self.lists[name]) self.versions[name] += 1 key = self.private_usk(name, version=self.versions[name]) self.put(key, data) def publish(self, data): self.versions["messages"] += 1 messagekey = self.private_usk("messages", version=self.versions["messages"]) print self.name, "published a message:", data self.put(messagekey, data) def check_network_start(self): """start all network checks.""" # first cancel all running jobs which will be replaced here. for name in ["answers", "watched", "known", "knowntocheck", "spammers"]: for job in self.jobs.get(name, []): job.cancel() # start jobs for checking answers, for checking all known people and for checking all messagelists for new messages. for name in ["answers"]: self.jobs[name] = [self.get(i) for i in self.lists[name]] for name in ["watched"]: self.jobs["messages"] = [self.get(get_usk_namespace(i, "messages")) for i in self.lists[name]] self.jobs["spammers"] = [] for name in ["known", "knowntocheck"]: # find new nodes self.jobs[name] = [self.get(get_usk_namespace(i, "known")) for i in self.lists[name]] # register new nodes marked as spammers self.jobs["spammers"].extend([self.get(get_usk_namespace(i, "spammers")) for i in self.lists[name]]) def process_network_results(self): """wait for completion of all network checks and process the results.""" for kind, jobs in self.jobs.items(): for job in jobs: if not kind in ["getriddle", "answerriddle"]: try: res = job.wait(60)[1] self.handle(res, kind, job) except: continue def handle(self, result, kind, job): """Handle a successful job of type kind.""" # travel the known nodes to find new ones if kind in ["known", "knowntocheck"]: for k in deserialize_keylist(result): if (not k in self.lists["spammers"] and not k in self.lists["known"] and not k == self.public_key): self.lists["knowntocheck"].add(k) self.lists["watched"].add(k) print self.name, "found and started to watch", k # read introductions elif kind in ["answers"]: self.lists[kind].remove(job.uri) # no longer need to watch this riddle k = extract_raw_from_usk(result) if not k in self.lists["spammers"]: self.lists["watched"].add(k) print self.name, "discovered", k, "through a solved riddle" # remove found spammers elif kind in ["spammers"]: for k in deserialize_keylist(result): if not result in self.lists["known"]: self.lists["watched"].remove(result) # check all messages for spam elif kind in ["messages"]: k = extract_raw_from_usk(job.uri) if not "spam" in result: if not k == self.public_key: print self.name, "read a message:", result self.chat(result) # just for fun :) if not k in self.lists["known"]: self.lists["known"].add(k) self.update_info() print self.name, "marked", k, "as known person" else: self.lists["watched"].remove(k) if not k in self.lists["spammers"]: self.lists["spammers"].add(k) self.update_info() print self.name, "marked", k, "as spammer" def chat(self, message): if self.chatbot and not "spam" in self.message: msg = message[message.index(":")+1:-10].strip() # remove name and step self.message = self.name + ": " + self.chatbot.get_response(msg) # some helper functions; the closest equivalent to structure definition def get_usk_namespace(key, name, version=0): """Get a USK key with the given namespace (foldername).""" return "U" + key[1:] + name + "/" + str(version) + "/" def extract_raw_from_usk(key): """Get an SSK key as used to identify a person from an arbitrary USK.""" return "S" + (key[1:]+"/").split("/")[0] + "/" def deserialize_keylist(keys_data): """Parse a known file to get a list of keys. Reverse: serialize_keylist.""" return [i for i in keys_data.split("\n") if i] def serialize_keylist(keys_list): """Serialize the known keys into a text file. Reverse: parse_known.""" return "\n".join(keys_list) people = [] def update(step): for p in people: if not p.introduced: p.introduce_start() for p in people: p.check_network_start() for p in people: if p.message: p.publish(p.name + ": " + p.message + " (step=%s)" % step) p.new_riddle() for p in people: if not p.introduced: p.introduce_process() for p in people: p.process_network_results() for p in people: if not p.introduced: p.introduce_finalize() def gen_person(name): try: return Person(myname=name, mymessage=random.choice(IRC_LOGLINES)) except: return Person(myname=name, mymessage="Hi, it's me!") # start with alice alice = gen_person("Alice") people.append(alice) # happy, friendly people for name in ["Bob", "Carol"]: p = gen_person(name) people.append(p) # and Chuck p = Person(myname="Chuck", mymessage="spam") people.append(p) # All people know Alice (except for Alice). for p in people: if p == alice: continue p.lists["known"].add(alice.public_key) p.lists["watched"].add(alice.public_key) # upload the first version of the spammer and known lists for p in people: p.update_info() for i in range(6): update(step=i) n.shutdown()