Skip to content

Commit

Permalink
Add support for watch-only addresses
Browse files Browse the repository at this point in the history
Changes:
* Add Add/Have WatchOnly methods to CKeyStore, and implementations
  in CBasicKeyStore.
* Add similar methods to CWallet, and support entries for it in
  CWalletDB.
* Make IsMine in script/wallet return a new enum 'isminetype',
  rather than a boolean. This allows distinguishing between
  spendable and unspendable coins.
* Add a field fSpendable to COutput (GetAvailableCoins' return type).
* Mark watchonly coins in listunspent as 'watchonly': true.
* Add 'watchonly' to validateaddress, suppressing script/pubkey/...
  in this case.

Based on a patch by Eric Lombrozo.

Conflicts:
	src/qt/walletmodel.cpp
	src/rpcserver.cpp
	src/wallet.cpp

Conflicts:
	src/script.h

Rebased-from: c898846 bitcoin#4045
  • Loading branch information
sipa authored and wtogami committed Oct 1, 2014
1 parent ab0ef75 commit 437278d
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 52 deletions.
12 changes: 12 additions & 0 deletions src/keystore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,15 @@ bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript& redeemScriptOut)
return false;
}

bool CBasicKeyStore::AddWatchOnly(const CTxDestination &dest)
{
LOCK(cs_KeyStore);
setWatchOnly.insert(dest);
return true;
}

bool CBasicKeyStore::HaveWatchOnly(const CTxDestination &dest) const
{
LOCK(cs_KeyStore);
return setWatchOnly.count(dest) > 0;
}
19 changes: 19 additions & 0 deletions src/keystore.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@

#include "key.h"
#include "sync.h"
#include "script.h" // for CNoDestination

#include <boost/signals2/signal.hpp>
#include <boost/variant.hpp>

class CScript;

/** A txout script template with a specific destination. It is either:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination
* * CScriptID: TX_SCRIPTHASH destination
* A CTxDestination is the internal data type encoded in a CBitcoinAddress
*/
typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;

/** A virtual base class for key stores */
class CKeyStore
{
Expand All @@ -36,17 +46,23 @@ class CKeyStore
virtual bool AddCScript(const CScript& redeemScript) =0;
virtual bool HaveCScript(const CScriptID &hash) const =0;
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const =0;

// Support for Watch-only addresses
virtual bool AddWatchOnly(const CTxDestination &dest) =0;
virtual bool HaveWatchOnly(const CTxDestination &dest) const =0;
};

typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CTxDestination> WatchOnlySet;

/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
{
protected:
KeyMap mapKeys;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;

public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
Expand Down Expand Up @@ -88,6 +104,9 @@ class CBasicKeyStore : public CKeyStore
virtual bool AddCScript(const CScript& redeemScript);
virtual bool HaveCScript(const CScriptID &hash) const;
virtual bool GetCScript(const CScriptID &hash, CScript& redeemScriptOut) const;

virtual bool AddWatchOnly(const CTxDestination &dest);
virtual bool HaveWatchOnly(const CTxDestination &dest) const;
};

typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
Expand Down
6 changes: 3 additions & 3 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vect
if (!wallet->mapWallet.count(outpoint.hash)) continue;
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
if (nDepth < 0) continue;
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth);
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true);
vOutputs.push_back(out);
}
}
Expand All @@ -575,7 +575,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
if (!wallet->mapWallet.count(outpoint.hash)) continue;
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
if (nDepth < 0) continue;
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth);
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true);
vCoins.push_back(out);
}

Expand All @@ -586,7 +586,7 @@ void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins)
while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0]))
{
if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break;
cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0);
cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0, true);
}

CTxDestination address;
Expand Down
1 change: 1 addition & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "lockunspent", 0 },
{ "lockunspent", 1 },
{ "importprivkey", 2 },
{ "importaddress", 2 },
{ "verifychain", 0 },
{ "verifychain", 1 },
{ "keypoolrefill", 0 },
Expand Down
45 changes: 45 additions & 0 deletions src/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,51 @@ Value importprivkey(const Array& params, bool fHelp)
return Value::null;
}

Value importaddress(const Array& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
"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.");

CBitcoinAddress address(params[0].get_str());
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
CTxDestination dest;
dest = address.Get();

string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();

// Whether to perform rescan after import
bool fRescan = true;
if (params.size() > 2)
fRescan = params[2].get_bool();

