Skip to content

Commit 7af07b3

Browse files
josibakeEunovo
authored andcommitted
use silent payments change when sending
1 parent 091fab6 commit 7af07b3

File tree

3 files changed

+43
-24
lines changed

3 files changed

+43
-24
lines changed

src/wallet/spend.cpp

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,12 +1180,12 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11801180
}
11811181

11821182
// Create change script that will be used if we need change
1183-
CScript scriptChange;
1183+
CTxDestination change_dest;
11841184
bilingual_str error; // possible error str
11851185

11861186
// coin control: send change to custom address
11871187
if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
1188-
scriptChange = GetScriptForDestination(coin_control.destChange);
1188+
change_dest = coin_control.destChange;
11891189
} else { // no coin control: send change to newly generated address
11901190
// Note: We use a new key here to keep it from being obvious which side is the change.
11911191
// The drawback is that by not reusing a previous key, the change may be lost if a
@@ -1196,20 +1196,18 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
11961196

11971197
// Reserve a new key pair from key pool. If it fails, provide a dummy
11981198
// destination in case we don't need change.
1199-
CTxDestination dest;
12001199
auto op_dest = reservedest.GetReservedDestination(true);
12011200
if (!op_dest) {
12021201
error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + util::ErrorString(op_dest);
12031202
} else {
1204-
dest = *op_dest;
1205-
scriptChange = GetScriptForDestination(dest);
1203+
change_dest = *op_dest;
12061204
}
1207-
// A valid destination implies a change script (and
1208-
// vice-versa). An empty change script will abort later, if the
1209-
// change keypool ran out, but change is required.
1210-
CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
12111205
}
1212-
CTxOut change_prototype_txout(0, scriptChange);
1206+
CScript change_prototype_script = GetScriptForDestination(change_dest);
1207+
if (std::get_if<V0SilentPaymentDestination>(&change_dest)) {
1208+
change_prototype_script = GetScriptForDestination(WitnessV1Taproot());
1209+
}
1210+
CTxOut change_prototype_txout(0, change_prototype_script);
12131211
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
12141212

12151213
// Get size of spending the change output
@@ -1306,25 +1304,40 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
13061304
sp_dests[i] = *sp;
13071305
}
13081306
}
1307+
if (const auto* change_sp = std::get_if<V0SilentPaymentDestination>(&change_dest)) {
1308+
// Generate output for change too
1309+
sp_dests[mutableVecSend.size()] = *change_sp;
1310+
}
13091311
const auto& silent_payment_tr_spks = CreateSilentPaymentOutputs(wallet, sp_dests, result.GetInputSet(), error);
13101312
if (!silent_payment_tr_spks.has_value()) {
13111313
return util::Error{error};
13121314
}
13131315
for (const auto& [out_idx, tr_dest] : *silent_payment_tr_spks) {
1316+
if (out_idx == mutableVecSend.size()) {
1317+
change_dest = tr_dest;
1318+
continue;
1319+
}
1320+
13141321
assert(out_idx < mutableVecSend.size());
13151322
mutableVecSend[out_idx].dest = tr_dest;
13161323
}
1317-
13181324
}
13191325
// vouts to the payees
13201326
txNew.vout.reserve(vecSend.size() + 1); // + 1 because of possible later insert
1321-
for (const auto& recipient : mutableVecSend)
1322-
{
1327+
for (const auto& recipient : mutableVecSend) {
13231328
txNew.vout.emplace_back(recipient.nAmount, GetScriptForDestination(recipient.dest));
13241329
}
13251330
const CAmount change_amount = result.GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
13261331
if (change_amount > 0) {
1327-
CTxOut newTxOut(change_amount, scriptChange);
1332+
// Give up if change keypool ran out as change is required
1333+
if (!IsValidDestination(change_dest)) {
1334+
return util::Error{error};
1335+
}
1336+
1337+
CScript change_script = GetScriptForDestination(change_dest);
1338+
Assert(!change_script.empty());
1339+
1340+
CTxOut newTxOut(change_amount, change_script);
13281341
if (!change_pos) {
13291342
// Insert change txn at random position:
13301343
change_pos = rng_fast.randrange(txNew.vout.size() + 1);
@@ -1465,11 +1478,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
14651478
return util::Error{Untranslated(STR_INTERNAL_BUG("Fee needed > fee paid"))};
14661479
}
14671480

1468-
// Give up if change keypool ran out and change is required
1469-
if (scriptChange.empty() && change_pos) {
1470-
return util::Error{error};
1471-
}
1472-
14731481
if (sign && !wallet.SignTransaction(txNew)) {
14741482
return util::Error{_("Signing transaction failed")};
14751483
}
@@ -1528,7 +1536,8 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
15281536
if (wallet.IsMine(*tx, spent_coins))
15291537
wallet.WalletLogPrintf("Detected Silent Payments self-transfer: %s", tx->GetHash().ToString());
15301538
}
1531-
return CreatedTransactionResult(tx, current_fee, change_pos, feeCalc);
1539+
LogPrintf("change_type: %s", FormatOutputType(change_type));
1540+
return CreatedTransactionResult(tx, current_fee, change_pos, change_type, feeCalc);
15321541
}
15331542

15341543
util::Result<CreatedTransactionResult> CreateTransaction(
@@ -1563,7 +1572,9 @@ util::Result<CreatedTransactionResult> CreateTransaction(
15631572
tmp_cc.m_avoid_partial_spends = true;
15641573

15651574
// Reuse the change destination from the first creation attempt to avoid skipping BIP44 indexes
1566-
if (txr_ungrouped.change_pos) {
1575+
// Do not reuse the change dest if it came from a silent payments destination
1576+
// silent payments change dest must be generated with the other silent payment outputs
1577+
if (txr_ungrouped.change_pos && txr_ungrouped.change_type != OutputType::SILENT_PAYMENTS) {
15671578
ExtractDestination(txr_ungrouped.tx->vout[*txr_ungrouped.change_pos].scriptPubKey, tmp_cc.destChange);
15681579
}
15691580

src/wallet/spend.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,10 @@ struct CreatedTransactionResult
208208
CAmount fee;
209209
FeeCalculation fee_calc;
210210
std::optional<unsigned int> change_pos;
211+
OutputType change_type;
211212

212-
CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, std::optional<unsigned int> _change_pos, const FeeCalculation& _fee_calc)
213-
: tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {}
213+
CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, std::optional<unsigned int> _change_pos, const OutputType change_type, const FeeCalculation& _fee_calc)
214+
: tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos), change_type(change_type) {}
214215
};
215216

216217
/**

src/wallet/wallet.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2259,13 +2259,16 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
22592259
return OutputType::LEGACY;
22602260
}
22612261

2262+
bool any_sp{false};
22622263
bool any_tr{false};
22632264
bool any_wpkh{false};
22642265
bool any_sh{false};
22652266
bool any_pkh{false};
22662267

22672268
for (const auto& recipient : vecSend) {
2268-
if (std::get_if<WitnessV1Taproot>(&recipient.dest) || std::get_if<V0SilentPaymentDestination>(&recipient.dest)) {
2269+
if (std::get_if<V0SilentPaymentDestination>(&recipient.dest)) {
2270+
any_sp = true;
2271+
} else if (std::get_if<WitnessV1Taproot>(&recipient.dest)) {
22692272
any_tr = true;
22702273
} else if (std::get_if<WitnessV0KeyHash>(&recipient.dest)) {
22712274
any_wpkh = true;
@@ -2276,6 +2279,10 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
22762279
}
22772280
}
22782281

2282+
const bool has_sp_spkman(GetScriptPubKeyMan(OutputType::SILENT_PAYMENTS, /*internal=*/true));
2283+
if (has_sp_spkman && any_sp) {
2284+
return OutputType::SILENT_PAYMENTS;
2285+
}
22792286
const bool has_bech32m_spkman(GetScriptPubKeyMan(OutputType::BECH32M, /*internal=*/true));
22802287
if (has_bech32m_spkman && any_tr) {
22812288
// Currently tr is the only type supported by the BECH32M spkman

0 commit comments

Comments
 (0)