Skip to content

fix(android): make wallet migrations fail-safe and data-preserving - #1259

Draft
takemiyamakoto wants to merge 25 commits into
developfrom
codex/android-migration-no-wipe-20260726
Draft

fix(android): make wallet migrations fail-safe and data-preserving#1259
takemiyamakoto wants to merge 25 commits into
developfrom
codex/android-migration-no-wipe-20260726

Conversation

@takemiyamakoto

@takemiyamakoto takemiyamakoto commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Status

Migration-safe Android 4.2.0/230 candidate. This supersedes #1258 without modifying, retargeting, or closing it. The PR intentionally remains draft and requires protected-branch review.

The previously withdrawn 921da5f6 artifact remains invalid and must not be installed or shared. The exact successor below fixes the delayed WalletConnect startup crash, makes AAB identity verification distinguish a non-minified debug bundle (R8 metadata must be absent) from minified IAS/release bundles (exact R8 metadata remains required), and permanently exercises the R8-fixture synthesizer's configured rejection boundaries.

What changed

  • Makes released-schema upgrades fail closed and data-preserving; no destructive database reset or silent wallet wipe.
  • Hardens v27-v31 migration preflight, bounded foreign-key checking, secure-storage recovery, and restoration/security-warning export gates.
  • Adds released-schema, failure-injection, instrumentation-result, and CI migration contracts.
  • Removes Reown SDK calls from the WCDelegate object initializer.
  • Centralizes delegate registration in a synchronized, idempotent helper; App invokes it only after WalletKit succeeds, RootViewModel retries safely, and recoverable registration failures are contained without raw output.
  • Keeps startup and connections nonfatal while the optional SDK is unavailable, while still propagating cancellation and fatal JVM errors.
  • Adds a source-bound Internal App Sharing pipeline with unsigned quarantine, exact bundle identity, public Firebase resources, complete JAR signing coverage, certificate-only qualification, and private-key erasure.
  • Applies an explicit absent R8 metadata policy to non-minified debug AAB verification and an explicit required policy to minified release, Play, and IAS verification.
  • Bounds synthesized ZIP fixtures by source bytes, output bytes, entry count, per-entry expansion, and aggregate expansion; rejects encrypted, unsupported-compression, symlink, race, duplicate, traversal, alias, and case-fold bypasses.
  • Adds a source-extracted R8 synthesis boundary suite: 1 positive + 15 negative/adversarial. Its crafted over-limit cases derive the production constants from the exact synthesizer and hit the first rejected byte for source size, output size, per-entry expansion, and aggregate expansion, plus the first rejected entry count.
  • Pins that boundary suite and its strict comparison operators into the Play release meta-guard.
  • Stages migration instrumentation reports into an upload-portable, content-bound evidence tree before the pinned artifact uploader runs.

Exact source

  • Commit: f897c23cbdc4c13fdf3d1049bd07b588889c941f
  • Tree: 9f5e2ddd0050a55342c508ea37a7ebd5382def2d
  • Parent: 6d19fac384c445a471ea38db18b7eec525600baa
  • Pinned fearless-utils commit: 7500809f33243ee47ecb2ec8563fc284ac4de0d6
  • Effective fearless-utils tree: cc94232148c1bb268b104105e723d6a29019230a
  • Remote branch and PR head both resolve to the exact commit above.

Exact local validation

  • Final no-cache aggregate gate: postMergeVerify, 1,444/1,444 tasks executed in 2 minutes 38 seconds.
  • Exact secretless IAS AAB build: 1,333/1,333 tasks executed in 3 minutes 8 seconds.
  • The exact AAB identity stack passed independently under macOS Bash 3.2 with Python 3.14 and system Python 3.9:
    • AAB signature: 1 positive + 6 adversarial.
    • R8 synthesis boundaries: 1 positive + 15 negative/adversarial.
    • R8 metadata policy: 4 positive + 19 negative/adversarial.
    • AAB identity: 2 positive + 135 adversarial.
  • Play release guard: 4 positive + 123 negative/adversarial.
  • Play source snapshot: 1 positive + 8 negative/adversarial.
  • Full IAS Gradle contract: 8 positive + 39 behavioral negative + 645 static adversarial.
  • Dependency provenance: 2 positive + 40 negative/adversarial.
  • Exact-head signed IAS keystore qualification: 7 positive + 84 negative/adversarial; signed-from non-signature payload equality passed.
  • Exact-head private-key-free public-certificate qualification: 7 positive + 76 negative/adversarial; verification passed again after the ephemeral private key was unlinked.
  • At the 6d19fac38 runtime predecessor, a real 550 MiB declared
    aggregate-expansion ZIP fixture was rejected before entry reads. The
    permanent exact-head suite now covers the configured source/output,
    entry-count, per-entry, and aggregate over-limit branches with crafted
    fixtures.

Exact-head local artifact candidates

  • Unsigned AAB: 41,772,632 bytes; SHA-256 28d73e4c03fe26dc988a71f1caef6a39d2f2e8748dddf1ae242d995381c573d4
  • Signed AAB: 41,901,667 bytes; SHA-256 67f0dd27b3fd65eb53d1b736c8fa572ff781afe79fb0f92afc9bf601cb7406b3
  • Test-only qualifier certificate DER SHA-256 / X.509 fingerprint: 53A73BAAAA2745CEAD1D52CD48E7C3E6E89CE94242DB6B1B1F92A7630073D4E1
  • Bundletool 1.18.3 SHA-256: a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29
  • Signed-from non-signature payload equality passed in both keystore and
    public-certificate modes. The ephemeral private key was then unlinked; the
    retained evidence contains only the public test certificate, and no
    *.jks, *.keystore, *.p12, or *.key remains.
  • These are test-only local candidates. Passing identity and signing
    qualification does not make the signed candidate distributable or
    production-signed.

Device regression

Bundletool derived a universal APK from the exact f897c23cb signed local AAB
and signed the installable APK with the local Android debug key. On a wiped,
disposable API 36 emulator, the exact candidate installed as
4.2.0-ias/230. Its first and second cold launches reached
WalletRootActivity in 128 ms and 101 ms, and the PIDs survived 30-second and
45-second soaks. Exact fatal app signatures, FATAL EXCEPTION, E AndroidRuntime, SQLiteException, ExceptionInInitializerError, and
CoreClient needs to be initialized first scans found zero hits.

On the preserved-data API 34 emulator, the installed predecessor manifest was
exact bcce813b6. Its signer matched the exact f897 universal APK, so package
manager replacement used adb install -r only—no uninstall and no data clear.
The installed base APK then matched the candidate byte-for-byte at SHA-256
fd57aa310645b7ec521e55937496529e7860b361f3f891fd1106e378a9f0f786,
and its manifest identified exact source f897c23c.

The in-place replacement preserved the original first-install timestamp,
package UID 10223, app-data inode 319502, database-directory inode 319908,
and the 39-file private-data inventory. The first cold launch reached
WalletRootActivity in 169 ms and retained PID 23496 across 11 samples over
300 seconds. A second cold launch reached the root activity in 226 ms and
retained PID 23964 for 45 seconds. Both crash buffers were empty; fatal app,
Room/migration, SQLite, ANR, native-signal, initializer, and exact
CoreClient needs to be initialized first scans found zero hits.

Additional runtime-equivalent device evidence was collected from exact
6d19fac38, the parent of f897c23cb. The child changes only test and
documentation files. Bundletool-derived APKs were installed on clean arm64
Android emulators:

  • API 30: cold launch reached WalletRootActivity in 129 ms and retained one live PID for 30 seconds.
  • API 31: cold launch reached WalletRootActivity in 120 ms and retained one live PID for 30 seconds.
  • API 36: first and second cold launches reached the root activity in 122 ms and 175 ms; the PIDs survived 30-second and 45-second soaks.
  • Across those exact-6d19fac38 runs, crash buffers were empty and exact fatal
    app signatures, FATAL EXCEPTION, E AndroidRuntime, SQLiteException,
    Room/migration failure, native-signal, ExceptionInInitializerError, and
    CoreClient needs to be initialized first scans found zero hits.
  • On API 36, the onboarding, existing-wallet, and mnemonic-import screens opened successfully. A deliberately invalid 12-word mnemonic was rejected with Your mnemonic is invalid while the process remained alive and the crash buffer stayed empty.
  • No physical Android device was visible to adb during this qualification, so none is claimed.

The API 30/31/36 checks are fresh-install startup/onboarding evidence. The API
34 lane is an exact-final-source, test-signed in-place replacement over
preserved predecessor data. It does not prove a production-Play-signed,
Google-delivered, or physical-device upgrade.

Handled secretless Reown initialization failures remain in the logs, as do
TypeDefinitionsTreeV2.runtime_id fallback warnings for malformed/legacy
remote runtime metadata. API 30 also logs handled fresh-keystore recovery
exceptions before removing legacy aliases. The two exact-f897 API 34 logs each
contained nine handled Reown/WalletConnect entries. None terminated the process
or was a Room/database migration failure.

Hosted exact-head validation

  • Branch Flow: run 30593523725 — passed.
  • Android CI: run 30593523787 — passed all 42/42 job steps with zero failures or skips, including detekt, unit/coverage, lint, API 30/31/36 migration/restart compatibility, the full API 34 wallet migration/startup matrix, assembly, exact source-bound AAB identity/native-payload verification, and readelf sanity.
  • Android Internal App Sharing Candidate: run 30593523746 — passed; main job completed 20 executed steps successfully with four intentional PR-only skips, the exact IAS AAB gate executed 1,333/1,333 Gradle tasks, and PR artifact publication remained zero.
  • The portable migration evidence artifact records 228/228 tests with zero failures, errors, or skips: 39 each on API 30, API 31, and API 36, plus 111 in the full API 34 matrix. Its streamed ZIP SHA-256 is 1324572aa1b65a87dd5bde7ad7d4de9dace94b9b2afd2a7e0d733ec8b4a9ac4e; all 522/522 manifest payload hashes, the 524-entry path set, CRCs, sidecar, portability/collision rules, and no-symlink/no-encryption constraints passed an independent audit.
  • Hosted exact-AAB boundary results also passed: signature 1 + 6, synthesis 1 + 15, R8 policy 4 + 19, and identity 2 + 135.

The predecessor bcce813b6 Android CI run completed 40 steps successfully and exposed one downstream policy mismatch: its valid non-minified debug AAB had no R8 metadata, while the old verifier required one. Runtime predecessor 6d19fac38 fixes that exact mismatch without weakening release, Play, or IAS verification. Exact head f897c23cb is its test/documentation-only child and adds the permanent R8 synthesis boundary suite and release meta-guards; it does not change runtime or production source files.

All three exact-head hosted workflows are terminal and green. The migration
artifact and the local AAB/APK evidence were independently checked with no
P1/P2 finding. The PR intentionally remains draft and review-blocked; these
results do not authorize merge or distribution.

Distribution boundary

This pull-request validation is intentionally secretless and non-distributable. It proves graceful WalletConnect unavailability, not a successful live pairing. A trusted build with the exact 32-hex WalletConnect project ID may run only after review and merge to protected develop, followed by a manual workflow dispatch.

No Google Play or Internal App Sharing upload occurs from this PR, and no public Android testing link exists yet. Test/debug signing cannot upgrade a Play-installed wallet. Never uninstall a funded wallet to install a test build. Production/Open Testing still requires the authorized Google Play operator and production upload key.

Pin the Maven Central checksum for junit-bom 5.9.2 Gradle module metadata so strict dependency verification succeeds in fresh emulator lanes.
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