{
LOCK2(cs_main, pwalletMain->cs_wallet);

// Don't throw error in case an address is already there
if (pwalletMain->HaveWatchOnly(dest))
return Value::null;

pwalletMain->MarkDirty();
pwalletMain->SetAddressBook(dest, strLabel, "receive");

if (!pwalletMain->AddWatchOnly(dest))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");

if (fRescan)
{
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
pwalletMain->ReacceptWalletTransactions();
}
}

return Value::null;
}

Value importwallet(const Array& params, bool fHelp)
{
if (fHelp || params.size() != 1)
Expand Down
52 changes: 31 additions & 21 deletions src/rpcmisc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,45 @@ Value getinfo(const Array& params, bool fHelp)
#ifdef ENABLE_WALLET
class DescribeAddressVisitor : public boost::static_visitor<Object>
{
private:
isminetype mine;

public:
DescribeAddressVisitor(isminetype mineIn) : mine(mineIn) {}

Object operator()(const CNoDestination &dest) const { return Object(); }

Object operator()(const CKeyID &keyID) const {
Object obj;
CPubKey vchPubKey;
pwalletMain->GetPubKey(keyID, vchPubKey);
obj.push_back(Pair("isscript", false));
obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
if (mine == MINE_SPENDABLE) {
pwalletMain->GetPubKey(keyID, vchPubKey);
obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
}
return obj;
}

Object operator()(const CScriptID &scriptID) const {
Object obj;
obj.push_back(Pair("isscript", true));
CScript subscript;
pwalletMain->GetCScript(scriptID, subscript);
std::vector<CTxDestination> addresses;
txnouttype whichType;
int nRequired;
ExtractDestinations(subscript, whichType, addresses, nRequired);
obj.push_back(Pair("script", GetTxnOutputType(whichType)));
obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end())));
Array a;
BOOST_FOREACH(const CTxDestination& addr, addresses)
a.push_back(CBitcoinAddress(addr).ToString());
obj.push_back(Pair("addresses", a));
if (whichType == TX_MULTISIG)
obj.push_back(Pair("sigsrequired", nRequired));
if (mine == MINE_SPENDABLE) {
CScript subscript;
pwalletMain->GetCScript(scriptID, subscript);
std::vector<CTxDestination> addresses;
txnouttype whichType;
int nRequired;
ExtractDestinations(subscript, whichType, addresses, nRequired);
obj.push_back(Pair("script", GetTxnOutputType(whichType)));
obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end())));
Array a;
BOOST_FOREACH(const CTxDestination& addr, addresses)
a.push_back(CBitcoinAddress(addr).ToString());
obj.push_back(Pair("addresses", a));
if (whichType == TX_MULTISIG)
obj.push_back(Pair("sigsrequired", nRequired));
}
return obj;
}
};
Expand Down Expand Up @@ -160,10 +169,11 @@ Value validateaddress(const Array& params, bool fHelp)
string currentAddress = address.ToString();
ret.push_back(Pair("address", currentAddress));
#ifdef ENABLE_WALLET
bool fMine = pwalletMain ? IsMine(*pwalletMain, dest) : false;
ret.push_back(Pair("ismine", fMine));
if (fMine) {
Object detail = boost::apply_visitor(DescribeAddressVisitor(), dest);
isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : MINE_NO;
ret.push_back(Pair("ismine", mine != MINE_NO));
if (mine != MINE_NO) {
ret.push_back(Pair("watchonly", mine == MINE_WATCH_ONLY));
Object detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest);
ret.insert(ret.end(), detail.begin(), detail.end());
}
if (pwalletMain && pwalletMain->mapAddressBook.count(dest))
Expand Down
1 change: 1 addition & 0 deletions src/rpcrawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ Value listunspent(const Array& params, bool fHelp)
}
entry.push_back(Pair("amount",ValueFromAmount(nValue)));
entry.push_back(Pair("confirmations",out.nDepth));
entry.push_back(Pair("spendable", out.fSpendable));
results.push_back(entry);
}

Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ static const CRPCCommand vRPCCommands[] =
{ "getwalletinfo", &getwalletinfo, true, false, true },
{ "importprivkey", &importprivkey, false, false, true },
{ "importwallet", &importwallet, false, false, true },
{ "importaddress", &importaddress, false, false, true },
{ "keypoolrefill", &keypoolrefill, true, false, true },
{ "listaccounts", &listaccounts, false, false, true },
{ "listaddressgroupings", &listaddressgroupings, false, false, true },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ extern json_spirit::Value getnettotals(const json_spirit::Array& params, bool fH

extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp
extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value importaddress(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp);

Expand Down
52 changes: 39 additions & 13 deletions src/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1446,36 +1446,57 @@ class CKeyStoreIsMineVisitor : public boost::static_visitor<bool>
bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); }
};

bool IsMine(const CKeyStore &keystore, const CTxDestination &dest)
isminetype IsMine(const CKeyStore &keystore, const CTxDestination &dest)
{
return boost::apply_visitor(CKeyStoreIsMineVisitor(&keystore), dest);
if (boost::apply_visitor(CKeyStoreIsMineVisitor(&keystore), dest))
return MINE_SPENDABLE;
if (keystore.HaveWatchOnly(dest))
return MINE_WATCH_ONLY;
return MINE_NO;
}

bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
{
vector<valtype> vSolutions;
txnouttype whichType;
if (!Solver(scriptPubKey, whichType, vSolutions))
return false;
if (!Solver(scriptPubKey, whichType, vSolutions)) {
if (keystore.HaveWatchOnly(scriptPubKey.GetID()))
return MINE_WATCH_ONLY;
return MINE_NO;
}

CKeyID keyID;
switch (whichType)
{
case TX_NONSTANDARD:
case TX_NULL_DATA:
return false;
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
return keystore.HaveKey(keyID);
if (keystore.HaveKey(keyID))
return MINE_SPENDABLE;
if (keystore.HaveWatchOnly(keyID))
return MINE_WATCH_ONLY;
break;
case TX_PUBKEYHASH:
keyID = CKeyID(uint160(vSolutions[0]));
return keystore.HaveKey(keyID);
if (keystore.HaveKey(keyID))
return MINE_SPENDABLE;
if (keystore.HaveWatchOnly(keyID))
return MINE_WATCH_ONLY;
break;
case TX_SCRIPTHASH:
{
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript;
if (!keystore.GetCScript(CScriptID(uint160(vSolutions[0])), subscript))
return false;
return IsMine(keystore, subscript);
if (keystore.GetCScript(scriptID, subscript)) {
isminetype ret = IsMine(keystore, subscript);
if (ret)
return ret;
}
if (keystore.HaveWatchOnly(scriptID))
return MINE_WATCH_ONLY;
break;
}
case TX_MULTISIG:
{
Expand All @@ -1485,10 +1506,15 @@ bool IsMine(const CKeyStore &keystore, const CScript& scriptPubKey)
// them) enable spend-out-from-under-you attacks, especially
// in shared-wallet situations.
vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
return HaveKeys(keys, keystore) == keys.size();
if (HaveKeys(keys, keystore) == keys.size())
return MINE_SPENDABLE;
break;
}
}
return false;

if (keystore.HaveWatchOnly(scriptPubKey.GetID()))
return MINE_WATCH_ONLY;
return MINE_NO;
}

bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
Expand Down
12 changes: 10 additions & 2 deletions src/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ enum
SCRIPT_VERIFY_NOCACHE = (1U << 3), // do not store results in signature cache (but do query it)
};

/** IsMine() return codes */
enum isminetype
{
MINE_NO = 0,
MINE_WATCH_ONLY = 1,
MINE_SPENDABLE = 2,
};

enum txnouttype
{
TX_NONSTANDARD,
Expand Down Expand Up @@ -791,8 +799,8 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet);
int ScriptSigArgsExpected(txnouttype t, const std::vector<std::vector<unsigned char> >& vSolutions);
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType);
bool IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
bool IsMine(const CKeyStore& keystore, const CTxDestination &dest);
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
isminetype IsMine(const CKeyStore& keystore, const CTxDestination &dest);
void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys);
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
Expand Down
2 changes: 1 addition & 1 deletion src/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ static void add_coin(int64_t nValue, int nAge = 6*24, bool fIsFromMe = false, in
wtx->fDebitCached = true;
wtx->nDebitCached = 1;
}
COutput output(wtx, nInput, nAge);
COutput output(wtx, nInput, nAge, true);
vCoins.push_back(output);
}

Expand Down
Loading

0 comments on commit 437278d

Please sign in to comment.