Commit Graph
10 Commits
Author SHA1 Message Date
Dorian dd07da53f9 chore(android): update companion apk download 2026-08-31 19:34:57 +01:00
Dorian 7d09418a09 Companion 0.5.28 — backup & restore (#128), NIP-46 remote signer (#139), companion-gated install pitch (#61 residual)
The companion-agent queue from the 2026-08-30 handoff, complete:

- Backup & Restore: hub sub-page, SAF export/import sealed in the
  node's ADR-005 envelope (Argon2id + ChaCha20-Poly1305, byte-compatible
  with core backup.rs), merge-only restore, no cloud.
- Remote Signer: the phone is the NIP-46 bunker — nsec generate/import,
  nostrconnect:// QR pairing (scanner + OS deep link), per-request
  approve/deny card, NIP-44 v2 transport with NIP-04 receive fallback,
  wire-faithful to rust-nostr's reference bunker. Crypto pinned to the
  official NIP-44 + BIP-340 vectors; e2e harness included.
- #61 residual: banner + manual intro trigger + overlay all gate on
  isCompanionApp() (web-side, vitest-covered).
- Hub modal: new sub-pages like Nodes/FIPS, 70% height cap, node ULA
  display/copy in the Nodes list, fipssh Termux helper (npub→ULA is a
  pure public-key function — verified against the fips crate).

Issues #61 (comment), #128, #139 closed on the tracker.
2026-08-31 19:34:05 +01:00
Dorian b927461f8e feat(companion): fipssh — ssh to a mesh node by npub (Termux helper)
Verified against the fips crate source: the mesh ULA is a pure function
of the PUBLIC key — fd || sha256(x-only pubkey)[0..15] — so the npub is
the durable address and needs no resolver. Android/tools/fipssh wraps
ssh for Termux: 'fipssh user@npub1…' derives the ULA (pure-python
bech32 + sha256, checksum-validated, typo protection) and execs ssh
over the companion's split tunnel; --resolve prints the ULA alone.

The derivation is pinned by a new Rust test
(npub_derives_the_same_mesh_ula_as_the_fips_identity, 3 seeds against
fips::Identity) and the helper's output was verified byte-identical
against a live fips identity pair. SSH-over-mesh handover updated with
an addendum: node docs/UI can advertise npub-based addressing, no
node-side DNS needed for this case.
2026-08-31 15:00:42 +01:00
Dorian 8cf45377a2 docs: handoff — SSH over the FIPS mesh (node-side toggle) for the node agent
Today's field test: Termux over the companion's split tunnel reaches the
node's fips0 ULA and gets RST — the mesh path works end to end, port 22
is refused by the node (fips0 default-deny, no 22 in the fips.d drop-ins;
sshd IPv6 listening unverified). The interim manual unblock (a
source-restricted 90-ssh.nft drop-in) is documented, but the real ask is
a first-class 'SSH over mesh' settings toggle in the node UI, with the
drop-in lifecycle owned by the daemon, a source-scope decision (paired
phones vs any mesh peer), sshd preflights, and an acceptance checklist.
Companion side is done (device-wide split tunnel + the node ULA now
displayed/copyable in the hub Nodes page) — the node agent is downstream.
2026-08-31 14:54:28 +01:00
Dorian 4efac99e97 feat(companion): show + copy the node's mesh ULA in the hub Nodes list
FIPS nodes carry their fips0 ULA in the saved entry, but it was never
displayed — the only way to learn it was the node itself. Each FIPS
node row in the Nodes page now shows its mesh address as a monospace
subtitle with a tap-to-copy affordance, which is exactly the address
other apps on the phone (Termux ssh over the split tunnel, for
example) need. Non-FIPS entries are unchanged.
2026-08-31 14:48:53 +01:00
Dorian 981296e8b0 fix(companion): backup + signer live inside the hub modal, not standalone screens
Field feedback on 0.5.28: the standalone Backup/Signer screens were hard
to read over the synthwave background, back left the app instead of the
menu, and they broke the hub's one-container interaction model. Both are
now hub sub-pages exactly like Nodes/FIPS:

- BackupSection / SignerSection (ui/components) render inside the NESMenu
  panel with the menu's own dark glass surface, scrim, and palette — the
  readability and theming problem disappears with the standalone surface.
- The header back arrow returns to the hub card page (same as Nodes).
- The panel height cap drops from 92% to 70% of the screen — ~15%
  breathing margin top and bottom; content scrolls inside.
- The pairing QR scanner is hosted by NESMenu OUTSIDE the panel
  (QrGlassModal is a full-screen Box, not a Dialog — inside the panel's
  scroll it would clip), and decoded nostrconnect:// URIs funnel into the
  signer section through the same latch as the deep link.
- The nostrconnect:// deep link now routes to the session and pops the
  hub open on the signer sub-page (SignerLaunch singleton) instead of a
  dedicated route; standalone screens and routes removed.
