Owner apps
An owner app runs your node from another device, such as your phone, with everything the wallet can do. The node's side is there: it pairs apps and answers them on an encrypted stream. This page shows how that works, for the day you build one.
No owner app exists yet, and the contract an app speaks is not frozen: it can change in any release, since today every client ships with the node. What's next
On this page
Pairing
An app pairs with a key of its own, its device key, and from then on acts as the owner until the owner revokes it. So only the owner, on the node's own machine, can start a pairing:
-
On the command line, with
dassie pair, which prints a pairing code. -
In the wallet, in the Paired devices card of its
settings, which shows the
code as a QR code. The node starts a pairing for a browser only when
the browser reached it from the same machine, at
localhost. See Paired devices.
$dassie pair
dassie-pair:<node ID>?v=1&secret=<hex>&direct=https%3A%2F%2Fnode.example
Enter this code in the app within five minutes. It works once, and `dassie pair` again replaces it. The wallet on localhost shows it as a QR code.
A code works for five minutes, once. Starting another replaces it, and a restart of the node forgets it. The node keeps it in memory only.
The code is a URI with these parts:
dassie-pair:<node ID>-
The node to pair with. The app pins the node's key to this ID.
v-
The code's version,
1today. An app refuses a version it does not know, and ignores parameters it does not know, so a version can gain parameters without a new number. secret-
The pairing's secret, in hex. The app claims the pairing with it.
direct-
The node's own URL, percent-encoded, when it has a domain name. Its QUIC endpoint answers at that host and port, over UDP.
relay-
For a node without a domain name, once for each of its peers that is online and has a URL, a few at most: the peer's
<node ID>@<URL>, percent-encoded, so the@is%40. Each peer relays to the node over the connection between them. A node without a domain name and with no peer online gives a code with neitherdirectnorrelay, which no app can use.The relay a node keeps, its default or the one
DASSIE_RELAYnames, is listed only if it is also one of those peers. Thedassiepay.netfront door, the default relay of a node without a hostname, never is: it passes on Open Payments only.
The owner sees every paired device in the wallet, with when it was paired and last seen, and revokes one there. Revoking closes its streams at once. A node keeps a bounded list of devices; when it is full, a new device is refused until the owner revokes one.
Why only the owner on the node's machine can pair: a stolen browser session must not become a device that outlives it.
Relayed streams
An app talks to its node on a stream with the purpose
owner, over QUIC, one of two ways:
-
Directly, to the node's QUIC endpoint, at the
directURL's host and port. -
Through a relay, one of the
relayentries: the app asks it to join a stream to the node by its ID. A relay reaches only nodes that hold a connection to it, so a node behind a router is reached through the nodes it is connected to.
Inside the stream, the app runs TLS 1.3 with the node itself, with
the ALPN dassie-owner/1. The app pins the node's key, and
proves its device key, an Ed25519 key it makes, with the SLH-DSA key
it derives from it, just as a node proves its own. The device is
known by the ID of those two keys. A relay sees only ciphertext, and
cannot pass its own stream off as the app's.
On that stream, the app speaks the same RPC as the wallet and the
dassie command: the
dassie.owner.v1
protobuf contract, framed as on the IPC socket, each message with a
4-byte big-endian length in front, at most 1 MiB.
transport.proto
describes the envelopes, calls and errors.
-
Claim the pairing. On the first stream, call
ClaimPairingwith the code's secret and the name the owner will see, 1 to 64 bytes. A device that is not paired may call only that andGetBasicState.NOT_FOUNDmeans the secret is wrong, expired or used;RESOURCE_EXHAUSTEDmeans the device list is full. -
Connect again. A stream opened before the pairing ends after a short while, even once the claim succeeds. Open a new one: it is the paired device's.
-
Call as the owner. Every method is open to the device now, the live
Subscribe*streams included, except starting a pairing: a device cannot pair another. The node checks the device on every call, so a revoked device is out at once.
In Rust, dassie_node::owner_app::connect opens such a
stream, directly or through a relay, from a QUIC endpoint whose key
is the device key.
crates/dassie-node/tests/owner_app.rs
pairs a device and calls the node through both routes. The crate is
part of the node and is not published on its own.
The full rules for paired devices, and how relays carry streams.
What is not there yet
The node pairs apps and serves them. What an app needs beyond that is still to come:
- the apps themselves: a remote mode of the desktop app, and a mobile app;
- approving an app's grant inside the owner app as it connects;
- finding the node on the local network;
- finding the relays that reach a node from its ID alone, without a pairing code;
- a frozen contract: once the first app ships apart from the node, the owner contract and the pairing code become surfaces that stay compatible across releases.
Until then, build against the contract of the release you run, and expect it to change. What's next