File-Transfer Protocol

The bin/wormhole tool uses a Wormhole to establish a connection, then speaks a file-transfer -specific protocol over that Wormhole to decide how to transfer the data. This application-layer protocol is described here.

All application-level messages are dictionaries, which are JSON-encoded and and UTF-8 encoded before being handed to wormhole.send (which then encrypts them before sending through the rendezvous server to the peer).

Sender

wormhole send has two main modes: file/directory (which requires a non-wormhole Transit connection), or text (which does not).

If the sender is doing files or directories, its first message contains just a transit key, whose value is a dictionary with abilities-v1 and hints-v1 keys. These are given to the Transit object, described below.

Then (for both files/directories and text) it sends a message with an offer key. The offer contains a single key, exactly one of (message, file, or directory). For message, the value is the message being sent. For file and directory, it contains a dictionary with additional information:

  • message: the text message, for text-mode
  • file: for file-mode, a dict with filename and filesize
  • directory: for directory-mode, a dict with:
  • mode: the compression mode, currently always zipfile/deflated
  • dirname
  • zipsize: integer, size of the transmitted data in bytes
  • numbytes: integer, estimated total size of the uncompressed directory
  • numfiles: integer, number of files+directories being sent

The sender runs a loop where it waits for similar dictionary-shaped messages from the recipient, and processes them. It reacts to the following keys:

  • error: use the value to throw a TransferError and terminates
  • transit: use the value to build the Transit instance
  • answer:
  • if message_ack: ok is in the value (we're in text-mode), then exit with success
  • if file_ack: ok in the value (and we're in file/directory mode), then wait for Transit to connect, then send the file through Transit, then wait for an ack (via Transit), then exit

The sender can handle all of these keys in the same message, or spaced out over multiple ones. It will ignore any keys it doesn't recognize, and will completely ignore messages that don't contain any recognized key. The only constraint is that the message containing message_ack or file_ack is the last one: it will stop looking for wormhole messages at that point.

Recipient

wormhole receive is used for both file/directory-mode and text-mode: it learns which is being used from the offer message.

The recipient enters a loop where it processes the following keys from each received message:

  • error: if present in any message, the recipient raises TransferError (with the value) and exits immediately (before processing any other keys)
  • transit: the value is used to build the Transit instance
  • offer: parse the offer:
  • message: accept the message and terminate
  • file: connect a Transit instance, wait for it to deliver the indicated number of bytes, then write them to the target filename
  • directory: as with file, but unzip the bytes into the target directory

Transit

The Wormhole API does not currently provide for large-volume data transfer (this feature will be added to a future version, under the name "Dilated Wormhole"). For now, bulk data is sent through a "Transit" object, which does not use the Rendezvous Server. Instead, it tries to establish a direct TCP connection from sender to recipient (or vice versa). If that fails, both sides connect to a "Transit Relay", a very simple Server that just glues two TCP sockets together when asked.

The Transit object is created with a key (the same key on each side), and all data sent through it will be encrypted with a derivation of that key. The transit key is also used to derive handshake messages which are used to make sure we're talking to the right peer, and to help the Transit Relay match up the two client connections. Unlike Wormhole objects (which are symmetric), Transit objects come in pairs: one side is the Sender, and the other is the Receiver.

Like Wormhole, Transit provides an encrypted record pipe. If you call .send() with 40 bytes, the other end will see a .gotData() with exactly 40 bytes: no splitting, merging, dropping, or re-ordering. The Transit object also functions as a twisted Producer/Consumer, so it can be connected directly to file-readers and writers, and does flow-control properly.

Most of the complexity of the Transit object has to do with negotiating and scheduling likely targets for the TCP connection.

Each Transit object has a set of "abilities". These are outbound connection mechanisms that the client is capable of using. The basic CLI tool (running on a normal computer) has two abilities: direct-tcp-v1 and relay-v1.

  • direct-tcp-v1 indicates that it can make outbound TCP connections to a requested host and port number. "v1" means that the first thing sent over these connections is a specific derived handshake message, e.g. transit sender HEXHEX ready\n\n.
  • relay-v1 indicates it can connect to the Transit Relay and speak the matching protocol (in which the first message is please relay HEXHEX for side HEX\n, and the relay might eventually say ok\n).

Future implementations may have additional abilities, such as connecting directly to Tor onion services, I2P services, WebSockets, WebRTC, or other connection technologies. Implementations on some platforms (such as web browsers) may lack direct-tcp-v1 or relay-v1.

While it isn't strictly necessary for both sides to emit what they're capable of using, it does help performance: a Tor Onion-service -capable receiver shouldn't spend the time and energy to set up an onion service if the sender can't use it.

After learning the abilities of its peer, the Transit object can create a list of "hints", which are endpoints that the peer should try to connect to. Each hint will fall under one of the abilities that the peer indicated it could use. Hints have types like direct-tcp-v1, tor-tcp-v1, and relay-v1. Hints are encoded into dictionaries (with a mandatory type key, and other keys as necessary):

  • direct-tcp-v1 {hostname:, port:, priority:?}
  • tor-tcp-v1 {hostname:, port:, priority:?}
  • relay-v1 {hints: [{hostname:, port:, priority:?}, ..]}

For example, if our peer can use direct-tcp-v1, then our Transit object will deduce our local IP addresses (unless forbidden, i.e. we're using Tor), listen on a TCP port, then send a list of direct-tcp-v1 hints pointing at all of them. If our peer can use relay-v1, then we'll connect to our relay server and give the peer a hint to the same.

tor-tcp-v1 hints indicate an Onion service, which cannot be reached without Tor. direct-tcp-v1 hints can be reached with direct TCP connections (unless forbidden) or by proxying through Tor. Onion services take about 30 seconds to spin up, but bypass NAT, allowing two clients behind NAT boxes to connect without a transit relay (really, the entire Tor network is acting as a relay).

The file-transfer application uses transit messages to convey these abilities and hints from one Transit object to the other. After updating the Transit objects, it then asks the Transit object to connect, whereupon Transit will try to connect to all the hints that it can, and will use the first one that succeeds.

The file-transfer application, when actually sending file/directory data, will close the Wormhole as soon as it has enough information to begin opening the Transit connection. The final ack of the received data is sent through the Transit object, as a UTF-8-encoded JSON-encoded dictionary with ack: ok and sha256: HEXHEX containing the hash of the received data.

Future Extensions

Transit will be extended to provide other connection techniques:

  • WebSocket: usable by web browsers, not too hard to use by normal computers, requires direct (or relayed) TCP connection
  • WebRTC: usable by web browsers, hard-but-technically-possible to use by normal computers, provides NAT hole-punching for "free"
  • (web browsers cannot make direct TCP connections, so interop between browsers and CLI clients will either require adding WebSocket to CLI, or a relay that is capable of speaking/bridging both)
  • I2P: like Tor, but not capable of proxying to normal TCP hints.
  • ICE-mediated STUN/STUNT: NAT hole-punching, assisted somewhat by a server that can tell you your external IP address and port. Maybe implemented as a uTP stream (which is UDP based, and thus easier to get through NAT).

The file-transfer protocol will be extended too:

  • "command mode": establish the connection, then figure out what we want to use it for, allowing multiple files to be exchanged, in either direction. This is to support a GUI that lets you open the wormhole, then drop files into it on either end.
  • some Transit messages being sent early, so ports and Onion services can be spun up earlier, to reduce overall waiting time
  • transit messages being sent in multiple phases: maybe the transit connection can progress while waiting for the user to confirm the transfer

The hope is that by sending everything in dictionaries and multiple messages, there will be enough wiggle room to make these extensions in a backwards-compatible way. For example, to add "command mode" while allowing the fancy new (as yet unwritten) GUI client to interoperate with old-fashioned one-file-only CLI clients, we need the GUI tool to send an "I'm capable of command mode" in the VERSION message, and look for it in the received VERSION. If it isn't present, it will either expect to see an offer (if the other side is sending), or nothing (if it is waiting to receive), and can explain the situation to the user accordingly. It might show a locked set of bars over the wormhole graphic to mean "cannot send", or a "waiting to send them a file" overlay for send-only.