feat: upgrade paykit to rc51 - #1200
Conversation
Greptile SummaryUpgrades Paykit to rc50 and adopts app-scoped Pubky grants, remote grant revocation on normal sign-out, and local-only session forgetting for destructive flows.
Confidence Score: 2/5This PR should not merge until cancellation reliably revokes newly activated grants and failed sign-out no longer leaves authenticated users with removed payment endpoints. Session activation can complete before its cleanup guard is set, leaving a valid grant after cancellation, while the sign-out ordering removes payment publication before a revocation failure returns with the identity still active. Files Needing Attention: app/src/main/java/to/bitkit/repositories/PubkyRepo.kt
|
| Filename | Overview |
|---|---|
| app/src/main/java/to/bitkit/repositories/PubkyRepo.kt | Adds revocation-aware authentication, creation, sign-out, wipe, and restore behavior, but cancellation can bypass revocation and failed sign-out can leave endpoint publication inconsistent. |
| app/src/main/java/to/bitkit/services/PaykitSdkService.kt | Adopts rc50 client IDs and local session forgetting while preserving the existing serialized SDK boundary. |
| app/src/main/java/to/bitkit/services/PubkyService.kt | Replaces force/local-clear APIs with the rc50 local forget operation and retains queued service execution. |
| app/src/main/java/to/bitkit/ui/screens/profile/ProfileViewModel.kt | Clears private Paykit state only after confirmed remote sign-out and preserves it on failure. |
| app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt | Aligns profile disconnect behavior with fail-closed remote grant revocation. |
Sequence Diagram
sequenceDiagram
participant UI
participant Repo as PubkyRepo
participant Paykit
participant Pubky
UI->>Repo: Sign out
Repo->>Paykit: Remove published endpoints
Paykit-->>Repo: Endpoints removed
Repo->>Pubky: Revoke Bitkit grant
alt Revocation succeeds
Pubky-->>Repo: Success
Repo->>Repo: Clear local identity and Paykit state
Repo-->>UI: Signed out
else Revocation fails
Pubky-->>Repo: Error
Repo-->>UI: Preserve identity and report error
Note over Repo,Paykit: Published endpoints remain removed
end
Reviews (1): Last reviewed commit: "feat: upgrade paykit auth to rc50" | Re-trigger Greptile
|
The cancellation and cleanup findings are fixed in signed commit |
piotr-iohk
left a comment
There was a problem hiding this comment.
e2e ACK.
Latest (dd3b7ba) with matching e2e branch codex/paykit-rc50-auth (#212). Recreated the two staging Paykit fixture pubkys. e2e-tests-staging - pubky_paykit green. Full CI green.
Manual on Pixel emu: online Delete/Disconnect clears session + secret for pubkyif…ejhsa1y. Offline Delete: transport_error, profile kept. Offline Disconnect from that dialog: endpoint cleanup WARN, then ~22s later network returned and session cleared. Offline Disconnect waited for network instead of a clean fail-closed error. Not a blocker.
Did not retest other-app grant stays authorized, or backup replace.
|
Non blocking: I got this error when trying to sign in with Pubky Ring Probably same issue of jvsena42/loopky#130 Screen_recording_20260902_142153.mp4Will probably work on next Ring release |
jvsena42
left a comment
There was a problem hiding this comment.
Review notes from testing this branch on a dev build (regtest, Pixel_9 emulator).
The first two comments were reproduced on device with logs attached inline; the rest come from reading the diff. The happy paths all worked: create identity, restore across restart, and online profile delete with full keychain teardown.
|
@jvsena42 Confirmed the remaining Ring observation separately: rc50 intentionally emits the grant-auth request shape, while the currently installed Ring build does not yet recognize it. That is a Ring compatibility gap for its next release, not another Bitkit change. We will retest the end-to-end handoff with a Ring build that supports grant auth. |
jvsena42
left a comment
There was a problem hiding this comment.
Follow-up on error localization in 65491308d/54a620df0.
jvsena42
left a comment
There was a problem hiding this comment.
Follow-up on the sign-out cancellation hardening in 54a620df0.
jvsena42
left a comment
There was a problem hiding this comment.
One more on the client-ID change.
jvsena42
left a comment
There was a problem hiding this comment.
The consequence of the client-ID change on the companion-claim path.
447e781 to
36bfd02
Compare
jvsena42
left a comment
There was a problem hiding this comment.
Approved with one non-blocking comment
a681dfc
21d5a06 to
a681dfc
Compare
|
Follow-up cross-platform lifecycle audit is fixed in |
|
One final self-review follow-up is in |
ovitrif
left a comment
There was a problem hiding this comment.
The ghost-session fix is close but stops one level short. discardAbandonedSession() now falls back to forgetSessionAccess() when the remote revoke fails, and that is the right shape, but forgetSessionAccess() does not delete the keychain credentials the way the code it replaced did. PubkyRepo already has a network-independent clearLocalState() that both signOut() and wipeLocalState() use; the abandoned-session path is the only cleanup that never reaches it, so a canceled Ring completion or a failed identity creation can still leave a live grant behind PubkyAuthState.Idle when both calls fail.
One non-blocking note follows: the new fallback has no test that would fail if it stopped clearing the session.
| runSuspendCatching { | ||
| withContext(NonCancellable + ioDispatcher) { | ||
| pubkyService.clearSessionAccess() | ||
| pubkyService.forgetSessionAccess() |
There was a problem hiding this comment.
This fallback calls pubkyService.forgetSessionAccess() when the remote revoke fails, but its result only reaches .onFailure { Logger.warn(...) }, so a second failure is swallowed and nothing else clears the session. forgetSessionAccess() in PaykitSdkService.kt:802 only calls handle.forgetSessionAccess() and resetRuntime(); it never touches Keychain.Key.PAYKIT_SESSION. The base's clearSessionAccessLocked() did delete both keys directly on this path, and this PR removed that. What makes the gap concrete is that PubkyRepo still has a network-independent local teardown: clearLocalState() at :1299 deletes PAYKIT_SESSION and PUBKY_SECRET_KEY under runCatching, and signOut() calls it at :1118 while wipeLocalState() calls it at :1128. discardAbandonedSession() is the only cleanup path that never does, so it depends entirely on the SDK calling back into PaykitSdkSessionProvider.clearSessionAccess() (:1100), which has no in-app caller to guarantee it. When both calls fail, completeAuthentication() still reaches restoreAuthStateAfterAuthFlow() and shows PubkyAuthState.Idle while the grant survives in the keychain, and initialize() reads that key on the next launch and restores it as InitResult.Restored. That is the same ghost session this fallback was added to close, one level deeper. Could we call the existing clearLocalState() as a last resort when the forgetSessionAccess() fallback also fails, so the keychain always matches PubkyAuthState.Idle?
| assertTrue(result.isFailure) | ||
| verifyBlocking(pubkyService) { clearSessionAccess() } | ||
| verifyBlocking(pubkyService) { signOut() } | ||
| verifyBlocking(pubkyService) { forgetSessionAccess() } |
There was a problem hiding this comment.
This test stubs only pubkyService.signOut() to throw and then asserts that forgetSessionAccess() was called, so it proves the fallback is invoked but not that the abandoned session is actually gone. Nothing here stubs forgetSessionAccess() to fail as well, and nothing asserts keychain.delete(Keychain.Key.PAYKIT_SESSION.name) — the sibling cases at :828-830 and :1089-1091 do make that assertion, so the style is already established in this file. That matters because the whole point of this change is that PubkyAuthState.Idle can no longer hide a persisted session, and as written both added tests would still pass if forgetSessionAccess() left PAYKIT_SESSION behind. Could we add a case where pubkyService.forgetSessionAccess() also throws and assert the session credentials are cleared anyway?
Fixes #1219
This PR:
0.1.0-rc46to0.1.0-rc51.Description
Bitkit now identifies itself as
bitkit.toon mainnet andstaging.bitkit.toelsewhere. Normal sign-out remotely revokes only Bitkit's current grant. If revocation cannot be confirmed, the profile and private Paykit state remain available so the user can retry.Completed Ring authentication that is later canceled, and identity creation that fails after activating a session, also attempt secure revocation. Explicit wallet wipe and backup replacement forget the local session. Existing local E2E homeserver behavior remains unchanged.
Paykit rc51 also fixes replay ordering when incoming and outgoing Payment Request lifecycles interleave. Each request's events are derived in the correct order, preserving its payer/payee role so the existing history mapping retains the row. The fix is in paykit-rs #151 and is available in the published release before that PR merges.
Preview
N/A — no visual changes.
QA Notes
Manual Tests
regression:restore a wallet backup with different Pubky state: the previous local session is forgotten and the backup identity is installed.regression:two privately linked wallets → each creates a 1,000-sat Lightning request and a 25,000-sat on-chain request → the peer pays all four → Payment Requests: both wallets retain all four rows after refresh and app restart.Automated Checks
PubkyRepoTest.kt: covers canceled completed authentication revocation, revocation failure, wallet wipe, and backup replacement.ProfileViewModelTest.ktandEditProfileViewModelTest.kt: cover preserving private Paykit state when sign-out fails.PaykitSdkServiceTest.kt: covers the stable Bitkit client ID and Pubky client configuration.just lintpassed; no app tests were added or removed.bitkit-core-android:0.5.14andldk-node-android:0.7.0-rc.66dependencies. CI must verify the app against the published package.Linked Issues/Tasks