Use script matching rather than destination matching for watch-only.

This changes the keystore data format, wallet format and IsMine logic
to detect watch-only outputs based on direct script matching rather
than first trying to convert outputs to destinations (addresses).

The reason is that we don't know how the software that has the spending
keys works. It may support the same types of scripts as us, but that is
not guaranteed. Furthermore, it removes the ambiguity between addresses
used as identifiers for output scripts or identifiers for public keys.

One practical implication is that adding a normal pay-to-pubkey-hash
address via importaddress will not cause payments to the corresponding
full public key to be detected as IsMine. If that is wanted, add those
scripts directly (importaddress now also accepts any hex-encoded script).

Conflicts:
	src/wallet.cpp
This commit is contained in:
Pieter Wuille 2014-06-09 21:11:59 +02:00 committed by JaSK
parent 0fa2f8899a
commit d5087d1ba0
9 changed files with 47 additions and 47 deletions

View File

@ -59,14 +59,14 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
return false; return false;
} }
bool CBasicKeyStore::AddWatchOnly(const CTxDestination &dest) bool CBasicKeyStore::AddWatchOnly(const CScript &dest)
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
setWatchOnly.insert(dest); setWatchOnly.insert(dest);
return true; return true;
} }
bool CBasicKeyStore::HaveWatchOnly(const CTxDestination &dest) const bool CBasicKeyStore::HaveWatchOnly(const CScript &dest) const
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);
return setWatchOnly.count(dest) > 0; return setWatchOnly.count(dest) > 0;

View File

@ -48,13 +48,13 @@ public:
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0; virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0;
// Support for Watch-only addresses // Support for Watch-only addresses
virtual bool AddWatchOnly(const CTxDestination &dest) =0; virtual bool AddWatchOnly(const CScript &dest) =0;
virtual bool HaveWatchOnly(const CTxDestination &dest) const =0; virtual bool HaveWatchOnly(const CScript &dest) const =0;
}; };
typedef std::map<CKeyID, CKey> KeyMap; typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CScriptID, CScript > ScriptMap; typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CTxDestination> WatchOnlySet; typedef std::set<CScript> WatchOnlySet;
/** Basic key store, that keeps keys in an address->secret map */ /** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore class CBasicKeyStore : public CKeyStore
@ -105,8 +105,8 @@ public:
virtual bool HaveCScript(const CScriptID &hash) const; virtual bool HaveCScript(const CScriptID &hash) const;
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const; virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const;
virtual bool AddWatchOnly(const CTxDestination &dest); virtual bool AddWatchOnly(const CScript &dest);
virtual bool HaveWatchOnly(const CTxDestination &dest) const; virtual bool HaveWatchOnly(const CScript &dest) const;
}; };
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial; typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;

View File

@ -138,13 +138,19 @@ Value importaddress(const Array& params, bool fHelp)
if (fHelp || params.size() < 1 || params.size() > 3) if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error( throw runtime_error(
"importaddress <address> [label] [rescan=true]\n" "importaddress <address> [label] [rescan=true]\n"
"Adds an address that can be watched as if it were in your wallet but cannot be used to spend."); "Adds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.");
CScript script;
CBitcoinAddress address(params[0].get_str()); CBitcoinAddress address(params[0].get_str());
if (!address.IsValid()) if (address.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); script.SetDestination(address.Get());
CTxDestination dest; } else if (IsHex(params[0].get_str())) {
dest = address.Get(); std::vector<unsigned char> data(ParseHex(params[0].get_str()));
script = CScript(data.begin(), data.end());
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
string strLabel = ""; string strLabel = "";
if (params.size() > 1) if (params.size() > 1)
@ -159,15 +165,16 @@ Value importaddress(const Array& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet); LOCK2(cs_main, pwalletMain->cs_wallet);
// add to address book or update label // add to address book or update label
pwalletMain->SetAddressBook(dest, strLabel, "receive"); if (address.IsValid())
pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
// Don't throw error in case an address is already there // Don't throw error in case an address is already there
if (pwalletMain->HaveWatchOnly(dest)) if (pwalletMain->HaveWatchOnly(script))
return Value::null; return Value::null;
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->AddWatchOnly(dest)) if (!pwalletMain->AddWatchOnly(script))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
if (fRescan) if (fRescan)

View File

@ -1456,13 +1456,11 @@ public:
bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); } bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); }
}; };
isminetype IsMine(const CKeyStore &keystore, const CTxDestination &dest) isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest)
{ {
if (boost::apply_visitor(CKeyStoreIsMineVisitor(&keystore), dest)) CScript script;
return MINE_SPENDABLE; script.SetDestination(dest);
if (keystore.HaveWatchOnly(dest)) return IsMine(keystore, script);
return MINE_WATCH_ONLY;
return MINE_NO;
} }
isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
@ -1470,7 +1468,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
vector<valtype> vSolutions; vector<valtype> vSolutions;
txnouttype whichType; txnouttype whichType;
if (!Solver(scriptPubKey, whichType, vSolutions)) { if (!Solver(scriptPubKey, whichType, vSolutions)) {
if (keystore.HaveWatchOnly(scriptPubKey.GetID())) if (keystore.HaveWatchOnly(scriptPubKey))
return MINE_WATCH_ONLY; return MINE_WATCH_ONLY;
return MINE_NO; return MINE_NO;
} }
@ -1485,15 +1483,11 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
keyID = CPubKey(vSolutions[0]).GetID(); keyID = CPubKey(vSolutions[0]).GetID();
if (keystore.HaveKey(keyID)) if (keystore.HaveKey(keyID))
return MINE_SPENDABLE; return MINE_SPENDABLE;
if (keystore.HaveWatchOnly(keyID))
return MINE_WATCH_ONLY;
break; break;
case TX_PUBKEYHASH: case TX_PUBKEYHASH:
keyID = CKeyID(uint160(vSolutions[0])); keyID = CKeyID(uint160(vSolutions[0]));
if (keystore.HaveKey(keyID)) if (keystore.HaveKey(keyID))
return MINE_SPENDABLE; return MINE_SPENDABLE;
if (keystore.HaveWatchOnly(keyID))
return MINE_WATCH_ONLY;
break; break;
case TX_SCRIPTHASH: case TX_SCRIPTHASH:
{ {
@ -1501,11 +1495,9 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
CScript subscript; CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) { if (keystore.GetCScript(scriptID, subscript)) {
isminetype ret = IsMine(keystore, subscript); isminetype ret = IsMine(keystore, subscript);
if (ret) if (ret == MINE_SPENDABLE)
return ret; return ret;
} }
if (keystore.HaveWatchOnly(scriptID))
return MINE_WATCH_ONLY;
break; break;
} }
case TX_MULTISIG: case TX_MULTISIG:
@ -1522,7 +1514,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
} }
} }
if (keystore.HaveWatchOnly(scriptPubKey.GetID())) if (keystore.HaveWatchOnly(scriptPubKey))
return MINE_WATCH_ONLY; return MINE_WATCH_ONLY;
return MINE_NO; return MINE_NO;
} }

View File

@ -812,7 +812,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v
int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions); int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions);
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType);
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
isminetype IsMine(const CKeyStore& keystore, const CTxDestination &dest); isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest);
void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys); void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys);
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);

View File

@ -145,7 +145,7 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
return CCryptoKeyStore::AddCScript(redeemScript); return CCryptoKeyStore::AddCScript(redeemScript);
} }
bool CWallet::AddWatchOnly(const CTxDestination &dest) bool CWallet::AddWatchOnly(const CScript &dest)
{ {
if (!CCryptoKeyStore::AddWatchOnly(dest)) if (!CCryptoKeyStore::AddWatchOnly(dest))
return false; return false;
@ -155,9 +155,8 @@ bool CWallet::AddWatchOnly(const CTxDestination &dest)
return CWalletDB(strWalletFile).WriteWatchOnly(dest); return CWalletDB(strWalletFile).WriteWatchOnly(dest);
} }
bool CWallet::LoadWatchOnly(const CTxDestination &dest) bool CWallet::LoadWatchOnly(const CScript &dest)
{ {
LogPrintf("Loaded %s!\n", CBitcoinAddress(dest).ToString().c_str());
return CCryptoKeyStore::AddWatchOnly(dest); return CCryptoKeyStore::AddWatchOnly(dest);
} }
@ -729,17 +728,19 @@ int64_t CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
bool CWallet::IsChange(const CTxOut& txout) const bool CWallet::IsChange(const CTxOut& txout) const
{ {
CTxDestination address;
// TODO: fix handling of 'change' outputs. The assumption is that any // TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a TX_PUBKEYHASH that is mine but isn't in the address book // payment to a script that is ours, but is not in the address book
// is change. That assumption is likely to break when we implement multisignature // is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address; // wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are // a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember // 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change). // which output, if any, was change).
if (ExtractDestination(txout.scriptPubKey, address) && ::IsMine(*this, address) == MINE_SPENDABLE) if (::IsMine(*this, txout.scriptPubKey))
{ {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
return true;
LOCK(cs_wallet); LOCK(cs_wallet);
if (!mapAddressBook.count(address)) if (!mapAddressBook.count(address))
return true; return true;

View File

@ -227,9 +227,9 @@ public:
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
// Adds a watch-only address to the store, and saves it to disk. // Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CTxDestination &dest); bool AddWatchOnly(const CScript &dest);
// Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) // Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
bool LoadWatchOnly(const CTxDestination &dest); bool LoadWatchOnly(const CScript &dest);
bool Unlock(const SecureString& strWalletPassphrase); bool Unlock(const SecureString& strWalletPassphrase);
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);

View File

@ -112,10 +112,10 @@ bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false); return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false);
} }
bool CWalletDB::WriteWatchOnly(const CTxDestination &dest) bool CWalletDB::WriteWatchOnly(const CScript &dest)
{ {
nWalletDBUpdated++; nWalletDBUpdated++;
return Write(std::make_pair(std::string("watch"), CBitcoinAddress(dest).ToString()), '1'); return Write(std::make_pair(std::string("watchs"), dest), '1');
} }
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
@ -410,14 +410,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
wss.fAnyUnordered = true; wss.fAnyUnordered = true;
} }
} }
else if (strType == "watch") else if (strType == "watchs")
{ {
std::string strAddress; CScript script;
ssKey >> strAddress; ssKey >> script;
char fYes; char fYes;
ssValue >> fYes; ssValue >> fYes;
if (fYes == '1') if (fYes == '1')
pwallet->LoadWatchOnly(CBitcoinAddress(strAddress).Get()); pwallet->LoadWatchOnly(script);
// Watch-only addresses have no birthday information for now, // Watch-only addresses have no birthday information for now,
// so set the wallet birthday to the beginning of time. // so set the wallet birthday to the beginning of time.

View File

@ -94,7 +94,7 @@ public:
bool WriteCScript(const uint160& hash, const CScript& redeemScript); bool WriteCScript(const uint160& hash, const CScript& redeemScript);
bool WriteWatchOnly(const CTxDestination &dest); bool WriteWatchOnly(const CScript &script);
bool WriteBestBlock(const CBlockLocator& locator); bool WriteBestBlock(const CBlockLocator& locator);
bool ReadBestBlock(CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator);