diff options
Diffstat (limited to 'src')
28 files changed, 482 insertions, 146 deletions
diff --git a/src/keystore.cpp b/src/keystore.cpp index 55c9656438..fab1b81c9a 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -11,6 +11,31 @@ bool CKeyStore::AddKey(const CKey &key) { 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 { CKey key; @@ -31,6 +56,7 @@ bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); mapKeys[pubkey.GetID()] = key; + ImplicitlyLearnRelatedKeyScripts(pubkey); return true; } @@ -120,8 +146,10 @@ bool CBasicKeyStore::AddWatchOnly(const CScript &dest) LOCK(cs_KeyStore); setWatchOnly.insert(dest); CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) + if (ExtractPubKey(dest, pubKey)) { mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); + } return true; } @@ -130,8 +158,11 @@ bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) LOCK(cs_KeyStore); setWatchOnly.erase(dest); CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) + if (ExtractPubKey(dest, pubKey)) { mapWatchKeys.erase(pubKey.GetID()); + } + // Related CScripts are not removed; having superfluous scripts around is + // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). return true; } @@ -146,3 +177,25 @@ bool CBasicKeyStore::HaveWatchOnly() const LOCK(cs_KeyStore); return (!setWatchOnly.empty()); } + +CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest) +{ + // Only supports destinations which map to single public keys, i.e. P2PKH, + // P2WPKH, and P2SH-P2WPKH. + if (auto id = boost::get<CKeyID>(&dest)) { + return *id; + } + if (auto witness_id = boost::get<WitnessV0KeyHash>(&dest)) { + return CKeyID(*witness_id); + } + if (auto script_id = boost::get<CScriptID>(&dest)) { + CScript script; + CTxDestination inner_dest; + if (store.GetCScript(*script_id, script) && ExtractDestination(script, inner_dest)) { + if (auto inner_witness_id = boost::get<WitnessV0KeyHash>(&inner_dest)) { + return CKeyID(*inner_witness_id); + } + } + } + return CKeyID(); +} diff --git a/src/keystore.h b/src/keystore.h index 8ce6a076c5..ffd3238fd6 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -60,6 +60,8 @@ protected: ScriptMap mapScripts; WatchOnlySet setWatchOnly; + void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey); + public: bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; @@ -80,4 +82,7 @@ public: typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial; typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap; +/** Return the CKeyID of the key involved in a script (if there is a unique one). */ +CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest); + #endif // BITCOIN_KEYSTORE_H diff --git a/src/policy/policy.h b/src/policy/policy.h index d670f2d8f0..3d96406bbc 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -49,28 +49,28 @@ static const unsigned int DUST_RELAY_TX_FEE = 3000; * with. However scripts violating these flags may still be present in valid * blocks and we must accept those blocks. */ -static const unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | - SCRIPT_VERIFY_DERSIG | - SCRIPT_VERIFY_STRICTENC | - SCRIPT_VERIFY_MINIMALDATA | - SCRIPT_VERIFY_NULLDUMMY | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | - SCRIPT_VERIFY_CLEANSTACK | - SCRIPT_VERIFY_MINIMALIF | - SCRIPT_VERIFY_NULLFAIL | - SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | - SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | - SCRIPT_VERIFY_LOW_S | - SCRIPT_VERIFY_WITNESS | - SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | - SCRIPT_VERIFY_WITNESS_PUBKEYTYPE; +static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS | + SCRIPT_VERIFY_DERSIG | + SCRIPT_VERIFY_STRICTENC | + SCRIPT_VERIFY_MINIMALDATA | + SCRIPT_VERIFY_NULLDUMMY | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS | + SCRIPT_VERIFY_CLEANSTACK | + SCRIPT_VERIFY_MINIMALIF | + SCRIPT_VERIFY_NULLFAIL | + SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | + SCRIPT_VERIFY_CHECKSEQUENCEVERIFY | + SCRIPT_VERIFY_LOW_S | + SCRIPT_VERIFY_WITNESS | + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | + SCRIPT_VERIFY_WITNESS_PUBKEYTYPE; /** For convenience, standard but not mandatory verify flags. */ -static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; +static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; /** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */ -static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | - LOCKTIME_MEDIAN_TIME_PAST; +static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | + LOCKTIME_MEDIAN_TIME_PAST; CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index c672486984..a2521a1e9e 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } - strAddress = EncodeDestination(newKey.GetID()); + wallet->LearnRelatedScripts(newKey, g_address_type); + strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type)); } else { diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 8efa009460..afd90a3bc6 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1206,7 +1206,7 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *pl const QFontMetrics fm(font()); for (const BitcoinUnits::Unit unit : units) { - max_width = qMax(max_width, fm.width(BitcoinUnits::name(unit))); + max_width = qMax(max_width, fm.width(BitcoinUnits::longName(unit))); } setMinimumSize(max_width, 0); setAlignment(Qt::AlignRight | Qt::AlignVCenter); @@ -1225,7 +1225,7 @@ void UnitDisplayStatusBarControl::createContextMenu() menu = new QMenu(this); for (BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { - QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this); + QAction *menuAction = new QAction(QString(BitcoinUnits::longName(u)), this); menuAction->setData(QVariant(u)); menu->addAction(menuAction); } @@ -1250,7 +1250,7 @@ void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) /** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */ void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) { - setText(BitcoinUnits::name(newUnits)); + setText(BitcoinUnits::longName(newUnits)); } /** Shows context menu with Display Unit options by the mouse coordinates */ diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 55811458a9..9df05d2a13 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -36,24 +36,33 @@ bool BitcoinUnits::valid(int unit) } } -QString BitcoinUnits::name(int unit) +QString BitcoinUnits::longName(int unit) { switch(unit) { case BTC: return QString("BTC"); case mBTC: return QString("mBTC"); - case uBTC: return QString::fromUtf8("μBTC"); + case uBTC: return QString::fromUtf8("µBTC (bits)"); default: return QString("???"); } } +QString BitcoinUnits::shortName(int unit) +{ + switch(unit) + { + case uBTC: return QString::fromUtf8("bits"); + default: return longName(unit); + } +} + QString BitcoinUnits::description(int unit) { switch(unit) { case BTC: return QString("Bitcoins"); case mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)"); - case uBTC: return QString("Micro-Bitcoins (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); + case uBTC: return QString("Micro-Bitcoins (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)"); default: return QString("???"); } } @@ -121,7 +130,7 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator QString BitcoinUnits::formatWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) { - return format(unit, amount, plussign, separators) + QString(" ") + name(unit); + return format(unit, amount, plussign, separators) + QString(" ") + shortName(unit); } QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) @@ -176,7 +185,7 @@ QString BitcoinUnits::getAmountColumnTitle(int unit) QString amountTitle = QObject::tr("Amount"); if (BitcoinUnits::valid(unit)) { - amountTitle += " ("+BitcoinUnits::name(unit) + ")"; + amountTitle += " ("+BitcoinUnits::shortName(unit) + ")"; } return amountTitle; } @@ -197,7 +206,7 @@ QVariant BitcoinUnits::data(const QModelIndex &index, int role) const { case Qt::EditRole: case Qt::DisplayRole: - return QVariant(name(unit)); + return QVariant(longName(unit)); case Qt::ToolTipRole: return QVariant(description(unit)); case UnitRole: diff --git a/src/qt/bitcoinunits.h b/src/qt/bitcoinunits.h index d94b33acde..310f651815 100644 --- a/src/qt/bitcoinunits.h +++ b/src/qt/bitcoinunits.h @@ -76,8 +76,10 @@ public: static QList<Unit> availableUnits(); //! Is unit ID valid? static bool valid(int unit); + //! Long name + static QString longName(int unit); //! Short name - static QString name(int unit); + static QString shortName(int unit); //! Longer description static QString description(int unit); //! Number of Satoshis (1e-8) per unit diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 8a83b513e1..dc729649b8 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -636,27 +636,24 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r // Create a new refund address, or re-use: QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant); std::string strAccount = account.toStdString(); - std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount); - if (!refundAddresses.empty()) { - CScript s = GetScriptForDestination(*refundAddresses.begin()); + CPubKey newKey; + if (wallet->GetKeyFromPool(newKey)) { + // BIP70 requests encode the scriptPubKey directly, so we are not restricted to address + // types supported by the receiver. As a result, we choose the address format we also + // use for change. Despite an actual payment and not change, this is a close match: + // it's the output type we use subject to privacy issues, but not restricted by what + // other software supports. + wallet->LearnRelatedScripts(newKey, g_change_type); + CTxDestination dest = GetDestinationForKey(newKey, g_change_type); + wallet->SetAddressBook(dest, strAccount, "refund"); + + CScript s = GetScriptForDestination(dest); payments::Output* refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); - } - else { - CPubKey newKey; - if (wallet->GetKeyFromPool(newKey)) { - CKeyID keyID = newKey.GetID(); - wallet->SetAddressBook(keyID, strAccount, "refund"); - - CScript s = GetScriptForDestination(keyID); - payments::Output* refund_to = payment.add_refund_to(); - refund_to->set_script(&s[0], s.size()); - } - else { - // This should never happen, because sending coins should have - // just unlocked the wallet and refilled the keypool. - qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set"; - } + } else { + // This should never happen, because sending coins should have + // just unlocked the wallet and refilled the keypool. + qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set"; } int length = payment.ByteSize(); diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index c8c2cb76a3..0dd7d46960 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -123,7 +123,7 @@ void RecentRequestsTableModel::updateAmountColumnTitle() /** Gets title for amount column including current display unit if optionsModel reference available. */ QString RecentRequestsTableModel::getAmountTitle() { - return (this->walletModel->getOptionsModel() != nullptr) ? tr("Requested") + " ("+BitcoinUnits::name(this->walletModel->getOptionsModel()->getDisplayUnit()) + ")" : ""; + return (this->walletModel->getOptionsModel() != nullptr) ? tr("Requested") + " ("+BitcoinUnits::shortName(this->walletModel->getOptionsModel()->getDisplayUnit()) + ")" : ""; } QModelIndex RecentRequestsTableModel::index(int row, int column, const QModelIndex &parent) const diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 56b62f97fb..9fd61db70e 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -336,7 +336,7 @@ void SendCoinsDialog::on_sendButton_clicked() } questionString.append(tr("Total Amount %1") .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))); - questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%2)</span>") + questionString.append(QString("<span style='font-size:10pt;font-weight:normal;'><br />(=%1)</span>") .arg(alternativeUnits.join(" " + tr("or") + "<br />"))); questionString.append("<hr /><span>"); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 4b7c3bd726..a270e5de59 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st // src/qt/test/test_bitcoin-qt -platform cocoa # macOS void TestGUI() { + g_address_type = OUTPUT_TYPE_P2SH_SEGWIT; + g_change_type = OUTPUT_TYPE_P2SH_SEGWIT; + // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { @@ -161,7 +164,7 @@ void TestGUI() wallet.LoadWallet(firstRun); { LOCK(wallet.cs_wallet); - wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive"); + wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive"); wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 868934288b..fa43ab750a 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -365,7 +365,7 @@ void TransactionView::exportClicked() // name, column, role writer.setModel(transactionProxyModel); writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole); - if (model && model->haveWatchOnly()) + if (model->haveWatchOnly()) writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly); writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole); writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index e7f9657603..1b2c71c4a4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1358,6 +1358,7 @@ UniValue mempoolInfoToJSON() size_t maxmempool = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; ret.push_back(Pair("maxmempool", (int64_t) maxmempool)); ret.push_back(Pair("mempoolminfee", ValueFromAmount(std::max(mempool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()))); + ret.push_back(Pair("minrelaytxfee", ValueFromAmount(::minRelayTxFee.GetFeePerK()))); return ret; } @@ -1374,7 +1375,8 @@ UniValue getmempoolinfo(const JSONRPCRequest& request) " \"bytes\": xxxxx, (numeric) Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted\n" " \"usage\": xxxxx, (numeric) Total memory usage for the mempool\n" " \"maxmempool\": xxxxx, (numeric) Maximum memory usage for the mempool\n" - " \"mempoolminfee\": xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted\n" + " \"mempoolminfee\": xxxxx (numeric) Minimum fee rate in " + CURRENCY_UNIT + "/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee\n" + " \"minrelaytxfee\": xxxxx (numeric) Current minimum relay fee for transactions\n" "}\n" "\nExamples:\n" + HelpExampleCli("getmempoolinfo", "") diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 1c8a24ed36..95a27d474b 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -40,6 +40,46 @@ public: explicit DescribeAddressVisitor(CWallet *_pwallet) : pwallet(_pwallet) {} + void ProcessSubScript(const CScript& subscript, UniValue& obj, bool include_addresses = false) const + { + // Always present: script type and redeemscript + txnouttype which_type; + std::vector<std::vector<unsigned char>> solutions_data; + Solver(subscript, which_type, solutions_data); + obj.pushKV("script", GetTxnOutputType(which_type)); + obj.pushKV("hex", HexStr(subscript.begin(), subscript.end())); + + CTxDestination embedded; + UniValue a(UniValue::VARR); + if (ExtractDestination(subscript, embedded)) { + // Only when the script corresponds to an address. + UniValue subobj = boost::apply_visitor(*this, embedded); + subobj.pushKV("address", EncodeDestination(embedded)); + subobj.pushKV("scriptPubKey", HexStr(subscript.begin(), subscript.end())); + // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works. + if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]); + obj.pushKV("embedded", std::move(subobj)); + if (include_addresses) a.push_back(EncodeDestination(embedded)); + } else if (which_type == TX_MULTISIG) { + // Also report some information on multisig scripts (which do not have a corresponding address). + // TODO: abstract out the common functionality between this logic and ExtractDestinations. + obj.pushKV("sigsrequired", solutions_data[0][0]); + UniValue pubkeys(UniValue::VARR); + for (size_t i = 1; i < solutions_data.size() - 1; ++i) { + CPubKey key(solutions_data[i].begin(), solutions_data[i].end()); + if (include_addresses) a.push_back(EncodeDestination(key.GetID())); + pubkeys.push_back(HexStr(key.begin(), key.end())); + } + obj.pushKV("pubkeys", std::move(pubkeys)); + } + + // The "addresses" field is confusing because it refers to public keys using their P2PKH address. + // For that reason, only add the 'addresses' field when needed for backward compatibility. New applications + // can use the 'embedded'->'address' field for P2SH or P2WSH wrapped addresses, and 'pubkeys' for + // inspecting multisig participants. + if (include_addresses) obj.pushKV("addresses", std::move(a)); + } + UniValue operator()(const CNoDestination &dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CKeyID &keyID) const { @@ -60,19 +100,7 @@ public: obj.push_back(Pair("isscript", true)); obj.push_back(Pair("iswitness", false)); if (pwallet && pwallet->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()))); - UniValue a(UniValue::VARR); - for (const CTxDestination& addr : addresses) { - a.push_back(EncodeDestination(addr)); - } - obj.push_back(Pair("addresses", a)); - if (whichType == TX_MULTISIG) - obj.push_back(Pair("sigsrequired", nRequired)); + ProcessSubScript(subscript, obj, true); } return obj; } @@ -103,7 +131,7 @@ public: uint160 hash; hasher.Write(id.begin(), 32).Finalize(hash.begin()); if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) { - obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end()))); + ProcessSubScript(subscript, obj); } return obj; } @@ -131,23 +159,32 @@ UniValue validateaddress(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n" - " \"address\" : \"address\", (string) The bitcoin address validated\n" + " \"address\" : \"address\", (string) The bitcoin address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" - " \"isscript\" : true|false, (boolean) If the key is a script\n" - " \"script\" : \"type\" (string, optional) The output script type. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash\n" - " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n" - " \"addresses\" (string, optional) Array of addresses associated with the known redeemscript\n" + " \"isscript\" : true|false, (boolean, optional) If the address is P2SH or P2WSH. Not included for unknown witness types.\n" + " \"iswitness\" : true|false, (boolean) If the address is P2WPKH, P2WSH, or an unknown witness version\n" + " \"witness_version\" : version (number, optional) For all witness output types, gives the version number.\n" + " \"witness_program\" : \"hex\" (string, optional) For all witness output types, gives the script or key hash present in the address.\n" + " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n" + " \"hex\" : \"hex\", (string, optional) The redeemscript for the P2SH or P2WSH address\n" + " \"addresses\" (string, optional) Array of addresses associated with the known redeemscript (only if \"iswitness\" is false). This field is superseded by the \"pubkeys\" field and the address inside \"embedded\".\n" " [\n" " \"address\"\n" " ,...\n" " ]\n" - " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output\n" - " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" + " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n" + " [\n" + " \"pubkey\"\n" + " ,...\n" + " ]\n" + " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" + " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" + " \"embedded\" : {...}, (object, optional) information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all validateaddress output fields for the embedded address, excluding \"isvalid\", metadata (\"timestamp\", \"hdkeypath\", \"hdmasterkeyid\") and relation to the wallet (\"ismine\", \"iswatchonly\", \"account\").\n" " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" - " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" + " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\n" "}\n" @@ -188,8 +225,9 @@ UniValue validateaddress(const JSONRPCRequest& request) } if (pwallet) { const CKeyMetadata* meta = nullptr; - if (const CKeyID* key_id = boost::get<CKeyID>(&dest)) { - auto it = pwallet->mapKeyMetadata.find(*key_id); + CKeyID key_id = GetKeyForDestination(*pwallet, dest); + if (!key_id.IsNull()) { + auto it = pwallet->mapKeyMetadata.find(key_id); if (it != pwallet->mapKeyMetadata.end()) { meta = &it->second; } @@ -242,12 +280,12 @@ CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& pa // Case 1: Bitcoin address and we have full public key: CTxDestination dest = DecodeDestination(ks); if (pwallet && IsValidDestination(dest)) { - const CKeyID *keyID = boost::get<CKeyID>(&dest); - if (!keyID) { + CKeyID key = GetKeyForDestination(*pwallet, dest); + if (key.IsNull()) { throw std::runtime_error(strprintf("%s does not refer to a key", ks)); } CPubKey vchPubKey; - if (!pwallet->GetPubKey(*keyID, vchPubKey)) { + if (!pwallet->GetPubKey(key, vchPubKey)) { throw std::runtime_error(strprintf("no full public key for address %s", ks)); } if (!vchPubKey.IsFullyValid()) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index f1ec5e2d13..838e502a0a 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -422,3 +422,22 @@ bool DummySignatureCreator::CreateSig(std::vector<unsigned char>& vchSig, const vchSig[6 + 33 + 32] = SIGHASH_ALL; return true; } + +bool IsSolvable(const CKeyStore& store, const CScript& script) +{ + // This check is to make sure that the script we created can actually be solved for and signed by us + // if we were to have the private keys. This is just to make sure that the script is valid and that, + // if found in a transaction, we would still accept and relay that transaction. In particular, + // it will reject witness outputs that require signing with an uncompressed public key. + DummySignatureCreator creator(&store); + SignatureData sigs; + // Make sure that STANDARD_SCRIPT_VERIFY_FLAGS includes SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, the most + // important property this function is designed to test for. + static_assert(STANDARD_SCRIPT_VERIFY_FLAGS & SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, "IsSolvable requires standard script flags to include WITNESS_PUBKEYTYPE"); + if (ProduceSignature(creator, script, sigs)) { + // VerifyScript check is just defensive, and should never fail. + assert(VerifyScript(sigs.scriptSig, script, &sigs.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker())); + return true; + } + return false; +} diff --git a/src/script/sign.h b/src/script/sign.h index 0fe97a80e8..97c0014cd0 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -81,4 +81,10 @@ SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignature SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn); void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data); +/* Check whether we know how to sign for an output like this, assuming we + * have all private keys. While this function does not need private keys, the passed + * keystore is used to look up public keys and redeemscripts by hash. + * Solvability is unrelated to whether we consider this output to be ours. */ +bool IsSolvable(const CKeyStore& store, const CScript& script); + #endif // BITCOIN_SCRIPT_SIGN_H diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 43961d0572..cfb3c58588 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -348,19 +348,14 @@ CScript GetScriptForWitness(const CScript& redeemscript) std::vector<std::vector<unsigned char> > vSolutions; if (Solver(redeemscript, typ, vSolutions)) { if (typ == TX_PUBKEY) { - unsigned char h160[20]; - CHash160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160); - ret << OP_0 << std::vector<unsigned char>(&h160[0], &h160[20]); - return ret; + return GetScriptForDestination(WitnessV0KeyHash(Hash160(vSolutions[0].begin(), vSolutions[0].end()))); } else if (typ == TX_PUBKEYHASH) { - ret << OP_0 << vSolutions[0]; - return ret; + return GetScriptForDestination(WitnessV0KeyHash(vSolutions[0])); } } uint256 hash; CSHA256().Write(&redeemscript[0], redeemscript.size()).Finalize(hash.begin()); - ret << OP_0 << ToByteVector(hash); - return ret; + return GetScriptForDestination(WitnessV0ScriptHash(hash)); } bool IsValidDestination(const CTxDestination& dest) { diff --git a/src/script/standard.h b/src/script/standard.h index 5925f2a512..3b2838a5bb 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -73,8 +73,19 @@ public: friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; } }; -struct WitnessV0ScriptHash : public uint256 {}; -struct WitnessV0KeyHash : public uint160 {}; +struct WitnessV0ScriptHash : public uint256 +{ + WitnessV0ScriptHash() : uint256() {} + explicit WitnessV0ScriptHash(const uint256& hash) : uint256(hash) {} + using uint256::uint256; +}; + +struct WitnessV0KeyHash : public uint160 +{ + WitnessV0KeyHash() : uint160() {} + explicit WitnessV0KeyHash(const uint160& hash) : uint160(hash) {} + using uint160::uint160; +}; //! CTxDestination subtype to encode any future Witness version struct WitnessUnknown @@ -144,6 +155,10 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) * addressRet is populated with a single value and nRequiredRet is set to 1. * Returns true if successful. Currently does not extract address from * pay-to-witness scripts. + * + * Note: this function confuses destinations (a subset of CScripts that are + * encodable as an address) with key identifiers (of keys involved in a + * CScript), and its use should be phased out. */ bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet); @@ -164,6 +179,9 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); * Generate a pay-to-witness script for the given redeem script. If the redeem * script is P2PK or P2PKH, this returns a P2WPKH script, otherwise it returns a * P2WSH script. + * + * TODO: replace calls to GetScriptForWitness with GetScriptForDestination using + * the various witness-specific CTxDestination subtypes. */ CScript GetScriptForWitness(const CScript& redeemscript); diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 19060eccc9..cd30fbeda7 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -508,12 +508,7 @@ BOOST_AUTO_TEST_CASE(script_standard_IsMine) scriptPubKey.clear(); scriptPubKey << OP_0 << ToByteVector(pubkeys[0].GetID()); - // Keystore has key, but no P2SH redeemScript - result = IsMine(keystore, scriptPubKey, isInvalid); - BOOST_CHECK_EQUAL(result, ISMINE_NO); - BOOST_CHECK(!isInvalid); - - // Keystore has key and P2SH redeemScript + // Keystore implicitly has key and P2SH redeemScript keystore.AddCScript(scriptPubKey); result = IsMine(keystore, scriptPubKey, isInvalid); BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 6a02ccb5c1..55d60d95e9 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -121,7 +121,7 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) testArgs.ParseParameters(1, (char**)argv_test); BOOST_CHECK(testArgs.GetMapArgs().empty() && testArgs.GetMapMultiArgs().empty()); - testArgs.ParseParameters(5, (char**)argv_test); + testArgs.ParseParameters(7, (char**)argv_test); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index fd7e1bb56c..6ad18721fd 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -245,6 +245,7 @@ bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector< } mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); return true; } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 71e7111b1a..0f453f154f 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -16,6 +16,8 @@ std::string GetWalletHelpString(bool showDebug) { std::string strUsage = HelpMessageGroup(_("Wallet options:")); + strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT))); + strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)")); strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE)); strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), @@ -175,6 +177,16 @@ bool WalletParameterInteraction() bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); + g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", "")); + if (g_address_type == OUTPUT_TYPE_NONE) { + return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""))); + } + + g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type); + if (g_change_type == OUTPUT_TYPE_NONE) { + return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""))); + } + return true; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index fc84fe6468..645e776b68 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request) CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); - pwallet->SetAddressBook(vchAddress, strLabel, "receive"); + + // We don't know which corresponding address will be used; label them all + for (const auto& dest : GetAllDestinationsForKey(pubkey)) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } // Don't throw error in case a key is already there if (pwallet->HaveKey(vchAddress)) { @@ -143,6 +147,7 @@ UniValue importprivkey(const JSONRPCRequest& request) if (!pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } + pwallet->LearnAllRelatedScripts(pubkey); // whenever a key is imported, we need to scan the whole chain pwallet->UpdateTimeFirstKey(1); @@ -433,8 +438,11 @@ UniValue importpubkey(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - ImportAddress(pwallet, pubKey.GetID(), strLabel); + for (const auto& dest : GetAllDestinationsForKey(pubKey)) { + ImportAddress(pwallet, dest, strLabel); + } ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + pwallet->LearnAllRelatedScripts(pubKey); if (fRescan) { @@ -595,12 +603,12 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - const CKeyID *keyID = boost::get<CKeyID>(&dest); - if (!keyID) { + auto keyid = GetKeyForDestination(*pwallet, dest); + if (keyid.IsNull()) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; - if (!pwallet->GetKey(*keyID, vchSecret)) { + if (!pwallet->GetKey(keyid, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return CBitcoinSecret(vchSecret).ToString(); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 873fbd1bab..e307623fd5 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) + if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getnewaddress ( \"account\" )\n" + "getnewaddress ( \"account\" \"address_type\" )\n" "\nReturns a new Bitcoin address for receiving payments.\n" "If 'account' is specified (DEPRECATED), it is added to the address book \n" "so payments received with the address will be credited to 'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" + "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n" "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" @@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!request.params[0].isNull()) strAccount = AccountFromValue(request.params[0]); + OutputType output_type = g_address_type; + if (!request.params[1].isNull()) { + output_type = ParseOutputType(request.params[1].get_str(), g_address_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); + } + } + if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } @@ -167,22 +176,23 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - CKeyID keyID = newKey.GetID(); + pwallet->LearnRelatedScripts(newKey, output_type); + CTxDestination dest = GetDestinationForKey(newKey, output_type); - pwallet->SetAddressBook(keyID, strAccount, "receive"); + pwallet->SetAddressBook(dest, strAccount, "receive"); - return EncodeDestination(keyID); + return EncodeDestination(dest); } -CTxDestination GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) +CTxDestination GetAccountDestination(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) { - CPubKey pubKey; - if (!pwallet->GetAccountPubkey(pubKey, strAccount, bForceNew)) { + CTxDestination dest; + if (!pwallet->GetAccountDestination(dest, strAccount, bForceNew)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - return pubKey.GetID(); + return dest; } UniValue getaccountaddress(const JSONRPCRequest& request) @@ -214,7 +224,7 @@ UniValue getaccountaddress(const JSONRPCRequest& request) UniValue ret(UniValue::VSTR); - ret = EncodeDestination(GetAccountAddress(pwallet, strAccount)); + ret = EncodeDestination(GetAccountDestination(pwallet, strAccount)); return ret; } @@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) + if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getrawchangeaddress\n" + "getrawchangeaddress ( \"address_type\" )\n" "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n" + "\nArguments:\n" + "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n" "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" @@ -244,6 +256,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); } + OutputType output_type = g_change_type; + if (!request.params[0].isNull()) { + output_type = ParseOutputType(request.params[0].get_str(), g_change_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); + } + } + CReserveKey reservekey(pwallet); CPubKey vchPubKey; if (!reservekey.GetReservedKey(vchPubKey, true)) @@ -251,9 +271,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) reservekey.KeepKey(); - CKeyID keyID = vchPubKey.GetID(); + pwallet->LearnRelatedScripts(vchPubKey, output_type); + CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); - return EncodeDestination(keyID); + return EncodeDestination(dest); } @@ -292,8 +313,8 @@ UniValue setaccount(const JSONRPCRequest& request) // Detect when changing the account of an address that is the 'unused current key' of another account: if (pwallet->mapAddressBook.count(dest)) { std::string strOldAccount = pwallet->mapAddressBook[dest].name; - if (dest == GetAccountAddress(pwallet, strOldAccount)) { - GetAccountAddress(pwallet, strOldAccount, true); + if (dest == GetAccountDestination(pwallet, strOldAccount)) { + GetAccountDestination(pwallet, strOldAccount, true); } } pwallet->SetAddressBook(dest, strAccount, "receive"); @@ -1155,6 +1176,8 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n" "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" + "This functionality is only intended for use with non-watchonly addresses.\n" + "See `importaddress` for watchonly p2sh address support.\n" "If 'account' is specified (DEPRECATED), assign address to that account.\n" "\nArguments:\n" @@ -1186,11 +1209,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) // Construct using pay-to-script-hash: CScript inner = _createmultisig_redeemScript(pwallet, request.params); - CScriptID innerID(inner); pwallet->AddCScript(inner); - pwallet->SetAddressBook(innerID, strAccount, "send"); - return EncodeDestination(innerID); + CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type); + + pwallet->SetAddressBook(dest, strAccount, "send"); + return EncodeDestination(dest); } class Witnessifier : public boost::static_visitor<bool> @@ -1206,12 +1230,7 @@ public: if (pwallet) { CScript basescript = GetScriptForDestination(keyID); CScript witscript = GetScriptForWitness(basescript); - SignatureData sigs; - // This check is to make sure that the script we created can actually be solved for and signed by us - // if we were to have the private keys. This is just to make sure that the script is valid and that, - // if found in a transaction, we would still accept and relay that transaction. - if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || - !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + if (!IsSolvable(*pwallet, witscript)) { return false; } return ExtractDestination(witscript, result); @@ -1230,12 +1249,7 @@ public: return true; } CScript witscript = GetScriptForWitness(subscript); - SignatureData sigs; - // This check is to make sure that the script we created can actually be solved for and signed by us - // if we were to have the private keys. This is just to make sure that the script is valid and that, - // if found in a transaction, we would still accept and relay that transaction. - if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || - !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + if (!IsSolvable(*pwallet, witscript)) { return false; } return ExtractDestination(witscript, result); @@ -1319,7 +1333,7 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types"); } } 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"); } @@ -3464,8 +3478,8 @@ static const CRPCCommand commands[] = { "wallet", "getaccount", &getaccount, {"address"} }, { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, - { "wallet", "getnewaddress", &getnewaddress, {"account"} }, - { "wallet", "getrawchangeaddress", &getrawchangeaddress, {} }, + { "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} }, + { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index b2e297755e..7797f85f07 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName): bitdb.MakeMock(); bool fFirstRun; + g_address_type = OUTPUT_TYPE_DEFAULT; + g_change_type = OUTPUT_TYPE_DEFAULT; std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat")); pwalletMain = MakeUnique<CWallet>(std::move(dbw)); pwalletMain->LoadWallet(fFirstRun); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index e18ae4b3b7..7b3c283f37 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -602,6 +602,8 @@ public: { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); ::bitdb.MakeMock(); + g_address_type = OUTPUT_TYPE_DEFAULT; + g_change_type = OUTPUT_TYPE_DEFAULT; wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat")))); bool firstRun; wallet->LoadWallet(firstRun); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e8b21b3d6f..da721ea008 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -42,6 +42,8 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; bool fWalletRbf = DEFAULT_WALLET_RBF; +OutputType g_address_type = OUTPUT_TYPE_NONE; +OutputType g_change_type = OUTPUT_TYPE_NONE; const char * DEFAULT_WALLET_DAT = "wallet.dat"; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; @@ -821,7 +823,7 @@ bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmoun return true; } -bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew) +bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew) { CWalletDB walletdb(*dbw); @@ -832,8 +834,8 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo if (!account.vchPubKey.IsValid()) bForceNew = true; else { - // Check if the current key has been used - CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); + // Check if the current key has been used (TODO: check other addresses with the same key) + CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, g_address_type)); for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end() && account.vchPubKey.IsValid(); ++it) @@ -850,12 +852,14 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo if (!GetKeyFromPool(account.vchPubKey, false)) return false; - SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); + LearnRelatedScripts(account.vchPubKey, g_address_type); + dest = GetDestinationForKey(account.vchPubKey, g_address_type); + SetAddressBook(dest, strAccount, "receive"); walletdb.WriteAccount(strAccount, account); + } else { + dest = GetDestinationForKey(account.vchPubKey, g_address_type); } - pubKey = account.vchPubKey; - return true; } @@ -2735,7 +2739,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT return false; } - scriptChange = GetScriptForDestination(vchPubKey.GetID()); + LearnRelatedScripts(vchPubKey, g_change_type); + scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, g_change_type)); } CTxOut change_prototype_txout(0, scriptChange); size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); @@ -3624,6 +3629,7 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); } + LearnAllRelatedScripts(keypool.vchPubKey); walletdb.ErasePool(index); LogPrintf("keypool index %d removed\n", index); it = setKeyPool->erase(it); @@ -4128,3 +4134,106 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& fInMempool = ret; return ret; } + +static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; +static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; +static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; + +OutputType ParseOutputType(const std::string& type, OutputType default_type) +{ + if (type.empty()) { + return default_type; + } else if (type == OUTPUT_TYPE_STRING_LEGACY) { + return OUTPUT_TYPE_LEGACY; + } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { + return OUTPUT_TYPE_P2SH_SEGWIT; + } else if (type == OUTPUT_TYPE_STRING_BECH32) { + return OUTPUT_TYPE_BECH32; + } else { + return OUTPUT_TYPE_NONE; + } +} + +const std::string& FormatOutputType(OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return OUTPUT_TYPE_STRING_LEGACY; + case OUTPUT_TYPE_P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; + case OUTPUT_TYPE_BECH32: return OUTPUT_TYPE_STRING_BECH32; + default: assert(false); + } +} + +void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) +{ + if (key.IsCompressed() && (type == OUTPUT_TYPE_P2SH_SEGWIT || type == OUTPUT_TYPE_BECH32)) { + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + // Make sure the resulting program is solvable. + assert(IsSolvable(*this, witprog)); + AddCScript(witprog); + } +} + +void CWallet::LearnAllRelatedScripts(const CPubKey& key) +{ + // OUTPUT_TYPE_P2SH_SEGWIT always adds all necessary scripts for all types. + LearnRelatedScripts(key, OUTPUT_TYPE_P2SH_SEGWIT); +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return key.GetID(); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + if (type == OUTPUT_TYPE_P2SH_SEGWIT) { + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key) +{ + CKeyID keyid = key.GetID(); + if (key.IsCompressed()) { + CTxDestination segwit = WitnessV0KeyHash(keyid); + CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); + return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)}; + } else { + return std::vector<CTxDestination>{std::move(keyid)}; + } +} + +CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type) +{ + // Note that scripts over 520 bytes are not yet supported. + switch (type) { + case OUTPUT_TYPE_LEGACY: + return CScriptID(script); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + WitnessV0ScriptHash hash; + CSHA256().Write(script.data(), script.size()).Finalize(hash.begin()); + CTxDestination witdest = hash; + CScript witprog = GetScriptForDestination(witdest); + // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) + if (!IsSolvable(*this, witprog)) return CScriptID(script); + // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. + AddCScript(witprog); + if (type == OUTPUT_TYPE_BECH32) { + return witdest; + } else { + return CScriptID(witprog); + } + } + default: assert(false); + } +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 08950869b7..53a2c6b9d5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -99,6 +99,19 @@ enum WalletFeature FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version }; +enum OutputType +{ + OUTPUT_TYPE_NONE, + OUTPUT_TYPE_LEGACY, + OUTPUT_TYPE_P2SH_SEGWIT, + OUTPUT_TYPE_BECH32, + + OUTPUT_TYPE_DEFAULT = OUTPUT_TYPE_P2SH_SEGWIT +}; + +extern OutputType g_address_type; +extern OutputType g_change_type; + /** A key pool entry */ class CKeyPool @@ -923,7 +936,7 @@ public: int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr); DBErrors ReorderTransactions(); bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = ""); - bool GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew = false); + bool GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew = false); void MarkDirty(); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); @@ -1129,6 +1142,26 @@ public: * deadlock */ void BlockUntilSyncedToCurrentChain(); + + /** + * Explicitly make the wallet learn the related scripts for outputs to the + * given key. This is purely to make the wallet file compatible with older + * software, as CBasicKeyStore automatically does this implicitly for all + * keys now. + */ + void LearnRelatedScripts(const CPubKey& key, OutputType); + + /** + * Same as LearnRelatedScripts, but when the OutputType is not known (and could + * be anything). + */ + void LearnAllRelatedScripts(const CPubKey& key); + + /** + * Get a destination of the requested type (if possible) to the specified script. + * This function will automatically add the necessary scripts to the wallet. + */ + CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); }; /** A key allocated from the key pool. */ @@ -1218,4 +1251,16 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins return true; } +OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT); +const std::string& FormatOutputType(OutputType type); + +/** + * Get a destination of the requested type (if possible) to the specified key. + * The caller must make sure LearnRelatedScripts has been called beforehand. + */ +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); + +/** Get all destinations (potentially) supported by the wallet for the given key. */ +std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); + #endif // BITCOIN_WALLET_WALLET_H |