From c2a021be5391efe0b811846737f281fd632c5eb1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 20:45:21 +0000 Subject: [PATCH] fix: scope R8 keep rules so Play's DEX optimization checks pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Play Console reports Optimization 13% / Obfuscation 0%, both under the 25% threshold that restricts store visibility and publishing. The cause was three blanket rules, present in both amethyst/proguard-rules.pro and quartz/consumer-rules.pro (the latter merged into every consuming app's R8 config, this one included): -dontobfuscate -keepnames class ** { *; } -keep class com.vitorpamplona.{quartz,amethyst}.** { *; } -dontobfuscate turns renaming off program-wide, and -keepnames is shorthand for -keep,allowshrinking: it permits shrinking but neither renaming nor optimization. Applied to `**` it disabled R8 for the whole program, third-party libraries included — hence 0% and 13%. Replaced with keeps scoped to what is actually reached by name at runtime: - ArtiLogCallback — libarti_android.so does GetMethodID("onLogLine") on it. (ArtiNative itself is already covered by the default `native ` rule, which pins the class of a JNI symbol.) - Jackson's reflective data binding only: nip47WalletConnect.rpc.** and experimental.clink.**, plus the three on-disk JSON stores (ScheduledPost/ScheduledPostFile, PowJobsFile/PersistedPoWJob, ResourceUsageStore$UsageFile). Event, Filter, Message, Command, Rumor, EventTemplate, TagArray and the NIP-46/NIP-55 messages all go through hand-written StdSerializer/StdDeserializer pairs that use string literals, so they need no keep. - Enum constant FIELD names, kept blanket: the preference stores persist `enum.name` into DataStore and read it back with valueOf(), so renamed constants would reset every user's theme/font/Tor/connectivity setting on upgrade. Only the field names are pinned; the enum classes are still renamed and their methods still optimized. - AmethystCastOptionsProvider — AGP generates keeps from manifest component android:name attributes but not from . - ListenableWorker subclasses — WorkManager stores the class name in its own database and instantiates it by name after an update. - androidx.appfunctions declarations (pre-stable library). Dropped -keepparameternames and the LocalVariableTable/LocalVariableTypeTable/ MethodParameters attributes: debug metadata nothing reads (jackson-module-kotlin takes parameter names from @kotlin.Metadata). Added -renamesourcefileattribute so the retained SourceFile cannot be read back as the original class name. Also removed quartz/proguard-rules.pro, commons/proguard-rules.pro and commonsUI/proguard-rules.pro: no build script references them, and each was a stale copy of the blanket keeps being removed here. Verified on the playRelease DEX (:amethyst:minifyPlayReleaseWithR8 then packagePlayRelease, both green, no R8 warnings): 24,994 classes -> 96.0% renamed (996 kept) 102,555 field refs -> 86.3% renamed org.webrtc's 376 kept classes come from stream-webrtc-android's own consumer rules, not from us. Every one of the 41 kept first-party classes is accounted for: manifest components, the two Arti JNI types, the JSON file DTOs and the Cast provider. Enum constants (PENDING, SYSTEM, PURPLE, ALWAYS, ...) confirmed present in the DEX field table. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DEoxktEZyTrAS33vVZBiwm --- .../references/proguard-rules.md | 326 ++++++------------ amethyst/proguard-rules.pro | 167 +++++++-- commons/proguard-rules.pro | 24 -- commonsUI/proguard-rules.pro | 24 -- quartz/consumer-rules.pro | 49 ++- quartz/proguard-rules.pro | 40 --- 6 files changed, 271 insertions(+), 359 deletions(-) delete mode 100644 commons/proguard-rules.pro delete mode 100644 commonsUI/proguard-rules.pro delete mode 100644 quartz/proguard-rules.pro diff --git a/.claude/skills/android-expert/references/proguard-rules.md b/.claude/skills/android-expert/references/proguard-rules.md index b09f3e1b5d..2adabf4593 100644 --- a/.claude/skills/android-expert/references/proguard-rules.md +++ b/.claude/skills/android-expert/references/proguard-rules.md @@ -11,230 +11,108 @@ Proguard configuration for optimizing and obfuscating Android APK while preservi ## Amethyst Proguard Configuration -**File:** `amethyst/proguard-rules.pro` +**Files:** -### Keep Kotlin Metadata +- `amethyst/proguard-rules.pro` — the app's rules. Read it before adding + anything: it is commented rule by rule. +- `quartz/consumer-rules.pro` — merged into the R8 configuration of **every** + app that depends on Quartz, this one included (wired via + `optimization.consumerKeepRules` in `quartz/build.gradle.kts`). A rule added + here silently applies to somebody else's whole program. +- The AGP default `proguard-android-optimize.txt`, which already contributes the + Android-wide basics (Parcelable CREATOR fields, `native `, the enum + `values()`/`valueOf()` pair, …). Don't restate its rules. +- `commons/`, `commonsUI/` and the other library modules deliberately have **no** + rules files. They are not minified and they wire no consumer rules, so a file + there would be dead configuration. + +### The policy: keep only what is reached BY NAME + +Google Play measures how much of a shipped app's DEX R8 actually optimized and +renamed, and warns — then restricts store visibility and publishing — below 25% +in either category. Amethyst has been on the wrong side of that line: a +`-dontobfuscate` plus `-keepnames class ** { *; }` at the top of both +`proguard-rules.pro` and `quartz/consumer-rules.pro`, and outright +`-keep class com.vitorpamplona.** { *; }` for the app's own code, produced 0% +obfuscation and 13% optimization. (`-keepnames` is not the mild rule it looks +like: it expands to `-keep,allowshrinking`, which permits shrinking but neither +renaming nor optimization — applied to `**` it disables R8 for the entire +program, libraries included.) + +So the standing rule is: **a keep needs a named runtime mechanism that reads the +name.** In this app those are, exhaustively: + +| Mechanism | Example | Rule shape | +|---|---|---| +| JNI symbol `Java__` | `ArtiNative`, secp256k1 | covered by the default `native ` rule | +| Native code calling *back* by name | `ArtiLogCallback.onLogLine`, looked up with `GetMethodID` in `tools/arti-build/src/lib.rs` | `-keep class …ArtiLogCallback { *; }` | +| JNA struct/callback mapping | lazysodium | `-keep class com.goterl.lazysodium.** { *; }` | +| Jackson **reflective** data binding | `nip47WalletConnect.rpc.**`, `experimental.clink.**` | `-keep class .** { *; }` | +| Enum constant persisted as a string | `UISharedPreferences` writes `enum.name`, reads `Type.valueOf(s)` | `-keepclassmembers enum * { ; … }` | +| Class name in a manifest `` | `AmethystCastOptionsProvider` | explicit `-keep` — AGP generates keeps from component `android:name`, **not** from meta-data | +| Class name in WorkManager's database | the three `CoroutineWorker`s | `-keep class * extends androidx.work.ListenableWorker { (...); }` | + +What does **not** need a keep, and where the temptation usually comes from: + +- **Quartz events and tags.** `Event`, `Filter`, `Message`, `Command`, `Rumor`, + `EventTemplate`, `TagArray`, the NIP-46 Bunker messages and the NIP-55 intent + results all go through hand-written `StdSerializer`/`StdDeserializer` pairs + registered on `JacksonMapper` / `JsonMapperNip55`. Those read and write + property names as string literals, and `EventFactory` dispatches on kind with + a `when`, not by reflection. Renaming their fields changes nothing on the wire. +- **`@Serializable` (kotlinx) classes**, including the type-safe navigation + routes. The compiler plugin generates a descriptor holding the serial name and + every property name as **compile-time string literals**, so obfuscation cannot + reach them. kotlinx-serialization ships its own consumer rules for the + `$$serializer`/`Companion` plumbing. +- **Compose, Coil, OkHttp, Media3, Firebase, kotlin-reflect.** Every one of them + ships consumer rules inside its own artifact. Check + `build/outputs/mapping//configuration.txt` — the fully merged + configuration — before writing a rule for a third-party library. +- **Manifest-declared components** (activities, services, receivers, providers) + and classes named in layout/`res/xml`. AGP generates those keeps itself, which + is why `Intent().setClassName(ctx, "…NappletBrowserService")` is safe. + +### Attributes ```proguard -# Kotlin metadata is required for reflection --keep class kotlin.Metadata { *; } --keep class kotlin.** { *; } --dontwarn kotlin.** - -# Kotlin serialization --keepattributes *Annotation*, InnerClasses --dontnote kotlinx.serialization.AnnotationsKt --dontnote kotlinx.serialization.SerializationKt - --keep,includedescriptorclasses class com.vitorpamplona.**$$serializer { *; } --keepclassmembers class com.vitorpamplona.** { - *** Companion; -} --keepclasseswithmembers class com.vitorpamplona.** { - kotlinx.serialization.KSerializer serializer(...); -} -``` - -### Keep Nostr Event Classes - -```proguard -# Nostr events are serialized/deserialized --keep class com.vitorpamplona.quartz.events.** { *; } --keep class com.vitorpamplona.quartz.encoders.** { *; } - -# Keep event builders --keep class com.vitorpamplona.quartz.builders.** { *; } - -# Keep tag classes --keep class com.vitorpamplona.quartz.nip01Core.tags.** { *; } -``` - -### Keep Data Classes - -```proguard -# Data classes used in ViewModels and serialization --keep @kotlinx.serialization.Serializable class * { *; } - -# Keep all data classes --keep class com.vitorpamplona.amethyst.model.** { *; } --keep class com.vitorpamplona.amethyst.service.model.** { *; } -``` - -### Keep Compose Classes - -```proguard -# Jetpack Compose --keep class androidx.compose.** { *; } --dontwarn androidx.compose.** - -# Compose runtime --keep class androidx.compose.runtime.** { *; } - -# Compose UI --keep class androidx.compose.ui.** { *; } - -# Material3 --keep class androidx.compose.material3.** { *; } - -# Navigation Compose - Keep serializable routes --keep class * implements java.io.Serializable { *; } --keepclassmembers class * implements java.io.Serializable { - static final long serialVersionUID; - private static final java.io.ObjectStreamField[] serialPersistentFields; - !static !transient ; - private void writeObject(java.io.ObjectOutputStream); - private void readObject(java.io.ObjectInputStream); - java.lang.Object writeReplace(); - java.lang.Object readResolve(); -} -``` - -### Keep OkHttp/Retrofit - -```proguard -# OkHttp --dontwarn okhttp3.** --dontwarn okio.** --keep class okhttp3.** { *; } --keep class okio.** { *; } - -# OkHttp WebSockets (for Nostr relays) --keep class okhttp3.internal.ws.** { *; } - -# Retrofit (if used) --keepattributes Signature --keepattributes Exceptions --keep class retrofit2.** { *; } -``` - -### Keep Jackson (JSON) - -```proguard -# Jackson JSON library --keep class com.fasterxml.jackson.** { *; } --keep class org.codehaus.** { *; } --keepclassmembers class * { - @com.fasterxml.jackson.annotation.* ; -} - -# Jackson polymorphic types --keepattributes RuntimeVisibleAnnotations --keep @com.fasterxml.jackson.annotation.JsonTypeInfo class * -``` - -### Keep Secp256k1 (Crypto) - -```proguard -# Secp256k1 native library --keep class fr.acinq.secp256k1.** { *; } - -# Keep native methods --keepclasseswithmembernames class * { - native ; -} -``` - -### Keep Tor - -```proguard -# Tor library --keep class com.msopentech.thali.toronionproxy.** { *; } --dontwarn com.msopentech.thali.toronionproxy.** -``` - -### Keep ExoPlayer (Media) - -```proguard -# ExoPlayer (Media3) --keep class androidx.media3.** { *; } --dontwarn androidx.media3.** - --keep class com.google.android.exoplayer2.** { *; } --dontwarn com.google.android.exoplayer2.** -``` - -### Keep Coil (Image Loading) - -```proguard -# Coil image loading --keep class coil.** { *; } --keep class coil3.** { *; } --dontwarn coil.** --dontwarn coil3.** -``` - -### Keep ViewModels - -```proguard -# ViewModel classes --keep class * extends androidx.lifecycle.ViewModel { - (); -} - -# ViewModel factories --keep class * extends androidx.lifecycle.ViewModelProvider$Factory { - (...); -} - -# Keep ViewModel constructors for reflection --keepclassmembers class * extends androidx.lifecycle.ViewModel { - (...); -} -``` - -### Keep Parcelable - -```proguard -# Parcelable --keep class * implements android.os.Parcelable { - public static final android.os.Parcelable$Creator *; -} - --keepclassmembers class * implements android.os.Parcelable { - public ; - private ; -} -``` - -### Keep Enums - -```proguard -# Enums --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} -``` - -### Remove Logging (Production) - -```proguard -# Remove debug logging in release builds --assumenosideeffects class android.util.Log { - public static *** d(...); - public static *** v(...); - public static *** i(...); -} - -# Keep error/warning logs --assumenosideeffects class android.util.Log { - public static *** e(...) return false; - public static *** w(...) return false; -} -``` - -### Keep Crashlytics/Firebase - -```proguard -# Firebase Crashlytics +# Retraceable stack traces from the uploaded mapping.txt, without leaking the +# class name back through the file name. -keepattributes SourceFile,LineNumberTable --keep public class * extends java.lang.Exception +-renamesourcefileattribute SourceFile -# Firebase --keep class com.google.firebase.** { *; } --dontwarn com.google.firebase.** +# jackson-module-kotlin reads @kotlin.Metadata; R8 requires InnerClasses and +# EnclosingMethod alongside Signature. +-keepattributes *Annotation*,Signature,Exceptions,InnerClasses,EnclosingMethod ``` +`LocalVariableTable`, `LocalVariableTypeTable`, `MethodParameters` and +`-keepparameternames` are debug metadata that nothing in the app reads — +jackson-module-kotlin takes parameter names from `@kotlin.Metadata`, not from +`MethodParameters`. They were removed; don't add them back. + +### Verifying a change to these rules + +R8 cannot see reflection, so a wrong keep rule fails **only in a release build, +at runtime**. Before changing them: + +```bash +./gradlew :amethyst:assemblePlayRelease -PdisableAbiSplits=true -PdisableUniversalApk=true +``` + +then read `amethyst/build/outputs/mapping/playRelease/`: + +- `configuration.txt` — every rule R8 actually saw, including each AAR's + consumer rules. This is the file that answers "does library X already keep + itself?" +- `mapping.txt` — what got renamed. Lines whose left and right sides are equal + are classes a keep rule pinned; scan them for anything you did not intend. +- `seeds.txt` / `usage.txt` — what the keeps matched, and what was removed. + +Exercise NIP-47 wallet connect, NIP-46 bunker login, Tor, scheduled posts and a +settings round-trip (change theme/font, kill, relaunch) on the minified build — +those are the paths the keeps above exist for. + ## Build Configuration ### Enable R8 in build.gradle @@ -356,13 +234,15 @@ adb install app/build/outputs/apk/release/app-release.apk ### Issue: Compose Navigation Crashes -**Cause:** @Serializable route classes were obfuscated. +**Not** the route classes being obfuscated — that cannot happen. A type-safe +route's pattern comes from its kotlinx-serialization descriptor, and the compiler +plugin bakes the serial name and every property name in as string literals, so +renaming the class leaves the route string untouched. Adding +`-keep @kotlinx.serialization.Serializable class …routes.** { *; }` pins a large +tree for no reason and hides the real cause. -**Solution:** -```proguard -# Keep all route classes --keep @kotlinx.serialization.Serializable class com.vitorpamplona.amethyst.ui.navigation.routes.** { *; } -``` +Look instead at whether `navigation-common`'s own consumer rules made it into +`configuration.txt`, and at the stack trace retraced through `mapping.txt`. ### Issue: Native Library Crashes diff --git a/amethyst/proguard-rules.pro b/amethyst/proguard-rules.pro index ccd571f29e..1f225dbea8 100644 --- a/amethyst/proguard-rules.pro +++ b/amethyst/proguard-rules.pro @@ -1,38 +1,70 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# ============================================================================= +# R8 configuration for the release build. # -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html +# Two things are balanced here. +# +# 1. Google Play measures how much of the shipped DEX R8 actually optimized +# and renamed, and warns (then restricts visibility/publishing) below 25% +# in either category. This file used to open with `-dontobfuscate` plus +# `-keepnames class ** { *; }`, and then kept all of `com.vitorpamplona.**` +# outright. That is the whole app and every library: `-dontobfuscate` +# turns renaming off globally, and `-keepnames` is shorthand for +# `-keep,allowshrinking`, which permits shrinking but neither renaming nor +# optimization. Hence 0% obfuscation / 13% optimization. +# +# 2. Anything the runtime reaches by NAME rather than by reference has to keep +# that name: JNI symbols, Jackson's reflective data binding, enum constants +# persisted into DataStore, class names written into a manifest meta-data +# value or into WorkManager's database. +# +# So every keep below is scoped to (2), and R8 gets everything else. mapping.txt +# is uploaded with each release, so stack traces stay retraceable. +# +# When adding a keep, say in a comment WHAT reads the name at runtime. A keep +# without that is usually a keep that is not needed. +# ============================================================================= -# preserve the line number information for debugging stack traces. --dontobfuscate --keepattributes LocalVariableTable --keepattributes LocalVariableTypeTable --keepattributes *Annotation* --keepattributes SourceFile --keepattributes LineNumberTable --keepattributes Signature --keepattributes Exceptions --keepattributes InnerClasses --keepattributes EnclosingMethod --keepattributes MethodParameters --keepparameternames +# ----------------------------------------------------------------------------- +# Attributes +# ----------------------------------------------------------------------------- +# SourceFile + LineNumberTable are what let Play (and `retrace`) turn an +# obfuscated stack trace back into real line numbers via mapping.txt. +# -renamesourcefileattribute replaces the real file name with a constant so the +# class name cannot simply be read back off it. +-keepattributes SourceFile,LineNumberTable +-renamesourcefileattribute SourceFile + +# Annotations (jackson-module-kotlin reads @kotlin.Metadata; Jackson mixins and +# kotlinx.serialization read their own), generic signatures, and the +# inner/enclosing-class links that R8 requires alongside Signature. +-keepattributes *Annotation*,Signature,Exceptions,InnerClasses,EnclosingMethod + +# LocalVariableTable, LocalVariableTypeTable, MethodParameters and +# -keepparameternames used to be kept here as well. They are debug metadata: +# nothing in the app reads them (jackson-module-kotlin takes parameter names +# from @kotlin.Metadata, not from MethodParameters), and they are pure DEX +# weight in a release build. -keepdirectories libs -# Keep all names --keepnames class ** { *; } +# ----------------------------------------------------------------------------- +# JNI — names that live in a .so, not in the DEX +# ----------------------------------------------------------------------------- +# proguard-android-optimize.txt already contributes +# -keepclasseswithmembernames class * { native ; } +# which pins every class that DECLARES a native method (ArtiNative, +# secp256k1's loader, …) together with those methods' names, because the +# exported symbol is Java__. What that does NOT cover is the +# traffic in the other direction: Java/Kotlin the native side looks up itself. -# Keep All enums --keep enum ** { *; } +# libarti_android.so calls back into this interface by name — +# tools/arti-build/src/lib.rs does GetMethodID("onLogLine") on it. Renaming the +# method silently kills all Tor log output. +-keep class com.vitorpamplona.amethyst.ui.tor.ArtiLogCallback { *; } -# preserve access to native classses +# secp256k1's JNI layer resolves these from native code. -keep class fr.acinq.secp256k1.** { *; } -# JNA For Libsodium --keep class com.goterl.lazysodium.** { *; } - # libscrypt -keep class com.lambdaworks.codec.** { *; } -keep class com.lambdaworks.crypto.** { *; } @@ -40,6 +72,10 @@ -keep class info.guardianproject.** { *; } +# JNA for Libsodium: JNA maps these types onto the C ABI by reflecting over +# their fields and method signatures at runtime. +-keep class com.goterl.lazysodium.** { *; } + # JNA also requires AWT, which Android does not have. So the classes are broken down to filter AWT out -keep class com.sun.jna.ToNativeConverter { *; } -keep class com.sun.jna.NativeMapped { *; } @@ -59,13 +95,76 @@ private static java.lang.Object fromNative(com.sun.jna.FromNativeConverter, java.lang.Object, java.lang.reflect.Method); } -# JSON parsing --keep class com.vitorpamplona.quartz.** { *; } --keep class com.vitorpamplona.amethyst.** { *; } - -# Room generates *_Impl subclasses instantiated reflectively via no-arg constructor. -# -keepnames preserves the name but R8 still strips the unused (). --keep class * extends androidx.room.RoomDatabase { - (); +# ----------------------------------------------------------------------------- +# Enum constant names +# ----------------------------------------------------------------------------- +# An enum constant's NAME is persisted data in this app. The preference stores +# write `enum.name` into DataStore and read it back with `Type.valueOf(string)` +# (see model/preferences/UISharedPreferences.kt, TorSharedPreferences.kt, +# NamecoinSharedPreferences.kt), and Jackson serialises enums by name too. +# Enum.valueOf resolves that string against the static FIELD name, so renaming +# the constants would reset every user's theme/font/Tor/connectivity setting on +# the first launch after an update. +# +# Deliberately blanket rather than a list of the enums that happen to be +# persisted today: the failure mode is silent, release-only, and one new +# `preferences[KEY] = value.name` line away. Only the field names are pinned — +# the enum classes themselves are still renamed and their methods still +# optimized. +-keepclassmembers enum * { + ; + public static **[] values(); + public static ** valueOf(java.lang.String); } +# ----------------------------------------------------------------------------- +# Jackson — reflective data binding only +# ----------------------------------------------------------------------------- +# Most of Quartz's wire format does NOT need a keep. Event, Filter, Message, +# Command, Rumor, EventTemplate, TagArray, the NIP-46 Bunker messages and the +# NIP-55 intent results all go through hand-written StdSerializer/StdDeserializer +# pairs registered on JacksonMapper / JsonMapperNip55, which read and write +# property names as string literals. Renaming their fields changes nothing on +# the wire. +# +# What remains is the code Jackson data-binds REFLECTIVELY — `treeToValue(...)` +# and `readValue()` with no custom deserializer. There the JSON property +# names come from the Kotlin constructor parameter names, so a renamed field is +# a changed wire format. + +# NIP-47 Wallet Connect RPC: every *Method / *Params / *SuccessResponse plus +# NwcError, NwcTransaction and the NwcMethod/NwcErrorCode enums are reached via +# treeToValue() from RequestDeserializer / ResponseDeserializer / +# NotificationDeserializer. +-keep class com.vitorpamplona.quartz.nip47WalletConnect.rpc.** { *; } + +# CLINK (experimental NIP-XX offers/debits/manage): parsed with +# OptimizedJsonMapper.fromJsonTo() and friends — reflective. +-keep class com.vitorpamplona.quartz.experimental.clink.** { *; } + +# On-disk JSON written and re-read by the app itself. The field names are the +# file format, so renaming them makes every existing file unreadable. +-keep class com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPost { *; } +-keep class com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostFile { *; } +-keep class com.vitorpamplona.amethyst.service.pow.PowJobsFile { *; } +-keep class com.vitorpamplona.amethyst.commons.service.pow.PersistedPoWJob { *; } +-keep class com.vitorpamplona.amethyst.service.resourceusage.ResourceUsageStore$UsageFile { *; } + +# ----------------------------------------------------------------------------- +# Names referenced from outside the DEX +# ----------------------------------------------------------------------------- +# AGP generates keeps from the merged manifest's component `android:name` +# attributes, but NOT from . The Cast framework reads +# this one out of the manifest and Class.forName()s it. +-keep class com.vitorpamplona.amethyst.service.cast.chromecast.AmethystCastOptionsProvider { *; } + +# WorkManager stores the worker's class name in its own database at enqueue +# time and instantiates it by name on a later process start — including after +# an app update, when R8 has produced a different mapping. +-keep class * extends androidx.work.ListenableWorker { (...); } + +# androidx.appfunctions: the KSP-generated invokers and the app_functions.xml +# the system reads are keyed off these declarations. One class plus its +# generated neighbours — cheap enough not to be worth proving unnecessary +# against a pre-stable (alpha) library. +-keep class com.vitorpamplona.amethyst.appfunctions.** { *; } diff --git a/commons/proguard-rules.pro b/commons/proguard-rules.pro deleted file mode 100644 index 0bf7b32616..0000000000 --- a/commons/proguard-rules.pro +++ /dev/null @@ -1,24 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - --keep class com.vitorpamplona.quartz.** { *; } --keep class com.vitorpamplona.amethyst.** { *; } \ No newline at end of file diff --git a/commonsUI/proguard-rules.pro b/commonsUI/proguard-rules.pro deleted file mode 100644 index 0bf7b32616..0000000000 --- a/commonsUI/proguard-rules.pro +++ /dev/null @@ -1,24 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - --keep class com.vitorpamplona.quartz.** { *; } --keep class com.vitorpamplona.amethyst.** { *; } \ No newline at end of file diff --git a/quartz/consumer-rules.pro b/quartz/consumer-rules.pro index 04373ed538..ec562e366e 100644 --- a/quartz/consumer-rules.pro +++ b/quartz/consumer-rules.pro @@ -1,19 +1,18 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# ============================================================================= +# Keep rules Quartz contributes to every app that consumes it (wired through +# `optimization.consumerKeepRules` in build.gradle.kts, so these end up merged +# into the consumer's own R8 configuration). # -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html +# Scope them tightly. A rule here applies to the CONSUMER's whole program, so +# the `-keepnames class ** { *; }` that used to sit in this file pinned every +# name in every app that depends on Quartz — ours included — and blocked R8 +# from optimizing any member anywhere (`-keepnames` is `-keep,allowshrinking`). +# Only list what breaks at runtime if the name changes. +# ============================================================================= -keepdirectories libs -# Keep all names --keepnames class ** { *; } - -# Keep All enums --keep enum ** { *; } - -# preserve access to native classses +# secp256k1's JNI layer resolves these from native code. -keep class fr.acinq.secp256k1.** { *; } # libscrypt @@ -21,5 +20,27 @@ -keep class com.lambdaworks.crypto.** { *; } -keep class com.lambdaworks.jni.** { *; } -# JSON parsing --keep class com.vitorpamplona.quartz.** { *; } \ No newline at end of file +# Jackson data binding, reflective paths only. +# +# Nearly all of Quartz's wire format is handled by the hand-written +# StdSerializer/StdDeserializer pairs registered on JacksonMapper (Event, +# Filter, Message, Command, Rumor, EventTemplate, TagArray, the NIP-46 Bunker +# messages) and JsonMapperNip55 (IntentResult, Permission). Those read and +# write property names as string literals, so their fields are free to be +# renamed. +# +# These two trees are not: they are data-bound reflectively, via +# `treeToValue(...)` and `OptimizedJsonMapper.fromJsonTo()`, which derive the +# JSON property names from the Kotlin constructor parameter names. +-keep class com.vitorpamplona.quartz.nip47WalletConnect.rpc.** { *; } +-keep class com.vitorpamplona.quartz.experimental.clink.** { *; } + +# Quartz serialises enums by name (Jackson writes/reads Enum.name, and +# Enum.valueOf resolves that string against the static field name), so the +# constants of its own enums have to keep their names. Consumers that persist +# their OWN enums by name need the equivalent rule in their own configuration. +-keepclassmembers enum com.vitorpamplona.quartz.** { + ; + public static **[] values(); + public static ** valueOf(java.lang.String); +} diff --git a/quartz/proguard-rules.pro b/quartz/proguard-rules.pro deleted file mode 100644 index e1f545f4da..0000000000 --- a/quartz/proguard-rules.pro +++ /dev/null @@ -1,40 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# preserve the line number information for debugging stack traces. --dontobfuscate --keepattributes LocalVariableTable --keepattributes LocalVariableTypeTable --keepattributes *Annotation* --keepattributes SourceFile --keepattributes LineNumberTable --keepattributes Signature --keepattributes Exceptions --keepattributes InnerClasses --keepattributes EnclosingMethod --keepattributes MethodParameters --keepparameternames - --keepdirectories libs - -# Keep all names --keepnames class ** { *; } - -# Keep All enums --keep enum ** { *; } - -# preserve access to native classses --keep class fr.acinq.secp256k1.** { *; } - -# libscrypt --keep class com.lambdaworks.codec.** { *; } --keep class com.lambdaworks.crypto.** { *; } --keep class com.lambdaworks.jni.** { *; } - -# JSON parsing --keep class com.vitorpamplona.quartz.** { *; } -