Skip to content

fix(mobile): render push notification sender identity as npub - #7494

Merged
loganj merged 3 commits into
mainfrom
fix/mobile-npub-identity-m2
Sep 9, 2026
Merged

fix(mobile): render push notification sender identity as npub#7494
loganj merged 3 commits into
mainfrom
fix/mobile-npub-identity-m2

Conversation

@loganj

@loganj loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🤖

Summary

When an iOS push notification comes from someone the app has no cached name for, the notification title showed the first characters of the sender's raw public key — for example aa4fc866…. That fragment is unreadable and doesn't match how the same person appears anywhere else in Buzz. This PR changes that title to the compact form of the sender's npub (npub is the human-readable encoding of a Nostr public key): first 8 and last 4 characters — for example npub14f8…9nsy, the same identity shape used across the desktop and mobile apps.

  • Unnamed senders: raw hex fragment → compact npub.
  • Named senders: unchanged — a sender the app has a display name for still titles the notification with that name.
  • Unverifiable sender identities (malformed keys, or lookalike strings that are not literal 64-hex-digit keys) now render a neutral "Someone" instead of partial raw key material.
  • Everything else about the notification is unchanged: body text, subtitle, thread matching and grouping, deep-link navigation, thread identifiers, and the internal hex public key the resolver matches on.

The native iOS notification-service package (BuzzPushKit) gains a minimal in-house bech32 codec (bech32 is the checksummed string encoding npubs use) — checksum-validated, 32-byte keys only, and no new external dependency. The hex input branch accepts exactly a 64 ASCII hex digit key before any parsing, so strings that merely parse like hex (for example a run of +a pairs) cannot become a displayed identity; this is input validation for presentation. Event signature verification is untouched.

Related issue

Fixes: N/A. Searched existing issues/PRs for push-notification npub identity — closest related: none found.

Testing

At head 3e3f2813b8864b76257ccb50dea3a4b31fa4de0d (base 44316ff72f5f7de014c66b01cbf534298a70c249; 4 files, +321/−4):

  • CI Mobile Swift lane, at this exact head — all passed: swift test (73 tests, 0 failures), the SwiftPM debug and release builds of mobile/ios/BuzzPushKit, and the unsigned iOS release build.
  • Test coverage: npub encoding cross-checked against independent nostr-rs/NIP-19 vectors; rejection of bad checksums, mixed case, wrong lengths, invalid alphabet, padding, and non-32-byte payloads; resolver boundary matrix — hex/npub/invalid sender keys render compact npub or "Someone" while body, subtitle, sender key, and thread identifier pass through; named senders keep cached display names.

Task provenance

Buzz channel: 1f0e4a3d-7e01-4efe-bb16-843b357f85c9

Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6

loganj and others added 3 commits September 8, 2026 14:39
PR-M2 of the npub identity display standardization: iOS push
notification sender labels match PR-M1's compact npub shape
(first 8 + last 4 characters of the full npub).

## Summary

- mobile/ios/BuzzPushKit Bech32.swift: minimal BIP-173 bech32 codec
  for NIP-19 npub labels — charset/polymod checksum with strict case,
  length, and printable-ASCII validation, convertbits regrouping with
  Int-bounded input guards (no UInt8 shift overflow), and
  canonicalNpub(from:) accepting 64-hex-digit keys (any case) or valid
  npubs (including uppercase bech32) while returning nil for anything
  else. Segwit addresses and bech32m stay out of scope: NIP-19 uses
  the original bech32 checksum.
- BuzzPushNotificationResolver.shortPubkey: unnamed senders render
  "npub14f8…9nsy" instead of truncated raw hex ("c6047f94…"); keys
  that cannot be canonicalized fall back to the neutral "Someone"
  label so malformed payloads never leak key material into a
  notification title. Named-sender display, body sanitization,
  matching, internal IDs, senderPubkey passthrough, and tie-breaks
  are unchanged.
- Bech32Tests pin the codec to independently published vectors only:
  the nostr crate 0.44 key, the NIP-19 spec example key, secp256k1
  generator points, degenerate zero/FF keys, and the BIP-173
  valid/invalid decode vectors.
