Skip to content

feat: reuse Pubky Ring identities - #643

Open
Jasonvdb wants to merge 23 commits into
masterfrom
codex/shared-pubky-ring
Open

Jasonvdb wants to merge 23 commits into
masterfrom
codex/shared-pubky-ring

Conversation

@Jasonvdb

@Jasonvdb Jasonvdb commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Part of #571. Builds on merged #636.

Description

  • Discover Ring-owned Pubky profiles and explicitly connect the selected identity from Profile setup.
  • Keep only a source reference and session. Borrowed keys are verified just in time and are never persisted or backed up.
  • Share Bitkit-owned identities with Ring through the non-synchronizing pubky.shared Keychain group.
  • Revalidate borrowed access on launch, foreground, and Paykit writes. Definitive source loss disconnects; temporary Keychain failures remain retryable.
  • Clear borrowed Paykit state and published endpoints before removing local session access.
  • Preserve the existing callback, signup/auth approval, watch-only, and deletion flows.

Mirrors are verified before pruning or private deletion. Missing entitlements defer mirroring without deleting private data.

Release gate

Merge/release after Ring #342. Apple must provision pubky.shared for both App IDs, regenerate profiles, and pass signed two-app physical-device interoperability.

Design

Bitkit handoff: Profile choice and contact import overview.

Validation

Current head f0266f12 contains master f4d4dbe25; every PR commit is signed.

  • Shared-source gate/cache follow-up: 137 focused tests passed.
  • Source-loss endpoint/private-state cleanup: 134 focused tests passed.
  • Channel-usable refresh replay: 4 focused simulator tests passed.
  • Earlier branch validation: 319 focused simulator tests, 49 profile-manager tests, signed simulator build/entitlement checks.
  • SwiftFormat, translation validation, and git diff --check passed.
  • Current hosted CI is running.

Manual Tests

Tests used a disposable zero-balance simulator wallet and staging identities. Simulator results do not replace the signed physical-device release gate.

  • 1. Select the exact signed-up Ring key among four identities; full Profile QR matches and Pay Contacts opens.
  • 2a. Restart both apps with an active Bitkit-owned profile; the same profile remains and Ring adopts that exact identity.
  • 2b. Pre-sharing and recoverable profiles survive update/relaunch with exact key/name and restored session.
  • 3. Restart both current apps; borrowed profile, imported contact, watch-only account, and tracking persist without copying a private key.
  • 4a. Uninstall Ring; foregrounding Bitkit clears the borrowed reference/session and returns to Profile choice.
  • 4b. Delete only the active Ring source; Bitkit disconnects it while other Ring identities remain intact.
  • 5. Missing-entitlement faults make Delete Profile and Reset Wallet fail safely; restoring entitlement allows normal cleanup and Ring disconnection.
  • 6a. Pre-sharing update retains the private source and produces the exact shared mirror.
  • 6b. A current-code missing-entitlement fixture retains the exact private identity/session and defers mirroring.
  • 6c. Ring rediscovers the exact upgraded Bitkit identity after foreground refresh.
  • 7. Borrowing Ring, approve /pub/pubky.app/:rw; an independent requester verifies the exact key and scope.
  • 8. Stale success/cancel/error callbacks are consumed without payment routing or profile replacement.
  • 9. Bitkit-owned identity adopted by Ring survives restart; delete/uninstall/reset makes Ring disconnect without deleting owned Ring keys.
  • 10a. Watch-only consent Cancel leaves the identity unchanged.
  • 10b. Full borrowed-identity watch-only approval yields a verified account xpub claim and exact two-path grant.
  • 11. Ring authorizes a borrowed Bitkit identity; requester verifies the expected key and capabilities.
  • 12. Published Ring profile and explicitly selected followed contact import with exact public keys; no private source key is copied.

Automated coverage

PubkyAuthURLSchemeTests, PubkyProfileManagerTests, SharedPubkyIdentityTests, PaykitSdkClientConfigTests, PubkyAuthApprovalSheetTests, PubkyAuthRequestTests, and WalletViewModelReceiveTests.

