re add check to ensure change addresses are not shown as receiving addresses
What changed, and why it matters
This commit fixes a bug in the Stack Wallet app where a special internal 'change' address for the Spark privacy feature could accidentally be shown to the user as a normal receiving address. If a user copied and shared that address, funds meant as change from a transaction could be sent to a publicly visible address, weakening the privacy protections that Spark is designed to provide. The fix re-adds checks that skip over the change diversifier when generating addresses to display.
Review git history to confirm when the sparkChange guard was removed and whether any release shipped without it. Audit other Spark address derivation paths for similar missing guards, and add regression tests that assert sparkChange is never returned as AddressSubType.receiving.
Security signals we found
Privacy degradation: change address exposed as receiving address
Re-addition of previously removed guard indicates regression fix
Spark anonymity/privacy model relies on change address separation
User could unknowingly reuse or share a change address
Evidence from the diff
The patch restores guards around libSpark.sparkChange, a reserved diversifier index used for Spark change outputs. Previously, generateNextSparkAddress() and the address lookahead scan could land on or expose that index as a receiving address. The fix sets AddressSubType.change when the diversifier equals sparkChange, and increments the diversifier in generation/scan loops so only receiving addresses are presented to the user. This prevents users from depositing funds into the change address and degrading Spark anonymity set behavior.
Changed components
lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dartSpark address generationSpark address lookahead scannerAddress sub-type classificationInspect captured patch +10 / −4
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
index b5c95ab..212ecb4 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
@@ -147,7 +147,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
derivationIndex: diversifier,
derivationPath: DerivationPath()..value = sparkDerivationPath,
type: AddressType.spark,
- subType: AddressSubType.receiving,
+ subType: diversifier == libSpark.sparkChange ? .change : .receiving,
);
}
@@ -349,9 +349,11 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
Future<Address> generateNextSparkAddress() async {
- final newAddress = await generateSparkAddress(
- _currentSparkAddress.derivationIndex + 1,
- );
+ int diversifier = _currentSparkAddress.derivationIndex + 1;
+ if (diversifier == libSpark.sparkChange) {
+ diversifier++; // ensure only receiving addresses are shown
+ }
+ final newAddress = await generateSparkAddress(diversifier);
_currentSparkAddress = newAddress;
return newAddress;
}
@@ -1338,6 +1340,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final maxDiversifier = diversifier + lookAheadCount;
while (diversifier < maxDiversifier) {
+ // change address check
+ if (diversifier == libSpark.sparkChange) {
+ diversifier++;
+ }
final addressString = await generateSparkAddress(diversifier);
myAddresses.add(addressString.value);
Why this scored 60/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.