- Resolver tests cover unnamed-sender compact titles, npub
  canonicalization at the sender boundary, malformed-key neutral
  fallback with raw pubkey passthrough, and the gateway-content title
  expectations.

Local verification: standalone driver on this CLT-only machine (no
XCTest module) passes 102/102 checks and `swift build -c release`
succeeds; the committed XCTest suite is gated on CI's mobile-swift
lane (`swift test`).

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Pre-push review P2 (independently reproduced): the hex branch of
Bech32.canonicalNpub accepted anything VerifiedNostrEvent.hexBytes
parsed to 32 bytes, but UInt8(_:radix: 16) also parses signed
chunks — "+a" is 10 and "-0" is 0 — so impostors like "+a"×32 or
"-0"×32 rendered as npubs in push sender titles despite not being
64 ASCII hex digits. Presentation contract violation only;
signature verification and delivery are unaffected.

## Summary

- Bech32.swift: the hex branch of canonicalNpub now gates on
  isHexKey — exactly 64 characters, each an ASCII 0-9/A-F/a-f —
  before any hex parsing or allocation. The strict npub alternative
  and valid uppercase/mixed-letter-case hex keys are unchanged, and
  VerifiedNostrEvent.hexBytes (the shared protocol path) is
  untouched.
- Bech32Tests: reject "+a"×32, "+A"×32, and "-0"×32 through both
  canonicalNpub and npubBytes; pin mixed-letter-case hex
  canonicalization. Overlong/malformed lengths were already covered
  ("ab"×31/×33, "a"×63).
- BuzzPushNotificationResolverTests: signed-chunk sender keys hit
  the neutral "Someone" identity at the actual decodeResolution
  boundary with senderPubkey/threadIdentifier/body passthrough
  asserted; the same impostors join the shortPubkey fallback loop.

Local verification: the standalone driver on this CLT-only machine
(no XCTest module) was extended from 102 to 121 checks; the new
negative assertions fail 7/7 against the unpatched codec ("+a"×32
titled "npub1pg9…c7xw") and pass 121/121 after the gate. `swift
build` and `swift build -c release` both succeed. The committed
XCTest suite remains gated on CI's mobile-swift lane (`swift test`).

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
Tests-only cleanup for the M2 Swift slice, applying the audited M2
consolidations from the npub test proportionality review. No production
change: Bech32.swift (the ASCII 64-hex gate, strict npub decode),
BuzzPushNotificationResolver.swift (shortPubkey, "Someone" fallback,
named/body/matching behavior), and every internal ID are untouched, so
the slice's committed ASCII-guard evidence still binds.

## Summary

- Bech32Tests.swift: 142 -> 98 lines, 7 -> 4 cases. Keep the
  independently published npub vectors (the nostr-rs 0.44 key and the
  NIP-19 spec example) plus the degenerate zero key, and keep the strict
  rejection table: mutated checksum, mixed case, wrong payload length,
  other HRP, non-32-byte keys, and the signed radix chunks "+a"/"+A"/"-0"
  the isHexKey guard rejects. Drop the secp256k1-generator and all-ff
  vector repeats and the standalone BIP-173 valid/invalid/non-ASCII
  decode matrices: nothing outside the codec calls raw decode, so
  alphabet rejection is retained at canonicalNpub instead, as an
  npub-shaped payload character outside the bech32 charset.
- BuzzPushNotificationResolverTests.swift: replace the four standalone
  sender-label tests and both shortPubkey unit matrices with one
  hex/npub/invalid/signed boundary table that checks the title plus
  unchanged body, subtitle, senderPubkey, and threadIdentifier per row.
  The compact npub shape stays pinned at the real seams (the
  decodeResolution titles and the npub1ccz…mnyd resolve() expectations);
  malformed-input classification stays pinned at the codec; named senders
  winning over these labels stay covered by the cached-profile resolve
  tests. 26 -> 21 cases; the three existing npub expectation updates are
  unchanged.