@ovitrif
ovitrif force-pushed the codex/pubky-paykit-figma-alignment branch from d769ad1 to cc972d7 Compare July 27, 2026 15:54
Base automatically changed from codex/pubky-paykit-figma-alignment to master July 27, 2026 17:40
@Jasonvdb
Jasonvdb force-pushed the codex/shared-pubky-ring branch from dcf89bb to 54c60ac Compare September 17, 2026 11:17
@Jasonvdb

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-21T13:23:00.619760Z d7e860e Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54c60ac13c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/Services/SharedPubkyIdentityVault.swift Outdated
Comment thread Bitkit/Managers/PubkyProfileManager.swift
@Jasonvdb

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b94ba49ee7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/Managers/PubkyProfileManager.swift
@Jasonvdb

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c611c54f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/Managers/PubkyProfileManager.swift
Comment thread Bitkit/Views/Profile/PubkyChoiceView.swift Outdated
@Jasonvdb
Jasonvdb marked this pull request as ready for review September 18, 2026 13:47
@greptile-apps

greptile-apps Bot commented Sep 18, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

The PR is not yet safe to merge because contact import can perform authenticated writes through a borrowed identity before foreground source revocation has been validated.

Findings

  1. P1 Security Revoked Identity Can Write

Summary

This PR replaces the legacy Pubky Ring relay flow with shared-Keychain identity discovery and adoption, while adding provenance checks, lifecycle serialization, mirror reconciliation, and deletion ordering.

  • Introduces validated, non-synchronizing shared identity records and app-private source references.
  • Restores and refreshes borrowed sessions without persisting Ring-owned private keys.
  • Mirrors Bitkit-owned identities for Ring and removes mirrors before destructive cleanup.
  • Updates profile selection and contact-import presentation for explicit identity choice.
  • One authenticated contact-write path is not ordered behind source revalidation.

Diagram

sequenceDiagram
    participant Ring
    participant SharedKC as Shared Keychain
    participant Bitkit
    participant Pubky as Pubky Service

    Ring->>SharedKC: Publish owned identity record
    Bitkit->>SharedKC: Discover public references
    Bitkit->>SharedKC: Load selected credential
    Bitkit->>Pubky: Establish external session
    Bitkit->>Bitkit: Store source reference only
    Ring-->>SharedKC: Remove credential
    Bitkit-->>Bitkit: Foreground validation task
    Bitkit->>Pubky: Import contacts using existing session
    Note over Bitkit,Pubky: Import is not ordered after source revalidation
Loading

Reviews (1) · Last reviewed commit: "chore: merge master into shared pubky ri..."

Comment thread Bitkit/Views/Profile/PubkyChoiceView.swift

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Three LOW notes inline and no HIGH/MEDIUM. Pubky Ring adoption sits behind the Paykit UI flag, so all of this reaches only opted-in users on released builds.

Checked:

  • Borrowed secret is never persisted. persistSessionAccess(shouldStoreLocalSecret:false) handles that. It throws if a local secret already exists, the backup snapshot is nil while a reference exists, and the receiver marker is suppressed for borrowed identities.
  • validate. It pins version, sourceApp, pubky and a 64-hex secret, and re-derives the public key. A Ring record can't be attributed to another pubky, and a to.bitkit: record can't be adopted as Ring's.
  • No secrets in logs, URLs, the pasteboard or toasts.
  • Approval sheet. It resolves the key for the displayed publicKey, rejects local and shared keys coexisting, and approves the pinned request.rawUrl.
  • Adoption rollback order. Reference first, then signIn, then the pubky check. Failure runs clearExternalSessionAccessdeleteReference. createIdentityLocked and useSharedRingIdentityLocked refuse to run over existing state.
  • Lifecycle lock. No nested acquisition.
  • AppReset. Mirrors are deleted and re-verified before private state is touched. wipeEntireKeychain is now scoped to Env.keychainGroup. The fail-closed wipe is deliberate and tested, and the pubky.shared entitlement is present for Debug and Release.
  • Upgrade from v2.5.0. Existing keys are unchanged. With no reference, resolveSessionInitialization restores as before, and legacy relay sessions pass the new provenance guards.
  • Parity with synonymdev/bitkit-android#1109. Android disconnects on unavailable too, but it reads through a ContentProvider, so the locked-Keychain case below is iOS-only.
  • Figma, Handoff v62. Contacts Import, the identity cards, the key icon, the pubkyGreen token and the title/illustration layout all match.

