Browse Source

Implicitly know about P2WPKH redeemscripts

Make CKeyStore automatically known about the redeemscripts necessary for P2SH-P2WPKH
(and due to the extra checks in IsMine, also P2WPKH) spending.
0.16
Pieter Wuille 7 years ago
parent
commit
f37c64e477
  1. 35
      src/keystore.cpp
  2. 2
      src/keystore.h
  3. 7
      src/test/script_standard_tests.cpp
  4. 1
      src/wallet/crypter.cpp
  5. 2
      src/wallet/rpcwallet.cpp
  6. 36
      test/functional/segwit.py

35
src/keystore.cpp

@ -11,6 +11,31 @@ bool CKeyStore::AddKey(const CKey &key) {
return AddKeyPubKey(key, key.GetPubKey()); return AddKeyPubKey(key, key.GetPubKey());
} }
void CBasicKeyStore::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey)
{
AssertLockHeld(cs_KeyStore);
CKeyID key_id = pubkey.GetID();
// We must actually know about this key already.
assert(HaveKey(key_id) || mapWatchKeys.count(key_id));
// This adds the redeemscripts necessary to detect P2WPKH and P2SH-P2WPKH
// outputs. Technically P2WPKH outputs don't have a redeemscript to be
// spent. However, our current IsMine logic requires the corresponding
// P2SH-P2WPKH redeemscript to be present in the wallet in order to accept
// payment even to P2WPKH outputs.
// Also note that having superfluous scripts in the keystore never hurts.
// They're only used to guide recursion in signing and IsMine logic - if
// a script is present but we can't do anything with it, it has no effect.
// "Implicitly" refers to fact that scripts are derived automatically from
// existing keys, and are present in memory, even without being explicitly
// loaded (e.g. from a file).
if (pubkey.IsCompressed()) {
CScript script = GetScriptForDestination(WitnessV0KeyHash(key_id));
// This does not use AddCScript, as it may be overridden.
CScriptID id(script);
mapScripts[id] = std::move(script);
}
}
bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const
{ {
CKey key; CKey key;
@ -31,6 +56,7 @@ bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
mapKeys[pubkey.GetID()] = key; mapKeys[pubkey.GetID()] = key;
ImplicitlyLearnRelatedKeyScripts(pubkey);
return true; return true;
} }
@ -110,8 +136,10 @@ bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
setWatchOnly.insert(dest); setWatchOnly.insert(dest);
CPubKey pubKey; CPubKey pubKey;
if (ExtractPubKey(dest, pubKey)) if (ExtractPubKey(dest, pubKey)) {
mapWatchKeys[pubKey.GetID()] = pubKey; mapWatchKeys[pubKey.GetID()] = pubKey;
ImplicitlyLearnRelatedKeyScripts(pubKey);
}
return true; return true;
} }
@ -120,8 +148,11 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest)
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
setWatchOnly.erase(dest); setWatchOnly.erase(dest);
CPubKey pubKey; CPubKey pubKey;
if (ExtractPubKey(dest, pubKey)) if (ExtractPubKey(dest, pubKey)) {
mapWatchKeys.erase(pubKey.GetID()); mapWatchKeys.erase(pubKey.GetID());
}
// Related CScripts are not removed; having superfluous scripts around is
// harmless (see comment in ImplicitlyLearnRelatedKeyScripts).
return true; return true;
} }

2
src/keystore.h

@ -59,6 +59,8 @@ protected:
ScriptMap mapScripts; ScriptMap mapScripts;
WatchOnlySet setWatchOnly; WatchOnlySet setWatchOnly;
void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey);
public: public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;

7
src/test/script_standard_tests.cpp

@ -508,12 +508,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine)
scriptPubKey.clear(); scriptPubKey.clear();
scriptPubKey << OP_0 << ToByteVector(pubkeys[0].GetID()); scriptPubKey << OP_0 << ToByteVector(pubkeys[0].GetID());
// Keystore has key, but no P2SH redeemScript // Keystore implicitly has key and P2SH redeemScript
result = IsMine(keystore, scriptPubKey, isInvalid);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
BOOST_CHECK(!isInvalid);
// Keystore has key and P2SH redeemScript
keystore.AddCScript(scriptPubKey); keystore.AddCScript(scriptPubKey);
result = IsMine(keystore, scriptPubKey, isInvalid); result = IsMine(keystore, scriptPubKey, isInvalid);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);

1
src/wallet/crypter.cpp

@ -245,6 +245,7 @@ bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<
} }
mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
ImplicitlyLearnRelatedKeyScripts(vchPubKey);
return true; return true;
} }

2
src/wallet/rpcwallet.cpp

@ -1307,7 +1307,7 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types");
} }
} else { } else {
pwallet->AddCScript(witprogram); pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software
pwallet->SetAddressBook(w.result, "", "receive"); pwallet->SetAddressBook(w.result, "", "receive");
} }

36
test/functional/segwit.py

