Skip to content

Commit 487dcbe

Browse files
committed
Merge #13002: Do not treat bare multisig outputs as IsMine unless watched
7d0f80b Use anonymous namespace instead of static functions (Pieter Wuille) b61fb71 Mention removal of bare multisig IsMine in release notes (Pieter Wuille) 9c2a8b8 Do not treat bare multisig as IsMine (Pieter Wuille) 08f3228 Optimization: only test for witness scripts at top level (Pieter Wuille) 3619735 Track difference between scriptPubKey and P2SH execution in IsMine (Pieter Wuille) ac6ec62 Switch to a private version of SigVersion inside IsMine (Pieter Wuille) 19fc973 Do not expose SigVersion argument to IsMine (Pieter Wuille) fb1dfbb Remove unused IsMine overload (Pieter Wuille) 952d821 Make CScript -> CScriptID conversion explicit (Pieter Wuille) Pull request description: Currently our wallet code will treat bare multisig outputs (meaning scriptPubKeys with multiple public keys + `OP_CHECKMULTISIG` operator in it) as ours without the user asking for it, as long as all private keys in it are in our wallet. This is a pointless feature. As it only works when all private keys are in one place, it's useless compared to single key outputs (P2PK, P2PKH, P2WPKH, P2SH-P2WPKH), and worse in terms of space, cost, UTXO size, and ability to test (due to lack of address format for them). Furthermore, they are problematic in that producing a list of all `scriptPubKeys` we accept is not tractable (it involves all combinations of all public keys that are ours). In further wallet changes I'd like to move to a model where all scriptPubKeys that are treated as ours are explicit, rather than defined by whatever keys we have. The current behavior of the wallet is very hard to model in such a design, so I'd like to get rid of it. I think there are two options: * Remove it entirely (do not ever accept bare multisig outputs as ours, unless watched) * Only accept bare multisig outputs in situations where the P2SH version of that output would also be acceptable This PR implements the first option. The second option was explored in #12874. Tree-SHA512: 917ed45b3cac864cee53e27f9a3e900390c576277fbd6751b1250becea04d692b3b426fa09065a3399931013bd579c4f3dbeeb29d51d19ed0c64da75d430ad9a
2 parents 8d045a0 + 7d0f80b commit 487dcbe

File tree

7 files changed

+90
-44
lines changed

7 files changed

+90
-44
lines changed

doc/release-notes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ Low-level RPC changes
105105
with any `-wallet=<path>` options, there is no change in behavior, and the
106106
name of any wallet is just its `<path>` string.
107107

108+
- Bare multisig outputs to our keys are no longer automatically treated as
109+
incoming payments. As this feature was only available for multisig outputs for
110+
which you had all private keys in your wallet, there was generally no use for
111+
them compared to single-key schemes. Furthermore, no address format for such
112+
outputs is defined, and wallet software can't easily send to it. These outputs
113+
will no longer show up in `listtransactions`, `listunspent`, or contribute to
114+
your balance, unless they are explicitly watched (using `importaddress` or
115+
`importmulti` with hex script argument). `signrawtransaction*` also still
116+
works for them.
117+
108118
### Logging
109119

110120
- The log timestamp format is now ISO 8601 (e.g. "2018-02-28T12:34:56Z").

src/script/ismine.cpp

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,36 @@
1313

1414
typedef std::vector<unsigned char> valtype;
1515

16-
static bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore)
17-
{
18-
for (const valtype& pubkey : pubkeys) {
19-
CKeyID keyID = CPubKey(pubkey).GetID();
20-
if (!keystore.HaveKey(keyID)) return false;
21-
}
22-
return true;
23-
}
16+
namespace {
2417

25-
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion sigversion)
18+
/**
19+
* This is an enum that tracks the execution context of a script, similar to
20+
* SigVersion in script/interpreter. It is separate however because we want to
21+
* distinguish between top-level scriptPubKey execution and P2SH redeemScript
22+
* execution (a distinction that has no impact on consensus rules).
23+
*/
24+
enum class IsMineSigVersion
2625
{
27-
bool isInvalid = false;
28-
return IsMine(keystore, scriptPubKey, isInvalid, sigversion);
29-
}
26+
TOP = 0, //! scriptPubKey execution
27+
P2SH = 1, //! P2SH redeemScript
28+
WITNESS_V0 = 2 //! P2WSH witness script execution
29+
};
3030

31-
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion sigversion)
31+
bool PermitsUncompressed(IsMineSigVersion sigversion)
3232
{
33-
bool isInvalid = false;
34-
return IsMine(keystore, dest, isInvalid, sigversion);
33+
return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;
3534
}
3635

37-
isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest, bool& isInvalid, SigVersion sigversion)
36+
bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore)
3837
{
39-
CScript script = GetScriptForDestination(dest);
40-
return IsMine(keystore, script, isInvalid, sigversion);
38+
for (const valtype& pubkey : pubkeys) {
39+
CKeyID keyID = CPubKey(pubkey).GetID();
40+
if (!keystore.HaveKey(keyID)) return false;
41+
}
42+
return true;
4143
}
4244

43-
isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& isInvalid, SigVersion sigversion)
45+
isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, IsMineSigVersion sigversion)
4446
{
4547
isInvalid = false;
4648

@@ -61,7 +63,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
6163
break;
6264
case TX_PUBKEY:
6365
keyID = CPubKey(vSolutions[0]).GetID();
64-
if (sigversion != SigVersion::BASE && vSolutions[0].size() != 33) {
66+
if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) {
6567
isInvalid = true;
6668
return ISMINE_NO;
6769
}
@@ -70,20 +72,20 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
7072
break;
7173
case TX_WITNESS_V0_KEYHASH:
7274
{
73-
if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
75+
if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
7476
// We do not support bare witness outputs unless the P2SH version of it would be
7577
// acceptable as well. This protects against matching before segwit activates.
7678
// This also applies to the P2WSH case.
7779
break;
7880
}
79-
isminetype ret = ::IsMine(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, SigVersion::WITNESS_V0);
81+
isminetype ret = IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, IsMineSigVersion::WITNESS_V0);
8082
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
8183
return ret;
8284
break;
8385
}
8486
case TX_PUBKEYHASH:
8587
keyID = CKeyID(uint160(vSolutions[0]));
86-
if (sigversion != SigVersion::BASE) {
88+
if (!PermitsUncompressed(sigversion)) {
8789
CPubKey pubkey;
8890
if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) {
8991
isInvalid = true;
@@ -98,23 +100,23 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
98100
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
99101
CScript subscript;
100102
if (keystore.GetCScript(scriptID, subscript)) {
101-
isminetype ret = IsMine(keystore, subscript, isInvalid);
103+
isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::P2SH);
102104
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
103105
return ret;
104106
}
105107
break;
106108
}
107109
case TX_WITNESS_V0_SCRIPTHASH:
108110
{
109-
if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
111+
if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
110112
break;
111113
}
112114
uint160 hash;
113115
CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin());
114116
CScriptID scriptID = CScriptID(hash);
115117
CScript subscript;
116118
if (keystore.GetCScript(scriptID, subscript)) {
117-
isminetype ret = IsMine(keystore, subscript, isInvalid, SigVersion::WITNESS_V0);
119+
isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::WITNESS_V0);
118120
if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid))
119121
return ret;
120122
}
@@ -123,13 +125,16 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
123125

124126
case TX_MULTISIG:
125127
{
128+
// Never treat bare multisig outputs as ours (they can still be made watchonly-though)
129+
if (sigversion == IsMineSigVersion::TOP) break;
130+
126131
// Only consider transactions "mine" if we own ALL the
127132
// keys involved. Multi-signature transactions that are
128133
// partially owned (somebody else has a key that can spend
129134
// them) enable spend-out-from-under-you attacks, especially
130135
// in shared-wallet situations.
131136
std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
132-
if (sigversion != SigVersion::BASE) {
137+
if (!PermitsUncompressed(sigversion)) {
133138
for (size_t i = 0; i < keys.size(); i++) {
134139
if (keys[i].size() != 33) {
135140
isInvalid = true;
@@ -150,3 +155,22 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
150155
}
151156
return ISMINE_NO;
152157
}
158+
159+
} // namespace
160+
161+
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid)
162+
{
163+
return IsMineInner(keystore, scriptPubKey, isInvalid, IsMineSigVersion::TOP);
164+
}
165+
166+
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey)
167+
{
168+
bool isInvalid = false;
169+
return IsMine(keystore, scriptPubKey, isInvalid);
170+
}
171+
172+
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest)
173+
{
174+
CScript script = GetScriptForDestination(dest);
175+
return IsMine(keystore, script);
176+
}

src/script/ismine.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ typedef uint8_t isminefilter;
3333
* different SIGVERSION may have different network rules. Currently the only use of isInvalid is indicate uncompressed
3434
* keys in SigVersion::WITNESS_V0 script, but could also be used in similar cases in the future
3535
*/
36-
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, SigVersion = SigVersion::BASE);
37-
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion = SigVersion::BASE);
38-
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, bool& isInvalid, SigVersion = SigVersion::BASE);
39-
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion = SigVersion::BASE);
36+
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid);
37+
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
38+
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest);
4039

4140
#endif // BITCOIN_SCRIPT_ISMINE_H

src/script/standard.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class CScriptID : public uint160
2323
{
2424
public:
2525
CScriptID() : uint160() {}
26-
CScriptID(const CScript& in);
26+
explicit CScriptID(const CScript& in);
2727
CScriptID(const uint160& in) : uint160(in) {}
2828
};
2929

src/test/script_standard_tests.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,14 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine)
561561
keystore.AddKey(keys[1]);
562562

563563
result = IsMine(keystore, scriptPubKey, isInvalid);
564-
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
564+
BOOST_CHECK_EQUAL(result, ISMINE_NO);
565+
BOOST_CHECK(!isInvalid);
566+
567+
// Keystore has 2/2 keys and the script
568+
keystore.AddCScript(scriptPubKey);
569+
570+
result = IsMine(keystore, scriptPubKey, isInvalid);
571+
BOOST_CHECK_EQUAL(result, ISMINE_NO);
565572
BOOST_CHECK(!isInvalid);
566573
}
567574

src/wallet/rpcdump.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,11 @@ void ImportScript(CWallet* const pwallet, const CScript& script, const std::stri
224224
}
225225

226226
if (isRedeemScript) {
227-
if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) {
227+
const CScriptID id(script);
228+
if (!pwallet->HaveCScript(id) && !pwallet->AddCScript(script)) {
228229
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
229230
}
230-
ImportAddress(pwallet, CScriptID(script), strLabel);
231+
ImportAddress(pwallet, id, strLabel);
231232
} else {
232233
CTxDestination destination;
233234
if (ExtractDestination(script, destination)) {
@@ -602,7 +603,8 @@ UniValue importwallet(const JSONRPCRequest& request)
602603
} else if(IsHex(vstr[0])) {
603604
std::vector<unsigned char> vData(ParseHex(vstr[0]));
604605
CScript script = CScript(vData.begin(), vData.end());
605-
if (pwallet->HaveCScript(script)) {
606+
CScriptID id(script);
607+
if (pwallet->HaveCScript(id)) {
606608
LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
607609
continue;
608610
}
@@ -613,7 +615,7 @@ UniValue importwallet(const JSONRPCRequest& request)
613615
}
614616
int64_t birth_time = DecodeDumpTime(vstr[1]);
615617
if (birth_time > 0) {
616-
pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
618+
pwallet->m_script_metadata[id].nCreateTime = birth_time;
617619
nTimeBegin = std::min(nTimeBegin, birth_time);
618620
}
619621
}
@@ -899,12 +901,12 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6
899901
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
900902
}
901903

902-
if (!pwallet->HaveCScript(redeemScript) && !pwallet->AddCScript(redeemScript)) {
904+
CScriptID redeem_id(redeemScript);
905+
if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) {
903906
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
904907
}
905908

906-
CTxDestination redeem_dest = CScriptID(redeemScript);
907-
CScript redeemDestination = GetScriptForDestination(redeem_dest);
909+
CScript redeemDestination = GetScriptForDestination(redeem_id);
908910

909911
if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
910912
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");

test/functional/feature_segwit.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,10 @@ def run_test(self):
303303
v = self.nodes[0].getaddressinfo(i)
304304
if (v['isscript']):
305305
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
306-
# bare and p2sh multisig with compressed keys should always be spendable
307-
spendable_anytime.extend([bare, p2sh])
306+
# p2sh multisig with compressed keys should always be spendable
307+
spendable_anytime.extend([p2sh])
308+
# bare multisig can be watched and signed, but is not treated as ours
309+
solvable_after_importaddress.extend([bare])
308310
# P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after direct importaddress
309311
spendable_after_importaddress.extend([p2wsh, p2sh_p2wsh])
310312
else:
@@ -320,8 +322,10 @@ def run_test(self):
320322
v = self.nodes[0].getaddressinfo(i)
321323
if (v['isscript']):
322324
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
323-
# bare and p2sh multisig with uncompressed keys should always be spendable
324-
spendable_anytime.extend([bare, p2sh])
325+
# p2sh multisig with uncompressed keys should always be spendable
326+
spendable_anytime.extend([p2sh])
327+
# bare multisig can be watched and signed, but is not treated as ours
328+
solvable_after_importaddress.extend([bare])
325329
# P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen
326330
unseen_anytime.extend([p2wsh, p2sh_p2wsh])
327331
else:

0 commit comments

Comments
 (0)