rename SymmetricWormhole to just "Wormhole"

Update docs too. Now both blocking/ and twisted/ use "Wormhole".
This commit is contained in:
Brian Warner 2015-07-24 17:47:46 -07:00
parent d8ca850d1a
commit efd6d27cc6
4 changed files with 67 additions and 47 deletions

View File

@ -113,14 +113,11 @@ All four commands accept:
## Library ## Library
The `wormhole` module makes it possible for other applications to use these The `wormhole` module makes it possible for other applications to use these
code-protected channels. This includes blocking/synchronous support (for an code-protected channels. This includes blocking/synchronous support and
asymmetric pair of "initiator" and "receiver" endpoints), and async/Twisted async/Twisted support, both for a symmetric scheme. The main module is named
support (for a symmetric scheme). The main module is named
`wormhole.blocking.transcribe`, to reflect that it is for `wormhole.blocking.transcribe`, to reflect that it is for
synchronous/blocking code, and uses a PAKE mode whereby one user transcribes synchronous/blocking code, and uses a PAKE mode whereby one user transcribes
their code to the other. (internal names may change in the future). The their code to the other. (internal names may change in the future).
synchronous support uses distinctive sides: one `Initiator`, and one
`Receiver`.
The file-transfer tools use a second module named The file-transfer tools use a second module named
`wormhole.blocking.transit`, which provides an encrypted record-pipe. It `wormhole.blocking.transit`, which provides an encrypted record-pipe. It

View File

@ -12,26 +12,48 @@ server" that relays information from one machine to the other.
## Modes ## Modes
This library will eventually offer multiple modes. This library will eventually offer multiple modes. For now, only "transcribe
mode" is available.
The first mode provided is "transcribe" mode. In this mode, one machine goes Transcribe mode has two variants. In the "machine-generated" variant, the
first, and is called the "initiator". The initiator contacts the rendezvous "initiator" machine creates the invitation code, displays it to the first
server and allocates a "channel ID", which is a small integer. The initiator user, they convey it (somehow) to the second user, who transcribes it into
then displays the "invitation code", which is the channel-ID plus a few the second ("receiver") machine. In the "human-generated" variant, the two
secret words. The user copies the invitation code to the second machine, humans come up with the code (possibly without computers), then later
called the "receiver". The receiver connects to the rendezvous server, and transcribe it into both machines.
uses the invitation code to contact the initiator. They agree upon an
encryption key, and exchange a small encrypted+authenticated data message. When the initator machine generates the invitation code, the initiator
contacts the rendezvous server and allocates a "channel ID", which is a small
integer. The initiator then displays the invitation code, which is the
channel-ID plus a few secret words. The user copies the code to the second
machine. The receiver machine connects to the rendezvous server, and uses the
invitation code to contact the initiator. They agree upon an encryption key,
and exchange a small encrypted+authenticated data message.
When the humans create an invitation code out-of-band, they are responsible
for choosing an unused channel-ID (simply picking a random 3-or-more digit
number is probably enough), and some random words. The invitation code uses
the same format in either variant: channel-ID, a hyphen, and an arbitrary
string.
The two machines participating in the wormhole setup are not distinguished:
it doesn't matter which one goes first, and both use the same Wormhole class.
In the first variant, one side calls `get_code()` while the other calls
`set_code()`. In the second variant, both sides call `set_code()`. Note that
this is not true for the "Transit" protocol used for bulk data-transfer: the
Transit class currently distinguishes "Sender" from "Receiver", so the
programs on each side must have some way to decide (ahead of time) which is
which.
## Examples ## Examples
The synchronous+blocking flow looks like this: The synchronous+blocking flow looks like this:
```python ```python
from wormhole.transcribe import Initiator from wormhole.transcribe import Wormhole
from wormhole.public_relay import RENDEZVOUS_RELAY from wormhole.public_relay import RENDEZVOUS_RELAY
mydata = b"initiator's data" mydata = b"initiator's data"
i = Initiator("appid", RENDEZVOUS_RELAY) i = Wormhole("appid", RENDEZVOUS_RELAY)
code = i.get_code() code = i.get_code()
print("Invitation Code: %s" % code) print("Invitation Code: %s" % code)
theirdata = i.get_data(mydata) theirdata = i.get_data(mydata)
@ -40,11 +62,11 @@ print("Their data: %s" % theirdata.decode("ascii"))
```python ```python
import sys import sys
from wormhole.transcribe import Receiver from wormhole.transcribe import Wormhole
from wormhole.public_relay import RENDEZVOUS_RELAY from wormhole.public_relay import RENDEZVOUS_RELAY
mydata = b"receiver's data" mydata = b"receiver's data"
code = sys.argv[1] code = sys.argv[1]
r = Receiver("appid", RENDEZVOUS_RELAY) r = Wormhole("appid", RENDEZVOUS_RELAY)
r.set_code(code) r.set_code(code)
theirdata = r.get_data(mydata) theirdata = r.get_data(mydata)
print("Their data: %s" % theirdata.decode("ascii")) print("Their data: %s" % theirdata.decode("ascii"))
@ -57,9 +79,9 @@ The Twisted-friendly flow looks like this:
```python ```python
from twisted.internet import reactor from twisted.internet import reactor
from wormhole.public_relay import RENDEZVOUS_RELAY from wormhole.public_relay import RENDEZVOUS_RELAY
from wormhole.twisted.transcribe import SymmetricWormhole from wormhole.twisted.transcribe import Wormhole
outbound_message = b"outbound data" outbound_message = b"outbound data"
w1 = SymmetricWormhole("appid", RENDEZVOUS_RELAY) w1 = Wormhole("appid", RENDEZVOUS_RELAY)
d = w1.get_code() d = w1.get_code()
def _got_code(code): def _got_code(code):
print "Invitation Code:", code print "Invitation Code:", code
@ -75,9 +97,10 @@ reactor.run()
On the other side, you call `set_code()` instead of waiting for `get_code()`: On the other side, you call `set_code()` instead of waiting for `get_code()`:
```python ```python
w2 = SymmetricWormhole("appid", RENDEZVOUS_RELAY) w2 = Wormhole("appid", RENDEZVOUS_RELAY)
w2.set_code(code) w2.set_code(code)
d = w2.get_data(my_message) d = w2.get_data(my_message)
...
``` ```
You can call `d=w.get_verifier()` before `get_data()`: this will perform the You can call `d=w.get_verifier()` before `get_data()`: this will perform the
@ -90,14 +113,14 @@ pausing.
## Generating the Invitation Code ## Generating the Invitation Code
In most situations, the Initiator will call `i.get_code()` to generate the In most situations, the "sending" or "initiating" side will call
invitation code. This returns a string in the form `NNN-code-words`. The `i.get_code()` to generate the invitation code. This returns a string in the
numeric "NNN" prefix is the "channel id", and is a short integer allocated by form `NNN-code-words`. The numeric "NNN" prefix is the "channel id", and is a
talking to the rendezvous server. The rest is a randomly-generated selection short integer allocated by talking to the rendezvous server. The rest is a
from the PGP wordlist, providing a default of 16 bits of entropy. The randomly-generated selection from the PGP wordlist, providing a default of 16
initiating program should display this code to the user, who should bits of entropy. The initiating program should display this code to the user,
transcribe it to the receiving user, who gives it to the Receiver object by who should transcribe it to the receiving user, who gives it to the Receiver
calling `r.set_code()`. The receiving program can also use object by calling `r.set_code()`. The receiving program can also use
`input_code_with_completion()` to use a readline-based input function: this `input_code_with_completion()` to use a readline-based input function: this
offers tab completion of allocated channel-ids and known codewords. offers tab completion of allocated channel-ids and known codewords.
@ -168,12 +191,12 @@ Both have defaults suitable for face-to-face realtime setup environments.
TODO: only the Twisted form supports serialization so far TODO: only the Twisted form supports serialization so far
You may not be able to hold the Initiator/Receiver object in memory for the You may not be able to hold the Wormhole object in memory for the whole sync
whole sync process: maybe you allow it to wait for several days, but the process: maybe you allow it to wait for several days, but the program will be
program will be restarted during that time. To support this, you can persist restarted during that time. To support this, you can persist the state of the
the state of the object by calling `data = w.serialize()`, which will return object by calling `data = w.serialize()`, which will return a printable
a printable bytestring (the JSON-encoding of a small dictionary). To restore, bytestring (the JSON-encoding of a small dictionary). To restore, use the
use the `from_serialized(data)` classmethod (e.g. `w = `from_serialized(data)` classmethod (e.g. `w =
SymmetricWormhole.from_serialized(data)`). SymmetricWormhole.from_serialized(data)`).
There is exactly one point at which you can serialize the wormhole: *after* There is exactly one point at which you can serialize the wormhole: *after*

