Skip to content

feat: scan Sapwood's invite to add a phone for unlock - #22

Merged
TheCryptoDonkey merged 3 commits into
mainfrom
feat/enrol-invite
Sep 25, 2026
Merged

TheCryptoDonkey merged 3 commits into
mainfrom
feat/enrol-invite

Conversation

@TheCryptoDonkey

Copy link
Copy Markdown
Member

Cambium side of the reversed enrolment (see the Sapwood PR).

  • Scan Sapwood's code is the primary action. The invite is parsed strictly: v1, hex key and rendezvous, expiry in the future and at most an hour ahead, wss relays. A bunker link or a phone enrol code gets a wrong-direction message.
  • Cambium builds its enrolment as before and publishes one reply through rust-nostr: throwaway Keys.generate(), nip44Encrypt v2, tags h and expiration only. It shows sending/sent/failed, with a Retry that republishes the same event. It then waits for the hand-off on its own relays plus the invite's, and shows the words and check code as today.
  • Show a code instead keeps the old direction.
  • A pure-Kotlin NIP-44/secp256k1 lives under src/test only, to hold the wire format to the shared Sapwood vector byte for byte. Production never uses it.

Tests: 271 JVM unit tests pass, assembleDebug OK. Not yet bench-tested end to end; do not merge before a round trip on hardware.

Reverses the enrolment QR direction (enrol-invite spec v1): "Add a phone" in
Sapwood shows a one-off invite, and Cambium's "Scan Sapwood's code" reads it,
seals its usual enrolment code to the invite's throwaway key with NIP-44, and
publishes the reply once to the invite's relays, with a Retry on failure.
From there the flow is unchanged: the board's hand-off, the five request
words, then the check code. The old phone-shows-a-QR flow stays as a
secondary "Show a code instead" option. NIP-44 v2 and just enough secp256k1
are implemented in pure Kotlin so the reply's ciphertext can be held to the
shared Sapwood/Cambium test vector, which rust-nostr's native bindings
cannot do on the host JVM.
…crypto

The production reply builder used a naive, non-constant-time secp256k1 and a
from-scratch NIP-44 v2 to hold the wire format to the shared Sapwood/Cambium
vector, but that reasoning only holds for the test. UnlockNostr.inviteReplyEvent
now generates the throwaway key, encrypts and signs entirely through
rust-nostr's Keys/nip44Encrypt, matching deliveryEvent's existing pattern.
Secp256k1.kt and Nip44.kt move to app/src/test as a test-only reference
implementation; EnrolInviteVectorTest still checks the spec's wire format
(NIP-44 v2 content, h/expiration tags) byte for byte against Sapwood.
InviteReplyBuilder now only builds the pure, shared tag shape, tested by both
the production builder and the vector test.
The reply was published straight from the relay-connect coroutine, which
touches views off the main thread and would crash on the first scan.
@TheCryptoDonkey
TheCryptoDonkey merged commit bfa7b60 into main Sep 25, 2026
2 checks passed
@TheCryptoDonkey
TheCryptoDonkey deleted the feat/enrol-invite branch September 25, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant