Skip to content

fix(app): fetch protocol trees before the recovery registration check - #2272

Open
seshanthS wants to merge 2 commits into
devfrom
fix/self-3931-recovery-protocol-data
Open

fix(app): fetch protocol trees before the recovery registration check#2272
seshanthS wants to merge 2 commits into
devfrom
fix/self-3931-recovery-protocol-data

Conversation

@seshanthS

@seshanthS seshanthS commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes SELF-3931.

Problem

Account restore fails for almost everyone. Mixpanel, last 30 days: 15 unique
users restored successfully, 701 hit Cloud Restore Failed: Unknown Error
, and
84 more got a false "Passport Not Registered". Chronic for 4+ months, not a
regression.

Both recovery screens called isUserRegisteredWithAlternativeCSCA while reading
commitment_tree and alternative_csca straight out of the protocol store,
without ever fetching them. Both default to null, so LeanIMT.import(hash, null)
threw a TypeError, or an empty commitment list produced a false "not registered".
The phrase path's telemetry confirms it: error: TypeError on 151 events.

Nothing warmed the store on those paths — SplashScreen only calls
checkAndUpdateRegistrationStates when a document has isRegistered === undefined,
which is false on any modern install.

Fix

New app/src/proving/checkRestoredDocumentRegistration.ts, used by both screens,
replacing ~20 lines of duplicated inline callbacks in each. It fetches first, then
checks:

  • passport / id_card with an authority key identifier → fetchAllTreesAndCircuits.
    Without one → just fetch_identity_tree, since only fetch_alternative_csca
    consumes the AKI and a blank ski would fire a pointless /ski-pems/ request.
  • aadhaar / kycfetch_all(environment) directly, so they are no longer
    skipped for lacking a dsc_parsed (which they never have).
  • One hard failure: a missing commitment tree, raised as a retryable
    ProtocolDataUnavailableError. It is the only universally required input.
  • No key-material guards. An empty alternative_csca or absent AKI falls back
    to isUserRegistered on the document's own stored DSC/CSCA. Aadhaar seeds
    document_public_key itself; kyc's public_keys is always null by design
    (fetch_public_keys sets it unconditionally), so guarding either would reject
    restores that work today.
  • If the alternative-CSCA sweep reports not-registered, it still tries the
    single-commitment path before giving up — this has strictly more recall than
    either screen had before, and covers a stale /ski-pems/ response.

commitment_tree is typed any in the store; the endpoint returns it as a JSON
string today (verified against tree.self.xyz/identity) and LeanIMT.import
requires that, so a structured value is serialized rather than reported as missing.

Also fixed

  • Null deref: AccountRecoveryChoiceScreen called
    reStorePassportDataWithRightCSCA(data, csca as string) unconditionally, but
    csca is null for registered KYC documents and the callee dereferences it via
    parseCertificateSimple. Now guarded, matching the phrase screen.
  • Telemetry: 1787 of the 1938 unknown-error events carried no properties, so
    the failing branch was unknowable. All four sites now report a reason
    restore_failed, protocol_data_unavailable, backup_download_failed,
    unexpected_error. That last split matters for SELF-3934: it separates "no
    backup / OAuth failed" from "registration check failed".
  • Layering: getAlternativeCSCA moved to app/src/proving/alternativeCSCA.ts.
    Importing it from validateDocument pulled @/services/analytics into the
    screens' module graph, which broke their tests at import time.
    validateDocument re-exports it, so existing importers are unchanged.
  • Deletes the unreachable throw new Error('KYC is not supported yet') from both
    screens — validate.ts returns early for kyc before getAltCSCA is consulted.

Deliberately out of scope

  • packages/mobile-sdk-alpha/src/proving/recoveryValidation.ts has the same
    missing-fetch bug on the webview path. Separate issue — the app does not use it,
    and fixing it here would change webview behaviour in an app PR.
  • validateDocument.ts skipping every aadhaar/kyc document for lacking an AKI.
  • Error UI for the cloud screen, which has none at all — that's SELF-3932.
  • No change to protocolStore.ts's error-swallowing fetchers or to
    fetchAllTreesAndCircuits's signature; both ripple into provingMachine.

Validation

pnpm nice (0 errors), pnpm types, pnpm test111 suites / 1231 tests
passing. Every file these tests depend on is identical between this base and dev.

19 new tests: 15 on the helper (fetch-before-check ordering, missing-tree error,
both fallback paths, aadhaar/kyc not skipped, empty-string AKI, staging env,
wrapped fetch rejection, tree serialization) and 4 on
AccountRecoveryChoiceScreen, which had no test file at all.

Post-merge, watch Cloud Restore Success vs Cloud Restore Failed: * uniques
against the 15-vs-785 baseline.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved account recovery by validating restored document registration before completion.
    • Added support for restoring signing credentials from document data when applicable.
    • Added clearer messaging when required protocol data is unavailable.
  • Bug Fixes

    • Prevents unnecessary document re-storage when no matching signing credentials are available.
    • Improves recovery error reporting, analytics, and retry behavior across recovery methods.
  • Tests

    • Added coverage for restored-document validation, credential fallback, protocol-data failures, and recovery flows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
self-webview-app Ignored Ignored Preview Aug 21, 2026 11:31am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change centralizes restored-document registration checks. Recovery screens now use this workflow, restore CSCA data only when available, and distinguish protocol-data failures from other recovery errors.

Changes

Restored Document Recovery

Layer / File(s) Summary
Centralized registration workflow
app/src/proving/alternativeCSCA.ts, app/src/proving/checkRestoredDocumentRegistration.ts, app/src/proving/validateDocument.ts, app/tests/src/proving/checkRestoredDocumentRegistration.test.ts
The registration workflow loads and serializes protocol data, validates alternative-CSCA registration, supports document-key fallback, and wraps unavailable protocol data in ProtocolDataUnavailableError.
Account recovery integration
app/src/screens/account/recovery/*, app/tests/src/screens/account/recovery/*
Cloud and phrase recovery use the centralized check, conditionally restore CSCA data, expose a network error state, and record structured failure analytics.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟠 High · up to 4e18f

Account restore can still fail for passport or ID-card documents without an authority key identifier because a missing alternative CSCA value is dereferenced before the registration fallback runs. This is a high-impact correctness issue in the recovery flow and should be fixed before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: fetching protocol trees before the recovery registration check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/self-3931-recovery-protocol-data

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 98f2cefc-de45-41ee-a184-a8d36a73834e

📥 Commits

Reviewing files that changed from the base of the PR and between eaa208e and 5e59756.

📒 Files selected for processing (8)
  • app/src/proving/alternativeCSCA.ts
  • app/src/proving/checkRestoredDocumentRegistration.ts
  • app/src/proving/validateDocument.ts
  • app/src/screens/account/recovery/AccountRecoveryChoiceScreen.tsx
  • app/src/screens/account/recovery/RecoverWithPhraseScreen.tsx
  • app/tests/src/proving/checkRestoredDocumentRegistration.test.ts
  • app/tests/src/screens/account/recovery/AccountRecoveryChoiceScreen.test.tsx
  • app/tests/src/screens/account/recovery/RecoverWithPhraseScreen.test.tsx

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/proving/checkRestoredDocumentRegistration.ts
@seshanthS

Copy link
Copy Markdown
Collaborator Author

@codex

@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: 5e59756e9f

ℹ️ 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 on lines +96 to +98
await protocolState[documentCategory].fetch_all(environment);
} catch (error) {
throw new ProtocolDataUnavailableError(documentCategory, { cause: error });

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 Fetch only required registration data for Aadhaar and KYC

When recovering an Aadhaar or KYC document, fetch_all also waits for deployed-circuit, DNS-mapping, and OFAC requests (packages/mobile-sdk-alpha/src/stores/protocolStore.ts), even though this check only needs the commitment tree and, for Aadhaar, public keys. If any unrelated endpoint rejects while those registration inputs are available, this catch converts the rejection to ProtocolDataUnavailableError, and both recovery screens prevent the user from recovering. Fetch the required slices directly or verify the required fields after a partial fetch_all failure.

Useful? React with 👍 / 👎.

Comment on lines +136 to +138
if (csca) {
await reStorePassportDataWithRightCSCA(passportDataParsed, csca);
}

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 Skip CSCA restoration for Aadhaar matches

For a registered Aadhaar document, isUserRegisteredWithAlternativeCSCA returns the matched public key through its csca field, so this newly reachable recovery path passes an Aadhaar public key to reStorePassportDataWithRightCSCA. That function treats the value as an X.509 CSCA certificate and calls parseCertificateSimple and brutforceSignatureAlgorithmDsc on passport-only fields, causing otherwise successful Aadhaar recovery to fall into the error handler. Restrict restoration to passport and ID-card categories, as validateDocument.ts already does.

Useful? React with 👍 / 👎.

Comment on lines +127 to +130
const readCommitmentTree = (category: DocumentCategory) => {
const serialized = serializeCommitmentTree(
getCommitmentTree(selfClient, category),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject a stale Aadhaar tree after a failed refresh

If an Aadhaar commitment tree is already cached and the next identity-tree request fails, aadhaar.fetch_identity_tree catches the failure without clearing commitment_tree, while fetch_all can still resolve after the other requests finish. This read then accepts the stale tree as if the requested environment and refresh succeeded, potentially reporting a valid recovery phrase as unregistered—for example after switching between production and staging data or when the cached snapshot predates registration. Clear the slice before fetching or have the fetcher null the tree on failure so the helper raises ProtocolDataUnavailableError instead.

Useful? React with 👍 / 👎.

Comment on lines +151 to +155
const hasAlternativeCSCA =
Object.keys(readAlternativeCSCA(documentCategory)).length > 0;

if (!isMrzDocument || hasAlternativeCSCA) {
const { isRegistered, csca } = await isUserRegisteredWithAlternativeCSCA(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to the document commitment for Aadhaar

When the Aadhaar public-key endpoint successfully returns an empty list, this branch still calls isUserRegisteredWithAlternativeCSCA, whose Aadhaar path returns false immediately for empty keys, and the later fallback is restricted to MRZ documents. A valid Aadhaar document is therefore reported as unregistered even though isUserRegistered can derive and check its commitment directly from the restored document. Extend the single-commitment fallback to Aadhaar when no public keys are available.

Useful? React with 👍 / 👎.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/src/proving/checkRestoredDocumentRegistration.ts (1)

152-168: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Unguarded alternative_csca still crashes checkRestoredDocumentRegistration for MRZ documents.

readAlternativeCSCA at Line 152-153 calls getAlternativeCSCA(useProtocolStore, category) without a fallback. For passport/id_card, getAlternativeCSCA returns useProtocolStore.getState()[docCategory].alternative_csca directly, and this field stays unset when fetchProtocolData takes the no-AKI branch (Line 93), which only calls fetch_identity_tree and never populates alternative_csca.

At Line 167-168, Object.keys(readAlternativeCSCA(documentCategory)) then throws a TypeError on null. This happens after readCommitmentTree(documentCategory) succeeds (Line 158), because that check only validates commitment_tree, not alternative_csca. The crash aborts the function before the isUserRegistered fallback runs, so a restorable document fails with an unhandled exception instead of a controlled result.

This is the same defect flagged in the earlier review on this file (Lines 167-168). The proposed fix was not applied.

🐛 Proposed fix
   const readAlternativeCSCA = (category: DocumentCategory) =>
-    getAlternativeCSCA(useProtocolStore, category);
+    getAlternativeCSCA(useProtocolStore, category) ?? {};

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 545ca37d-5e60-43d5-a5d5-566143356d19

📥 Commits

Reviewing files that changed from the base of the PR and between 5e59756 and 4e18f54.

📒 Files selected for processing (2)
  • app/src/proving/checkRestoredDocumentRegistration.ts
  • app/tests/src/proving/checkRestoredDocumentRegistration.test.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.

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.

1 participant