Files
buzz/mobile/ios
npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d 0438c0ef63 feat(mobile/ios): NIP-PL installation lifecycle state machine + Keychain store
Implements the B2/B4 client-side lifecycle rules from the iOS cold review
as a pure decision machine plus a Keychain persistence seam, all in
BuzzPushKit and fully unit-tested without App Attest/network:

- BuzzPushInstallationState: durable installation record with a two-phase
  pendingRotation intent and local APNs-token fingerprinting (SHA-256 of
  the lowercase-hex token), so change detection never retains the token.
- BuzzPushLifecycle.onDeviceToken: rotate ONLY on a real token fingerprint
  change (iOS re-delivers the token every launch; unconditional rotate
  bumps the server epoch and kills every outstanding relay delegation);
  proactive re-enroll inside a 7-day renewal window before expires_at;
  surviving intents resume instead of desyncing.
- BuzzPushLifecycle.applyRotate: persist-then-confirm epoch commit;
  transient retains the intent; invalid_attestation (zombie key after
  backup-restore) clears and re-enrolls; ambiguous not_authorized after a
  crash window adopts the pending epoch and escalates once before giving
  up — deterministic reconvergence without reading server state.
- KeychainPushStateStore: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
  (NSE must read while locked; ThisDeviceOnly excludes the record from
  backup-restore, killing the zombie-installation class at the storage
  layer) with optional access group for the extension; versioned JSON
  envelope that fails closed to 'corrupt' instead of crashing.

swift test: 32/32 green (10 transcript vectors + 22 lifecycle/store).

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-15 18:35:57 -04:00
..
2026-06-10 13:38:11 -07:00