Skip to content

Device Pairing

Online pairing is the default way to add a device to an initialized vault. It uses the same signed invite, join request, acceptance, and membership checks as the file transport, with encrypted envelopes relayed through a short-lived HTTP session.

Both devices must already have the vault configured and their Lattice daemons running. On the current membership owner:

Terminal window
lattice vault invite personal

The CLI uses https://pair.latticepm.net by default for a 10-minute session, renders a QR code when standard output is an interactive terminal with enough width, and always prints the canonical LPM1-... pairing code. The QR payload is exactly that same code, so it can be scanned, read from the terminal, or delivered manually without changing the protocol.

On the joining device:

Terminal window
lattice vault join

Enter the LPM1-... code at the hidden prompt. The joining device fetches and decrypts the invite, creates its signed join request, and prints a confirmation code. The owner prints the same confirmation code after receiving that request. Compare this second code over an independent channel and approve the owner’s default-no prompt only when both values match.

After approval, the joining device verifies and applies the signed acceptance, then deletes the rendezvous session. Expired sessions are unavailable and garbage-collected by the server. Interrupted attempts can resume while the session and local pending state remain valid.

The pairing capability and confirmation code serve different purposes:

  • The LPM1-... value locates the session and derives its encryption keys. It is a secret capability even though it is designed for manual entry.
  • The confirmation code binds the actual invite and joining device keys. Its out-of-band comparison detects a substituted join request before membership is changed.

Use --role helper on the owner command to add a helper instead of the default writer:

Terminal window
lattice vault invite personal --role helper

Select a different rendezvous on both devices with an HTTPS URL:

Terminal window
lattice vault invite personal --server https://pairing.example.net
lattice vault join --server https://pairing.example.net

Plain HTTP is accepted only for a loopback server, which supports local testing and a same-host reverse proxy. Redirects are not followed.

--code-file <path> supplies the exact LPM1-... capability without putting it on the command line or in shell history:

Terminal window
lattice vault invite personal --code-file /private/path/pairing-code
lattice vault join --code-file /private/path/pairing-code

The owner command normally generates the capability; with --code-file, it uses the supplied value instead. Keep the file private, do not place it in a synchronized directory, and remove it after the attempt. The option cannot be combined with the file-based invite transport.

When no HTTP rendezvous is available, transport the signed packages yourself:

Terminal window
# Owner
lattice vault invite personal --out personal.lattice-invite
# Joining device
lattice vault join personal.lattice-invite --out personal.lattice-join
# Owner, after comparing the confirmation code out of band
lattice vault invite approve personal personal.lattice-join \
--out personal.lattice-acceptance
# Joining device
lattice vault join accept personal.lattice-acceptance

The files contain protocol packages rather than the LPM1-... capability. The same signature, role, confirmation, membership, and package-size validation is applied to both transports.

The rendezvous receives a derived opaque session identifier, encrypted phase envelopes, expiry metadata, sizes, timing, and ordinary network metadata. It does not receive the LPM1-... capability, vault plaintext, membership plaintext, or envelope keys. Envelope encryption and phase authentication use keys derived from the pairing capability; the server is not a membership authority.

Anyone who obtains the pairing capability before expiry can access that session, so the separate confirmation is mandatory. The service also needs normal public-endpoint protections against traffic analysis, denial of service, and abusive session creation; encryption does not address those risks.

The backend runs as a foreground service:

Terminal window
lattice pairing-rendezvous config-check \
--config packaging/lattice-pairing-rendezvous.toml
lattice pairing-rendezvous run \
--config packaging/lattice-pairing-rendezvous.toml

The packaged config listens on 127.0.0.1:4211, limits sessions to 10 minutes, and exposes /healthz and /readyz on that private listener. Place a TLS reverse proxy in front of /v1/sessions/*; do not expose port 4211 or the probe routes directly. System and per-user systemd units, installation commands, state paths, and readiness checks are documented in Self-Hosted Availability Profile.

The repository provides the backend and packaging but does not deploy https://pair.latticepm.net, its DNS, certificate, or reverse proxy.

An equivalent backend for Cloudflare Workers and Durable Objects is in serverless/pairing-rendezvous. It serves the same routes, statuses, headers, TTL, phase ordering, idempotency, and quotas, so clients select it with the same --server option and require no protocol change. The worker owns a hostname rooted at /; the packaged route binds pair.latticepm.net, which is also the client default. Build, test, and deploy procedures are in serverless/pairing-rendezvous/README.md.