Page MenuHomePhabricator

[Cashtab] Store private key info in android secure storage
ClosedPublic

Authored by bytesofman on Aug 26 2025, 21:56.

Details

Reviewers
Fabien
Group Reviewers
Restricted Project
Commits
rABCb64fa8789ee8: [Cashtab] Store private key info in android secure storage
Summary

Implement customization in the android storage adaptor so that private keys are stored in hardware-encrypted secure storage

Note that, we still have not optimized storage for Android or for using sqlite. We should do this, but will take some more thought around

  • How to best implement sqlite in the web wallet
  • How to minimize storage and maximize usefulness
  • How ecash-wallet will be implemented into Cashtab / initialized from storage

So, we will inevitably have some migrations later. This approach does though silo private keys in special storage.

An alt option would be to encrypt the whole sqlite database. But this would make Cashtab more battery intensive and slow down read-write operations. It's also silly to encrypt the sqlite database which is storing, mostly, cached info that is already public on the blockchain.

This implementation is arguably overkill. Just keeping everything in sqlite is ... pretty secure. To the point where the phone probably has to be rooted to get the private key. It's unclear how much "more secure" keeping the private keys in hardware-encrypted storage actually is. Google / Grok basic consensus seems to be that it is better, though difficult to measure.

Ultimately the "secure" solution is a hardware wallet. The primary benefit of the android app is better control over storage, i.e. less likely to have an android app phantom delete storage vs a webapp.

Test Plan

npm test

Note that storage migration from the previous version is NOT supported

So, to test, run adb shell pm clear org.bitcoinabc.cashtab to clear your app storage, then

./start-android with an android device debug connected to your machine, run the app

While the app is running on your debug-connected phone, adb logcat -s "Capacitor/Console" in a separate terminal to check the logs. you should see

08-27 11:10:53.955 27940 27940 I Capacitor/Console: File: https://localhost/static/js/main.41f859f3.js - Line 82 - Msg: Storage initialized successfully on capacitor-android
08-27 11:10:53.955 27940 27940 I Capacitor/Console: File: https://localhost/static/js/main.41f859f3.js - Line 82 - Msg: Storage config: {
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "env": "capacitor-android",
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "persistent": true,
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "description": "SQLite Database (unlimited storage)"
08-27 11:10:53.955 27940 27940 I Capacitor/Console: }
08-27 11:10:53.955 27940 27940 I Capacitor/Console: File: https://localhost/static/js/main.41f859f3.js - Line 82 - Msg: Storage info: {
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "quota": "Unknown",
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "usage": "Unknown",
08-27 11:10:53.955 27940 27940 I Capacitor/Console:   "available": "Unknown"
08-27 11:10:53.955 27940 27940 I Capacitor/Console: }

create wallets, import wallets, switch wallets, rename wallets, send and receive txs

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

Failed tests logs:

====== CashTab Unit Tests: AndroidStorageAdapter Error Handling should handle SQLite errors gracefully ======
Error: SQLite error
    at Object.<anonymous> (/work/cashtab/src/platform/adapters/__tests__/android.test.ts:252:46)
    at Promise.then.completed (/work/cashtab/node_modules/jest-circus/build/utils.js:298:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (/work/cashtab/node_modules/jest-circus/build/utils.js:231:10)
    at _callCircusTest (/work/cashtab/node_modules/jest-circus/build/run.js:316:40)
    at _runTest (/work/cashtab/node_modules/jest-circus/build/run.js:252:3)
    at _runTestsForDescribeBlock (/work/cashtab/node_modules/jest-circus/build/run.js:126:9)
    at _runTestsForDescribeBlock (/work/cashtab/node_modules/jest-circus/build/run.js:121:9)
    at _runTestsForDescribeBlock (/work/cashtab/node_modules/jest-circus/build/run.js:121:9)
    at run (/work/cashtab/node_modules/jest-circus/build/run.js:71:3)
    at runAndTransformResultsToJestFormat (/work/cashtab/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:122:21)
    at jestAdapter (/work/cashtab/node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:79:19)
    at runTestInternal (/work/cashtab/node_modules/jest-runner/build/runTest.js:367:16)
    at runTest (/work/cashtab/node_modules/jest-runner/build/runTest.js:444:34)
    at Object.worker (/work/cashtab/node_modules/jest-runner/build/testWorker.js:106:12)

Each failure log is accessible here:
CashTab Unit Tests: AndroidStorageAdapter Error Handling should handle SQLite errors gracefully

back out debug log unrelated to the storage adaptor

use correct types, stop supporting maps when we expect array

remove debug logging and legacy fallback

expand comments, back out unrelated line break

throw error if we have issue reconstructing wallets

add logging to ensure we are using android capacitor storage and not falling back to web

Fabien added a subscriber: Fabien.
Fabien added inline comments.
cashtab/src/platform/adapters/android.ts
662 ↗(On Diff #55389)
This revision is now accepted and ready to land.Aug 29 2025, 14:15