View File

@ -3,7 +3,7 @@ from twisted.trial import unittest
from twisted.internet import defer from twisted.internet import defer
from twisted.application import service from twisted.application import service
from ..servers.relay import RelayServer from ..servers.relay import RelayServer
from ..twisted.transcribe import SymmetricWormhole, UsageError from ..twisted.transcribe import Wormhole, UsageError
from ..twisted.util import allocate_ports from ..twisted.util import allocate_ports
from .. import __version__ from .. import __version__
#from twisted.python import log #from twisted.python import log
@ -31,8 +31,8 @@ class Basic(unittest.TestCase):
def test_basic(self): def test_basic(self):
appid = "appid" appid = "appid"
w1 = SymmetricWormhole(appid, self.relayurl) w1 = Wormhole(appid, self.relayurl)
w2 = SymmetricWormhole(appid, self.relayurl) w2 = Wormhole(appid, self.relayurl)
d = w1.get_code() d = w1.get_code()
def _got_code(code): def _got_code(code):
w2.set_code(code) w2.set_code(code)
@ -52,8 +52,8 @@ class Basic(unittest.TestCase):
def test_fixed_code(self): def test_fixed_code(self):
appid = "appid" appid = "appid"
w1 = SymmetricWormhole(appid, self.relayurl) w1 = Wormhole(appid, self.relayurl)
w2 = SymmetricWormhole(appid, self.relayurl) w2 = Wormhole(appid, self.relayurl)
w1.set_code("123-purple-elephant") w1.set_code("123-purple-elephant")
w2.set_code("123-purple-elephant") w2.set_code("123-purple-elephant")
d1 = w1.get_data("data1") d1 = w1.get_data("data1")
@ -71,22 +71,22 @@ class Basic(unittest.TestCase):
def test_errors(self): def test_errors(self):
appid = "appid" appid = "appid"
w1 = SymmetricWormhole(appid, self.relayurl) w1 = Wormhole(appid, self.relayurl)
self.assertRaises(UsageError, w1.get_verifier) self.assertRaises(UsageError, w1.get_verifier)
self.assertRaises(UsageError, w1.get_data, "data") self.assertRaises(UsageError, w1.get_data, "data")
w1.set_code("123-purple-elephant") w1.set_code("123-purple-elephant")
self.assertRaises(UsageError, w1.set_code, "123-nope") self.assertRaises(UsageError, w1.set_code, "123-nope")
self.assertRaises(UsageError, w1.get_code) self.assertRaises(UsageError, w1.get_code)
w2 = SymmetricWormhole(appid, self.relayurl) w2 = Wormhole(appid, self.relayurl)
d = w2.get_code() d = w2.get_code()
self.assertRaises(UsageError, w2.get_code) self.assertRaises(UsageError, w2.get_code)
return d return d
def test_serialize(self): def test_serialize(self):
appid = "appid" appid = "appid"
w1 = SymmetricWormhole(appid, self.relayurl) w1 = Wormhole(appid, self.relayurl)
self.assertRaises(UsageError, w1.serialize) # too early self.assertRaises(UsageError, w1.serialize) # too early
w2 = SymmetricWormhole(appid, self.relayurl) w2 = Wormhole(appid, self.relayurl)
d = w1.get_code() d = w1.get_code()
def _got_code(code): def _got_code(code):
self.assertRaises(UsageError, w2.serialize) # too early self.assertRaises(UsageError, w2.serialize) # too early
@ -96,7 +96,7 @@ class Basic(unittest.TestCase):
self.assertEqual(type(s), type("")) self.assertEqual(type(s), type(""))
unpacked = json.loads(s) # this is supposed to be JSON unpacked = json.loads(s) # this is supposed to be JSON
self.assertEqual(type(unpacked), dict) self.assertEqual(type(unpacked), dict)
new_w1 = SymmetricWormhole.from_serialized(s) new_w1 = Wormhole.from_serialized(s)
d1 = new_w1.get_data("data1") d1 = new_w1.get_data("data1")
d2 = w2.get_data("data2") d2 = w2.get_data("data2")
return defer.DeferredList([d1,d2], fireOnOneErrback=False) return defer.DeferredList([d1,d2], fireOnOneErrback=False)

View File

@ -43,7 +43,7 @@ class DataProducer:
pass pass
class SymmetricWormhole: class Wormhole:
motd_displayed = False motd_displayed = False
version_warning_displayed = False version_warning_displayed = False