Skip to content

Repository files navigation

Cambium

GitHub Sponsors

An Android NIP-55 signer that holds no user keys. Cryptographic results come from your Heartwood hardware signer over NIP-46 (Nostr relays), with only safe, exact repeats answered from Cambium's bounded caches. The name follows the tree: cambium is the living layer between bark and the wood.

Why

Amethyst, Primal and most other Amber-compatible Android Nostr clients cannot log in to a remote NIP-46 bunker directly, but they all support signing in via any installed NIP-55 external signer. Cambium fills that gap: it registers as a signer, but it is not one. It is a thin proxy that turns uncached NIP-55 intents into NIP-46 requests against your Heartwood, and hands the response straight back.

Security model

  • No user secrets ever touch the phone. Cambium stores only the bunker pairing (the paired Heartwood's public key, its relay list, and the per-pairing connection secret from the bunker URI) and Cambium's own ephemeral NIP-46 client keypair, generated on first pairing. Compromising the phone exposes the ability to request signatures -- which Heartwood's policy engine and physical confirmation button still gate -- never the identity key itself.
  • All of the above is stored in Android Keystore-backed EncryptedSharedPreferences.
  • Per-calling-app approval is tracked locally (a simple allow-set, mirroring Amber's UX) in addition to whatever Heartwood's own slot policy enforces. Cambium's local layer is a convenience filter; Heartwood remains the authority on what actually gets signed.
  • All NIP-46 payloads are NIP-44-encrypted, matching Heartwood's firmware.
Android apps              Websites
        | NIP-55 native      | nostrsigner: callback / clipboard
        +--------------------+
     Cambium
        | NIP-46 over relays (NIP-44 envelopes)
   Nostr relay(s)  <--  Heartwood (WiFi-standalone)

Pairing from Sapwood

  1. Install Cambium.
  2. In Sapwood (desktop or phone handoff): Apps -> Connect an app -> name it (e.g. "Cambium on my phone") -> it shows a bunker:// URI as a QR code (and as text).
  3. In Cambium: tap Scan QR and point the camera at it, or paste the URI text and press Pair. A successful scan pairs immediately, no separate Pair tap needed. Either way, Cambium connects to your Heartwood over the relays in the URI, confirms the handshake, and stores the pairing. The camera is optional -- pasting works without it, on any device.
  4. In any Amber-compatible app: choose "login with external signer" and pick Cambium. Approve the request the first time; Cambium remembers that app afterwards. Every signature still comes from your hardware signer, gated by its own policy and physical button, not from this phone.

Websites that support NIP-55 can open Cambium with a nostrsigner: link. Website requests always show a one-shot approval and are never remembered for the whole browser. Cambium returns the result to a validated HTTPS callback (or localhost during development), or copies it to the clipboard when the website did not provide a callback.

On a healthy kept-warm session, a fresh signing request takes one relay round trip and commonly completes in roughly half a second to a couple of seconds. A cold reconnect, contention, or an unhealthy relay can take longer. Repeat deterministic decrypts and exact duplicate NIP-42 AUTH requests can be answered from bounded per-identity caches without another hardware round trip.

Install

Grab the signed APK from GitHub Releases, or point Obtainium at this repository for automatic update tracking (one-tap add: obtainium://add/https://github.com/forgesworn/cambium). Chrome is not needed; Cambium runs on any 64-bit Android 8.1+ device, including GrapheneOS (no Google Play services, no Firebase, no analytics).

Also on Zapstore, with v0.4.3 published from the same signing key as the GitHub release. An F-Droid listing is under review (fdroiddata!42875); live per-store status and details are kept in docs/DISTRIBUTION.md.

Verify the APK signature with AppVerifier before installing:

dev.forgesworn.cambium
9E:A1:88:EF:A9:01:5F:7E:7F:90:E1:88:8F:58:6F:52:7B:2A:0E:8A:6D:CD:B3:99:1E:41:FB:4F:14:EE:EF:C6

Releases before 0.2.0 were never published; the 0.2.0 key is the trust root.

Build

Requires Android SDK (sdk.dir in local.properties) and JDK 21.

./gradlew :app:testDebugUnitTest   # unit tests (pairing parser, QR scan validation, NIP-55 request parsing)
./gradlew :app:assembleDebug       # debug APK -> app/build/outputs/apk/debug/

Install directly with an attached device:

./gradlew :app:installDebug

Status

Pairing (scan or paste) with multiple Heartwood identities, NIP-46 client with a kept-warm session per identity, NIP-55 native and web-intent handling (get_public_key, sign_event, nip04/nip44 encrypt/decrypt, decrypt_zap_event, current_user identity selection), HTTPS callback and clipboard delivery for websites, a silent content-provider path that forwards those methods to Heartwood without a visible popup for already-approved apps, and terminal unavailable responses that prevent a technical failure from amplifying into foreground popups. Per-identity bounded priority queues reserve capacity for user work. Exact NIP-42 AUTH duplicates coalesce and cache briefly; at most one distinct AUTH challenge is admitted per identity, it is never retried internally, and a transport failure opens a 60-second AUTH circuit while ordinary signing, reactions, and encryption remain available. Cambium also has persistent per-app approval or denial, an optional keep-warm foreground service, a metadata-only activity log, and an optional biometric app lock. Kind-level permissions live on the signer itself (Heartwood's policy engine, managed via Sapwood), not on the phone.

Private zaps

decrypt_zap_event decodes DIP-03 "private zaps" (a de facto convention used by Damus, Amethyst and Amber -- not part of the core NIP-57 spec, which explicitly defers zap privacy to future work) for the recipient of a private zap: Cambium unpacks the zap request's encrypted anon tag and asks Heartwood to decrypt it, the same as any other nip04_decrypt.

Viewing your own sent private zaps is not supported and never will be through Cambium. DIP-03's sender-side path needs an ephemeral key derived as sha256(your raw private key + note id + created_at) -- that requires the raw private key itself, which a NIP-46 remote signer like Heartwood never exposes over the wire. Attempting it just fails as an ordinary decrypt error.

Support

If Cambium is useful to you, support development via GitHub Sponsors, Geyser or Ko-fi, or zap sats over Lightning: profusemeat89@walletofsatoshi.com (Nostr: npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2).

Licence

MIT. See LICENSE.

About

Android NIP-55 signer that holds no keys: every request is proxied to a Heartwood hardware signer over NIP-46

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages