Skip to content

Commit cf82a9e

Browse files
committed
Do not allow users to get keys from keypool without reserving them
fundrawtransaction allows users to add a change output and then not have it removed from keypool. While it would be nice to have users follow the normal CreateTransaction/CommitTransaction process we use internally, there isnt much benefit in exposing this option, especially with HD wallets, while there is ample room for users to misunderstand or misuse this option. This could be particularly nasty in some use-cases (especially pre-HD-split) - eg a user might fundrawtransaction, then call getnewaddress, hand out the address for someone to pay them, then sendrawtransaction. This may result in the user thinking they have received payment, even though it was really just their own change! This could obviously result in needless key-reuse.
1 parent 7b6e8bc commit cf82a9e

File tree

4 files changed

+13
-27
lines changed

4 files changed

+13
-27
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,7 +2693,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
26932693
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
26942694
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
26952695
" \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
2696-
" \"reserveChangeKey\" (boolean, optional, default true) Reserves the change output key from the keypool\n"
26972696
" \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n"
26982697
" \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
26992698
" The fee will be equally deducted from the amount of each specified output.\n"
@@ -2732,7 +2731,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27322731
CCoinControl coinControl;
27332732
int changePosition = -1;
27342733
bool lockUnspents = false;
2735-
bool reserveChangeKey = true;
27362734
UniValue subtractFeeFromOutputs;
27372735
std::set<int> setSubtractFeeFromOutputs;
27382736

@@ -2752,7 +2750,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27522750
{"changePosition", UniValueType(UniValue::VNUM)},
27532751
{"includeWatching", UniValueType(UniValue::VBOOL)},
27542752
{"lockUnspents", UniValueType(UniValue::VBOOL)},
2755-
{"reserveChangeKey", UniValueType(UniValue::VBOOL)},
2753+
{"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so.
27562754
{"feeRate", UniValueType()}, // will be checked below
27572755
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
27582756
{"replaceable", UniValueType(UniValue::VBOOL)},
@@ -2779,9 +2777,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
27792777
if (options.exists("lockUnspents"))
27802778
lockUnspents = options["lockUnspents"].get_bool();
27812779

2782-
if (options.exists("reserveChangeKey"))
2783-
reserveChangeKey = options["reserveChangeKey"].get_bool();
2784-
27852780
if (options.exists("feeRate"))
27862781
{
27872782
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
@@ -2830,7 +2825,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
28302825
CAmount nFeeOut;
28312826
std::string strFailReason;
28322827

2833-
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl, reserveChangeKey)) {
2828+
if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
28342829
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
28352830
}
28362831

src/wallet/wallet.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,7 +2471,7 @@ bool CWallet::SignTransaction(CMutableTransaction &tx)
24712471
return true;
24722472
}
24732473

2474-
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl, bool keepReserveKey)
2474+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
24752475
{
24762476
std::vector<CRecipient> vecSend;
24772477

@@ -2493,8 +2493,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
24932493
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
24942494
return false;
24952495
}
2496-
if (nChangePosInOut != -1)
2496+
2497+
if (nChangePosInOut != -1) {
24972498
tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]);
2499+
// we dont have the normal Create/Commit cycle, and dont want to risk reusing change,
2500+
// so just remove the key from the keypool here.
2501+
reservekey.KeepKey();
2502+
}
24982503

24992504
// Copy output sizes from new transaction; they may have had the fee subtracted from them
25002505
for (unsigned int idx = 0; idx < tx.vout.size(); idx++)
@@ -2515,9 +2520,6 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
25152520
}
25162521
}
25172522

2518-
// optionally keep the change output key
2519-
if (keepReserveKey)
2520-
reservekey.KeepKey();
25212523

25222524
return true;
25232525
}

src/wallet/wallet.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
949949
* Insert additional inputs into the transaction by
950950
* calling CreateTransaction();
951951
*/
952-
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl, bool keepReserveKey = true);
952+
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
953953
bool SignTransaction(CMutableTransaction& tx);
954954

955955
/**

test/functional/fundrawtransaction.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -636,20 +636,9 @@ def run_test(self):
636636
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
637637
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
638638

639-
#############################
640-
# Test address reuse option #
641-
#############################
642-
643-
result3 = self.nodes[3].fundrawtransaction(rawtx, {"reserveChangeKey": False})
644-
res_dec = self.nodes[0].decoderawtransaction(result3["hex"])
645-
changeaddress = ""
646-
for out in res_dec['vout']:
647-
if out['value'] > 1.0:
648-
changeaddress += out['scriptPubKey']['addresses'][0]
649-
assert(changeaddress != "")
650-
nextaddr = self.nodes[3].getrawchangeaddress()
651-
# frt should not have removed the key from the keypool
652-
assert(changeaddress == nextaddr)
639+
################################
640+
# Test no address reuse occurs #
641+
################################
653642

654643
result3 = self.nodes[3].fundrawtransaction(rawtx)
655644
res_dec = self.nodes[0].decoderawtransaction(result3["hex"])

0 commit comments

Comments
 (0)