From 175fef2ab49d20e5b474d33fe554aaa5b3ce9400 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 18 Mar 2017 00:50:37 +0100 Subject: [PATCH] clean up wordlist handling --- docs/state-machines/allocator.dot | 6 +- docs/state-machines/code.dot | 25 ++-- docs/state-machines/input.dot | 2 +- docs/state-machines/machines.dot | 1 + docs/state-machines/nameplate.dot | 2 +- src/wormhole/_allocator.py | 23 +++- src/wormhole/_boss.py | 8 +- src/wormhole/_code.py | 170 ++++++---------------------- src/wormhole/_input.py | 79 ++++++------- src/wormhole/_interfaces.py | 7 ++ src/wormhole/_lister.py | 3 + src/wormhole/_nameplate.py | 11 +- src/wormhole/_wordlist.py | 182 ++++++++++++++++++++++++++++++ 13 files changed, 317 insertions(+), 202 deletions(-) create mode 100644 src/wormhole/_wordlist.py diff --git a/docs/state-machines/allocator.dot b/docs/state-machines/allocator.dot index a9228bb..2e2e280 100644 --- a/docs/state-machines/allocator.dot +++ b/docs/state-machines/allocator.dot @@ -7,8 +7,8 @@ digraph { S0A -> S0B [label="connected"] S0B -> S0A [label="lost"] S0B [label="S0B:\nidle\nconnected"] - S0A -> S1A [label="allocate" color="orange"] - S0B -> P_allocate [label="allocate"] + S0A -> S1A [label="allocate(length, wordlist)" color="orange"] + S0B -> P_allocate [label="allocate(length, wordlist)"] P_allocate [shape="box" label="RC.tx_allocate" color="orange"] P_allocate -> S1B [color="orange"] {rank=same; S1A P_allocate S1B} @@ -21,7 +21,7 @@ digraph { S1A -> P_allocate [label="connected" color="orange"] S1B -> P_allocated [label="rx_allocated" color="orange"] - P_allocated [shape="box" label="C.allocated_nameplate" color="orange"] + P_allocated [shape="box" label="choose words\nC.allocated(nameplate,code)" color="orange"] P_allocated -> S2 [color="orange"] S2 [label="S2:\ndone" color="orange"] diff --git a/docs/state-machines/code.dot b/docs/state-machines/code.dot index dcb66d3..078950c 100644 --- a/docs/state-machines/code.dot +++ b/docs/state-machines/code.dot @@ -4,36 +4,31 @@ digraph { {rank=same; start S0} start -> S0 [style="invis"] S0 [label="S0:\nidle"] - S0 -> P0_got_code [label="set_code"] + S0 -> P0_got_code [label="set_code\n(code)"] P0_got_code [shape="box" label="N.set_nameplate"] P0_got_code -> P_done P_done [shape="box" label="K.got_code\nB.got_code"] - P_done -> S5 - S5 [label="S5: known" color="green"] + P_done -> S4 + S4 [label="S4: known" color="green"] {rank=same; S1_inputting_nameplate S3_allocating} {rank=same; P0_got_code P1_set_nameplate P3_got_nameplate} S0 -> P_input [label="input_code"] - P_input [shape="box" label="I.start"] + P_input [shape="box" label="I.start\n(helper)"] P_input -> S1_inputting_nameplate S1_inputting_nameplate [label="S1:\ninputting\nnameplate"] - S1_inputting_nameplate -> P1_set_nameplate [label="got_nameplate"] + S1_inputting_nameplate -> P1_set_nameplate [label="got_nameplate\n(nameplate)"] P1_set_nameplate [shape="box" label="N.set_nameplate"] P1_set_nameplate -> S2_inputting_words S2_inputting_words [label="S2:\ninputting\nwords"] - S2_inputting_words -> P1_got_words [label="finished_input"] - P1_got_words [shape="box" label="assemble\ncode"] - P1_got_words -> P_done - P_done + S2_inputting_words -> P_done [label="finished_input\n(code)"] - S0 -> P_allocate [label="allocate_code"] - P_allocate [shape="box" label="A.allocate"] + S0 -> P_allocate [label="allocate_code\n(length,\nwordlist)"] + P_allocate [shape="box" label="A.allocate\n(length, wordlist)"] P_allocate -> S3_allocating S3_allocating [label="S3:\nallocating"] - S3_allocating -> P3_got_nameplate [label="allocated_nameplate"] + S3_allocating -> P3_got_nameplate [label="allocated\n(nameplate,\ncode)"] P3_got_nameplate [shape="box" label="N.set_nameplate"] - P3_got_nameplate -> P3_generate - P3_generate [shape="box" label="append\nrandom words"] - P3_generate -> P_done + P3_got_nameplate -> P_done } diff --git a/docs/state-machines/input.dot b/docs/state-machines/input.dot index 06013fc..0902988 100644 --- a/docs/state-machines/input.dot +++ b/docs/state-machines/input.dot @@ -31,7 +31,7 @@ digraph { S3 [label="S3: typing\ncode\n(yes wordlist)"] S3 -> S3 [label="got_nameplates"] S3 -> P_done [label="choose_words" color="orange" fontcolor="orange"] - P_done [shape="box" label="build code\nC.finished_input"] + P_done [shape="box" label="build code\nC.finished_input(code)"] P_done -> S4 S4 [label="S4: done" color="green"] S4 -> S4 [label="got_nameplates\ngot_wordlist"] diff --git a/docs/state-machines/machines.dot b/docs/state-machines/machines.dot index 015b138..39f7d83 100644 --- a/docs/state-machines/machines.dot +++ b/docs/state-machines/machines.dot @@ -61,6 +61,7 @@ digraph { label="connected\nlost\nrx_claimed\nrx_released"] Mailbox -> Nameplate [style="dashed" label="release"] Nameplate -> Mailbox [style="dashed" label="got_mailbox"] + Nameplate -> Input [style="dashed" label="got_wordlist"] Mailbox -> Connection [style="dashed" color="red" fontcolor="red" label="tx_open\ntx_add\ntx_close" diff --git a/docs/state-machines/nameplate.dot b/docs/state-machines/nameplate.dot index 4e6e682..8ddeabd 100644 --- a/docs/state-machines/nameplate.dot +++ b/docs/state-machines/nameplate.dot @@ -38,7 +38,7 @@ digraph { S2A -> S3A [label="(none)" style="invis"] S2B -> P_open [label="rx_claimed" color="orange" fontcolor="orange"] - P_open [shape="box" label="M.got_mailbox" color="orange"] + P_open [shape="box" label="I.got_wordlist\nM.got_mailbox" color="orange"] P_open -> S3B [color="orange"] subgraph {rank=same; S3A S3B} diff --git a/src/wormhole/_allocator.py b/src/wormhole/_allocator.py index 9e3f574..040b913 100644 --- a/src/wormhole/_allocator.py +++ b/src/wormhole/_allocator.py @@ -30,7 +30,7 @@ class Allocator(object): # from Code @m.input() - def allocate(self): pass + def allocate(self, length, wordlist): pass # from RendezvousConnector @m.input() @@ -40,26 +40,37 @@ class Allocator(object): @m.input() def rx_allocated(self, nameplate): pass + @m.output() + def stash(self, length, wordlist): + self._length = length + self._wordlist = _interfaces.IWordlist(wordlist) + @m.output() + def stash_and_RC_rx_allocate(self, length, wordlist): + self._length = length + self._wordlist = _interfaces.IWordlist(wordlist) + self._RC.tx_allocate() @m.output() def RC_tx_allocate(self): self._RC.tx_allocate() @m.output() - def C_allocated_nameplate(self, nameplate): - self._C.allocated_nameplate(nameplate) + def build_and_notify(self, nameplate): + words = self._wordlist.choose_words(self._length) + code = nameplate + "-" + words + self._C.allocated(nameplate, code) S0A_idle.upon(connected, enter=S0B_idle_connected, outputs=[]) S0B_idle_connected.upon(lost, enter=S0A_idle, outputs=[]) - S0A_idle.upon(allocate, enter=S1A_allocating, outputs=[]) + S0A_idle.upon(allocate, enter=S1A_allocating, outputs=[stash]) S0B_idle_connected.upon(allocate, enter=S1B_allocating_connected, - outputs=[RC_tx_allocate]) + outputs=[stash_and_RC_rx_allocate]) S1A_allocating.upon(connected, enter=S1B_allocating_connected, outputs=[RC_tx_allocate]) S1B_allocating_connected.upon(lost, enter=S1A_allocating, outputs=[]) S1B_allocating_connected.upon(rx_allocated, enter=S2_done, - outputs=[C_allocated_nameplate]) + outputs=[build_and_notify]) S2_done.upon(connected, enter=S2_done, outputs=[]) S2_done.upon(lost, enter=S2_done, outputs=[]) diff --git a/src/wormhole/_boss.py b/src/wormhole/_boss.py index 934c298..fd21b9e 100644 --- a/src/wormhole/_boss.py +++ b/src/wormhole/_boss.py @@ -19,6 +19,7 @@ from ._allocator import Allocator from ._input import Input from ._code import Code from ._terminator import Terminator +from ._wordlist import PGPWordList from .errors import (ServerError, LonelyError, WrongPasswordError, KeyFormatError, OnlyOneCodeError) from .util import bytes_to_dict @@ -50,13 +51,13 @@ class Boss(object): self._RC = RendezvousConnector(self._url, self._appid, self._side, self._reactor, self._journal, self._tor_manager, self._timing) - self._L = Lister() + self._L = Lister(self._timing) self._A = Allocator(self._timing) self._I = Input(self._timing) self._C = Code(self._timing) self._T = Terminator() - self._N.wire(self._M, self._RC, self._T) + self._N.wire(self._M, self._I, self._RC, self._T) self._M.wire(self._N, self._RC, self._O, self._T) self._S.wire(self._M) self._O.wire(self._K, self._R) @@ -129,7 +130,8 @@ class Boss(object): if self._did_start_code: raise OnlyOneCodeError() self._did_start_code = True - self._C.allocate_code(code_length) + wl = PGPWordList() + self._C.allocate_code(code_length, wl) def set_code(self, code): if ' ' in code: raise KeyFormatError("code (%s) contains spaces." % code) diff --git a/src/wormhole/_code.py b/src/wormhole/_code.py index 4ca2a12..2787e12 100644 --- a/src/wormhole/_code.py +++ b/src/wormhole/_code.py @@ -1,24 +1,9 @@ from __future__ import print_function, absolute_import, unicode_literals -import os from zope.interface import implementer from attr import attrs, attrib from attr.validators import provides from automat import MethodicalMachine from . import _interfaces -from .wordlist import (byte_to_even_word, byte_to_odd_word, - #even_words_lowercase, odd_words_lowercase, - ) - -def make_code(nameplate, code_length): - assert isinstance(nameplate, type("")), type(nameplate) - words = [] - for i in range(code_length): - # we start with an "odd word" - if i % 2 == 0: - words.append(byte_to_odd_word[os.urandom(1)].lower()) - else: - words.append(byte_to_even_word[os.urandom(1)].lower()) - return "%s-%s" % (nameplate, "-".join(words)) @attrs @implementer(_interfaces.ICode) @@ -28,7 +13,7 @@ class Code(object): @m.setTrace() def set_trace(): pass # pragma: no cover - def wire(self, boss, rendezvous_connector, lister): + def wire(self, boss, allocator, nameplate, key, input): self._B = _interfaces.IBoss(boss) self._A = _interfaces.IAllocator(allocator) self._N = _interfaces.INameplate(nameplate) @@ -36,37 +21,27 @@ class Code(object): self._I = _interfaces.IInput(input) @m.state(initial=True) - def S0A_unknown(self): pass # pragma: no cover + def S0_idle(self): pass # pragma: no cover @m.state() - def S0B_unknown_connected(self): pass # pragma: no cover + def S1_inputting_nameplate(self): pass # pragma: no cover @m.state() - def S1A_connecting(self): pass # pragma: no cover + def S2_inputting_words(self): pass # pragma: no cover @m.state() - def S1B_allocating(self): pass # pragma: no cover + def S3_allocating(self): pass # pragma: no cover @m.state() - def S2_input_nameplate(self): pass # pragma: no cover - @m.state() - def S3_input_code_no_wordlist(self): pass # pragma: no cover - @m.state() - def S4_input_code_wordlist(self): pass # pragma: no cover - @m.state() - def S5_known(self): pass # pragma: no cover + def S4_known(self): pass # pragma: no cover # from App @m.input() - def allocate_code(self, code_length): pass + def allocate_code(self, length, wordlist): pass @m.input() def input_code(self, input_helper): pass @m.input() def set_code(self, code): pass - # from Lister + # from Allocator @m.input() - def got_nameplates(self, nameplates): pass - - # from Nameplate - @m.input() - def got_wordlist(self, wordlist): pass + def allocated(self, nameplate, code): pass # from Input @m.input() @@ -75,109 +50,38 @@ class Code(object): def finished_input(self, code): pass @m.output() - def L_refresh_nameplates(self): - self._L.refresh_nameplates() - @m.output() - def start_input_and_L_refresh_nameplates(self, input_helper): - self._input_helper = input_helper - self._L.refresh_nameplates() - @m.output() - def stash_code_length_and_RC_tx_allocate(self, code_length): - self._code_length = code_length - self._RC.tx_allocate() - @m.output() - def stash_code_length(self, code_length): - self._code_length = code_length - @m.output() - def RC_tx_allocate(self): - self._RC.tx_allocate() - @m.output() - def stash_wordlist(self, wordlist): - # TODO - pass - @m.output() - def stash_nameplates(self, nameplates): - self._known_nameplates = nameplates - pass - @m.output() - def lookup_wordlist(self): - pass - @m.output() - def do_completion_code(self): - pass - @m.output() - def record_nameplate(self, nameplate): - self._nameplate = nameplate - @m.output() - def N_set_nameplate(self, nameplate): + def do_set_code(self, code): + nameplate = code.split("-", 2)[0] self._N.set_nameplate(nameplate) + self._K.got_code(code) + self._B.got_code(code) @m.output() - def generate_and_B_got_code(self, nameplate): - self._code = make_code(nameplate, self._code_length) - self._B_got_code() + def do_start_input(self, input_helper): + self._I.start(input_helper) + @m.output() + def do_middle_input(self, nameplate): + self._N.set_nameplate(nameplate) + @m.output() + def do_finish_input(self, code): + self._K.got_code(code) + self._B.got_code(code) @m.output() - def submit_words_and_B_got_code(self, words): - assert self._nameplate - self._code = self._nameplate + "-" + words - self._B_got_code() - + def do_start_allocate(self, length, wordlist): + self._A.allocate(length, wordlist) @m.output() - def B_got_code(self, code): - self._code = code - self._B_got_code() + def do_finish_allocate(self, nameplate, code): + self._N.set_nameplate(nameplate) + self._K.got_code(code) + self._B.got_code(code) - def _B_got_code(self): - self._N.set_nameplate(nameplate) XXX - self._B.got_code(self._code) - - S0A_unknown.upon(connected, enter=S0B_unknown_connected, outputs=[]) - S0B_unknown_connected.upon(lost, enter=S0A_unknown, outputs=[]) - - S0A_unknown.upon(set_code, enter=S5_known, outputs=[B_got_code]) - S0B_unknown_connected.upon(set_code, enter=S5_known, outputs=[B_got_code]) - - S0A_unknown.upon(allocate_code, enter=S1A_connecting, - outputs=[stash_code_length]) - S0B_unknown_connected.upon(allocate_code, enter=S1B_allocating, - outputs=[stash_code_length_and_RC_tx_allocate]) - S1A_connecting.upon(connected, enter=S1B_allocating, - outputs=[RC_tx_allocate]) - S1B_allocating.upon(lost, enter=S1A_connecting, outputs=[]) - S1B_allocating.upon(rx_allocated, enter=S5_known, - outputs=[generate_and_B_got_code]) - - S0A_unknown.upon(input_code, enter=S2_input_nameplate, - outputs=[start_input_and_L_refresh_nameplates]) - S0B_unknown_connected.upon(input_code, enter=S2_input_nameplate, - outputs=[start_input_and_L_refresh_nameplates]) - S2_input_nameplate.upon(update_nameplates, enter=S2_input_nameplate, - outputs=[L_refresh_nameplates]) - S2_input_nameplate.upon(got_nameplates, - enter=S2_input_nameplate, - outputs=[stash_nameplates]) - S2_input_nameplate.upon(claim_nameplate, enter=S3_input_code_no_wordlist, - outputs=[record_nameplate, N_set_nameplate]) - S2_input_nameplate.upon(connected, enter=S2_input_nameplate, outputs=[]) - S2_input_nameplate.upon(lost, enter=S2_input_nameplate, outputs=[]) - - S3_input_code_no_wordlist.upon(got_wordlist, - enter=S4_input_code_wordlist, - outputs=[stash_wordlist]) - S3_input_code_no_wordlist.upon(submit_words, enter=S5_known, - outputs=[submit_words_and_B_got_code]) - S3_input_code_no_wordlist.upon(connected, enter=S3_input_code_no_wordlist, - outputs=[]) - S3_input_code_no_wordlist.upon(lost, enter=S3_input_code_no_wordlist, - outputs=[]) - - S4_input_code_wordlist.upon(submit_words, enter=S5_known, - outputs=[submit_words_and_B_got_code]) - S4_input_code_wordlist.upon(connected, enter=S4_input_code_wordlist, - outputs=[]) - S4_input_code_wordlist.upon(lost, enter=S4_input_code_wordlist, - outputs=[]) - - S5_known.upon(connected, enter=S5_known, outputs=[]) - S5_known.upon(lost, enter=S5_known, outputs=[]) + S0_idle.upon(set_code, enter=S4_known, outputs=[do_set_code]) + S0_idle.upon(input_code, enter=S1_inputting_nameplate, + outputs=[do_start_input]) + S1_inputting_nameplate.upon(got_nameplate, enter=S2_inputting_words, + outputs=[do_middle_input]) + S2_inputting_words.upon(finished_input, enter=S4_known, + outputs=[do_finish_input]) + S0_idle.upon(allocate_code, enter=S3_allocating, outputs=[do_start_allocate]) + S3_allocating.upon(allocated, enter=S4_known, outputs=[do_finish_allocate]) diff --git a/src/wormhole/_input.py b/src/wormhole/_input.py index f742b95..858ad9a 100644 --- a/src/wormhole/_input.py +++ b/src/wormhole/_input.py @@ -16,7 +16,6 @@ class Input(object): def __attrs_post_init__(self): self._nameplate = None self._wordlist = None - self._claimed_waiter = None def wire(self, code, lister): self._C = _interfaces.ICode(code) @@ -25,23 +24,23 @@ class Input(object): @m.state(initial=True) def S0_idle(self): pass # pragma: no cover @m.state() - def S1_nameplate(self): pass # pragma: no cover + def S1_typing_nameplate(self): pass # pragma: no cover @m.state() - def S2_code_no_wordlist(self): pass # pragma: no cover + def S2_typing_code_no_wordlist(self): pass # pragma: no cover @m.state() - def S3_code_yes_wordlist(self): pass # pragma: no cover + def S3_typing_code_yes_wordlist(self): pass # pragma: no cover @m.state(terminal=True) def S4_done(self): pass # pragma: no cover # from Code @m.input() - def start(self): pass + def start(self, input_helper): pass # from Lister @m.input() def got_nameplates(self, nameplates): pass - # from Nameplate?? + # from Nameplate @m.input() def got_wordlist(self, wordlist): pass @@ -49,58 +48,62 @@ class Input(object): @m.input() def refresh_nameplates(self): pass @m.input() - def _choose_nameplate(self, nameplate): pass + def choose_nameplate(self, nameplate): pass @m.input() def choose_words(self, words): pass @m.output() - def L_refresh_nameplates(self): - self._L.refresh_nameplates() - @m.output() - def start_and_L_refresh_nameplates(self, input_helper): + def do_start(self, input_helper): self._input_helper = input_helper self._L.refresh_nameplates() @m.output() - def stash_wordlist_and_notify(self, wordlist): - self._wordlist = wordlist - if self._claimed_waiter: - self._claimed_waiter.callback(None) - del self._claimed_waiter + def do_refresh(self): + self._L.refresh_nameplates() @m.output() - def stash_nameplate(self, nameplate): + def do_nameplate(self, nameplate): self._nameplate = nameplate - @m.output() - def C_got_nameplate(self, nameplate): self._C.got_nameplate(nameplate) + @m.output() + def do_wordlist(self, wordlist): + self._wordlist = wordlist @m.output() - def finished(self, words): + def do_words(self, words): code = self._nameplate + "-" + words self._C.finished_input(code) - S0_idle.upon(start, enter=S1_nameplate, outputs=[L_refresh_nameplates]) - S1_nameplate.upon(refresh_nameplates, enter=S1_nameplate, - outputs=[L_refresh_nameplates]) - S1_nameplate.upon(_choose_nameplate, enter=S2_code_no_wordlist, - outputs=[stash_nameplate, C_got_nameplate]) - S2_code_no_wordlist.upon(got_wordlist, enter=S3_code_yes_wordlist, - outputs=[stash_wordlist_and_notify]) - S2_code_no_wordlist.upon(choose_words, enter=S4_done, outputs=[finished]) - S3_code_yes_wordlist.upon(choose_words, enter=S4_done, outputs=[finished]) + S0_idle.upon(start, enter=S1_typing_nameplate, outputs=[do_start]) + S1_typing_nameplate.upon(refresh_nameplates, enter=S1_typing_nameplate, + outputs=[do_refresh]) + S1_typing_nameplate.upon(choose_nameplate, enter=S2_typing_code_no_wordlist, + outputs=[do_nameplate]) + S2_typing_code_no_wordlist.upon(got_wordlist, + enter=S3_typing_code_yes_wordlist, + outputs=[do_wordlist]) + S2_typing_code_no_wordlist.upon(choose_words, enter=S4_done, + outputs=[do_words]) + S2_typing_code_no_wordlist.upon(got_nameplates, + enter=S2_typing_code_no_wordlist, outputs=[]) + S3_typing_code_yes_wordlist.upon(choose_words, enter=S4_done, + outputs=[do_words]) + S3_typing_code_yes_wordlist.upon(got_nameplates, + enter=S3_typing_code_yes_wordlist, + outputs=[]) + S4_done.upon(got_nameplates, enter=S4_done, outputs=[]) + S4_done.upon(got_wordlist, enter=S4_done, outputs=[]) # methods for the CodeInputHelper to use #refresh_nameplates/_choose_nameplate/choose_words: @m.input methods def get_nameplate_completions(self, prefix): + lp = len(prefix) completions = [] - for nameplate in self._nameplates - pass - def choose_nameplate(self, nameplate): - if self._claimed_waiter is not None: - raise X - d = self._claimed_waiter = defer.Deferred() - self._choose_nameplate(nameplate) + for nameplate in self._nameplates: + if nameplate.startswith(prefix): + completions.append(nameplate[lp:]) + return completions def get_word_completions(self, prefix): - pass - + if self._wordlist: + return self._wordlist.get_completions(prefix) + return [] diff --git a/src/wormhole/_interfaces.py b/src/wormhole/_interfaces.py index f21e0dd..11b562a 100644 --- a/src/wormhole/_interfaces.py +++ b/src/wormhole/_interfaces.py @@ -33,6 +33,13 @@ class ITiming(Interface): pass class ITorManager(Interface): pass +class IWordlist(Interface): + def choose_words(length): + """Randomly select LENGTH words, join them with hyphens, return the + result.""" + def get_completions(prefix): + """Return a list of all suffixes that could complete the given + prefix.""" class IJournal(Interface): # TODO: this needs to be public pass diff --git a/src/wormhole/_lister.py b/src/wormhole/_lister.py index dbd8d4b..8b49d91 100644 --- a/src/wormhole/_lister.py +++ b/src/wormhole/_lister.py @@ -1,10 +1,13 @@ from __future__ import print_function, absolute_import, unicode_literals from zope.interface import implementer +from attr import attrib +from attr.validators import provides from automat import MethodicalMachine from . import _interfaces @implementer(_interfaces.ILister) class Lister(object): + _timing = attrib(validator=provides(_interfaces.ITiming)) m = MethodicalMachine() @m.setTrace() def set_trace(): pass # pragma: no cover diff --git a/src/wormhole/_nameplate.py b/src/wormhole/_nameplate.py index 777098d..599573b 100644 --- a/src/wormhole/_nameplate.py +++ b/src/wormhole/_nameplate.py @@ -2,6 +2,7 @@ from __future__ import print_function, absolute_import, unicode_literals from zope.interface import implementer from automat import MethodicalMachine from . import _interfaces +from ._wordlist import PGPWordList @implementer(_interfaces.INameplate) class Nameplate(object): @@ -12,8 +13,9 @@ class Nameplate(object): def __init__(self): self._nameplate = None - def wire(self, mailbox, rendezvous_connector, terminator): + def wire(self, mailbox, input, rendezvous_connector, terminator): self._M = _interfaces.IMailbox(mailbox) + self._I = _interfaces.IInput(input) self._RC = _interfaces.IRendezvousConnector(rendezvous_connector) self._T = _interfaces.ITerminator(terminator) @@ -96,6 +98,11 @@ class Nameplate(object): # when invoked via M.connected(), we must use the stored nameplate self._RC.tx_claim(self._nameplate) @m.output() + def I_got_wordlist(self, mailbox): + # TODO select wordlist based on nameplate properties, in rx_claimed + wordlist = PGPWordList() + self._I.got_wordlist(wordlist) + @m.output() def M_got_mailbox(self, mailbox): self._M.got_mailbox(mailbox) @m.output() @@ -120,7 +127,7 @@ class Nameplate(object): S2A.upon(connected, enter=S2B, outputs=[RC_tx_claim]) S2A.upon(close, enter=S4A, outputs=[]) S2B.upon(lost, enter=S2A, outputs=[]) - S2B.upon(rx_claimed, enter=S3B, outputs=[M_got_mailbox]) + S2B.upon(rx_claimed, enter=S3B, outputs=[I_got_wordlist, M_got_mailbox]) S2B.upon(close, enter=S4B, outputs=[RC_tx_release]) S3A.upon(connected, enter=S3B, outputs=[]) diff --git a/src/wormhole/_wordlist.py b/src/wormhole/_wordlist.py new file mode 100644 index 0000000..6c266a6 --- /dev/null +++ b/src/wormhole/_wordlist.py @@ -0,0 +1,182 @@ +from __future__ import unicode_literals +import os + +# The PGP Word List, which maps bytes to phonetically-distinct words. There +# are two lists, even and odd, and encodings should alternate between then to +# detect dropped words. https://en.wikipedia.org/wiki/PGP_Words + +# Thanks to Warren Guy for transcribing them: +# https://github.com/warrenguy/javascript-pgp-word-list + +from binascii import unhexlify + +raw_words = { +'00': ['aardvark', 'adroitness'], '01': ['absurd', 'adviser'], +'02': ['accrue', 'aftermath'], '03': ['acme', 'aggregate'], +'04': ['adrift', 'alkali'], '05': ['adult', 'almighty'], +'06': ['afflict', 'amulet'], '07': ['ahead', 'amusement'], +'08': ['aimless', 'antenna'], '09': ['Algol', 'applicant'], +'0A': ['allow', 'Apollo'], '0B': ['alone', 'armistice'], +'0C': ['ammo', 'article'], '0D': ['ancient', 'asteroid'], +'0E': ['apple', 'Atlantic'], '0F': ['artist', 'atmosphere'], +'10': ['assume', 'autopsy'], '11': ['Athens', 'Babylon'], +'12': ['atlas', 'backwater'], '13': ['Aztec', 'barbecue'], +'14': ['baboon', 'belowground'], '15': ['backfield', 'bifocals'], +'16': ['backward', 'bodyguard'], '17': ['banjo', 'bookseller'], +'18': ['beaming', 'borderline'], '19': ['bedlamp', 'bottomless'], +'1A': ['beehive', 'Bradbury'], '1B': ['beeswax', 'bravado'], +'1C': ['befriend', 'Brazilian'], '1D': ['Belfast', 'breakaway'], +'1E': ['berserk', 'Burlington'], '1F': ['billiard', 'businessman'], +'20': ['bison', 'butterfat'], '21': ['blackjack', 'Camelot'], +'22': ['blockade', 'candidate'], '23': ['blowtorch', 'cannonball'], +'24': ['bluebird', 'Capricorn'], '25': ['bombast', 'caravan'], +'26': ['bookshelf', 'caretaker'], '27': ['brackish', 'celebrate'], +'28': ['breadline', 'cellulose'], '29': ['breakup', 'certify'], +'2A': ['brickyard', 'chambermaid'], '2B': ['briefcase', 'Cherokee'], +'2C': ['Burbank', 'Chicago'], '2D': ['button', 'clergyman'], +'2E': ['buzzard', 'coherence'], '2F': ['cement', 'combustion'], +'30': ['chairlift', 'commando'], '31': ['chatter', 'company'], +'32': ['checkup', 'component'], '33': ['chisel', 'concurrent'], +'34': ['choking', 'confidence'], '35': ['chopper', 'conformist'], +'36': ['Christmas', 'congregate'], '37': ['clamshell', 'consensus'], +'38': ['classic', 'consulting'], '39': ['classroom', 'corporate'], +'3A': ['cleanup', 'corrosion'], '3B': ['clockwork', 'councilman'], +'3C': ['cobra', 'crossover'], '3D': ['commence', 'crucifix'], +'3E': ['concert', 'cumbersome'], '3F': ['cowbell', 'customer'], +'40': ['crackdown', 'Dakota'], '41': ['cranky', 'decadence'], +'42': ['crowfoot', 'December'], '43': ['crucial', 'decimal'], +'44': ['crumpled', 'designing'], '45': ['crusade', 'detector'], +'46': ['cubic', 'detergent'], '47': ['dashboard', 'determine'], +'48': ['deadbolt', 'dictator'], '49': ['deckhand', 'dinosaur'], +'4A': ['dogsled', 'direction'], '4B': ['dragnet', 'disable'], +'4C': ['drainage', 'disbelief'], '4D': ['dreadful', 'disruptive'], +'4E': ['drifter', 'distortion'], '4F': ['dropper', 'document'], +'50': ['drumbeat', 'embezzle'], '51': ['drunken', 'enchanting'], +'52': ['Dupont', 'enrollment'], '53': ['dwelling', 'enterprise'], +'54': ['eating', 'equation'], '55': ['edict', 'equipment'], +'56': ['egghead', 'escapade'], '57': ['eightball', 'Eskimo'], +'58': ['endorse', 'everyday'], '59': ['endow', 'examine'], +'5A': ['enlist', 'existence'], '5B': ['erase', 'exodus'], +'5C': ['escape', 'fascinate'], '5D': ['exceed', 'filament'], +'5E': ['eyeglass', 'finicky'], '5F': ['eyetooth', 'forever'], +'60': ['facial', 'fortitude'], '61': ['fallout', 'frequency'], +'62': ['flagpole', 'gadgetry'], '63': ['flatfoot', 'Galveston'], +'64': ['flytrap', 'getaway'], '65': ['fracture', 'glossary'], +'66': ['framework', 'gossamer'], '67': ['freedom', 'graduate'], +'68': ['frighten', 'gravity'], '69': ['gazelle', 'guitarist'], +'6A': ['Geiger', 'hamburger'], '6B': ['glitter', 'Hamilton'], +'6C': ['glucose', 'handiwork'], '6D': ['goggles', 'hazardous'], +'6E': ['goldfish', 'headwaters'], '6F': ['gremlin', 'hemisphere'], +'70': ['guidance', 'hesitate'], '71': ['hamlet', 'hideaway'], +'72': ['highchair', 'holiness'], '73': ['hockey', 'hurricane'], +'74': ['indoors', 'hydraulic'], '75': ['indulge', 'impartial'], +'76': ['inverse', 'impetus'], '77': ['involve', 'inception'], +'78': ['island', 'indigo'], '79': ['jawbone', 'inertia'], +'7A': ['keyboard', 'infancy'], '7B': ['kickoff', 'inferno'], +'7C': ['kiwi', 'informant'], '7D': ['klaxon', 'insincere'], +'7E': ['locale', 'insurgent'], '7F': ['lockup', 'integrate'], +'80': ['merit', 'intention'], '81': ['minnow', 'inventive'], +'82': ['miser', 'Istanbul'], '83': ['Mohawk', 'Jamaica'], +'84': ['mural', 'Jupiter'], '85': ['music', 'leprosy'], +'86': ['necklace', 'letterhead'], '87': ['Neptune', 'liberty'], +'88': ['newborn', 'maritime'], '89': ['nightbird', 'matchmaker'], +'8A': ['Oakland', 'maverick'], '8B': ['obtuse', 'Medusa'], +'8C': ['offload', 'megaton'], '8D': ['optic', 'microscope'], +'8E': ['orca', 'microwave'], '8F': ['payday', 'midsummer'], +'90': ['peachy', 'millionaire'], '91': ['pheasant', 'miracle'], +'92': ['physique', 'misnomer'], '93': ['playhouse', 'molasses'], +'94': ['Pluto', 'molecule'], '95': ['preclude', 'Montana'], +'96': ['prefer', 'monument'], '97': ['preshrunk', 'mosquito'], +'98': ['printer', 'narrative'], '99': ['prowler', 'nebula'], +'9A': ['pupil', 'newsletter'], '9B': ['puppy', 'Norwegian'], +'9C': ['python', 'October'], '9D': ['quadrant', 'Ohio'], +'9E': ['quiver', 'onlooker'], '9F': ['quota', 'opulent'], +'A0': ['ragtime', 'Orlando'], 'A1': ['ratchet', 'outfielder'], +'A2': ['rebirth', 'Pacific'], 'A3': ['reform', 'pandemic'], +'A4': ['regain', 'Pandora'], 'A5': ['reindeer', 'paperweight'], +'A6': ['rematch', 'paragon'], 'A7': ['repay', 'paragraph'], +'A8': ['retouch', 'paramount'], 'A9': ['revenge', 'passenger'], +'AA': ['reward', 'pedigree'], 'AB': ['rhythm', 'Pegasus'], +'AC': ['ribcage', 'penetrate'], 'AD': ['ringbolt', 'perceptive'], +'AE': ['robust', 'performance'], 'AF': ['rocker', 'pharmacy'], +'B0': ['ruffled', 'phonetic'], 'B1': ['sailboat', 'photograph'], +'B2': ['sawdust', 'pioneer'], 'B3': ['scallion', 'pocketful'], +'B4': ['scenic', 'politeness'], 'B5': ['scorecard', 'positive'], +'B6': ['Scotland', 'potato'], 'B7': ['seabird', 'processor'], +'B8': ['select', 'provincial'], 'B9': ['sentence', 'proximate'], +'BA': ['shadow', 'puberty'], 'BB': ['shamrock', 'publisher'], +'BC': ['showgirl', 'pyramid'], 'BD': ['skullcap', 'quantity'], +'BE': ['skydive', 'racketeer'], 'BF': ['slingshot', 'rebellion'], +'C0': ['slowdown', 'recipe'], 'C1': ['snapline', 'recover'], +'C2': ['snapshot', 'repellent'], 'C3': ['snowcap', 'replica'], +'C4': ['snowslide', 'reproduce'], 'C5': ['solo', 'resistor'], +'C6': ['southward', 'responsive'], 'C7': ['soybean', 'retraction'], +'C8': ['spaniel', 'retrieval'], 'C9': ['spearhead', 'retrospect'], +'CA': ['spellbind', 'revenue'], 'CB': ['spheroid', 'revival'], +'CC': ['spigot', 'revolver'], 'CD': ['spindle', 'sandalwood'], +'CE': ['spyglass', 'sardonic'], 'CF': ['stagehand', 'Saturday'], +'D0': ['stagnate', 'savagery'], 'D1': ['stairway', 'scavenger'], +'D2': ['standard', 'sensation'], 'D3': ['stapler', 'sociable'], +'D4': ['steamship', 'souvenir'], 'D5': ['sterling', 'specialist'], +'D6': ['stockman', 'speculate'], 'D7': ['stopwatch', 'stethoscope'], +'D8': ['stormy', 'stupendous'], 'D9': ['sugar', 'supportive'], +'DA': ['surmount', 'surrender'], 'DB': ['suspense', 'suspicious'], +'DC': ['sweatband', 'sympathy'], 'DD': ['swelter', 'tambourine'], +'DE': ['tactics', 'telephone'], 'DF': ['talon', 'therapist'], +'E0': ['tapeworm', 'tobacco'], 'E1': ['tempest', 'tolerance'], +'E2': ['tiger', 'tomorrow'], 'E3': ['tissue', 'torpedo'], +'E4': ['tonic', 'tradition'], 'E5': ['topmost', 'travesty'], +'E6': ['tracker', 'trombonist'], 'E7': ['transit', 'truncated'], +'E8': ['trauma', 'typewriter'], 'E9': ['treadmill', 'ultimate'], +'EA': ['Trojan', 'undaunted'], 'EB': ['trouble', 'underfoot'], +'EC': ['tumor', 'unicorn'], 'ED': ['tunnel', 'unify'], +'EE': ['tycoon', 'universe'], 'EF': ['uncut', 'unravel'], +'F0': ['unearth', 'upcoming'], 'F1': ['unwind', 'vacancy'], +'F2': ['uproot', 'vagabond'], 'F3': ['upset', 'vertigo'], +'F4': ['upshot', 'Virginia'], 'F5': ['vapor', 'visitor'], +'F6': ['village', 'vocalist'], 'F7': ['virus', 'voyager'], +'F8': ['Vulcan', 'warranty'], 'F9': ['waffle', 'Waterloo'], +'FA': ['wallet', 'whimsical'], 'FB': ['watchword', 'Wichita'], +'FC': ['wayside', 'Wilmington'], 'FD': ['willow', 'Wyoming'], +'FE': ['woodlark', 'yesteryear'], 'FF': ['Zulu', 'Yucatan'] +}; + +byte_to_even_word = dict([(unhexlify(k.encode("ascii")), both_words[0]) + for k,both_words + in raw_words.items()]) + +byte_to_odd_word = dict([(unhexlify(k.encode("ascii")), both_words[1]) + for k,both_words + in raw_words.items()]) + +even_words_lowercase, odd_words_lowercase = set(), set() + +for k,both_words in raw_words.items(): + even_word, odd_word = both_words + even_words_lowercase.add(even_word.lower()) + odd_words_lowercase.add(odd_word.lower()) + +class PGPWordList(object): + def get_completions(self, prefix): + # start with the odd words + if prefix.count("-") % 2 == 0: + words = odd_words_lowercase + else: + words = even_words_lowercase + last_partial_word = prefix.split("-")[-1] + lp = len(last_partial_word) + completions = [] + for word in words: + if word.startswith(prefix): + completions.append(word[lp:]) + return completions + + def choose_words(self, length): + words = [] + for i in range(length): + # we start with an "odd word" + if i % 2 == 0: + words.append(byte_to_odd_word[os.urandom(1)].lower()) + else: + words.append(byte_to_even_word[os.urandom(1)].lower()) + return "-".join(words)