portals 0.0.9

  • Readme
  • Changelog
  • Example
  • Installing
  • new62

⚠️ This package is still in technical preview. The API may change substantially in the future and it's not safe to use this package in production yet – several features like reconnecting when the network is lost, or using a transfer server if the two devices can't see each other still need to be implemented.


Portals are strongly encrypted peer-to-peer connections. Inspired by Magic Wormhole.

TODO: Flutter web & app demo

Features #

❤️ Easy to use: Portals connect by letting users transcribe short human-readable codes from one device to another. TODO: There's a beautiful pre-built UI for Flutter.

🔒 Secure: Strong end-to-end encryption using Spake2 is built in. Man-in-the-middle attacks are virtually impossible because both sides share a secret from the beginning.

Fast: Data is transferred using peer-to-peer connections whenever possible. That makes portals incredibly fast when used on the same wifi or in the same geographic area.

🎈 Lightweight: There are no native dependencies. That also means, portals work anywhere where Dart runs: on mobile, desktop & the web. Portals use lightweight WebSockets to communicate.

How to use #

Create a portal #

To connect two devices, you need to create a portal on each of them.

var portal = Portal(appId: 'github.com/marcelgarus/portals');

The appId can be any arbitrary string used only by your application. It's recommended to use a url.

Optionally you can pass in an info string containing meta-information like your app version or something else. It will be exchanged as soon as the portals are linked.

var portal = Portal(
  appId: 'github.com/marcelgarus/portals',
  info: json.encode({ 'app_version': '1.0.0', ... }),
);
// Later, when linked:
print(portal.remoteInfo);

Set up the portal #

💙 Flutter

There's a beautiful pre-built UI for Flutter that you can find nowhere yet. TODO

🖥️ Command line

TODO

🎯 Pure Dart

On the first device, open the portal. It will return a phrase that uniquely identifies it among other portals using your appId:

String phrase = await portal.open();
// TODO: Show the phrase to the user.
String key = await portal.waitForLink();

Let the user transcribe the phrase to the second user in the real world. The second user can then link the two portals:

// TODO: Let the user enter the phrase.
String key = await portal.openAndLinkTo(phrase);

Now the two portals are linked. Optionally, you can let the users compare the key to completely rule out man-in-the-middle attacks.

In the background, both clients try to establish a peer-to-peer connection to each other. Wait for it on both sides by calling:

await portal.waitUntilReady();

Send stuff #

Anything that goes into one of the two portals comes out the other.

portal.send(something);
var somethingElse = await portal.receive();

All primitive types are supported by default, including int, double, bool, List, Map, Set, Duration, DateTime, RegExp, StackTrace, Uint8List and many more. Under the hood, the binary serializer is used – see its documentation for more information. Here's a quick summary:

TODO: The following doesn't work yet – adapters still need to be written by hand.

To send arbitrary Dart objects, annotate them with @BinaryType() and the fields with @BinaryField(id):

part 'my_file.g.dart';

@BinaryType()
class MyClass {
  @BinaryField(0, defaultValue: <int>[])
  List<int> someThings;

  @BinaryField(1)
  Duration duration;
}

Then, run pub run build_runner build in the command line to generate the AdapterForMyClass. Finally, register it at the beginning of your main method:

AdapterForMyClass().registerWithId(0);

Now, you can send MyClasses through portals!

How it works #

Sadly, true peer-to-peer connection establishment is impossible to realize – if you're looking for another running portal, you can't just try talking to all the devices in the internet.
Also, it's not even guaranteed that two devices see each other – they might be in different wifis which usually block incoming connection attempts.
That's why a central public server is needed for connection establishment. It merely offers clients the feature to leave messages for each other, thus it's called the mailbox server. By default, portals use the mailbox server at ws://relay.magic-wormhole.io:4000/v1, but especially if you generate a lot of traffic, you're welcome to run your own server.

The mailbox server manages multiple communication channels between clients, intuitively called mailboxes. The first portal asks for a new mailbox and gets a unique id identifying the mailbox on the server for the client's app id.
The mailbox id and a randomly generated shared key are converted into a human-readable phrase that's shown to the user.
The second portal lets the user input the same phrase. It then extracts both the mailbox id as well as the shared key. After connecting to the mailbox server and requesting to join the mailbox with the given id, both portals talk to each other over the mailbox server.

That's when the encryption phase begins – to make transcribing the phrase as easy as possible, the shared key is pretty small. For a strong encryption, both portals will need to agree on a much larger key. That's why they produce a random secret key each and derive yet another key from that. By exchanging these, they can agree on a key only known to both of them.
Imagine you can multiply numbers easily, but dividing is really hard. (More technically speaking, elements of the Edwards curve group are used instead of numbers, but who minds.)
Having the small shared key $s$, portals generate huge random private keys $a$ and $b$. They calculate $A = sa$ and $B = sb$ and exchange $A$ and $B$. Then they multiply these with their private keys – the first portal calculates $N_a = aB$ and the second one $N_b = bA$. Because $N_a = aB = abs = bas = bA = N_b$, both keys are equal.
An attacker not knowing $s$ can only observe $A$ and $B$ being exchanged, but can't derive the resulting key, making man-in-the-middle-attacks virtually impossible.

Now, clients can use the mailbox to exchange encrypted messages. They exchange their ip addresses and try to connect to the other client. For each succeeding connection, they exchange a short message to verify that whoever is connected knows the encryption key. The first connection where this encryption verification succeeds gets chosen.
Now, both portals can directly talk over an encrypted peer-to-peer connection.

How it relates to Magic Wormhole #

The interface to the mailbox server conforms to the Magic Wormhole protocol. The rest doesn't.

0.0.9 – 2020-01-13 #

  • Add binary serializer: Now you can send anything through portals!
  • Updated readme. It now contains a "How it works" section.
  • Fix analysis issues. (A file wasn't saved yet and showed no errors in the editor.)

0.0.8 – 2020-01-02 #

  • Revise this changelog.
  • Revise readme.
  • Add waitForPhrase helper method.
  • More code reusage in helper methods.
  • Offer getter for key.
  • Implement close method.

0.0.7 – 2019-12-31 #

  • Export all the necessary stuff from the package: Not only the Portal, but also several phrase generators, events and errors.
  • Make it impossible for both sides to choose the same side id.

0.0.6 – 2019-12-31 #

  • Don't transfer Versions anymore, but rather more generic info Strings. Users can still exchange versions, but also human-readable display names or something like that. It just makes portals more flexible and the API surface easier to understand.
  • Remove version dependency.
  • Rename code to phrase. The term code generator conflicts with actual Dart code generators.
  • The reversibility of the PhraseGenerator now gets verified whenever a phrase gets generated in debug mode.
  • Added several utility methods and functions to make the code more readable and succinct.
  • The default phrase generator is now the new WordsPhraseGenerator, which turns both the nameplate and the key into a string of human-readable words.

0.0.5 – 2019-12-29 #

  • Laxen dependencies on collection so the package can be used together with Flutter.
  • Fix some analysis issues.

0.0.4 – 2019-12-29 #

  • Laxen dependencies on pedantic so the package can be used together with Flutter.
  • Fix version parameter in readme.
  • Fix some analysis issues.

0.0.3 – 2019-12-29 #

  • Add boilerplate for example.
  • Fix some analysis issues.

0.0.2 – 2019-12-29 #

  • Add this changelog.
  • Make package description longer.
  • Point to correct GitHub repository.
  • Add pedantic analysis.
  • Clean up readme.
  • Fix some analysis issues.

0.0.1 – 2019-12-29 #

  • Initial version. You can connect portals on devices that can see each other and then transfer bytes.

example/README.md

The Number Guesser example is still a work in progress.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  portals: ^0.0.9

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:portals/portals.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
28
Health:
Code health derived from static analysis. [more]
99
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
62
Learn more about scoring.

We analyzed this package on Jan 16, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.0
  • pana: 0.13.4

Health suggestions

Fix lib/src/connections/server_connection.dart. (-0.50 points)

Analysis of lib/src/connections/server_connection.dart reported 1 hint:

line 48 col 16: The declaration '_onClosed' isn't referenced.

Fix lib/src/portal.dart. (-0.50 points)

Analysis of lib/src/portal.dart reported 1 hint:

line 109 col 20: The declaration '_registry' isn't referenced.

Maintenance suggestions

Package is pre-v0.1 release. (-10 points)

While nothing is inherently wrong with versions of 0.0.*, it might mean that the author is still experimenting with the general direction of the API.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.6.0 <3.0.0
async ^2.4.0 2.4.0
collection ^1.14.11 1.14.12
crypto ^2.1.3 2.1.4
meta ^1.1.8 1.1.8
pedantic ^1.8.0 1.9.0
pinenacl ^0.1.3-dev 0.1.3-dev.1
web_socket_channel ^1.1.0 1.1.0
Transitive dependencies
bech32 0.1.2
charcode 1.1.2
convert 2.1.1
fixnum 0.10.11
stream_channel 2.0.0
typed_data 1.1.6