Comment thread Bitkit/Services/SharedPubkyIdentityVault.swift Outdated
Comment thread Bitkit/Managers/PubkyProfileManager.swift
Comment thread Bitkit/Views/Profile/PubkyChoiceView.swift Outdated
@Jasonvdb
Jasonvdb force-pushed the codex/shared-pubky-ring branch from 14de1dd to ab2d476 Compare September 21, 2026 10:49

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab2d476f02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/Managers/PubkyProfileManager.swift
Comment thread Bitkit/Managers/PubkyProfileManager.swift

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cdcb0c45e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/Views/Profile/PubkyChoiceView.swift Outdated

Copy link
Copy Markdown
Contributor Author

@codex review

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Delta since my last pass (ab2d476f, 930ed301, f738e631, 9b7cde64, merge 5cdcb0c4): no HIGH, no MEDIUM. One LOW follow-up on my existing thread: disconnecting on definitive source loss still leaves the borrowed key's Paykit endpoints published.

Checked against the Android twin synonymdev/bitkit-android#1109:

  • createIdentity over a restored legacy Ring session: absent here. createIdentityLocked refuses a stored .paykitSession with no .pubkySecretKey. It shares the lifecycle lock with initializeLocked, and .profile swaps to ProfileView once isAuthenticated flips.
  • Private endpoints on Ring loss: present, and broader. iOS removes neither public nor private endpoints on that path (thread reply).

Clean in the delta:

  • ab2d476f. errSecItemNotFound still maps to sourceIdentityMissing. Locked or unavailable Keychain statuses, and non-vault errors, are deferred with the reference kept, so writes still fail source validation.
  • 930ed301. Every Paykit request/proof mutation revalidates the borrowed source and checks cancellation before writing. No direct SDK mutation remains outside PubkyService, and the revalidation takes no lock.
  • f738e631. .wallet backup omits the SDK snapshot whenever a reference exists, and throws if the reference is unreadable, so the previous upload stays intact. Restoring a nil snapshot clears SDK state after the reference, session and secret are gone.
  • Borrowed secret. Still never persisted, backed up or logged. Keychain groups are unchanged.
  • 9b7cde64. Create shows only after a successful empty discovery. Loading and error states show only their own card, and the error card has Retry. This matches what codex asked for.
  • Merge. No conflict resolutions.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b7cde645b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Bitkit/AppScene.swift Outdated
Comment thread Bitkit/Managers/PubkyProfileManager.swift Outdated
@Jasonvdb

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7e860ec8a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -636,12 +641,14 @@ struct PaykitPaymentRequestService {
acceptedPaymentEndpointIdentifiers: acceptedPaymentEndpointIdentifiers,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate all remaining SDK writes on source validation

When Ring revokes a borrowed identity while Bitkit remains foreground, this wrapper protects the proposal itself but not every subsequent mutation: synchronize() still invokes processPendingMessages() without revalidation, and ContactPaymentsService.setEnabled() reaches the public endpoint and receiver-marker SDK writers without this hook. Polling can therefore deliver a previously queued message, or the user can toggle Contact Payments, using the cached session after its owner has revoked access. Fresh evidence after the prior fix is these remaining write paths; enforce source validation at the common SDK mutation boundary rather than only around selected request operations.

Useful? React with 👍 / 👎.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Delta since 9b7cde64 (950899df, d7e860ec): no HIGH, no MEDIUM. One LOW inline. The endpoint-removal point on my Keychain thread is still open; d7e860ec addressed codex's local-cache point only. I added notes there on the ordering to use when fixing it.

Checked and clean:

  • 950899df gating. isPaykitMaintenanceAllowed defaults to false, is reset on every .active, and is set from a validation that runs under identityLifecycleLock. Adoption and sign-in serialize on the same lock, so maintenance never runs for an unvalidated identity. A Bitkit-owned identity validates immediately (no reference), so pending sign-out and profile-deletion retries still run on foreground.
  • Endpoints. refreshPublicPaykitEndpoints returning empty only reaches applyPublishedEndpoints([]) through callers that are already gated. User toggles throw noSupportedEndpoint instead of unpublishing.
  • d7e860ec ordering. It runs session → closeAndClear → reference. The reference is kept if session deletion throws, and a missing session doesn't loop.
  • Key material. Neither commit touches the borrowed secret. The backup snapshot is still omitted while a reference exists.

Comment thread Bitkit/ViewModels/WalletViewModel.swift Outdated
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.

2 participants