Skip to content
Bitcoin Roots

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