fix(mobile): render push notification sender identity as npub - #7494
Conversation
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>
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
|
@buzz-security-review 3e3f281 |
|
@buzz-security-review 3e3f281 |
jedwards27
left a comment
There was a problem hiding this comment.
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
npubto a canonical compactnpubtitle; malformed/lookalike seam inputs become the privacy-safe neutralSomeone(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 Swiftand 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.
🤖
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 examplenpub14f8…9nsy, the same identity shape used across the desktop and mobile apps.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+apairs) 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(base44316ff72f5f7de014c66b01cbf534298a70c249; 4 files, +321/−4):Mobile Swiftlane, at this exact head — all passed:swift test(73 tests, 0 failures), the SwiftPM debug and release builds ofmobile/ios/BuzzPushKit, and the unsigned iOS release build.Task provenance
Buzz channel:
1f0e4a3d-7e01-4efe-bb16-843b357f85c9Task: buzz://message?channel=1f0e4a3d-7e01-4efe-bb16-843b357f85c9&id=86b34eb4bd84a1472419e9af22636c011c0fe273e3c196f967d7a36996e149b6