diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -235,10 +235,25 @@ virtual int64_t GetTimeFirstKey() const { return 0; } - //! Return address metadata virtual const CKeyMetadata *GetMetadata(const CTxDestination &dest) const { return nullptr; } + + virtual const SigningProvider * + GetSigningProvider(const CScript &script) const { + return nullptr; + } + + /** + * Whether this ScriptPubKeyMan can provide a SigningProvider (via + * GetSigningProvider) that, combined with sigdata, can produce a valid + * signature. + */ + virtual bool CanProvide(const CScript &script, SignatureData &sigdata) { + return false; + } + + virtual uint256 GetID() const { return uint256(); } }; class LegacyScriptPubKeyMan : public ScriptPubKeyMan, @@ -382,6 +397,13 @@ bool CanGetAddresses(bool internal = false) override; + const SigningProvider * + GetSigningProvider(const CScript &script) const override; + + bool CanProvide(const CScript &script, SignatureData &sigdata) override; + + uint256 GetID() const override; + // Map from Key ID to key metadata. std::map mapKeyMetadata GUARDED_BY(cs_KeyStore); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -428,6 +428,37 @@ return nTimeFirstKey; } +const SigningProvider * +LegacyScriptPubKeyMan::GetSigningProvider(const CScript &script) const { + return this; +} + +bool LegacyScriptPubKeyMan::CanProvide(const CScript &script, + SignatureData &sigdata) { + if (IsMine(script) != ISMINE_NO) { + // If it IsMine, we can always provide in some way + return true; + } + if (HaveCScript(CScriptID(script))) { + // We can still provide some stuff if we have the script, but IsMine + // failed because we don't have keys + return true; + } + // If, given the stuff in sigdata, we could make a valid sigature, then + // we can provide for this script + ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata); + if (!sigdata.signatures.empty()) { + // If we could make signatures, make sure we have a private key to + // actually make a signature + bool has_privkeys = false; + for (const auto &key_sig_pair : sigdata.signatures) { + has_privkeys |= HaveKey(key_sig_pair.first); + } + return has_privkeys; + } + return false; +} + const CKeyMetadata * LegacyScriptPubKeyMan::GetMetadata(const CTxDestination &dest) const { LOCK(cs_KeyStore); @@ -449,6 +480,10 @@ return nullptr; } +uint256 LegacyScriptPubKeyMan::GetID() const { + return UINT256_ONE(); +} + /** * Update wallet first key creation time. This should be called whenever keys * are added to the wallet, with the oldest key creation time. diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -800,6 +800,14 @@ int &nChangePosInOut, bilingual_str &error, const CCoinControl &coin_control, bool sign); + std::map m_external_spk_managers; + std::map m_internal_spk_managers; + + // Indexed by a unique identifier produced by each ScriptPubKeyMan using + // ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal + // structure + std::map> m_spk_managers; + public: const CChainParams &chainParams; /* @@ -1403,14 +1411,30 @@ parameters...); }; + //! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and + //! m_external_spk_managers + std::set GetActiveScriptPubKeyMans() const; + + //! Returns all unique ScriptPubKeyMans + std::set GetAllScriptPubKeyMans() const; + + //! Get the ScriptPubKeyMan for the given OutputType and internal/external + //! chain. + ScriptPubKeyMan *GetScriptPubKeyMan(const OutputType &type, + bool internal) const; + //! Get the ScriptPubKeyMan for a script ScriptPubKeyMan *GetScriptPubKeyMan(const CScript &script) const; + //! Get the ScriptPubKeyMan by id + ScriptPubKeyMan *GetScriptPubKeyMan(const uint256 &id) const; //! Get the SigningProvider for a script const SigningProvider *GetSigningProvider(const CScript &script) const; const SigningProvider *GetSigningProvider(const CScript &script, SignatureData &sigdata) const; + //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and + //! external. LegacyScriptPubKeyMan *GetLegacyScriptPubKeyMan() const; LegacyScriptPubKeyMan *GetOrCreateLegacyScriptPubKeyMan(); @@ -1421,10 +1445,6 @@ const CKeyingMaterial &GetEncryptionKey() const override; bool HasEncryptionKeys() const override; - // Temporary LegacyScriptPubKeyMan accessors and aliases. - friend class LegacyScriptPubKeyMan; - std::unique_ptr m_spk_man; - /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -253,7 +254,7 @@ // Set a seed for the wallet { LOCK(wallet->cs_wallet); - if (auto spk_man = wallet->m_spk_man.get()) { + for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { error = Untranslated("Unable to generate initial keys"); return WalletCreationStatus::CREATION_FAILED; @@ -651,7 +652,8 @@ } encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); - if (auto spk_man = m_spk_man.get()) { + for (const auto &spk_man_pair : m_spk_managers) { + auto spk_man = spk_man_pair.second.get(); if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) { encrypted_batch->TxnAbort(); delete encrypted_batch; @@ -682,7 +684,7 @@ Unlock(strWalletPassphrase); // if we are using HD, replace the HD seed with a new one - if (auto spk_man = m_spk_man.get()) { + if (auto spk_man = GetLegacyScriptPubKeyMan()) { if (spk_man->IsHDEnabled()) { if (!spk_man->SetupGeneration(true)) { return false; @@ -998,8 +1000,8 @@ // loop though all outputs for (const CTxOut &txout : tx.vout) { - if (auto spk_man = m_spk_man.get()) { - spk_man->MarkUnusedAddresses(txout.scriptPubKey); + for (const auto &spk_man_pair : m_spk_managers) { + spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey); } } @@ -1282,8 +1284,8 @@ isminetype CWallet::IsMine(const CScript &script) const { isminetype result = ISMINE_NO; - if (auto spk_man = m_spk_man.get()) { - result = spk_man->IsMine(script); + for (const auto &spk_man_pair : m_spk_managers) { + result = std::max(result, spk_man_pair.second->IsMine(script)); } return result; } @@ -1418,16 +1420,19 @@ bool CWallet::IsHDEnabled() const { bool result = true; - if (auto spk_man = m_spk_man.get()) { - result &= spk_man->IsHDEnabled(); + for (const auto &spk_man_pair : m_spk_managers) { + result &= spk_man_pair.second->IsHDEnabled(); } return result; } bool CWallet::CanGetAddresses(bool internal) { LOCK(cs_wallet); - { - auto spk_man = m_spk_man.get(); + if (m_spk_managers.empty()) { + return false; + } + for (OutputType t : OUTPUT_TYPES) { + auto spk_man = GetScriptPubKeyMan(t, internal); if (spk_man && spk_man->CanGetAddresses(internal)) { return true; } @@ -3356,18 +3361,20 @@ DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this); if (nLoadWalletRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { - if (auto spk_man = m_spk_man.get()) { - spk_man->RewriteDB(); + for (const auto &spk_man_pair : m_spk_managers) { + spk_man_pair.second->RewriteDB(); } } } // This wallet is in its first run if there are no ScriptPubKeyMans and it // isn't blank or no privkeys - { - fFirstRunRet = !m_spk_man && - !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && - !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); + fFirstRunRet = m_spk_managers.empty() && + !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && + !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); + if (fFirstRunRet) { + assert(m_external_spk_managers.empty()); + assert(m_internal_spk_managers.empty()); } if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -3391,8 +3398,8 @@ if (nZapSelectTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { - if (auto spk_man = m_spk_man.get()) { - spk_man->RewriteDB(); + for (const auto &spk_man_pair : m_spk_managers) { + spk_man_pair.second->RewriteDB(); } } } @@ -3410,8 +3417,8 @@ DBErrors nZapWalletTxRet = WalletBatch(*database, "cr+").ZapWalletTx(vWtx); if (nZapWalletTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { - if (auto spk_man = m_spk_man.get()) { - spk_man->RewriteDB(); + for (const auto &spk_man_pair : m_spk_managers) { + spk_man_pair.second->RewriteDB(); } } } @@ -3493,7 +3500,7 @@ AssertLockHeld(cs_wallet); unsigned int count = 0; - if (auto spk_man = m_spk_man.get()) { + for (auto spk_man : GetActiveScriptPubKeyMans()) { count += spk_man->KeypoolCountExternalKeys(); } @@ -3504,7 +3511,7 @@ AssertLockHeld(cs_wallet); unsigned int count = 0; - if (auto spk_man = m_spk_man.get()) { + for (auto spk_man : GetActiveScriptPubKeyMans()) { count += spk_man->GetKeyPoolSize(); } return count; @@ -3513,7 +3520,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) { LOCK(cs_wallet); bool res = true; - if (auto spk_man = m_spk_man.get()) { + for (auto spk_man : GetActiveScriptPubKeyMans()) { res &= spk_man->TopUp(kpSize); } return res; @@ -3524,7 +3531,7 @@ LOCK(cs_wallet); error.clear(); bool result = false; - auto spk_man = m_spk_man.get(); + auto spk_man = GetScriptPubKeyMan(type, false /* internal */); if (spk_man) { spk_man->TopUp(); result = spk_man->GetNewDestination(type, dest, error); @@ -3555,8 +3562,9 @@ int64_t CWallet::GetOldestKeyPoolTime() { LOCK(cs_wallet); int64_t oldestKey = std::numeric_limits::max(); - if (auto spk_man = m_spk_man.get()) { - oldestKey = spk_man->GetOldestKeyPoolTime(); + for (const auto &spk_man_pair : m_spk_managers) { + oldestKey = + std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime()); } return oldestKey; } @@ -3752,7 +3760,7 @@ bool ReserveDestination::GetReservedDestination(CTxDestination &dest, bool internal) { - m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); + m_spk_man = pwallet->GetScriptPubKeyMan(type, internal); if (!m_spk_man) { return false; } @@ -4177,7 +4185,7 @@ return nullptr; } - if (auto spk_man = walletInstance->m_spk_man.get()) { + for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (!spk_man->Upgrade(prev_version, error)) { return nullptr; } @@ -4197,7 +4205,7 @@ if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { LOCK(walletInstance->cs_wallet); - if (auto spk_man = walletInstance->m_spk_man.get()) { + for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { error = _("Unable to generate initial keys"); return nullptr; @@ -4215,8 +4223,8 @@ return nullptr; } else if (walletInstance->IsWalletFlagSet( WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (walletInstance->m_spk_man) { - if (walletInstance->m_spk_man->HavePrivateKeys()) { + for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { + if (spk_man->HavePrivateKeys()) { warnings.push_back( strprintf(_("Warning: Private keys detected in wallet {%s} " "with disabled private keys"), @@ -4393,8 +4401,10 @@ // No need to read and scan block if block was created before our wallet // birthday (as adjusted for block time variability) - Optional time_first_key; - if (auto spk_man = walletInstance->m_spk_man.get()) { + // The way the 'time_first_key' is initialized is just a workaround for + // the gcc bug #47679 since version 4.6.0. + Optional time_first_key = MakeOptional(false, int64_t()); + for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) { int64_t time = spk_man->GetTimeFirstKey(); if (!time_first_key || time < *time_first_key) { time_first_key = time; @@ -4633,8 +4643,9 @@ bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) { { LOCK(cs_wallet); - if (m_spk_man) { - if (!m_spk_man->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) { + for (const auto &spk_man_pair : m_spk_managers) { + if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn, + accept_no_keys)) { return false; } } @@ -4644,23 +4655,85 @@ return true; } +std::set CWallet::GetActiveScriptPubKeyMans() const { + std::set spk_mans; + for (bool internal : {false, true}) { + for (OutputType t : OUTPUT_TYPES) { + auto spk_man = GetScriptPubKeyMan(t, internal); + if (spk_man) { + spk_mans.insert(spk_man); + } + } + } + return spk_mans; +} + +std::set CWallet::GetAllScriptPubKeyMans() const { + std::set spk_mans; + for (const auto &spk_man_pair : m_spk_managers) { + spk_mans.insert(spk_man_pair.second.get()); + } + return spk_mans; +} + +ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const OutputType &type, + bool internal) const { + const std::map &spk_managers = + internal ? m_internal_spk_managers : m_external_spk_managers; + std::map::const_iterator it = + spk_managers.find(type); + if (it == spk_managers.end()) { + WalletLogPrintf( + "%s scriptPubKey Manager for output type %d does not exist\n", + internal ? "Internal" : "External", static_cast(type)); + return nullptr; + } + return it->second; +} + ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const CScript &script) const { - return m_spk_man.get(); + SignatureData sigdata; + for (const auto &spk_man_pair : m_spk_managers) { + if (spk_man_pair.second->CanProvide(script, sigdata)) { + return spk_man_pair.second.get(); + } + } + return nullptr; +} + +ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const uint256 &id) const { + if (m_spk_managers.count(id) > 0) { + return m_spk_managers.at(id).get(); + } + return nullptr; } const SigningProvider * CWallet::GetSigningProvider(const CScript &script) const { - return m_spk_man.get(); + SignatureData sigdata; + return GetSigningProvider(script, sigdata); } const SigningProvider * CWallet::GetSigningProvider(const CScript &script, SignatureData &sigdata) const { - return m_spk_man.get(); + for (const auto &spk_man_pair : m_spk_managers) { + if (spk_man_pair.second->CanProvide(script, sigdata)) { + return spk_man_pair.second->GetSigningProvider(script); + } + } + return nullptr; } LegacyScriptPubKeyMan *CWallet::GetLegacyScriptPubKeyMan() const { - return m_spk_man.get(); + // Legacy wallets only have one ScriptPubKeyMan which is a + // LegacyScriptPubKeyMan. Everything in m_internal_spk_managers and + // m_external_spk_managers point to the same legacyScriptPubKeyMan. + auto it = m_internal_spk_managers.find(OutputType::LEGACY); + if (it == m_internal_spk_managers.end()) { + return nullptr; + } + return dynamic_cast(it->second); } LegacyScriptPubKeyMan *CWallet::GetOrCreateLegacyScriptPubKeyMan() { @@ -4669,9 +4742,18 @@ } void CWallet::SetupLegacyScriptPubKeyMan() { - if (!m_spk_man) { - m_spk_man = std::make_unique(*this); + if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || + !m_spk_managers.empty()) { + return; + } + + auto spk_manager = + std::unique_ptr(new LegacyScriptPubKeyMan(*this)); + for (const auto &type : OUTPUT_TYPES) { + m_internal_spk_managers[type] = spk_manager.get(); + m_external_spk_managers[type] = spk_manager.get(); } + m_spk_managers[spk_manager->GetID()] = std::move(spk_manager); } const CKeyingMaterial &CWallet::GetEncryptionKey() const {