fix: Particl wallet P2PKH signing and checkBlockUTXO cast
What changed, and why it matters
This commit fixes two bugs in Stack Wallet's support for the Particl cryptocurrency. First, it corrects how the app reads transaction data returned by ElectrumX servers, replacing a brittle type cast with safer loop-based lookup. Second, it removes a hack that stripped trailing zero bytes from raw Particl transaction hex, and instead passes a flag so the underlying library produces the correct hex directly. These are correctness fixes for transaction building and UTXO parsing; they do not appear to be security patches for an exploitable vulnerability, but the removed hex-stripping hack could theoretically have produced malformed transactions.
Treat as a routine bug-fix commit. Review whether the removed trailing-null-byte stripping could have affected any previously broadcast Particl transactions, and verify that builtTx.toHex(isParticl: true) now produces the exact hex the network expects. No urgent security response is indicated from the diff alone.
Security signals we found
Removed custom hex malleability: previous code stripped trailing null bytes from raw transaction hex, which could alter transaction identity or validity.
Type-cast hardening: replaced unsafe cast<Map<String, dynamic>?>() on server-supplied JSON with explicit type checks, reducing crash surface.
Library flag now used for Particl-specific serialization instead of post-hoc string manipulation.
Evidence from the diff
The patch modifies lib/wallets/wallet/impl/particl_wallet.dart. In checkBlockUTXO, it replaces outputs.cast
Changed components
lib/wallets/wallet/impl/particl_wallet.dartParticl wallet P2PKH signingcheckBlockUTXO ElectrumX response parsingInspect captured patch +10 / −27
diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart
index 2d16f0b..65bc9c4 100644
--- a/lib/wallets/wallet/impl/particl_wallet.dart
+++ b/lib/wallets/wallet/impl/particl_wallet.dart
@@ -77,10 +77,14 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
final vout = jsonUTXO["tx_pos"] as int;
final outputs = jsonTX["vout"] as List? ?? [];
- final output = outputs.cast<Map<String, dynamic>?>().firstWhere(
- (e) => e?["n"] == vout,
- orElse: () => null,
- );
+ // Use Map<dynamic, dynamic>? because ElectrumX returns _Map<dynamic,dynamic>.
+ Map<dynamic, dynamic>? output;
+ for (final o in outputs) {
+ if (o is Map && o["n"] == vout) {
+ output = o;
+ break;
+ }
+ }
if (output != null) {
if (output['ct_fee'] != null) {
@@ -508,6 +512,7 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
),
witnessValue: insAndKeys[i].utxo.value,
redeemScript: extraData[i].redeem,
+ isParticl: true,
overridePrefix: cryptoCurrency.networkParams.bech32Hrp,
);
}
@@ -523,30 +528,8 @@ class ParticlWallet<T extends ElectrumXCurrencyInterface>
final builtTx = txb.build(cryptoCurrency.networkParams.bech32Hrp);
final vSize = builtTx.virtualSize();
- // Strip trailing 0x00 bytes from hex.
- //
- // This is done to match the previous particl_wallet implementation.
- // TODO: [prio=low] Rework Particl tx construction so as to obviate this.
- String hexString = builtTx.toHex(isParticl: true).toString();
- if (hexString.length % 2 != 0) {
- // Ensure the string has an even length.
- Logging.instance.e(
- "Hex string has odd length, which is unexpected.",
- stackTrace: StackTrace.current,
- );
- throw Exception("Invalid hex string length.");
- }
- // int maxStrips = 3; // Strip up to 3 0x00s (match previous particl_wallet).
- while (hexString.endsWith('00') && hexString.length > 2) {
- hexString = hexString.substring(0, hexString.length - 2);
- // maxStrips--;
- // if (maxStrips <= 0) {
- // break;
- // }
- }
-
return txData.copyWith(
- raw: hexString,
+ raw: builtTx.toHex(isParticl: true),
vSize: vSize,
tempTx: null,
// builtTx.getId() requires an isParticl flag as well but the lib does not support that yet
Why this scored 40/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.