wallet: default to rangeproof-committing sighash when dynafed active
What changed, and why it matters
This commit changes the Elements wallet so that, when a newer consensus feature called 'DynaFed' is active, any transaction the wallet signs without an explicit signature mode will automatically include the transaction's confidential output proofs ('rangeproofs') in the signature. Previously, those proofs could be changed by a third party after the transaction was signed, which could let someone mangle a transaction's witness data without invalidating the signature. The fix only changes the default behavior; users who explicitly pick a signature mode are unaffected.
Review the DefaultSighashType helper to confirm it returns the expected SIGHASH_ALL_WITH_RANGEPROOF when rangeproof sighash is active and SIGHASH_ALL otherwise. Verify that no other wallet signing paths bypass this default. Consider whether any tests or release notes should document the changed default for RPC users.
Security signals we found
Fixes a known malleability vector: pre-Taproot rangeproof/witness malleability
Changes wallet default sighash only when dynafed is active
Does not override explicit user sighash choices
Touches wallet signing paths (CWallet::SignTransaction, signrawtransactionwithwallet, walletprocesspsbt)
Adds ELEMENTS-specific comments indicating security relevance
Evidence from the diff
The patch closes a pre-Taproot rangeproof malleability gap by defaulting to a rangeproof-committing sighash (SIGHASH_ALL_WITH_RANGEPROOF) when dynamic federations (dynafed) are active at the chain tip. It routes the default sighash through DefaultSighashType(chain().isSighashRangeproofActive()) in CWallet::SignTransaction and in the signrawtransactionwithwallet and walletprocesspsbt RPCs when the caller does not supply a sighash. Explicit user-supplied sighash types are left untouched. The change is limited to wallet signing paths and does not alter consensus rules.
Changed components
src/wallet/rpc/spend.cppsrc/wallet/wallet.cppsrc/wallet/wallet.hInspect captured patch +12 / −3
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 393d047..cd86d18 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -1050,7 +1050,11 @@ RPCHelpMan signrawtransactionwithwallet()
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(*pwallet);
- int nHashType = ParseSighashString(request.params[2]);
+ // ELEMENTS: when no sighash is specified, default to committing to
+ // rangeproofs if SIGHASH_RANGEPROOF is active at the current tip.
+ int nHashType = request.params[2].isNull()
+ ? DefaultSighashType(pwallet->chain().isSighashRangeproofActive())
+ : ParseSighashString(request.params[2]);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
@@ -1755,7 +1759,11 @@ RPCHelpMan walletprocesspsbt()
}
// Get the sighash type
- int nHashType = ParseSighashString(request.params[2]);
+ // ELEMENTS: when no sighash is specified, default to committing to
+ // rangeproofs if SIGHASH_RANGEPROOF is active at the current tip.
+ int nHashType = request.params[2].isNull()
+ ? DefaultSighashType(pwallet->chain().isSighashRangeproofActive())
+ : ParseSighashString(request.params[2]);
// Don't sign, just fill data.
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index ef13356..52b7abd 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2306,7 +2306,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], prev_height, wtx.IsCoinBase());
}
std::map<int, bilingual_str> input_errors;
- return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors);
+ return SignTransaction(tx, coins, DefaultSighashType(chain().isSighashRangeproofActive()), input_errors); // ELEMENTS
}
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 62dcc74..d55ad56 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -702,6 +702,7 @@ public:
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
/** Fetch the inputs and sign with SIGHASH_ALL. */
+ // ELEMENTS: sign with SIGHASH_ALL_WITH_RANGEPROOF for DynaFed chains
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Sign the tx given the input coins and sighash. */
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const;
Why this scored 64/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.