Local verification: the standalone driver on this CLT-only machine (no
XCTest module) mirrors the retained assertions — 78/78 checks pass — and
both mutation probes still fail: reverting shortPubkey to raw-hex
truncation fails 5 retained checks, and removing the isHexKey gate fails
the "+a"/"+A"/"-0" codec rejections plus the signed boundary row.
`swift build` and `swift build -c release` succeed and both changed test
files parse via `swiftc -parse`. The committed XCTest suite remains
gated on CI's mobile-swift lane (`swift test`).

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 218633b8fd6ee41aee8eb18ba9806e8d90694751...3e3f2813b8864b76257ccb50dea3a4b31fa4de0d
  • Model: gpt-5.6-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability issues found. The new npub label path is syntactically strict and receives signature-verified events in production.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Static read-only review only; builds and tests were not run as instructed.

Generated by Codex Security Review |
Requested by: @loganj |
Workflow run

@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review 3e3f281

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 8, 2026
@loganj
loganj marked this pull request as ready for review September 8, 2026 21:27
@loganj
loganj requested a review from a team as a code owner September 8, 2026 21:27
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 8, 2026
@loganj

loganj commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

@buzz-security-review 3e3f281

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Sep 8, 2026

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Reviewed 44316ff72f5f7de014c66b01cbf534298a70c249..3e3f2813b8864b76257ccb50dea3a4b31fa4de0d as :bot: Jude’s code review agent.

Risk: medium — this changes user-visible iOS notification identity presentation at the relay-event → resolver → Notification Service Extension boundary, while intentionally preserving verified event identity, grouping, and navigation.

Behavior/contracts traced

  • A verified cached display name still wins. Otherwise the resolver converts an exact 64-ASCII-hex sender key or checksum-valid 32-byte npub to a canonical compact npub title; malformed/lookalike seam inputs become the privacy-safe neutral Someone (mobile/ios/BuzzPushKit/Sources/BuzzPushKit/Bech32.swift:71-113; BuzzPushNotificationResolver.swift:441-473,614-623).
  • Relay events are signature/ID validated before production formatting; the patch does not relax event verification. Body, subtitle, internal senderPubkey, conversation grouping, thread identifier, and deep-link target remain separate and unchanged.
  • The Notification Service Extension and Apple Communication Notification specialization consume the same resolved title, while preserving fallback on resolver failure/expiry (mobile/ios/NotificationService/NotificationService.swift:36-88; BuzzCommunicationNotification.swift:32-52,90-151).

Findings: no blocking or non-blocking author-actionable defects found.

Author action: none.

Verification owner: release/mobile validation owner for optional signed-device APNs presentation observation; repository CI owns required exact-head gates.

Validation at exact clean head

  • Two independent dedicated-worktree runs of swift test --package-path mobile/ios/BuzzPushKit: pass; 73 XCTest + 18 Swift Testing tests.
  • Two independent runs of swift build -c release --package-path mobile/ios/BuzzPushKit: pass.
  • git diff --check: pass; reviewed scope is four BuzzPushKit source/test files, +321/-4.
  • Causal mutation evidence: restoring raw-hex formatting failed 7 assertions across codec/resolver production paths; removing the literal-ASCII-hex guard failed 4 assertions covering +a, +A, -0, and resolver behavior. Both mutations were restored and clean-head suites re-passed.
  • Exact-head CI run 34270160112: Mobile Swift Domain / Mobile Swift and aggregate result passed; package tests plus debug/release SwiftPM builds and unsigned iOS release build passed. DCO, Semgrep, zizmor, and selected aggregate checks are green. Live PR base/head were re-read immediately before review submission and remained exact; PR was mergeable.

Manual/native evidence: no real APNs delivery was performed on a signed physical iPhone, so final lock-screen/banner/VoiceOver rendering was not independently witnessed. Source tracing and package tests prove the title computation and both native handoffs, not Apple’s final pixels or speech.

Residual risk: low and non-blocking. Signed-device presentation remains release-validation work; no source defect or required-gate failure was found. The optional Codex workflow had skipped jobs, but an exact-head static security review comment reported no finding, and Semgrep/zizmor passed.

@loganj
loganj merged commit 93761e4 into main Sep 9, 2026
70 checks passed
@loganj
loganj deleted the fix/mobile-npub-identity-m2 branch September 9, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants