v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31
src/wallet/spend.cpp
Before: src/wallet/spend.cpp · After: src/wallet/spend.cpp
File metadata
index 613375385d37e55c33c7e1c1fdbf0dbe0809cc31..30d0b1574b6816f133652d1fe7402b3229cc3e00 100644
Download original patch · Release notes
Diff rows 1–161 of 161. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.
src/wallet/spend.cpp
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
#include <consensus/amount.h>
|
|
10
10
|
#include <consensus/validation.h>
|
|
11
11
|
#include <interfaces/chain.h>
|
|
12
|
+
#include <interfaces/wallet.h>
|
|
12
13
|
#include <node/types.h>
|
|
13
14
|
#include <numeric>
|
|
14
15
|
#include <policy/policy.h>
|
|
15
16
|
#include <policy/truc_policy.h>
|
|
16
17
|
#include <primitives/transaction.h>
|
|
17
18
|
#include <primitives/transaction_identifier.h>
|
|
19
|
+
#include <rpc/rawtransaction_util.h>
|
|
18
20
|
#include <script/script.h>
|
|
19
21
|
#include <script/signingprovider.h>
|
|
20
22
|
#include <script/solver.h>
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
#include <wallet/coincontrol.h>
|
|
27
29
|
#include <wallet/fees.h>
|
|
28
30
|
#include <wallet/receive.h>
|
|
31
|
+
#include <wallet/scriptpubkeyman.h>
|
|
29
32
|
#include <wallet/spend.h>
|
|
30
33
|
#include <wallet/transaction.h>
|
|
31
34
|
#include <wallet/wallet.h>
|
|
@@ -43,6 +46,107 @@ TRACEPOINT_SEMAPHORE(coin_selection, attempting_aps_create_tx);
|
|
|
43
46
|
TRACEPOINT_SEMAPHORE(coin_selection, aps_create_tx_internal);
|
|
44
47
|
|
|
45
48
|
namespace wallet {
|
|
49
|
+
namespace {
|
|
50
|
+
// Test native script satisfaction without creating signatures or accessing secrets.
|
|
51
|
+
// Unlike the sizing dummy, absent keys must not produce successful signatures.
|
|
52
|
+
class AvailableSignatureCreator final : public BaseSignatureCreator {
|
|
53
|
+
const std::set<CKeyID>& m_keys;
|
|
54
|
+
|
|
55
|
+
bool HaveKey(const XOnlyPubKey& key) const
|
|
56
|
+
{
|
|
57
|
+
for (const auto& pubkey : key.GetCPubKeys()) {
|
|
58
|
+
if (m_keys.contains(pubkey.GetID())) return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public:
|
|
64
|
+
explicit AvailableSignatureCreator(const std::set<CKeyID>& keys) : m_keys(keys) {}
|
|
65
|
+
const BaseSignatureChecker& Checker() const override { return DUMMY_CHECKER; }
|
|
66
|
+
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const CKeyID& keyid, const CScript& script, SigVersion version) const override
|
|
67
|
+
{
|
|
68
|
+
return m_keys.contains(keyid) && DUMMY_SIGNATURE_CREATOR.CreateSig(provider, sig, keyid, script, version);
|
|
69
|
+
}
|
|
70
|
+
bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& key, const uint256* leaf, const uint256* root, SigVersion version) const override
|
|
71
|
+
{
|
|
72
|
+
return HaveKey(key) && DUMMY_SIGNATURE_CREATOR.CreateSchnorrSig(provider, sig, key, leaf, root, version);
|
|
73
|
+
}
|
|
74
|
+
std::vector<uint8_t> CreateMuSig2Nonce(const SigningProvider&, const CPubKey&, const CPubKey&, const CPubKey&, const uint256*, const uint256*, SigVersion, const SignatureData&) const override { return {}; }
|
|
75
|
+
bool CreateMuSig2PartialSig(const SigningProvider&, uint256&, const CPubKey&, const CPubKey&, const CPubKey&, const uint256*, const std::vector<std::pair<uint256, bool>>&, SigVersion, const SignatureData&) const override { return false; }
|
|
76
|
+
bool CreateMuSig2AggregateSig(const std::vector<CPubKey>& participants, std::vector<uint8_t>& sig, const CPubKey& aggregate, const CPubKey& script, const uint256* leaf, const std::vector<std::pair<uint256, bool>>& tweaks, SigVersion version, const SignatureData& data) const override
|
|
77
|
+
{
|
|
78
|
+
if (participants.empty() || !std::all_of(participants.begin(), participants.end(), [&](const CPubKey& key) { return m_keys.contains(key.GetID()); })) return false;
|
|
79
|
+
return DUMMY_SIGNATURE_CREATOR.CreateMuSig2AggregateSig(participants, sig, aggregate, script, leaf, tweaks, version, data);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
} // namespace
|
|
83
|
+
|
|
84
|
+
bool CanSweepTo(const CWallet& wallet, const CScript& destination)
|
|
85
|
+
{
|
|
86
|
+
AssertLockHeld(wallet.cs_wallet);
|
|
87
|
+
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ||
|
|
88
|
+
wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ||
|
|
89
|
+
wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER) || !wallet.IsMine(destination)) return false;
|
|
90
|
+
std::set<CKeyID> available_keys;
|
|
91
|
+
FlatSigningProvider provider;
|
|
92
|
+
for (const auto* manager : wallet.GetScriptPubKeyMans(destination)) {
|
|
93
|
+
if (const auto* descriptor_manager{dynamic_cast<const DescriptorScriptPubKeyMan*>(manager)}) {
|
|
94
|
+
const auto keys{descriptor_manager->GetAvailableKeys(destination)};
|
|
95
|
+
available_keys.insert(keys.begin(), keys.end());
|
|
96
|
+
// Combine public solving data just as native wallet signing combines
|
|
97
|
+
// providers, so one descriptor need not supply every usable path.
|
|
98
|
+
const auto solving{descriptor_manager->GetSolvingProvider(destination)};
|
|
99
|
+
if (const auto* public_data{dynamic_cast<const FlatSigningProvider*>(solving.get())}) provider.Merge(FlatSigningProvider{*public_data});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
SignatureData satisfaction;
|
|
103
|
+
return ProduceSignature(provider, AvailableSignatureCreator{available_keys}, destination, satisfaction);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
util::Result<SweepTransactionResult> CreateSweepTransaction(CWallet& wallet, const SigningProvider& provider,
|
|
107
|
+
const std::map<COutPoint, Coin>& coins,
|
|
108
|
+
const CScript& destination, const SweepBroadcastFn& broadcast,
|
|
109
|
+
interfaces::SweepAuthorization& authorization)
|
|
110
|
+
{
|
|
111
|
+
if (authorization.IsCancelled()) return util::Error{Untranslated("Sweep cancelled")};
|
|
112
|
+
CAmount amount{0};
|
|
113
|
+
CMutableTransaction tx;
|
|
114
|
+
for (const auto& [outpoint, coin] : coins) {
|
|
115
|
+
amount += coin.out.nValue;
|
|
116
|
+
tx.vin.emplace_back(outpoint);
|
|
117
|
+
}
|
|
118
|
+
if (coins.empty()) return util::Error{Untranslated("No spendable UTXOs found")};
|
|
119
|
+
tx.vout.emplace_back(amount, destination);
|
|
120
|
+
std::map<int, bilingual_str> errors;
|
|
121
|
+
if (!::SignTransaction(tx, &provider, coins, SIGHASH_DEFAULT, errors)) return util::Error{Untranslated("Unable to sign all swept inputs")};
|
|
122
|
+
// Fee settings can change while the UTXO scan runs. Take one synchronized
|
|
123
|
+
// snapshot for every signing iteration without holding the wallet lock
|
|
124
|
+
// during signing or broadcast.
|
|
125
|
+
const CFeeRate fee_rate{WITH_LOCK(wallet.cs_wallet, return GetMinimumFeeRate(wallet, CCoinControl{}, nullptr))};
|
|
126
|
+
CAmount fee{0};
|
|
127
|
+
for (int attempts{0}; attempts < 10; ++attempts) {
|
|
128
|
+
fee = fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}));
|
|
129
|
+
if (fee >= amount) return util::Error{Untranslated("Fee exceeds swept value")};
|
|
130
|
+
tx.vout[0].nValue = amount - fee;
|
|
131
|
+
errors.clear();
|
|
132
|
+
if (!::SignTransaction(tx, &provider, coins, SIGHASH_DEFAULT, errors)) return util::Error{Untranslated("Unable to sign all swept inputs")};
|
|
133
|
+
if (fee >= fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}))) break;
|
|
134
|
+
}
|
|
135
|
+
if (fee < fee_rate.GetFee(GetVirtualTransactionSize(CTransaction{tx}))) {
|
|
136
|
+
return util::Error{Untranslated("Unable to calculate a stable sweep fee")};
|
|
137
|
+
}
|
|
138
|
+
if (IsDust(tx.vout[0], wallet.chain().relayMinFee())) return util::Error{Untranslated("Swept output would be dust")};
|
|
139
|
+
if (authorization.IsCancelled()) return util::Error{Untranslated("Sweep cancelled")};
|
|
140
|
+
CTransactionRef final_tx{MakeTransactionRef(tx)};
|
|
141
|
+
if (broadcast) {
|
|
142
|
+
// This is the last safe cancellation point. BeginBroadcast and Cancel
|
|
143
|
+
// use one atomic state transition, so only one of them can succeed.
|
|
144
|
+
if (!authorization.BeginBroadcast()) return util::Error{Untranslated("Sweep cancelled")};
|
|
145
|
+
std::string error;
|
|
146
|
+
if (!broadcast(final_tx, error)) return util::Error{Untranslated("Sweep transaction broadcast failed: " + error)};
|
|
147
|
+
}
|
|
148
|
+
return SweepTransactionResult{final_tx, amount, fee};
|
|
149
|
+
}
|
|
46
150
|
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
|
|
47
151
|
|
|
48
152
|
/** Whether the descriptor represents, directly or not, a witness program. */
|
|
@@ -994,6 +1098,16 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256&
|
|
|
994
1098
|
return true;
|
|
995
1099
|
}
|
|
996
1100
|
|
|
1101
|
+
uint32_t GetAntiFeeSnipingLockTime(int block_height, FastRandomContext& rng_fast)
|
|
1102
|
+
{
|
|
1103
|
+
uint32_t lock_time{static_cast<uint32_t>(block_height)};
|
|
1104
|
+
if (rng_fast.randrange(10) == 0) {
|
|
1105
|
+
lock_time = std::max(0, int(lock_time) - int(rng_fast.randrange(100)));
|
|
1106
|
+
}
|
|
1107
|
+
if (lock_time == PARASITE_CAT21_LOCKTIME) --lock_time;
|
|
1108
|
+
return lock_time;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
997
1111
|
void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
|
|
998
1112
|
interfaces::Chain& chain, const uint256& block_hash, int block_height)
|
|
999
1113
|
{
|
|
@@ -1020,15 +1134,8 @@ void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast,
|
|
|
1020
1134
|
// now we ensure code won't be written that makes assumptions about
|
|
1021
1135
|
// nLockTime that preclude a fix later.
|
|
1022
1136
|
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
|
|
1023
|
-
tx.nLockTime = block_height;
|
|
1024
|
-
|
|
1025
|
-
// Secondly occasionally randomly pick a nLockTime even further back, so
|
|
1026
|
-
// that transactions that are delayed after signing for whatever reason,
|
|
1027
|
-
// e.g. high-latency mix networks and some CoinJoin implementations, have
|
|
1028
|
-
// better privacy.
|
|
1029
|
-
if (rng_fast.randrange(10) == 0) {
|
|
1030
|
-
tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100)));
|
|
1031
|
-
}
|
|
1137
|
+
// Avoid locktime values reserved by local policy.
|
|
1138
|
+
tx.nLockTime = GetAntiFeeSnipingLockTime(block_height, rng_fast);
|
|
1032
1139
|
} else {
|
|
1033
1140
|
// If our chain is lagging behind, we can't discourage fee sniping nor help
|
|
1034
1141
|
// the privacy of high-latency transactions. To avoid leaking a potentially
|