Skip to content

fix(mobile): keep BouncyCastle out of R8 shrinking - #3293

Open
re2zero wants to merge 1 commit into
GCWing:mainfrom
re2zero:fix/mobile-release-bouncycastle
Open

re2zero wants to merge 1 commit into
GCWing:mainfrom
re2zero:fix/mobile-release-bouncycastle

Conversation

@re2zero

@re2zero re2zero commented Oct 8, 2026

Copy link
Copy Markdown

Summary

Release Android builds minify with R8, and every BouncyCastle class reached only through JCA name lookup (Provider.getService) is invisible to that analysis: the shrinker strips the X25519 and AES-GCM implementations while the provider registration strings stay. A fresh GitHub relay sign-in sails through /api/auth/github/start and poll — neither touches crypto — and then dies inside the first local key derivation (DeviceIdentity.publicKey), which the account store's catch-all reports to the user as "The relay returned an invalid account response." No relay response is involved at all, so the message is both wrong and a dead end.

This PR keeps org.bouncycastle.** intact in release builds.

Type and Areas

Type: bug fix

Areas: mobile (Android app, release build config)

Motivation / Impact

On any release build (assembleRelease enables R8 minification), GitHub web sign-in could never complete: the browser authorization succeeds, and the moment the app exchanges the token locally the login fails with the misleading malformed-response error. Debug builds never shrink, which is why the breakage stayed invisible to contributors running :app:assembleDebug. After this change, release sign-in works end to end.

Verification

  • classes.dex inspection of :app:assembleRelease output at upstream/main + this change: org.bouncycastle.math.ec.rfc7748 (X25519) goes from 0 references to 8, GCMBlockCipher from 0 to 3; APK size grows 4.6 MB → 6.7 MB.
  • On-device (realme RMX1991, Android 11): with the pre-fix release APK, web sign-in reaches authorization poll result status=authorized and then fails with the malformed-response error — no /api/auth/login request is ever logged. With this change, the same flow logs account login response accepted, and a signed-in client immediately completes X25519-encrypted device RPCs (ping → pong) against a paired desktop.

Reviewer Notes

  • The whole-package keep matches the standard guidance for BouncyCastle on Android; measured cost is ~2 MB of APK size. A narrower rule (JCA SPI packages only) risks the same regression through BC's internal reflective paths.
  • The shared/ included build needs its own local.properties for local builds; that file is gitignored and is not part of this PR.

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable. (No user-facing text change.)

Release builds minify with R8, and every BouncyCastle class reached only
through JCA name lookup (Provider.getService) is invisible to that
analysis: the shrinker stripped the X25519 and AES-GCM implementations
while the provider registration strings stayed. A fresh GitHub sign-in
sailed through start and poll - neither touches crypto - and then died
inside the first local key derivation, which the account store's
catch-all reports as a malformed relay response. Debug builds never
shrink, which is why the breakage stayed invisible to contributors.

Keeping org.bouncycastle.** intact grows the release APK from 4.6 MB to
6.7 MB; a signed-in client immediately completes X25519-encrypted device
RPCs again.
@re2zero

re2zero commented Oct 8, 2026

Copy link
Copy Markdown
Author

用中文补充说明一下问题和原因:

现象:release 包上用 GitHub 网页授权登录,浏览器里一切正常、也提示登录成功,回到 App 却报「中继返回了无效的账号响应」,每次必现;debug 包完全正常。

原因:这个报错其实是个"冤案"——失败发生在本地,和中继返回了什么毫无关系。release 包开了 R8 代码收缩,而 BouncyCastle 的算法实现是通过 JCA 按名字查找的(KeyPairGenerator.getInstance("X25519") 这类反射路径),R8 静态分析看不见它们,就把 X25519、AES-GCM 等实现类当成死代码剥掉了,只留下了 provider 的注册字符串。登录链路里 start/poll 是纯 HTTPS+JSON、不碰加密,所以浏览器授权一路绿灯;拿到 token 之后,App 要在本地做第一次 X25519 公钥派生,就在这一步抛出类缺失异常,又被账号模块的兜底 catch 吞掉,统一映射成了这条"无效响应"文案——所以文案看起来驴唇不对马嘴。

证据:坏包的 classes.dex 里 org.bouncycastle.math.ec.rfc7748(X25519 实现)引用数为 0、GCMBlockCipher 为 0;实机日志显示 poll authorized 之后直接失败,/api/auth/login 请求从未发出。加上本 PR 的 keep 规则后 dex 恢复(X25519 引用 8 处),同一台设备登录立即成功,登录后的 X25519 加密设备 RPC(ping→pong)也一切正常。

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