Bitkit: marketplace session bootstrap and dialogs - #103
Merged
BitcoinErrorLog merged 5 commits intoSep 24, 2026
Merged
Conversation
A Bitkit (grant) sign-in has no AuthToken to redeem, so its purchase session comes from a browser bootstrap: the grant session writes a single-use proof document, the Shop BFF runs the CLI verifier's checks and opens a marketplace signin_grant, and after the Bitkit approval the BFF claims the bearer. The result PoP seed is derived from the BFF state key and the challenge id (HKDF key with its own salt and info, two labelled HMACs), so no migration is added and a key rotation inside the challenge window fails closed. The flow is bound to the __Host-shop-marketplace-grant cookie and sealed as a version 2 browser context; CLI routes refuse it, and the CLI verifier refuses a browser challenge row before consume. The connect dialog shows Bitkit copy for the bootstrap, reason codes get static copy, and the inventory, messaging and manual-claim dialogs name their Pubky Ring requirement.
… for both signers The session-required card names Bitkit for a Bitkit sign-in that can run the purchase bootstrap. The Lock Server step and its dialog now say to approve or scan with Pubky Ring or Bitkit, since the Lock Server offers a Bitkit QR too. The bootstrap client refuses (and cancels) a marketplace QR that asks Bitkit for anything beyond the marketplace-service capability.
…kit shows The bootstrap QR now says which client Bitkit will name and that the request covers marketplace purchases only, read from the authorization URL. A fixture pins the staging bootstrap URL shape (signin_grant with caps, relay, secret, cid, cpk and only the marketplace-service capability), and the client test checks the Shop's capability pin and caption against it.
BitcoinErrorLog
changed the base branch from
cursor/bitkit-signin-grant-a98e
to
release/shop-v0.6.8
September 24, 2026 07:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purchases for a Bitkit sign-in: the browser bootstrap (PR 3 of the Bitkit grant-auth wave, #48 / #49)
Stacked on #101 (
cursor/bitkit-signin-grant-a98e@fe3bc3d5, which includesrelease/shop-v0.6.8@3c1ac747); open it against that branch. A Bitkit (grant) sign-in has no AuthToken to redeem, so until now it could not get a marketplace session. That kept #48 Step 2 ("Open Bitkit setup") disabled and blocked the #49 connect. This PR adds the browser purchase bootstrap:signin_grant.Design:
/Volumes/t7/vibes-dev/.evidence/bitkit-signin-grant-48/REPORT.mdrev 2, rows R3.1–R3.10, and Addendum A inpr3/DESIGN-GAP-2026-09-23.md, rows A1–A11 (design review round 3: SHIP_DESIGN, cleanup list folded in).Changes
src/app/api/marketplace/bootstrap-challenges,…/[challengeId]/verify,bootstrap-flows/[stateId]/status,…/cancel). They are same-origin only, gated bySHOP_BFF_GRANT_FLOW_ENABLED(not the CLI flag), and use the CLI verifier's parameters. A missing state key or database URL returns404 grant_unavailable.browserSeedKeyis an HKDF key from the state key, with its own salt and info.deriveBrowserBootstrapderives the seed and the delivery id as two labelled HMACs over the challenge id.409 fresh_approval_requiredbefore consume if the key rotated.{version: 2, kind: 'browser'}context and is never returned, logged, or written to the proof document.verifyCliChallenge. It runs the listed checks itself, thenconsumeChallengeAndInsertCliFlowwith the flow-cookie hash and the version-2 context. The flow is bound to__Host-shop-marketplace-grant, the same cookie as the reconnect flow.cli_token_denied), and the browser routes refuse a CLI token (flow_binding_denied).verifyCliChallengenow refuses a browser challenge row before consume.403 result_denied.marketplace-bootstrap-client.ts).shop_session_expired./pub/pubky.app/marketplace-service/v1/:rw.shop_session_expiredandapproval_invalid(A10), has static copy. Afailedapproval shows theapproval_invalidcopy instead of an empty alert.Contract rows → tests
browser-bff.test.ts›browser challenge stores derived result_cpk and delivery hashbrowser-bff.test.ts›result seed never leaves bff(the challenge and verify responses, the poll result, the proof body, a refusal body, the service bootstrap arguments and every console call are scanned for the seed as hex, base64 and base64url; the response keys are exactly the proof-document fields)browser-bff.test.ts›verify refuses a challenge after a state key rotationbrowser-bff.test.ts›claim opens a context sealed under the previous epoch,claim fails closed when the sealing epoch is gonebrowser-bff.test.ts›browser seed key differs from every existing BFF key for the same epoch(the five existing keys recomputed with HKDF; both outputs compared with the raw state key and the assertion and request seeds; known-answer vector from an independent Python HKDF/HMAC)browser-bff.test.ts›cli routes refuse a browser bootstrap flow,browser routes refuse a cli flow,cli verify refuses a browser bootstrap challenge before consume,browser verify refuses a cli challenge before consumebrowser-bff.test.ts›browser claim refuses a cli contextbrowser-bff.test.ts›browser bootstrap does not need the cli flag,browser bootstrap 404s without grant config(flag off, state key missing, database URL missing)browser-bff.test.ts›no migration added by the browser bootstrap(db/bffholds exactly0001and0002);git diff fe3bc3d5..HEAD -- db/is emptyfailure-messages.test.ts›bootstrap reason code shop_session_expired maps to copy,bootstrap reason code approval_invalid maps to copymarketplace-bootstrap-client.test.ts›bootstrap url matches captured fixture(fixturesrc/test/fixtures/auth/marketplace-bootstrap-url.staging.json, captured from the staging bootstrap:signin_grant,caps,relay,secret,cid,cpk, only/pub/pubky.app/marketplace-service/v1/:rw,cid=marketplace.staging.shop.pubky.app),shows no caption for a URL that names no client;MarketplaceSessionConnectDialog.test.tsx›bootstrap QR carries the caption naming the client Bitkit will show. Caption: "Bitkit shows this request from , for marketplace purchases only."browser-bff.test.ts›bootstrap routes reject cross-origin; A8 testsuseMarketplaceSessionConnect.test.ts›challenge pubky comes from live grant session;marketplace-bootstrap-client.test.ts›proof for a pubky the session cannot write failsmarketplace-bootstrap-client.test.ts›grant session writes proof document,maps a refused homeserver write to shop_session_expired and never verifies; Chromiumbrowser-bff.test.ts›replayed challenge is consumed,expired challenge rejected,missing nonce rejected,proof from another pubky rejected,foreign homeserver proof rejected,verify binds the flow to the bootstrap cookie with a version 2 browser contextmarketplace-bootstrap-client.test.ts›refuses and cancels a bootstrap QR that asks Bitkit for more than purchases; A11; Chromiumbrowser-bff.test.ts›claim pubky mismatch rejected;useMarketplaceSessionConnect.test.ts›browser rejects bearer for another pubky;marketplace-bootstrap-client.test.ts›proof deleted after verify,keeps polling while another poll holds the claim, then returns the claimed session,cancel posts to the bootstrap cancel route once; Chromiumfailure-messages.test.ts›bootstrap reason code <code> maps to copy(18 codes),keeps the reconnect copy for shop_session_expired outside the bootstrap,an unknown bootstrap code falls back to static copy, never the code;useMarketplaceSessionConnect.test.ts›an ended grant sign-in shows the bootstrap copy, not the AuthToken fallback,a failed Bitkit approval shows the approval_invalid copyuseMarketplaceSessionConnect.test.ts›grant session connect uses bootstrap,a Ring (cookie) sign-in never runs the grant bootstrap;MarketplaceSessionConnectDialog.test.tsx›grant session connect uses bootstrap (Bitkit copy, no refusal, flow starts),bootstrap creating state confirms with the homeserver,grant session sees refusal not classic qr (grant flow off);MarketplaceSessionRequiredCard.test.tsx(3 tests); VRTsession-connect-bitkit-bootstrap-desktop; ChromiumMarketplaceGrantSessionRefusal.test.tsx›grant session sees inventory refusal,inventory copy names Ring only41287fba("drop the dead claim control"); Step 2 "Open Bitkit setup" is the only claim pathMarketplaceGrantSessionRefusal.test.tsx›grant session sees messaging refusal without qr,cookie session keeps messaging resume path (the paykit-wasm flow still starts)Fail-on-revert: four checks were removed or widened in turn and the named tests failed (
pr3/calibration.log):verify refuses a challenge after a state key rotationandbrowser verify refuses a cli challenge before consume;verifyCliChallengefailscli verify refuses a browser bootstrap challenge before consume;/pub/pubky.app/:rwfailsbootstrap url matches captured fixtureand the other bootstrap client tests.Proof
3be1ffd8. The only change after it,6c814e5d, adds the two VRT baselines.pubky-marketplace-staging(prj_V6j0IUoiX8U3imhavqenlvwdKRRc, teamteam_y2cqjCWZ9vTnCPWQkUAgfijD) asdpl_7eSs65hskmACPrDVuyjD8haTBNPp. The target was checked as not production and notshop.pubky.appbefore each deploy.dpl_GXBWJBkmvoZF4EREUu41mLKTbuhV, andvercel inspectconfirms the alias.7oboeqnf….signin_grantwithcaps,relay,secret,cid,cpk, only/pub/pubky.app/marketplace-service/v1/:rw, andcid=marketplace.staging.shop.pubky.app.d789080f(chromium-pr3-d789080f.log,chromium-d789080f/).@synonymdev/pubky0.11Signer.approveAuthRequest, run after the pre-checks Bitkit's Paykit rc55 applies. That is the JS binding of the pubky 0.11PubkySigner::approve_auth.pr3/chromium-pr3-3be1ffd8.log,pr3/chromium/(screenshots,results.json,network.json,bootstrap-url-shape.json),pr3/chromium-pr3.mjs, andpr3/staging/(target confirmation, deploy logs,vercel inspectbefore and after, promote-back logs).fe3bc3d5, the PR's 26 source files): prettier/eslint/typecheck/2,257 related unit tests pass. Log:pr3/gate-3be1ffd8.log.MarketplaceSessionConnect.vrt.test.tsx, Linux container, Chromium and Firefox): the new scenesession-connect-bitkit-bootstrap-desktopgot its Linux baselines in one run. Both were reviewed. Then the spec passed 18/18 withCI=true, and no other screenshot changed. Logs:pr3/vrt-new-baselines-3be1ffd8.log,pr3/vrt-marketplace-session-connect-3be1ffd8.log. CI runs the full suite.Notes for review
needs_reauthstate that opens the step-up dialog should not arise for it, and the dialog keeps Sign in with Bitkit (grant QR) #101's refusal. Building the grant step-up means a second grant session swap under the finalization lock.BitcoinErrorLog/locks) has no code dependency on this PR as long as it keeps the Lock Server contract the Shop reads.