Skip to content

Commit 56fe3dc

Browse files
committed
Merge #13142: Separate IsMine from solvability
c004ffc Make handling of invalid in IsMine more uniform (Pieter Wuille) a53f0fe Add some checks for invalid recursion in IsMine (Pieter Wuille) b5802a9 Simplify IsMine logic (Pieter Wuille) 4e91820 Make IsMine stop distinguishing solvable/unsolvable (Pieter Wuille) 6d714c3 Make coincontrol use IsSolvable to determine solvability (Pieter Wuille) Pull request description: Our current `IsMine` logic does several things with outputs: * Determine "spendability" (roughly corresponding to "could we sign for this") * Determine "watching" (is this an output directly or indirectly a watched script) * Determine invalidity (is this output definitely not legally spendable, detecting accidental uncompressed pubkeys in witnesses) * Determine "solvability" (would we be able to sign for this ignoring the fact that we may be missing some private keys). The last item (solvability) is mostly unrelated and only rarely needed (there is just one instance, inside the wallet's coin control logic). This PR changes that instance to use the separate `IsSolvable` function, and stop `IsMine` from distinguishing between solvable and unsolvable. As an extra, this also simplifies the `IsMine` logic and adds some extra checks (which wouldn't be hit unless someone adds already invalid scripts to their wallet). Tree-SHA512: 95a6ef75fbf2eedc5ed938c48a8e5d77dcf09c933372acdd0333129fb7301994a78498f9aacce2c8db74275e19260549dd67a83738e187d40b5090cc04f33adf
2 parents a315b79 + c004ffc commit 56fe3dc

File tree

4 files changed

+67
-45
lines changed

4 files changed

+67
-45
lines changed

src/script/ismine.cpp

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ enum class IsMineSigVersion
2828
WITNESS_V0 = 2 //! P2WSH witness script execution
2929
};
3030

31+
/**
32+
* This is an internal representation of isminetype + invalidity.
33+
* Its order is significant, as we return the max of all explored
34+
* possibilities.
35+
*/
36+
enum class IsMineResult
37+
{
38+
NO = 0, //! Not ours
39+
WATCH_ONLY = 1, //! Included in watch-only balance
40+
SPENDABLE = 2, //! Included in all balances
41+
INVALID = 3, //! Not spendable by anyone
42+
};
43+
3144
bool PermitsUncompressed(IsMineSigVersion sigversion)
3245
{
3346
return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;
@@ -42,17 +55,13 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore)
4255
return true;
4356
}
4457

45-
isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, IsMineSigVersion sigversion)
58+
IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
4659
{
47-
isInvalid = false;
60+
IsMineResult ret = IsMineResult::NO;
4861

4962
std::vector<valtype> vSolutions;
5063
txnouttype whichType;
51-
if (!Solver(scriptPubKey, whichType, vSolutions)) {
52-
if (keystore.HaveWatchOnly(scriptPubKey))
53-
return ISMINE_WATCH_UNSOLVABLE;
54-
return ISMINE_NO;
55-
}
64+
Solver(scriptPubKey, whichType, vSolutions);
5665

5766
CKeyID keyID;
5867
switch (whichType)
@@ -64,50 +73,58 @@ isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, b
6473
case TX_PUBKEY:
6574
keyID = CPubKey(vSolutions[0]).GetID();
6675
if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) {
67-
isInvalid = true;
68-
return ISMINE_NO;
76+
return IsMineResult::INVALID;
77+
}
78+
if (keystore.HaveKey(keyID)) {
79+
ret = std::max(ret, IsMineResult::SPENDABLE);
6980
}
70-
if (keystore.HaveKey(keyID))
71-
return ISMINE_SPENDABLE;
7281
break;
7382
case TX_WITNESS_V0_KEYHASH:
7483
{
84+
if (sigversion == IsMineSigVersion::WITNESS_V0) {
85+
// P2WPKH inside P2WSH is invalid.
86+
return IsMineResult::INVALID;
87+
}
7588
if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
7689
// We do not support bare witness outputs unless the P2SH version of it would be
7790
// acceptable as well. This protects against matching before segwit activates.
7891
// This also applies to the P2WSH case.
7992
break;
8093
}
81-
isminetype ret = IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, IsMineSigVersion::WITNESS_V0);
82-
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
83-
return ret;
94+
ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
8495
break;
8596
}
8697
case TX_PUBKEYHASH:
8798
keyID = CKeyID(uint160(vSolutions[0]));
8899
if (!PermitsUncompressed(sigversion)) {
89100
CPubKey pubkey;
90101
if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) {
91-
isInvalid = true;
92-
return ISMINE_NO;
102+
return IsMineResult::INVALID;
93103
}
94104
}
95-
if (keystore.HaveKey(keyID))
96-
return ISMINE_SPENDABLE;
105+
if (keystore.HaveKey(keyID)) {
106+
ret = std::max(ret, IsMineResult::SPENDABLE);
107+
}
97108
break;
98109
case TX_SCRIPTHASH:
99110
{
111+
if (sigversion != IsMineSigVersion::TOP) {
112+
// P2SH inside P2WSH or P2SH is invalid.
113+
return IsMineResult::INVALID;
114+
}
100115
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
101116
CScript subscript;
102117
if (keystore.GetCScript(scriptID, subscript)) {
103-
isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::P2SH);
104-
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
105-
return ret;
118+
ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
106119
}
107120
break;
108121
}
109122
case TX_WITNESS_V0_SCRIPTHASH:
110123
{
124+
if (sigversion == IsMineSigVersion::WITNESS_V0) {
125+
// P2WSH inside P2WSH is invalid.
126+
return IsMineResult::INVALID;
127+
}
111128
if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
112129
break;
113130
}
@@ -116,17 +133,17 @@ isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, b
116133
CScriptID scriptID = CScriptID(hash);
117134
CScript subscript;
118135
if (keystore.GetCScript(scriptID, subscript)) {
119-
isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::WITNESS_V0);
120-
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
121-
return ret;
136+
ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
122137
}
123138
break;
124139
}
125140

126141
case TX_MULTISIG:
127142
{
128143
// Never treat bare multisig outputs as ours (they can still be made watchonly-though)
129-
if (sigversion == IsMineSigVersion::TOP) break;
144+
if (sigversion == IsMineSigVersion::TOP) {
145+
break;
146+
}
130147

131148
// Only consider transactions "mine" if we own ALL the
132149
// keys involved. Multi-signature transactions that are
@@ -137,30 +154,39 @@ isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, b
137154
if (!PermitsUncompressed(sigversion)) {
138155
for (size_t i = 0; i < keys.size(); i++) {
139156
if (keys[i].size() != 33) {
140-
isInvalid = true;
141-
return ISMINE_NO;
157+
return IsMineResult::INVALID;
142158
}
143159
}
144160
}
145-
if (HaveKeys(keys, keystore))
146-
return ISMINE_SPENDABLE;
161+
if (HaveKeys(keys, keystore)) {
162+
ret = std::max(ret, IsMineResult::SPENDABLE);
163+
}
147164
break;
148165
}
149166
}
150167

151-
if (keystore.HaveWatchOnly(scriptPubKey)) {
152-
// TODO: This could be optimized some by doing some work after the above solver
153-
SignatureData sigs;
154-
return ProduceSignature(keystore, DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigs) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE;
168+
if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
169+
ret = std::max(ret, IsMineResult::WATCH_ONLY);
155170
}
156-
return ISMINE_NO;
171+
return ret;
157172
}
158173

159174
} // namespace
160175

161176
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid)
162177
{
163-
return IsMineInner(keystore, scriptPubKey, isInvalid, IsMineSigVersion::TOP);
178+
isInvalid = false;
179+
switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) {
180+
case IsMineResult::INVALID:
181+
isInvalid = true;
182+
case IsMineResult::NO:
183+
return ISMINE_NO;
184+
case IsMineResult::WATCH_ONLY:
185+
return ISMINE_WATCH_ONLY;
186+
case IsMineResult::SPENDABLE:
187+
return ISMINE_SPENDABLE;
188+
}
189+
assert(false);
164190
}
165191

166192
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey)

src/script/ismine.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@ class CScript;
1717
enum isminetype
1818
{
1919
ISMINE_NO = 0,
20-
//! Indicates that we don't know how to create a scriptSig that would solve this if we were given the appropriate private keys
21-
ISMINE_WATCH_UNSOLVABLE = 1,
22-
//! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys
23-
ISMINE_WATCH_SOLVABLE = 2,
24-
ISMINE_WATCH_ONLY = ISMINE_WATCH_SOLVABLE | ISMINE_WATCH_UNSOLVABLE,
25-
ISMINE_SPENDABLE = 4,
20+
ISMINE_WATCH_ONLY = 1,
21+
ISMINE_SPENDABLE = 2,
2622
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE
2723
};
2824
/** used for bitflags of isminetype */

src/wallet/coincontrol.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class CCoinControl
2222
boost::optional<OutputType> m_change_type;
2323
//! If false, allows unselected inputs, but requires all selected inputs be used
2424
bool fAllowOtherInputs;
25-
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
25+
//! Includes watch only addresses which are solvable
2626
bool fAllowWatchOnly;
2727
//! Override automatic min/max checks on fee, m_feerate must be set if true
2828
bool fOverrideFeeRate;

src/wallet/wallet.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2376,10 +2376,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
23762376
continue;
23772377
}
23782378

2379-
bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
2380-
bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
2379+
bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey);
2380+
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
23812381

2382-
vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
2382+
vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx));
23832383

23842384
// Checks the sum amount of all UTXO's.
23852385
if (nMinimumSumAmount != MAX_MONEY) {

0 commit comments

Comments
 (0)