detect hogex inputs and only require 6 confs if so (#2786)
What changed, and why it matters
This commit changes how Cake Wallet handles Litecoin MWEB (privacy feature) peg-out transactions. Previously, the wallet treated all non-MWEB inputs as if they were 'hog-ex' peg-outs and required 6 confirmations before spending them. The new code actually detects real hog-ex transactions by looking at transaction script patterns and only applies the 6-confirmation rule to genuine peg-outs. This is a correctness and usability fix, not a vulnerability patch, though the old behavior could have caused unnecessary transaction blocking.
No immediate security action required. Reviewers may verify the HogEx detection heuristic (34-byte scriptPubKey starting with 0x58 0x20) matches the Litecoin MWEB specification and that the 6-confirmation threshold is appropriate for peg-out security.
Security signals we found
Correctness fix for transaction confirmation policy enforcement
Replaces hardcoded/placeholder detection logic with actual scriptPubKey pattern matching
Reduces false positives that could block legitimate spends
No cryptographic, authentication, or memory-safety changes observed
Evidence from the diff
The patch adds an isHogEx flag to ElectrumTransactionInfo and an isPegOut flag to BitcoinUnspent. It detects MWEB HogEx transactions by checking that the first input has txIndex 0 and the first output’s scriptPubKey is 34 bytes starting with 0x58 0x20. The wallet then propagates this flag to unspent coins and uses it in LitecoinWallet to enforce the 6-confirmation minimum only for actual MWEB peg-out inputs, replacing a placeholder that always assumed true.
Changed components
cw_bitcoin/lib/bitcoin_unspent.dartcw_bitcoin/lib/electrum_transaction_info.dartcw_bitcoin/lib/electrum_wallet.dartcw_bitcoin/lib/litecoin_wallet.dartInspect captured patch +16 / −9
diff --git a/cw_bitcoin/lib/bitcoin_unspent.dart b/cw_bitcoin/lib/bitcoin_unspent.dart
index 3691a7a2..53700a27 100644
--- a/cw_bitcoin/lib/bitcoin_unspent.dart
+++ b/cw_bitcoin/lib/bitcoin_unspent.dart
@@ -25,6 +25,7 @@ class BitcoinUnspent extends Unspent {
}
final BaseBitcoinAddressRecord bitcoinAddressRecord;
+ bool? isPegOut;
}
class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart
index 407e405e..9c0beadd 100644
--- a/cw_bitcoin/lib/electrum_transaction_info.dart
+++ b/cw_bitcoin/lib/electrum_transaction_info.dart
@@ -25,6 +25,7 @@ class ElectrumTransactionBundle {
class ElectrumTransactionInfo extends TransactionInfo {
List<BitcoinSilentPaymentsUnspent>? unspents;
bool isReceivedSilentPayment;
+ bool isHogEx;
ElectrumTransactionInfo(
this.type, {
@@ -42,6 +43,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
String? to,
this.unspents,
this.isReceivedSilentPayment = false,
+ this.isHogEx = false,
Map<String, dynamic>? additionalInfo,
}) {
this.id = id;
@@ -174,6 +176,15 @@ class ElectrumTransactionInfo extends TransactionInfo {
amount = receivedAmounts.reduce((a, b) => a + b);
}
+ // MWEB HogEx
+ final isHogExTx = (BtcTransaction tx) {
+ if (tx.inputs.isEmpty || tx.inputs.first.txIndex > 0 || tx.outputs.isEmpty)
+ return false;
+ final b = tx.outputs.first.scriptPubKey.toBytes();
+ return b.length == 34 && b[0] == 88 && b[1] == 32;
+ };
+ final isHogEx = isHogExTx(bundle.originalTransaction) && isHogExTx(bundle.ins.first);
+
final fee = inputAmount - totalOutAmount;
return ElectrumTransactionInfo(type,
id: bundle.originalTransaction.txId(),
@@ -186,6 +197,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: direction,
amount: amount,
date: date,
+ isHogEx: isHogEx,
confirmations: bundle.confirmations);
}
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 08ca9f23..4bcee04d 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -1740,6 +1740,7 @@ abstract class ElectrumWalletBase
final tx = await fetchTransactionInfo(hash: coin.hash);
coin.isChange = address.isHidden;
coin.confirmations = tx?.confirmations;
+ coin.isPegOut = tx?.isHogEx;
updatedUnspentCoins.add(coin);
} catch (_) {}
diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart
index d80dedf9..0c2612a1 100644
--- a/cw_bitcoin/lib/litecoin_wallet.dart
+++ b/cw_bitcoin/lib/litecoin_wallet.dart
@@ -1214,21 +1214,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} else {
// check if any of the inputs of this transaction are hog-ex:
// this list is only non-mweb inputs:
- bool isHogEx = true;
-
final coin = unspentCoins
.firstWhere((coin) => coin.hash == utxo.utxo.txHash && coin.vout == utxo.utxo.vout);
-
- // TODO: detect actual hog-ex inputs
-
- if (!isHogEx) {
- continue;
- }
+ if (coin.isPegOut != true) continue;
int confirmations = coin.confirmations ?? 0;
if (confirmations < 6) {
throw Exception(
- "A transaction input has less than 6 confirmations, please try again later.");
+ "A transaction input is an MWEB peg-out and has less than 6 confirmations, please try again later.");
}
}
}
Why this scored 34/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.