What changed, and why it matters
This commit is a routine code cleanup and linter-compliance patch for the Stack Wallet Firo/Spark wallet code. It removes unused imports, renames a variable for clarity, refactors a small helper function, updates comments, and bumps a dependency (coinlib) to a newer commit. There is no direct evidence in the diff of a security vulnerability being fixed or introduced.
No immediate security action required. If the coinlib dependency bump is suspected to contain security fixes, verify the upstream coinlib changelog or commit history for the new ref `5c59c7e7d120d9c981f23008fa03421d39fe8631`.
Security signals we found
No direct security fix evident in diff
Dependency coinlib updated to newer git ref, but no security advisory or changelog supplied
Code cleanup only: unused imports removed, constants extracted, comments reformatted
No changes to authentication, key handling, network trust, or transaction validation semantics
Evidence from the diff
The diff shows non-functional changes: import pruning, renaming Cryptography import to crypto, extracting a repeated Decimal.fromInt(1000) into _masterNodeValue, moving inline hashTag parsing into a top-level helper _hashTag, removing an unused recoverViewOnlyWallet() method, comment/line-length reformatting, and updating pubspec.lock to a newer coinlib/coinlib_flutter git ref. No cryptographic, authorization, input-validation, or transaction-broadcasting logic is substantively altered. The dependency bump is not described by the vendor as security-relevant and no advisory or CVE is referenced.
Changed components
lib/wallets/wallet/impl/firo_wallet.dartlib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dartpubspec.lock (coinlib / coinlib_flutter git refs)Inspect captured patch +85 / −65
diff --git a/lib/wallets/wallet/impl/firo_wallet.dart b/lib/wallets/wallet/impl/firo_wallet.dart
index 49319c4..7954a76 100644
--- a/lib/wallets/wallet/impl/firo_wallet.dart
+++ b/lib/wallets/wallet/impl/firo_wallet.dart
@@ -1,16 +1,13 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:math';
import 'dart:typed_data';
-import 'package:coinlib_flutter/coinlib_flutter.dart'
- show base58Decode, P2SH, Base58Address, P2PKH;
-import 'package:crypto/crypto.dart' as Cryptography;
+import 'package:coinlib_flutter/coinlib_flutter.dart' show base58Decode, P2PKH;
+import 'package:crypto/crypto.dart' as crypto;
import 'package:decimal/decimal.dart';
import 'package:isar_community/isar.dart';
import '../../../db/sqlite/firo_cache.dart';
-import '../../../models/buy/response_objects/crypto.dart';
import '../../../models/input.dart';
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
@@ -23,7 +20,6 @@ import '../../../utilities/logger.dart';
import '../../../utilities/util.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
-import '../../crypto_currency/intermediate/bip39_hd_currency.dart';
import '../../isar/models/spark_coin.dart';
import '../../isar/models/wallet_info.dart';
import '../../models/tx_data.dart';
@@ -73,6 +69,8 @@ class MasternodeInfo {
});
}
+final _masterNodeValue = Decimal.fromInt(1000); // full value (not sats)
+
class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
with
ElectrumXInterface<T>,
@@ -726,9 +724,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
// Fall back to locked in case network call fails
blocked =
Amount.fromDecimal(
- Decimal.fromInt(
- 1000, // 1000 firo output is a possible master node
- ),
+ _masterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
).raw ==
BigInt.from(jsonUTXO["value"] as int);
@@ -925,7 +921,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
) async {
if (info.cachedBalance.spendable <
Amount.fromDecimal(
- Decimal.fromInt(1000),
+ _masterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
)) {
throw Exception(
@@ -970,7 +966,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
registrationTx.add(ByteData(32).buffer.asUint8List());
// collateralOutpoint.index (2 bytes)
- // This is going to be 0. (The only other output will be change at position 1.)
+ // This is going to be 0.
+ // (The only other output will be change at position 1.)
registrationTx.add(
(ByteData(4)..setInt16(0, 0, Endian.little)).buffer.asUint8List(),
);
@@ -990,7 +987,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
throw Exception("Invalid IP part: $part");
}
}
- // This is serialized as an IPv6 address (which it cannot be), so there will be 12 bytes of padding.
+ // This is serialized as an IPv6 address (which it cannot be),
+ // so there will be 12 bytes of padding.
registrationTx.add(ByteData(10).buffer.asUint8List());
registrationTx.add([0xff, 0xff]);
registrationTx.add(ipParts);
@@ -1015,7 +1013,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
// pubKeyOperator (48 bytes)
final operatorPubKeyBytes = operatorPubKey.toUint8ListFromHex;
if (operatorPubKeyBytes.length != 48) {
- // These actually have a required format, but we're not going to check it. The transaction will fail if it's not
+ // These actually have a required format, but we're not going to check it.
+ // The transaction will fail if it's not
// valid.
throw Exception("Invalid operator public key: $operatorPubKey");
}
@@ -1066,15 +1065,16 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
final partialTxData = TxData(
// nVersion: 3, nType: 1 (TRANSACTION_PROVIDER_REGISTER)
overrideVersion: 3 + (1 << 16),
- // coinSelection fee calculation uses a heuristic that doesn't know about vExtraData, so we'll just use a really
- // big fee to make sure the transaction confirms.
+ // coinSelection fee calculation uses a heuristic that doesn't know about
+ // vExtraData, so we'll just use a really big fee to make sure the
+ // transaction confirms.
feeRateAmount: cryptoCurrency.defaultFeeRate * BigInt.from(10),
recipients: [
TxRecipient(
- address: collateralAddress!.value,
+ address: collateralAddress.value,
addressType: AddressType.p2pkh,
amount: Amount.fromDecimal(
- Decimal.fromInt(1000),
+ _masterNodeValue,
fractionDigits: cryptoCurrency.fractionDigits,
),
isChange: false,
@@ -1107,13 +1107,12 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
.asUint8List(),
);
}
- final inputsHash = Cryptography.sha256
- .convert(inputsHashInput.toBytes())
- .bytes;
- final inputsHashHash = Cryptography.sha256.convert(inputsHash).bytes;
+ final inputsHash = crypto.sha256.convert(inputsHashInput.toBytes()).bytes;
+ final inputsHashHash = crypto.sha256.convert(inputsHash).bytes;
registrationTx.add(inputsHashHash);
- // vchSig is a variable length field that we need iff the collateral is NOT in the same transaction, but for us it is.
+ // vchSig is a variable length field that we need iff the collateral is
+ // NOT in the same transaction, but for us it is.
registrationTx.addByte(0);
final finalTxData = partialTx.copyWith(
@@ -1134,7 +1133,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
throw Exception("Failed to broadcast transaction: $broadcastedTxHash");
}
Logging.instance.i(
- "Successfully broadcasted masternode registration transaction: $finalTransactionHex (txid $broadcastedTxHash)",
+ "Successfully broadcasted masternode registration transaction: "
+ "$finalTransactionHex (txid $broadcastedTxHash)",
);
await updateSentCachedTxData(txData: finalTx);
@@ -1180,7 +1180,8 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
pubKeyOperator: info["state"]["pubKeyOperator"] as String,
);
} catch (err) {
- // getMyMasternodeProTxHashes() may give non-masternode txids, so only log as info.
+ // getMyMasternodeProTxHashes() may give non-masternode txids, so
+ // only log as info.
Logging.instance.i("Error getting masternode info for $e: $err");
return null;
}
@@ -1190,26 +1191,38 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
}
Future<List<String>> getMyMasternodeProTxHashes() async {
- // - This registers only masternodes which have collateral in the same transaction.
- // - If this seed is shared with firod or such and a masternode is created there, it will probably not appear here
+ // - This registers only masternodes which have collateral in the same
+ // transaction.
+ // - If this seed is shared with firod or such and a masternode is created
+ // there, it will probably not appear here
// because that doesn't put collateral in the protx tx.
- // - An exactly 1000 FIRO vout will show up here even if it's not a masternode collateral. This will just log an
+ // - An exactly 1000 FIRO vout will show up here even if it's not a
+ // masternode collateral. This will just log an
// info in getMyMasternodes.
- // - If this wallet created a masternode not owned by this wallet it will erroneously be emitted here and actually
- // shown to the user as our own masternode, but this is contrived and nothing actually produces transactions like
+ // - If this wallet created a masternode not owned by this wallet it will
+ // erroneously be emitted here and actually
+ // shown to the user as our own masternode, but this is contrived and
+ // nothing actually produces transactions like
// that.
- // utxos are UNSPENT txos, so broken masternodes will not show up here by design.
+ // utxos are UNSPENT txos, so broken masternodes will not show up here by
+ // design.
final utxos = await mainDB.getUTXOs(walletId).sortByBlockHeight().findAll();
final List<String> r = [];
+ final rawMasterNodeAmount = Amount.fromDecimal(
+ _masterNodeValue,
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ).raw.toInt();
+
for (final utxo in utxos) {
- if (utxo.value != cryptoCurrency.satsPerCoin.toInt() * 1000) {
+ if (utxo.value != rawMasterNodeAmount) {
continue;
}
- // A duplicate could occur if a protx transaction has a non-collateral 1000 FIRO vout.
+ // A duplicate could occur if a protx transaction has a non-collateral
+ // 1000 FIRO vout.
if (r.contains(utxo.txid)) {
continue;
}
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
index 1e097b5..37e8506 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
@@ -121,10 +121,12 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
return cryptoCurrency.network.isTestNet;
}
- // This is the BIP44 derivation path for the spark private key; spark public keys will have their own derivation path.
+ // This is the BIP44 derivation path for the spark private key; spark public
+ // keys will have their own derivation path.
String get sparkDerivationPath {
- // NOTE: This is reusing the sparkIndex for backwards compatibility, but these are actually distinct things which do
- // not have to be the same. sparkIndex has nothing at all to do with the derivation path.
+ // NOTE: This is reusing the sparkIndex for backwards compatibility, but
+ // these are actually distinct things which do not have to be the same.
+ // sparkIndex has nothing at all to do with the derivation path.
if (isTestNet) {
return "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
@@ -132,8 +134,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
}
- // This is the index for the spark key, which is NOT the diversifier or the BIP44 derivation path (which generates the
- // private key data).
+ // This is the index for the spark key, which is NOT the diversifier or the
+ // BIP44 derivation path (which generates the private key data).
int get sparkIndex => kDefaultSparkIndex;
Future<Address> _generateSparkAddress(int diversifier) async {
@@ -272,12 +274,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
Future<String> hashTag(String tag) async {
try {
- return await computeWithLibSparkLogging((t) {
- final components = t.split(",");
- final x = components[0].substring(1);
- final y = components[1].substring(0, components[1].length - 1);
- return libSpark.hashTag(x, y);
- }, tag);
+ return await computeWithLibSparkLogging(_hashTag, tag);
} catch (_) {
throw ArgumentError("Invalid tag string format", "tag");
}
@@ -901,7 +898,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
);
} catch (_) {
throw Exception(
- "Unexpectedly did not find used spark coin. This should never happen.",
+ "Unexpectedly did not find used spark coin. "
+ "This should never happen.",
);
}
}
@@ -952,7 +950,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// Update used spark coins as used in database. They should already have
// been marked as isUsed.
- // TODO: [prio=med] Could (probably should) throw an exception here if txData.usedSparkCoins is null or empty
+ // TODO: [prio=med] Could (probably should) throw an exception here
+ // if txData.usedSparkCoins is null or empty
if (txData.usedSparkCoins != null && txData.usedSparkCoins!.isNotEmpty) {
await mainDB.isar.writeTxn(() async {
await mainDB.isar.sparkCoins.putAll(txData.usedSparkCoins!);
@@ -1041,7 +1040,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
return current + increment;
}
- // Linearly make calls so there is less chance of timing out or otherwise breaking
+ // Linearly make calls so there is less chance of timing out or otherwise
+ // breaking
Future<void> refreshSparkData(
(double startingPercent, double endingPercent)? refreshProgressRange,
) async {
@@ -1388,10 +1388,6 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
}
- Future<void> recoverViewOnlyWallet() async {
- await recoverSparkWallet(latestSparkCoinId: 0);
- }
-
Future<({String address, int validUntil, String additionalInfo})>
getSparkNameData({required String sparkName}) async {
return await electrumXClient.getSparkNameData(sparkName: sparkName);
@@ -1423,8 +1419,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
.toSet();
// some look ahead
- // TODO revisit this and clean up (track pre gen'd addresses instead of generating every time)
- // arbitrary number of addresses
+ // TODO revisit this and clean up (track pre gen'd addresses instead of
+ // generating every time) arbitrary number of addresses
const lookAheadCount = 100;
int diversifier = _currentSparkAddress.derivationIndex;
@@ -1769,7 +1765,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
sd.utxo.txid,
sd.utxo.vout,
0xffffffff -
- 1, // minus 1 is important. 0xffffffff on its own will burn funds
+ 1, // - 1 is important. 0xffffffff on its own will burn funds
data!.output!,
);
}
@@ -1785,7 +1781,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
),
witnessValue: setCoins[i].utxo.value,
- // maybe not needed here as this was originally copied from btc? We'll find out...
+ // maybe not needed here as this was originally copied from btc?
+ // We'll find out...
// redeemScript: setCoins[i].redeemScript,
);
}
@@ -1992,7 +1989,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
),
witnessValue: vin[i].utxo.value,
- // maybe not needed here as this was originally copied from btc? We'll find out...
+ // maybe not needed here as this was originally copied from btc?
+ // We'll find out...
// redeemScript: setCoins[i].redeemScript,
);
}
@@ -2014,9 +2012,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
.where((e) => e.$1 is Uint8List) // ignore change
.map(
(e) => (
- address: outputs
- .first
- .address, // for display purposes on confirm tx screen. See todos above
+ // for display purposes on confirm tx screen.
+ // See todos above
+ address: outputs.first.address,
+
memo: "",
amount: Amount(
rawValue: BigInt.from(e.$2),
@@ -2316,7 +2315,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
if (additionalInfo.toUint8ListFromUtf8.length >
libSpark.maxAdditionalInfoLengthBytes) {
throw Exception(
- "Additional info exceeds ${libSpark.maxAdditionalInfoLengthBytes} bytes.",
+ "Additional info exceeds "
+ "${libSpark.maxAdditionalInfoLengthBytes}"
+ " bytes.",
);
}
@@ -2347,7 +2348,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
default:
throw Exception(
- "Invalid network '${cryptoCurrency.network}' for spark name registration.",
+ "Invalid network "
+ "'${cryptoCurrency.network}'"
+ " for spark name registration.",
);
}
@@ -2490,7 +2493,11 @@ class MutableSparkRecipient {
@override
String toString() {
- return 'MutableSparkRecipient{ address: $address, value: $value, memo: $memo }';
+ return 'MutableSparkRecipient{ '
+ 'address: $address, '
+ 'value: $value,'
+ ' memo: $memo'
+ ' }';
}
}
diff --git a/pubspec.lock b/pubspec.lock
index f0f635a..0aedf78 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -341,8 +341,8 @@ packages:
dependency: "direct overridden"
description:
path: coinlib
- ref: f90600053a4f149a6153f30057ac7f75c21ab962
- resolved-ref: f90600053a4f149a6153f30057ac7f75c21ab962
+ ref: "5c59c7e7d120d9c981f23008fa03421d39fe8631"
+ resolved-ref: "5c59c7e7d120d9c981f23008fa03421d39fe8631"
url: "https://www.github.com/julian-CStack/coinlib"
source: git
version: "4.1.0"
@@ -350,8 +350,8 @@ packages:
dependency: "direct main"
description:
path: coinlib_flutter
- ref: f90600053a4f149a6153f30057ac7f75c21ab962
- resolved-ref: f90600053a4f149a6153f30057ac7f75c21ab962
+ ref: "5c59c7e7d120d9c981f23008fa03421d39fe8631"
+ resolved-ref: "5c59c7e7d120d9c981f23008fa03421d39fe8631"
url: "https://www.github.com/julian-CStack/coinlib"
source: git
version: "4.0.0"
Why this scored 12/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.