- BunkerManager.refreshState is now a proper suspend fun (was
  runBlocking on the caller's dispatcher).

Docs updated to the new locations. Rebuilt for on-device testing
(v0.5.28-debug/vc48, same signing cert).
2026-08-31 14:23:04 +01:00
Dorian 22f8129b52 feat(companion): backup & restore + NIP-46 remote signer — 0.5.28 (#128, #139)
Companion 0.5.28 (versionCode 48), the companion-agent queue items:

#128 Backup & Restore — the phone side of losing your phone or wiping it
to cross a border. Hub card → SAF export/import of an encrypted .json:
everything the app holds (servers+passwords, FIPS identity/peers, signer
key) sealed in the node's ADR-005 envelope (Argon2id + ChaCha20-Poly1305,
native backup.rs — same blob layout as the node's, node-shaped envelopes
decrypt too). Restore is merge-only: servers upsert npub-first, identity
and signer key adopt only when absent, peers union by npub. No cloud, no
telemetry — the file goes wherever the user saves it.

#139 Remote Signer — the phone IS the NIP-46 bunker. Generate/import a
nostr key, scan a nostrconnect:// QR (in-app scanner or deep link), and
approve/deny each sign_event request from a legible card (kind label,
content, tags, time) — nothing signs without a human. Wire-faithful to
rust-nostr's reference bunker (connect-carrying-secret handshake, NIP-44
v2 transport with NIP-04 receive fallback, kind-24133 responses);
get_public_key/describe/ping handled, everything else 'not authorized'.
Session state in BunkerManager, UI in SignerScreen, hub card wired.

Plus NativeCore (JNI object for the new native surface), FipsPreferences
peers-merge for restore, nostrconnect:// intent filter, and the release
docs (companion-backup-restore.md, companion-nip46-remote-signer.md).

Also Android/tools/nip46-test-client.py: a pure-Python NIP-46 client that
plays the node's login role (QR, handshake, get_public_key, sign_event)
and verifies the phone's signature with an independent BIP-340 — the
end-to-end test for the feature until node-side lands. Its crypto matches
the official NIP-44 + BIP-340 vectors byte-for-byte, the same vectors the
Rust core passes, so the two interop by construction.

Built + smoke: assembleDebug v0.5.28/vc48, same signing cert as the
served 0.5.27 (d622e07e…644d) so it updates in place.
2026-08-31 13:53:52 +01:00
Dorian 57e31eb192 feat(companion): backup envelope + NIP-46 signer crypto in the native core
Extends archy-fips-core with the two companion-release features' crypto
(#128, #139), same JNI-over-JSON contract as the mesh surface:

backup.rs — the ADR-005 encrypted-backup envelope, byte-compatible with
the node's backup code (Argon2id default params + ChaCha20-Poly1305,
blob = base64(salt||nonce||ct)); decrypt ignores extra envelope fields
so node backups read here too. Round-trip, tamper, wrong-passphrase and
cross-shape tests included.

nostr.rs — the phone-side remote-signer crypto: nsec/npub bech32 keys,
BIP340 schnorr event signing (NIP-01 id serialization), NIP-44 v2
payloads (HKDF-SHA256 + ChaCha20 + HMAC-SHA256, both padding prefixes),
NIP-04 fallback, nostrconnect:// parsing with repeated relay params.
Verified against the official NIP-44 vectors (conversation keys, message
keys, padded lengths, byte-exact encrypt vectors), the BIP-340 reference
sign vectors, and round-trip/tamper/failure tests. secp256k1 0.29 note:
Keypair::public_key() is the 33-byte compressed key — x-only pubkeys
must go through .x_only_public_key().0 (one real bug the vectors caught).

JNI glue adds com.archipelago.app.NativeCore: backupEncrypt/Decrypt,
nostrGenerateSecret/SecretFromAny/ParseConnectUri/SignEvent and the
NIP-44/NIP-04 cipher pairs. Android arm64 build verified via cargo-ndk
(7.2 MB .so, +0.4 MB for both modules). Host: cargo test 23/23, clippy clean.
2026-08-31 13:29:36 +01:00
Dorian 12c853da45 docs(companion): verify the zxing-cpp integration sketch online
The QR-decoder option doc was written on an offline machine with the
Maven coordinates and wrapper API flagged as from-memory. Verified
against Maven Central + the wrapper source: artifact is
io.github.zxing-cpp:android:3.1.1 (current release), Format.QR_CODE is
nested inside BarcodeReader (not a top-level BarcodeFormat), options are
a constructor-argument data class, and read(ImageProxy) handles the
Y-plane/cropRect/rotation itself. Sketch updated accordingly; the option
itself stays NOT-actioned pending the move-to-the-code decision trigger.
2026-08-31 13:06:45 +01:00
Dorian d259f3cbb9 fix(web): companion-gate the store banner + manual intro trigger (#61 residual)
Only the auto-popup was companion-gated — inside the companion WebView
users still saw the 'install the companion' banner in the App Store and
could pop the intro overlay through it. Gates all three paths on
isCompanionApp(): CompanionBanner self-hides, openCompanionIntro() is a
no-op, and the manual-open watcher in CompanionIntroOverlay refuses to
open (the overlay's raw window check also moves to the canonical helper
so there is exactly one detection). No APK change.
2026-08-31 12:56:32 +01:00