@ -356,8 +356,10 @@ class SegWitTest(BitcoinTestFramework):
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH and P2PK with compressed keys should always be spendable # normal P2PKH and P2PK with compressed keys should always be spendable
spendable_anytime.extend([p2pkh, p2pk]) spendable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK, P2SH_P2PKH, and witness with compressed keys are spendable after direct importaddress # P2SH_P2PK, P2SH_P2PKH with compressed keys are spendable after direct importaddress
spendable_after_importaddress.extend([p2wpkh, p2sh_p2wpkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
# P2WPKH and P2SH_P2WPKH with compressed keys should always be spendable
spendable_anytime.extend([p2wpkh, p2sh_p2wpkh])
for i in uncompressed_spendable_address: for i in uncompressed_spendable_address:
v = self.nodes[0].validateaddress(i) v = self.nodes[0].validateaddress(i)
@ -373,7 +375,7 @@ class SegWitTest(BitcoinTestFramework):
spendable_anytime.extend([p2pkh, p2pk]) spendable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK and P2SH_P2PKH are spendable after direct importaddress # P2SH_P2PK and P2SH_P2PKH are spendable after direct importaddress
spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh]) spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh])
# witness with uncompressed keys are never seen # Witness output types with uncompressed keys are never seen
unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
for i in compressed_solvable_address: for i in compressed_solvable_address:
@ -384,10 +386,10 @@ class SegWitTest(BitcoinTestFramework):
solvable_after_importaddress.extend([bare, p2sh, p2wsh, p2sh_p2wsh]) solvable_after_importaddress.extend([bare, p2sh, p2wsh, p2sh_p2wsh])
else: else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH and P2PK with compressed keys should always be seen # normal P2PKH, P2PK, P2WPKH and P2SH_P2WPKH with compressed keys should always be seen
solvable_anytime.extend([p2pkh, p2pk]) solvable_anytime.extend([p2pkh, p2pk, p2wpkh, p2sh_p2wpkh])
# P2SH_P2PK, P2SH_P2PKH, and witness with compressed keys are seen after direct importaddress # P2SH_P2PK, P2SH_P2PKH with compressed keys are seen after direct importaddress
solvable_after_importaddress.extend([p2wpkh, p2sh_p2wpkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
for i in uncompressed_solvable_address: for i in uncompressed_solvable_address:
v = self.nodes[0].validateaddress(i) v = self.nodes[0].validateaddress(i)
@ -403,7 +405,7 @@ class SegWitTest(BitcoinTestFramework):
solvable_anytime.extend([p2pkh, p2pk]) solvable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK, P2SH_P2PKH with uncompressed keys are seen after direct importaddress # P2SH_P2PK, P2SH_P2PKH with uncompressed keys are seen after direct importaddress
solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh]) solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh])
# witness with uncompressed keys are never seen # Witness output types with uncompressed keys are never seen
unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]) unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
op1 = CScript([OP_1]) op1 = CScript([OP_1])
@ -496,6 +498,8 @@ class SegWitTest(BitcoinTestFramework):
spendable_after_addwitnessaddress = [] # These outputs should be seen after importaddress spendable_after_addwitnessaddress = [] # These outputs should be seen after importaddress
solvable_after_addwitnessaddress=[] # These outputs should be seen after importaddress but not spendable solvable_after_addwitnessaddress=[] # These outputs should be seen after importaddress but not spendable
unseen_anytime = [] # These outputs should never be seen unseen_anytime = [] # These outputs should never be seen
solvable_anytime = [] # These outputs should be solvable after importpubkey
unseen_anytime = [] # These outputs should never be seen
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])) uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]]))
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])) uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]]))
@ -514,9 +518,8 @@ class SegWitTest(BitcoinTestFramework):
premature_witaddress.append(script_to_p2sh(p2wsh)) premature_witaddress.append(script_to_p2sh(p2wsh))
else: else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# P2WPKH, P2SH_P2WPKH are spendable after addwitnessaddress # P2WPKH, P2SH_P2WPKH are always spendable
spendable_after_addwitnessaddress.extend([p2wpkh, p2sh_p2wpkh]) spendable_anytime.extend([p2wpkh, p2sh_p2wpkh])
premature_witaddress.append(script_to_p2sh(p2wpkh))
for i in uncompressed_spendable_address + uncompressed_solvable_address: for i in uncompressed_spendable_address + uncompressed_solvable_address:
v = self.nodes[0].validateaddress(i) v = self.nodes[0].validateaddress(i)
@ -538,10 +541,11 @@ class SegWitTest(BitcoinTestFramework):
premature_witaddress.append(script_to_p2sh(p2wsh)) premature_witaddress.append(script_to_p2sh(p2wsh))
else: else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v) [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# P2SH_P2PK, P2SH_P2PKH with compressed keys are seen after addwitnessaddress # P2SH_P2PK, P2SH_P2PKH with compressed keys are always solvable
solvable_after_addwitnessaddress.extend([p2wpkh, p2sh_p2wpkh]) solvable_anytime.extend([p2wpkh, p2sh_p2wpkh])
premature_witaddress.append(script_to_p2sh(p2wpkh))
self.mine_and_test_listunspent(spendable_anytime, 2)
self.mine_and_test_listunspent(solvable_anytime, 1)
self.mine_and_test_listunspent(spendable_after_addwitnessaddress + solvable_after_addwitnessaddress + unseen_anytime, 0) self.mine_and_test_listunspent(spendable_after_addwitnessaddress + solvable_after_addwitnessaddress + unseen_anytime, 0)
# addwitnessaddress should refuse to return a witness address if an uncompressed key is used # addwitnessaddress should refuse to return a witness address if an uncompressed key is used
@ -558,8 +562,8 @@ class SegWitTest(BitcoinTestFramework):
witaddress = self.nodes[0].addwitnessaddress(i) witaddress = self.nodes[0].addwitnessaddress(i)
assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress)) assert_equal(witaddress, self.nodes[0].addwitnessaddress(witaddress))
spendable_txid.append(self.mine_and_test_listunspent(spendable_after_addwitnessaddress, 2)) spendable_txid.append(self.mine_and_test_listunspent(spendable_after_addwitnessaddress + spendable_anytime, 2))
solvable_txid.append(self.mine_and_test_listunspent(solvable_after_addwitnessaddress, 1)) solvable_txid.append(self.mine_and_test_listunspent(solvable_after_addwitnessaddress + solvable_anytime, 1))
self.mine_and_test_listunspent(unseen_anytime, 0) self.mine_and_test_listunspent(unseen_anytime, 0)
# Check that createrawtransaction/decoderawtransaction with non-v0 Bech32 works # Check that createrawtransaction/decoderawtransaction with non-v0 Bech32 works

Loading…
Cancel
Save