Add CTX spend-api pubkey auth prototype - #6147
Open
j0ntz wants to merge 3 commits into
Open
Conversation
Contributor
Author
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:02
536021a to
09ab729
Compare
j0ntz
marked this pull request as ready for review
August 10, 2026 21:02
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Implements the anonymous secp256k1 register/login protocol documented by CTX-com/spend-api-pubkey-auth-demo, plus the read endpoints it unlocks. The keypair is the CTX account: it is generated on first use and kept in the Edge account's encrypted dataStore, so the same anonymous CTX user is recovered across launches and devices. Tokens are derived state and are never persisted. The protocol crypto is kept free of React Native imports so its wire format is unit testable in Node, and it avoids BigInt because Hermes falls back to a big-integer shim that DataView.setBigUint64 rejects.
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:16
09ab729 to
428ff98
Compare
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:25
428ff98 to
7151140
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7151140. Configure here.
Gives the pubkey auth prototype a surface that can be driven in the running app: connecting establishes the keypair identity, logs in, and reads the authenticated user, company, permission count, and merchant catalog size. The scene already exists as the developer-facing readout for this plugin, so the prototype rides along without touching the shipping purchase flow.
Documents the protocol as implemented: the wire format, where the keypair lives and why, the token lifecycle, and the decisions behind each, including the two places the reference script's crypto could not be copied verbatim.
j0ntz
force-pushed
the
jon/ctx-spend-prototype
branch
from
August 10, 2026 21:35
7151140 to
e222388
Compare
Contributor
Author
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.





Technical Design Document
ctx-spend-pubkey-auth.md
CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Asana: https://app.asana.com/0/1215088146871429/1216403654258326
CTX published a reference script for an anonymous secp256k1 register/login protocol against their spend-api (CTX-com/spend-api-pubkey-auth-demo). This is a prototype client for that protocol, wired into the existing EdgeSpend gift card plugin.
GET /meagainst CTX returns gift-card-scoped permissions (giftcard:create,giftcard:list,giftcard:redeem,merchant:list) andGET /merchantsreturns a 118-entry brand catalog, so CTX is a competing provider for what the Phaze plugin already does. The client is added alongside Phaze; the shipping purchase flow is untouched.The design doc committed in this PR covers the protocol, the decisions, and the two places the reference script's crypto could not be ported verbatim:
src/docs/ctx-spend-pubkey-auth.md.What is here:
ctxSpendCrypto.ts— the protocol wire format:sha256(uint64BE(nonce))digest, the 65-byte recoverable signature ([27 + recoveryId + 4] || R || S), public key recovery, and JWT expiry parsing. No React Native imports, so it is unit tested in Node.ctxSpendAuth.ts— the keypair is the CTX account. It is generated on first use and kept in the Edge account's encrypteddataStore, so the same anonymous CTX user is recovered across launches and devices. The reference script deliberately discards its keypair; a real client cannot. Tokens are derived state and are never persisted.ctxSpendApi.ts— the confirmed read surface (/me,/merchants,/gift-cards), with one re-auth retry on a 401.PLUGIN_API_KEYS.ctxSpend(clientId,baseUrl). There is no API key; the client id is registered server-side and the keypair identifies the user.Two substitutions the runtime forced, both covered in the doc's Decisions:
BigIntin the uint64 encoding.index.tsshimsBigIntwithbig-integerwhen it is missing, andDataView.setBigUint64rejects that shim.URL. React Native'sURLappends a trailing slash to any query-less path, and the spend-api routes/me/and/merchants/to 404.No new npm dependency:
@noble/curvesand@noble/hasheswere already present, and are the same libraries the reference script uses.Not in scope: replacing Phaze, purchase and redemption, a shared provider interface over both APIs, and identity rotation.
Test plan
src/__tests__/ctxSpendCrypto.test.ts. The load-bearing one recovers the signing public key from the signature, which is the same check the server performs, so the wire format is verified without a network round trip. Vectors were cross-checked againstBuffer.writeBigUInt64BEandnode:crypto.verify-repo.shpasses: eslint,npm run prepare, and the full 536-case suite.https://staging.spend.ctx.com. The app generated its own keypair, registered it, and read live data: 12 permissions, 118 merchants, company Edge. The server named the anonymous userAnon 032129dfafter the public key's own prefix.Screenshots in the test-evidence comments below.
Bugbot found 6 issues across three rounds, all fixed and re-tested on the sim. Four were in the first round: a dataStore read failure reading as "no identity" and silently minting a replacement keypair (the worst of them, since CTX has no recovery), malformed stored hex throwing past the replace path, a light-account status conflated with a failed write, and Connect not being retryable. The retry fix then surfaced two follow-ons it had created or exposed: an ungated button that could stack sessions, and the app-wide
retry: 2turning one failed connect into three login handshakes against a rate-limited API. Decision 8.6 in the design doc records the error-handling contract that came out of the first round.Requirements
If you have made any visual changes to the GUI. Make sure you have: