Two self-contained hardening measures:
- WebSocket frame cap: the Tor relay transport dialed every relay with
tungstenite's default 64 MiB message / 16 MiB frame ceiling. Pass a
WebSocketConfig capping both to 4 MiB via client_async_tls_with_config, so
a hostile or buggy relay can't stream a giant frame into wallet memory. The
pool only requires max_message_length >= 128 KiB and the wallet's own events
are far smaller, so 4 MiB keeps ample headroom.
- Secret zeroization: the raw NIP-44 v3 ECDH conversation keys in wrapv3.rs
are now Zeroizing<[u8; 32]> so they are scrubbed from memory on drop instead
of lingering. zeroize (already a transitive dep) is pulled in directly.
Adds a ws-config test asserting the caps sit below the tungstenite defaults
and above the pool minimum.
The goblin: scheme was registered everywhere last night, but macOS is the one
platform that delivers a scheme click as a Carbon/Apple Event (kAEGetURL), not on
argv and not through any path winit or eframe surface, so on a Mac the click went
nowhere.
Install a kAEGetURL handler straight on the shared NSAppleEventManager at startup
(macOS only), pull the URL string out of the event, and feed it to on_data, the
exact same entry the desktop argv path uses. The per-frame Goblin router then lands
the pay URI on the prefilled review screen, identical to a scanned checkout QR or a
Linux argv link. This covers both a cold launch (event queued at start-up) and a
warm click (app already running).
The bridge is entirely cfg(target_os = "macos"): zero bytes on Linux, Windows and
Android (Linux binary size byte-for-byte unchanged). It talks to the Objective-C
runtime through the classic objc crate, which is already in the macOS build graph
via nokhwa/cocoa/metal/wgpu, so it adds no new dependency tree, only its own small
handler class. It registers its own Apple Event handler rather than touching the
NSApplicationDelegate winit owns, so nothing winit does is clobbered.
The wallet's private transport moves from the Nym mixnet to embedded Tor
(arti, copied from GRIM's engine): it dials the relay's pinned .onion, so
the relay never learns your IP, while the relay + NIP-59 gift-wrap hide the
rest - content, sender, and (via a relay-side randomized release) timing.
The Grin node stays on the clear internet as before.
Why leave the mixnet: the Nym free-tier bandwidth this depended on is being
removed upstream (the grant expires at UTC midnight; the paid path requires
holding NYM tokens), so a payments wallet can't stand on it. Tor is
unmetered, embedded in-process on mobile, faster where users wait, and
lighter on the battery.
Preserved intact: the confirm-before-sent guard, relay-gated readiness, and
the lazy warm-on-activity node polling. src/nym/ is feature-gated off (arti
and nym-sdk can't share one binary); full removal is a follow-up.
Connect reliability, measured before release:
- Cold start: the wallet races two mixnet entry-gateway connects and takes the
first up, so a dead random draw no longer costs the full timeout (dead-round
rate drops from p to p^2; verified across 30 cold trials).
- Dead exits are condemned in ~10s instead of ~32s (fresh-tunnel probe budget
5s x 2 rounds, 4.2x margin over the worst measured healthy probe), removing
the 50-70s worst-case connects. Established-tunnel keepalive unchanged.
- "Connecting relays..." clears in ~2-4s after saving the relay list (the
Connected flag now tracks the real relay-up signal instead of waiting on a
30s catch-up fetch); identity publishes are time-boxed; the onboarding
Claim button un-gates equally sooner.
- Gateway-race and probe timings are now visible in [timing] logs.
Also: the update banner shows Goblin build numbers (not the inherited 0.3.6),
and the nip44 crate is consumed from crates.io (v0.3.0) - no sibling checkout
needed to build.
Bugfix: the default and fallback messaging relay moved from relay.goblin.st
(being retired) to relay.floonet.dev, so new installs and reconnects use the
live relay. Built-in relay fallback list updated to match.
Floonet scoped Nym exit on the money-path relay (dial a relay operator's
co-located exit over a MixnetStream, TLS end to end, no public DNS; anchor +
fallback, never pin-only), NIP-44 v3 gift wraps (G4), mix-DNS, localization,
GUI polish. Verified end to end: two wallets complete a real gift-wrapped Grin
payment over relay.goblin.st, finalized + posted on mainnet.