feat(auth): derive backend auth keypair from seed (#2769)#2876
Draft
piyalbasu wants to merge 10 commits into
Draft
feat(auth): derive backend auth keypair from seed (#2769)#2876piyalbasu wants to merge 10 commits into
piyalbasu wants to merge 10 commits into
Conversation
Design doc for the extension-side derivation primitive: HMAC-SHA256(seedBytes, "freighter-auth-v1") -> Ed25519 keypair, hex pubkey = anonymous backend user ID. Covers scope, threat model, crypto choices (crypto.subtle + stellar-sdk, zero new deps), exact algorithm, session-timeout lifecycle, verified cross-platform test vectors, and a reworded acceptance #2 (cryptographic independence, not "invalid G addr"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d/api (#2769) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#2769) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-480becda1ade92fa47a9 (SDF collaborators only — install instructions in the release description) |
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.
TL;DR
Adds the extension-side primitive that turns a wallet's recovery phrase into the user's anonymous backend identity — the first piece of Cross-Platform Contact Sync (#2769). It derives a dedicated auth key from the seed that is cryptographically independent from the user's Stellar wallet key, so the backend can recognize a returning user without ever learning their wallet address and without triggering any signing prompt.
This PR is derivation only — it deliberately does not generate request tokens, call any backend, or touch the contacts UI. Those are follow-up tickets. Nothing in the product changes yet; this is a self-contained, fully-tested building block.
The same recovery phrase must produce the exact same identity on the extension and on mobile, so this ships committed cross-platform test vectors that the mobile app will be held to as well.
Implementation details (for agents/reviewers)
What changed (all new, under
@shared/api/helpers/):deriveAuthKeypair.ts— the primitive:deriveAuthSeed(mnemonic):HMAC-SHA256(viacrypto.subtle) with key = the 64-byte BIP39 seed (bip39.mnemonicToSeedSync(mnemonic)) and message =utf8("freighter-auth-v1"), returning 32 bytes. Marked@internal(returns private-key material; exported only for test assertions).deriveAuthKeypair(mnemonic): feeds that 32-byte seed toKeypair.fromRawEd25519Seed;userId = keypair.rawPublicKey().toString("hex")(lowercase hex — matches the backend's canonicalsub). Pure: no logging, no keyManager, no messaging, no persistence.authKeypairVectors.ts— committed cross-platform vectors (mnemonic → authSeedHex → userId). Kept outside__tests__/on purpose: Jest's defaulttestMatchcollects every file under__tests__/as a suite and fails a fixture with "must contain at least one test." The intermediateauthSeedHexis included so a failing mobile test localizes the divergence (HMAC step vs Ed25519 step). freighter-mobile must mirror these.__tests__/deriveAuthKeypair.test.ts— 10 tests mapped to acceptance criteria: vector parity (Run eslint during build, minor adjustments #1), determinism, lowercase-64-hex format, independence from the wallet key (router fix #2), no messaging side-effects (Piyal dev #3), invalid-mnemonic rejection.@shared/api/package.json/yarn.lock— declaresbip39@3.1.0(exact pin; promoted from a transitive dep). No new library is introduced to the project.Notable decision —
bip39directly instead ofstellar-hd-wallet:stellar-hd-walletcannot run under jest/jsdom (its compiled bip39 import throwsCannot read properties of undefined (reading 'wordlists'); verified that adding it to the transform allowlist does not fix it).bip39.mnemonicToSeedSyncis byte-identical to whatstellar-hd-walletwraps, so cross-platform parity is unaffected.Acceptance #2 wording correction: the ticket says "auth pubkey is not a valid Stellar G address," which is technically false (any 32 bytes StrKey-encode to a format-valid
G…). The true, tested property is cryptographic independence from the wallet keypair. Ticket text should be updated.Verification:
yarn jest @shared/api(full collection) → 8 suites / 63 tests pass underjest-fixed-jsdom(real WebCrypto);tsc -p @shared/api/tsconfig.jsonclean. Test vectors were independently regenerated from the algorithm and matched.Known minor follow-ups (non-blocking, not yet applied):
authKeypairVectors.tsheader comment to state HMAC arg order explicitly (key = seedBytes, message = salt) for mobile implementers.userId(today it only asserts inequality with the wallet key; correctness is already covered by the vector test).Buffer.from(...)wraps and hoistAUTH_SALTbytes to a module constant (micro-cleanups).freighter-mobilemirrors the algorithm + vectors.The two
translation.jsonkeys in the diff ("Auto-lock timer" en/pt) are auto-generated by the repo's huskyi18next-scannerpre-commit hook filling a pre-existing gap onmaster; unrelated to this feature.