What changed, and why it matters
This commit adds custom JSON serializers for two new types of silent-payment address classes and blocks Gson from using Java reflection on any class outside the Sparrow project. The change is defensive: it makes tests fail in the same way production would if a custom serializer were missing, which helps prevent silent data corruption or crashes when saving/loading wallet data. There is no direct evidence of an exploitable vulnerability being fixed.
Treat as a hardening/maintenance change. Review whether the new ReflectionAccessFilter could affect any other external classes that still rely on default Gson reflection, and verify that all such classes now have custom serializers. No urgent security patch appears required based solely on this diff.
Security signals we found
Adds reflection-access filter to block reflective deserialization of external classes
Adds custom Gson serializers/deserializers for silent payment address classes
Comments explicitly describe intent to make tests fail like production to ensure serializers are registered
No direct fix of an injection, deserialization gadget, or cryptographic weakness is visible in the diff
Evidence from the diff
The patch registers Gson JsonSerializer/JsonDeserializer adapters for SilentPaymentAddress and SilentPaymentScanAddress, and adds a ReflectionAccessFilter that BLOCK_ALL reflection access for non-com.sparrowwallet.* classes. It also comments that this ensures classpath-based tests fail identically to module-path production when custom adapters are absent. The change is a follow-up to earlier external-class handling and is primarily about test parity and robust serialization.
Changed components
src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.javaGson serialization/deserialization configurationSilentPaymentAddress persistenceSilentPaymentScanAddress persistenceInspect captured patch +38 / −0
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
index 46f1248..8aff57f 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/JsonPersistence.java
@@ -16,6 +16,8 @@ import com.sparrowwallet.drongo.crypto.EncryptedData;
import com.sparrowwallet.drongo.crypto.EncryptionType;
import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.protocol.Transaction;
+import com.sparrowwallet.drongo.silentpayments.SilentPaymentAddress;
+import com.sparrowwallet.drongo.silentpayments.SilentPaymentScanAddress;
import com.sparrowwallet.drongo.policy.Miniscript;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
@@ -354,6 +356,14 @@ public class JsonPersistence implements Persistence {
gsonBuilder.registerTypeAdapter(PolicyType.class, new PolicyTypeDeserializer());
gsonBuilder.registerTypeAdapter(File.class, new FileSerializer());
gsonBuilder.registerTypeAdapter(File.class, new FileDeserializer());
+ gsonBuilder.registerTypeAdapter(SilentPaymentAddress.class, new SilentPaymentAddressSerializer());
+ gsonBuilder.registerTypeAdapter(SilentPaymentAddress.class, new SilentPaymentAddressDeserializer());
+ gsonBuilder.registerTypeAdapter(SilentPaymentScanAddress.class, new SilentPaymentScanAddressSerializer());
+ gsonBuilder.registerTypeAdapter(SilentPaymentScanAddress.class, new SilentPaymentScanAddressDeserializer());
+
+ //Reflection on any class outside this project fails at Gson adapter construction when running on the module path, unless its module opens the package to Gson
+ //Blocking these classes here ensures classpath-based tests fail in the same way production does, ensuring custom serializers are registered above as necessary
+ gsonBuilder.addReflectionAccessFilter(rawClass -> rawClass.getName().startsWith("com.sparrowwallet.") ? ReflectionAccessFilter.FilterResult.INDECISIVE : ReflectionAccessFilter.FilterResult.BLOCK_ALL);
//Instance creators are required for classes without no-args constructors, since Gson's fallback of sun.misc.Unsafe is unavailable on the module path (jdk.unsupported is not resolved)
//Unsafe is also explicitly disabled below so that classpath-based tests fail in the same way production does if a class is missed here
@@ -452,6 +462,34 @@ public class JsonPersistence implements Persistence {
}
}
+ private static class SilentPaymentAddressSerializer implements JsonSerializer<SilentPaymentAddress> {
+ @Override
+ public JsonElement serialize(SilentPaymentAddress src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.getAddress());
+ }
+ }
+
+ private static class SilentPaymentAddressDeserializer implements JsonDeserializer<SilentPaymentAddress> {
+ @Override
+ public SilentPaymentAddress deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ return SilentPaymentAddress.from(json.getAsJsonPrimitive().getAsString());
+ }
+ }
+
+ private static class SilentPaymentScanAddressSerializer implements JsonSerializer<SilentPaymentScanAddress> {
+ @Override
+ public JsonElement serialize(SilentPaymentScanAddress src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.toKeyString());
+ }
+ }
+
+ private static class SilentPaymentScanAddressDeserializer implements JsonDeserializer<SilentPaymentScanAddress> {
+ @Override
+ public SilentPaymentScanAddress deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ return SilentPaymentScanAddress.fromKeyString(json.getAsJsonPrimitive().getAsString());
+ }
+ }
+
private static class DateSerializer implements JsonSerializer<Date> {
@Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.