diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -120,6 +120,7 @@
     {"importpubkey", 2, "rescan"},
     {"importmulti", 0, "requests"},
     {"importmulti", 1, "options"},
+    {"importdescriptors", 0, "requests"},
     {"verifychain", 0, "checklevel"},
     {"verifychain", 1, "nblocks"},
     {"getblockstats", 0, "hash_or_height"},
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1828,6 +1828,420 @@
     return response;
 }
 
+static UniValue ProcessDescriptorImport(CWallet *const pwallet,
+                                        const UniValue &data,
+                                        const int64_t timestamp)
+    EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
+    UniValue warnings(UniValue::VARR);
+    UniValue result(UniValue::VOBJ);
+
+    try {
+        if (!data.exists("desc")) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
+        }
+
+        const std::string &descriptor = data["desc"].get_str();
+        const bool active =
+            data.exists("active") ? data["active"].get_bool() : false;
+        const bool internal =
+            data.exists("internal") ? data["internal"].get_bool() : false;
+        const std::string &label =
+            data.exists("label") ? data["label"].get_str() : "";
+
+        // Parse descriptor string
+        FlatSigningProvider keys;
+        std::string error;
+        auto parsed_desc =
+            Parse(descriptor, keys, error, /* require_checksum = */ true);
+        if (!parsed_desc) {
+            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
+        }
+
+        // Range check
+        int64_t range_start = 0, range_end = 1, next_index = 0;
+        if (!parsed_desc->IsRange() && data.exists("range")) {
+            throw JSONRPCError(
+                RPC_INVALID_PARAMETER,
+                "Range should not be specified for an un-ranged descriptor");
+        } else if (parsed_desc->IsRange()) {
+            if (data.exists("range")) {
+                auto range = ParseDescriptorRange(data["range"]);
+                range_start = range.first;
+                // Specified range end is inclusive, but we need range end as
+                // exclusive
+                range_end = range.second + 1;
+            } else {
+                warnings.push_back(
+                    "Range not given, using default keypool range");
+                range_start = 0;
+                range_end = gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE);
+            }
+            next_index = range_start;
+
+            if (data.exists("next_index")) {
+                next_index = data["next_index"].get_int64();
+                // bound checks
+                if (next_index < range_start || next_index >= range_end) {
+                    throw JSONRPCError(RPC_INVALID_PARAMETER,
+                                       "next_index is out of range");
+                }
+            }
+        }
+
+        // Active descriptors must be ranged
+        if (active && !parsed_desc->IsRange()) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER,
+                               "Active descriptors must be ranged");
+        }
+
+        // Ranged descriptors should not have a label
+        if (data.exists("range") && data.exists("label")) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER,
+                               "Ranged descriptors should not have a label");
+        }
+
+        // Internal addresses should not have a label either
+        if (internal && data.exists("label")) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER,
+                               "Internal addresses should not have a label");
+        }
+
+        // Combo descriptor check
+        if (active && !parsed_desc->IsSingleType()) {
+            throw JSONRPCError(RPC_WALLET_ERROR,
+                               "Combo descriptors cannot be set to active");
+        }
+
+        // If the wallet disabled private keys, abort if private keys exist
+        if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
+            !keys.keys.empty()) {
+            throw JSONRPCError(RPC_WALLET_ERROR,
+                               "Cannot import private keys to a wallet with "
+                               "private keys disabled");
+        }
+
+        // Need to ExpandPrivate to check if private keys are available for all
+        // pubkeys
+        FlatSigningProvider expand_keys;
+        std::vector<CScript> scripts;
+        parsed_desc->Expand(0, keys, scripts, expand_keys);
+        parsed_desc->ExpandPrivate(0, keys, expand_keys);
+
+        // Check if all private keys are provided
+        bool have_all_privkeys = !expand_keys.keys.empty();
+        for (const auto &entry : expand_keys.origins) {
+            const CKeyID &key_id = entry.first;
+            CKey key;
+            if (!expand_keys.GetKey(key_id, key)) {
+                have_all_privkeys = false;
+                break;
+            }
+        }
+
+        // If private keys are enabled, check some things.
+        if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+            if (keys.keys.empty()) {
+                throw JSONRPCError(
+                    RPC_WALLET_ERROR,
+                    "Cannot import descriptor without private keys to a wallet "
+                    "with private keys enabled");
+            }
+            if (!have_all_privkeys) {
+                warnings.push_back(
+                    "Not all private keys provided. Some wallet functionality "
+                    "may return unexpected errors");
+            }
+        }
+
+        WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start,
+                                range_end, next_index);
+
+        // Check if the wallet already contains the descriptor
+        auto existing_spk_manager =
+            pwallet->GetDescriptorScriptPubKeyMan(w_desc);
+        if (existing_spk_manager) {
+            LOCK(existing_spk_manager->cs_desc_man);
+            if (range_start >
+                existing_spk_manager->GetWalletDescriptor().range_start) {
+                throw JSONRPCError(
+                    RPC_INVALID_PARAMS,
+                    strprintf(
+                        "range_start can only decrease; current range = "
+                        "[%d,%d]",
+                        existing_spk_manager->GetWalletDescriptor().range_start,
+                        existing_spk_manager->GetWalletDescriptor().range_end));
+            }
+        }
+
+        // Add descriptor to the wallet
+        auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label);
+        if (spk_manager == nullptr) {
+            throw JSONRPCError(
+                RPC_WALLET_ERROR,
+                strprintf("Could not add descriptor '%s'", descriptor));
+        }
+
+        // Set descriptor as active if necessary
+        if (active) {
+            if (!w_desc.descriptor->GetOutputType()) {
+                warnings.push_back(
+                    "Unknown output type, cannot set descriptor to active.");
+            } else {
+                pwallet->SetActiveScriptPubKeyMan(
+                    spk_manager->GetID(), *w_desc.descriptor->GetOutputType(),
+                    internal);
+            }
+        }
+
+        result.pushKV("success", UniValue(true));
+    } catch (const UniValue &e) {
+        result.pushKV("success", UniValue(false));
+        result.pushKV("error", e);
+    } catch (...) {
+        result.pushKV("success", UniValue(false));
+
+        result.pushKV("error",
+                      JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
+    }
+    if (warnings.size()) {
+        result.pushKV("warnings", warnings);
+    }
+    return result;
+}
+
+UniValue importdescriptors(const Config &config,
+                           const JSONRPCRequest &main_request) {
+    // Acquire the wallet
+    std::shared_ptr<CWallet> const wallet =
+        GetWalletForJSONRPCRequest(main_request);
+    CWallet *const pwallet = wallet.get();
+    if (!EnsureWalletIsAvailable(pwallet, main_request.fHelp)) {
+        return NullUniValue;
+    }
+
+    RPCHelpMan{
+        "importdescriptors",
+        "\nImport descriptors. This will trigger a rescan of the blockchain "
+        "based on the earliest timestamp of all descriptors being imported. "
+        "Requires a new wallet backup.\n"
+        "\nNote: This call can take over an hour to complete if using an early "
+        "timestamp; during that time, other rpc calls\n"
+        "may report that the imported keys, addresses or scripts exist but "
+        "related transactions are still missing.\n",
+        {
+            {"requests",
+             RPCArg::Type::ARR,
+             RPCArg::Optional::NO,
+             "Data to be imported",
+             {
+                 {
+                     "",
+                     RPCArg::Type::OBJ,
+                     RPCArg::Optional::OMITTED,
+                     "",
+                     {
+                         {"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
+                          "Descriptor to import."},
+                         {"active", RPCArg::Type::BOOL, /* default */ "false",
+                          "Set this descriptor to be the active descriptor for "
+                          "the corresponding output type/externality"},
+                         {"range", RPCArg::Type::RANGE,
+                          RPCArg::Optional::OMITTED,
+                          "If a ranged descriptor is used, this specifies the "
+                          "end or the range (in the form [begin,end]) to "
+                          "import"},
+                         {"next_index", RPCArg::Type::NUM,
+                          RPCArg::Optional::OMITTED,
+                          "If a ranged descriptor is set to active, this "
+                          "specifies the next index to generate addresses "
+                          "from"},
+                         {"timestamp",
+                          RPCArg::Type::NUM,
+                          RPCArg::Optional::NO,
+                          "Time from which to start rescanning the blockchain "
+                          "for this descriptor, in " +
+                              UNIX_EPOCH_TIME +
+                              "\n"
+                              "                                                "
+                              "              Use the string \"now\" to "
+                              "substitute the current synced blockchain time.\n"
+                              "                                                "
+                              "              \"now\" can be specified to "
+                              "bypass scanning, for outputs which are known to "
+                              "never have been used, and\n"
+                              "                                                "
+                              "              0 can be specified to scan the "
+                              "entire blockchain. Blocks up to 2 hours before "
+                              "the earliest timestamp\n"
+                              "                                                "
+                              "              of all descriptors being imported "
+                              "will be scanned.",
+                          /* oneline_description */ "",
+                          {"timestamp | \"now\"", "integer / string"}},
+                         {"internal", RPCArg::Type::BOOL, /* default */ "false",
+                          "Whether matching outputs should be treated as not "
+                          "incoming payments (e.g. change)"},
+                         {"label", RPCArg::Type::STR, /* default */ "''",
+                          "Label to assign to the address, only allowed with "
+                          "internal=false"},
+                     },
+                 },
+             },
+             "\"requests\""},
+        },
+        RPCResult{RPCResult::Type::ARR,
+                  "",
+                  "Response is an array with the same size as the input that "
+                  "has the execution result",
+                  {
+                      {RPCResult::Type::OBJ,
+                       "",
+                       "",
+                       {
+                           {RPCResult::Type::BOOL, "success", ""},
+                           {RPCResult::Type::ARR,
+                            "warnings",
+                            /* optional */ true,
+                            "",
+                            {
+                                {RPCResult::Type::STR, "", ""},
+                            }},
+                           {RPCResult::Type::OBJ,
+                            "error",
+                            /* optional */ true,
+                            "",
+                            {
+                                {RPCResult::Type::ELISION, "", "JSONRPC error"},
+                            }},
+                       }},
+                  }},
+        RPCExamples{
+            HelpExampleCli("importdescriptors",
+                           "'[{ \"desc\": \"<my descriptor>\", "
+                           "\"timestamp\":1455191478, \"internal\": true }, "
+                           "{ \"desc\": \"<my desccriptor 2>\", \"label\": "
+                           "\"example 2\", \"timestamp\": 1455191480 }]'") +
+            HelpExampleCli(
+                "importdescriptors",
+                "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, "
+                "\"active\": true, \"range\": [0,100], \"label\": \"<my "
+                "cashaddr wallet>\" }]'")},
+    }
+        .Check(main_request);
+
+    //  Make sure wallet is a descriptor wallet
+    if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+        throw JSONRPCError(
+            RPC_WALLET_ERROR,
+            "importdescriptors is not available for non-descriptor wallets");
+    }
+
+    RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});
+
+    WalletRescanReserver reserver(*pwallet);
+    if (!reserver.reserve()) {
+        throw JSONRPCError(
+            RPC_WALLET_ERROR,
+            "Wallet is currently rescanning. Abort existing rescan or wait.");
+    }
+
+    const UniValue &requests = main_request.params[0];
+    const int64_t minimum_timestamp = 1;
+    int64_t now = 0;
+    int64_t lowest_timestamp = 0;
+    bool rescan = false;
+    UniValue response(UniValue::VARR);
+    {
+        LOCK(pwallet->cs_wallet);
+        EnsureWalletIsUnlocked(pwallet);
+
+        CHECK_NONFATAL(pwallet->chain().findBlock(
+            pwallet->GetLastBlockHash(),
+            FoundBlock().time(lowest_timestamp).mtpTime(now)));
+
+        // Get all timestamps and extract the lowest timestamp
+        for (const UniValue &request : requests.getValues()) {
+            // This throws an error if "timestamp" doesn't exist
+            const int64_t timestamp =
+                std::max(GetImportTimestamp(request, now), minimum_timestamp);
+            const UniValue result =
+                ProcessDescriptorImport(pwallet, request, timestamp);
+            response.push_back(result);
+
+            if (lowest_timestamp > timestamp) {
+                lowest_timestamp = timestamp;
+            }
+
+            // If we know the chain tip, and at least one request was successful
+            // then allow rescan
+            if (!rescan && result["success"].get_bool()) {
+                rescan = true;
+            }
+        }
+        pwallet->ConnectScriptPubKeyManNotifiers();
+    }
+
+    // Rescan the blockchain using the lowest timestamp
+    if (rescan) {
+        int64_t scanned_time = pwallet->RescanFromTime(
+            lowest_timestamp, reserver, true /* update */);
+        {
+            LOCK(pwallet->cs_wallet);
+            pwallet->ReacceptWalletTransactions();
+        }
+
+        if (pwallet->IsAbortingRescan()) {
+            throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
+        }
+
+        if (scanned_time > lowest_timestamp) {
+            std::vector<UniValue> results = response.getValues();
+            response.clear();
+            response.setArray();
+
+            // Compose the response
+            for (unsigned int i = 0; i < requests.size(); ++i) {
+                const UniValue &request = requests.getValues().at(i);
+
+                // If the descriptor timestamp is within the successfully
+                // scanned range, or if the import result already has an error
+                // set, let the result stand unmodified. Otherwise replace the
+                // result with an error message.
+                if (scanned_time <= GetImportTimestamp(request, now) ||
+                    results.at(i).exists("error")) {
+                    response.push_back(results.at(i));
+                } else {
+                    UniValue result = UniValue(UniValue::VOBJ);
+                    result.pushKV("success", UniValue(false));
+                    result.pushKV(
+                        "error",
+                        JSONRPCError(
+                            RPC_MISC_ERROR,
+                            strprintf(
+                                "Rescan failed for descriptor with timestamp "
+                                "%d. There was an error reading a block from "
+                                "time %d, which is after or within %d seconds "
+                                "of key creation, and could contain "
+                                "transactions pertaining to the desc. As a "
+                                "result, transactions and coins using this "
+                                "desc may not appear in the wallet. This error "
+                                "could be caused by pruning or data corruption "
+                                "(see bitcoind log for details) and could be "
+                                "dealt with by downloading and rescanning the "
+                                "relevant blocks (see -reindex and -rescan "
+                                "options).",
+                                GetImportTimestamp(request, now),
+                                scanned_time - TIMESTAMP_WINDOW - 1,
+                                TIMESTAMP_WINDOW)));
+                    response.push_back(std::move(result));
+                }
+            }
+        }
+    }
+
+    return response;
+}
+
 // clang-format off
 static const CRPCCommand commands[] = {
     //  category            name                        actor (function)          argNames
@@ -1835,6 +2249,7 @@
     { "wallet",             "abortrescan",              abortrescan,              {} },
     { "wallet",             "dumpprivkey",              dumpprivkey,              {"address"}  },
     { "wallet",             "dumpwallet",               dumpwallet,               {"filename"} },
+    { "wallet",             "importdescriptors",        importdescriptors,        {"requests"} },
     { "wallet",             "importmulti",              importmulti,              {"requests","options"} },
     { "wallet",             "importprivkey",            importprivkey,            {"privkey","label","rescan"} },
     { "wallet",             "importwallet",             importwallet,             {"filename"} },
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -746,6 +746,14 @@
     bool AddKey(const CKeyID &key_id, const CKey &key);
     bool AddCryptedKey(const CKeyID &key_id, const CPubKey &pubkey,
                        const std::vector<uint8_t> &crypted_key);
+
+    bool HasWalletDescriptor(const WalletDescriptor &desc) const;
+    void AddDescriptorKey(const CKey &key, const CPubKey &pubkey);
+    void WriteDescriptor();
+
+    const WalletDescriptor GetWalletDescriptor() const
+        EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+    const std::vector<CScript> GetScriptPubKeys() const;
 };
 
 #endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -1814,6 +1814,16 @@
     }
 }
 
+void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey &key,
+                                                 const CPubKey &pubkey) {
+    LOCK(cs_desc_man);
+    WalletBatch batch(m_storage.GetDatabase());
+    if (!AddDescriptorKeyWithDB(batch, key, pubkey)) {
+        throw std::runtime_error(std::string(__func__) +
+                                 ": writing descriptor private key failed");
+    }
+}
+
 bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch &batch,
                                                        const CKey &key,
                                                        const CPubKey &pubkey) {
@@ -2212,3 +2222,36 @@
     m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key);
     return true;
 }
+
+bool DescriptorScriptPubKeyMan::HasWalletDescriptor(
+    const WalletDescriptor &desc) const {
+    LOCK(cs_desc_man);
+    return m_wallet_descriptor.descriptor != nullptr &&
+           desc.descriptor != nullptr &&
+           m_wallet_descriptor.descriptor->ToString() ==
+               desc.descriptor->ToString();
+}
+
+void DescriptorScriptPubKeyMan::WriteDescriptor() {
+    LOCK(cs_desc_man);
+    WalletBatch batch(m_storage.GetDatabase());
+    if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
+        throw std::runtime_error(std::string(__func__) +
+                                 ": writing descriptor failed");
+    }
+}
+
+const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const {
+    return m_wallet_descriptor;
+}
+
+const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const {
+    LOCK(cs_desc_man);
+    std::vector<CScript> script_pub_keys;
+    script_pub_keys.reserve(m_map_script_pub_keys.size());
+
+    for (auto const &script_pub_key : m_map_script_pub_keys) {
+        script_pub_keys.push_back(script_pub_key.first);
+    }
+    return script_pub_keys;
+}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1529,6 +1529,18 @@
 
     //! Create new DescriptorScriptPubKeyMans and add them to the wallet
     void SetupDescriptorScriptPubKeyMans();
+
+    //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is
+    //! already in the wallet
+    DescriptorScriptPubKeyMan *
+    GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const;
+
+    //! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated
+    //! output type
+    ScriptPubKeyMan *
+    AddWalletDescriptor(WalletDescriptor &desc,
+                        const FlatSigningProvider &signing_provider,
+                        const std::string &label);
 };
 
 /**
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2788,7 +2788,8 @@
     // When there are no available providers for the remaining inputs, use the
     // legacy provider so we can get proper error messages.
     auto legacy_spk_man = GetLegacyScriptPubKeyMan();
-    if (legacy_spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
+    if (legacy_spk_man &&
+        legacy_spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
         return true;
     }
 
@@ -4927,6 +4928,9 @@
 
 void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type,
                                        bool internal, bool memonly) {
+    WalletLogPrintf(
+        "Setting spkMan to active: id = %s, type = %d, internal = %d\n",
+        id.ToString(), static_cast<int>(type), static_cast<int>(internal));
     auto &spk_mans =
         internal ? m_internal_spk_managers : m_external_spk_managers;
     auto spk_man = m_spk_managers.at(id).get();
@@ -4953,3 +4957,99 @@
         m_internal_spk_managers.at(OutputType::LEGACY));
     return spk_man != nullptr;
 }
+
+DescriptorScriptPubKeyMan *
+CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const {
+    for (auto &spk_man_pair : m_spk_managers) {
+        // Try to downcast to DescriptorScriptPubKeyMan then check if the
+        // descriptors match
+        DescriptorScriptPubKeyMan *spk_manager =
+            dynamic_cast<DescriptorScriptPubKeyMan *>(
+                spk_man_pair.second.get());
+        if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) {
+            return spk_manager;
+        }
+    }
+
+    return nullptr;
+}
+
+ScriptPubKeyMan *
+CWallet::AddWalletDescriptor(WalletDescriptor &desc,
+                             const FlatSigningProvider &signing_provider,
+                             const std::string &label) {
+    if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+        WalletLogPrintf(
+            "Cannot add WalletDescriptor to a non-descriptor wallet\n");
+        return nullptr;
+    }
+
+    LOCK(cs_wallet);
+    auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(
+        new DescriptorScriptPubKeyMan(*this, desc));
+
+    // If we already have this descriptor, remove it from the maps but add the
+    // existing cache to desc
+    auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
+    if (old_spk_man) {
+        WalletLogPrintf("Update existing descriptor: %s\n",
+                        desc.descriptor->ToString());
+
+        {
+            LOCK(old_spk_man->cs_desc_man);
+            new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
+        }
+
+        // Remove from maps of active spkMans
+        auto old_spk_man_id = old_spk_man->GetID();
+        for (bool internal : {false, true}) {
+            for (OutputType t : OUTPUT_TYPES) {
+                auto active_spk_man = GetScriptPubKeyMan(t, internal);
+                if (active_spk_man &&
+                    active_spk_man->GetID() == old_spk_man_id) {
+                    if (internal) {
+                        m_internal_spk_managers.erase(t);
+                    } else {
+                        m_external_spk_managers.erase(t);
+                    }
+                    break;
+                }
+            }
+        }
+        m_spk_managers.erase(old_spk_man_id);
+    }
+
+    // Add the private keys to the descriptor
+    for (const auto &entry : signing_provider.keys) {
+        const CKey &key = entry.second;
+        new_spk_man->AddDescriptorKey(key, key.GetPubKey());
+    }
+
+    // Top up key pool, the manager will generate new scriptPubKeys internally
+    new_spk_man->TopUp();
+
+    // Apply the label if necessary
+    // Note: we disable labels for ranged descriptors
+    if (!desc.descriptor->IsRange()) {
+        auto script_pub_keys = new_spk_man->GetScriptPubKeys();
+        if (script_pub_keys.empty()) {
+            WalletLogPrintf(
+                "Could not generate scriptPubKeys (cache is empty)\n");
+            return nullptr;
+        }
+
+        CTxDestination dest;
+        if (ExtractDestination(script_pub_keys.at(0), dest)) {
+            SetAddressBook(dest, label, "receive");
+        }
+    }
+
+    // Save the descriptor to memory
+    auto ret = new_spk_man.get();
+    m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
+
+    // Save the descriptor to DB
+    ret->WriteDescriptor();
+
+    return ret;
+}
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py
--- a/test/functional/test_framework/key.py
+++ b/test/functional/test_framework/key.py
@@ -12,6 +12,8 @@
 import hashlib
 import random
 
+from .address import byte_to_base58
+
 
 def modinv(a, n):
     """Compute the modular inverse of a modulo n
@@ -421,3 +423,16 @@
 
         assert pubkey.verify_schnorr(sig, msg32)
         return sig
+
+
+def bytes_to_wif(b, compressed=True):
+    if compressed:
+        b += b'\x01'
+    return byte_to_base58(b, 239)
+
+
+def generate_wif_key():
+    # Makes a WIF privkey for imports
+    k = ECKey()
+    k.generate()
+    return bytes_to_wif(k.get_bytes(), k.is_compressed)
diff --git a/test/functional/test_framework/wallet_util.py b/test/functional/test_framework/wallet_util.py
--- a/test/functional/test_framework/wallet_util.py
+++ b/test/functional/test_framework/wallet_util.py
@@ -9,6 +9,10 @@
     key_to_p2pkh,
     script_to_p2sh,
 )
+from test_framework.key import (
+    bytes_to_wif,
+    ECKey,
+)
 from test_framework.script import (
     CScript,
     OP_2,
@@ -49,6 +53,22 @@
                p2pkh_addr=key_to_p2pkh(pubkey))
 
 
+def get_generate_key():
+    """Generate a fresh key
+
+    Returns a named tuple of privkey, pubkey and all address and scripts."""
+    eckey = ECKey()
+    eckey.generate()
+    privkey = bytes_to_wif(eckey.get_bytes())
+    pubkey = eckey.get_pubkey().get_bytes().hex()
+    pkh = hash160(hex_str_to_bytes(pubkey))
+    return Key(privkey=privkey,
+               pubkey=pubkey,
+               p2pkh_script=CScript(
+                   [OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(),
+               p2pkh_addr=key_to_p2pkh(pubkey))
+
+
 def get_multisig(node):
     """Generate a fresh 2-of-3 multisig on node
 
diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py
new file mode 100755
--- /dev/null
+++ b/test/functional/wallet_importdescriptors.py
@@ -0,0 +1,465 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test the importdescriptors RPC.
+
+Test importdescriptors by generating keys on node0, importing the corresponding
+descriptors on node1 and then testing the address info for the different address
+variants.
+
+- `get_generate_key()` is called to generate keys and return the privkeys,
+  pubkeys and all variants of scriptPubKey and address.
+- `test_importdesc()` is called to send an importdescriptors call to node1, test
+  success, and (if unsuccessful) test the error code and error message returned.
+- `test_address()` is called to call getaddressinfo for an address on node1
+  and test the values returned."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.descriptors import descsum_create
+from test_framework.util import (
+    assert_equal,
+    assert_raises_rpc_error,
+    find_vout_for_address,
+)
+from test_framework.wallet_util import (
+    get_generate_key,
+    test_address,
+)
+
+
+class ImportDescriptorsTest(BitcoinTestFramework):
+    def set_test_params(self):
+        self.num_nodes = 2
+        self.extra_args = [[], ["-keypool=5"]]
+        self.setup_clean_chain = True
+
+    def skip_test_if_missing_module(self):
+        self.skip_if_no_wallet()
+
+    def test_importdesc(self, req, success, error_code=None,
+                        error_message=None, warnings=None, wallet=None):
+        """Run importdescriptors and assert success"""
+        if warnings is None:
+            warnings = []
+        wrpc = self.nodes[1].get_wallet_rpc('w1')
+        if wallet is not None:
+            wrpc = wallet
+
+        result = wrpc.importdescriptors([req])
+        observed_warnings = []
+        if 'warnings' in result[0]:
+            observed_warnings = result[0]['warnings']
+        assert_equal(
+            "\n".join(
+                sorted(warnings)), "\n".join(
+                sorted(observed_warnings)))
+        assert_equal(result[0]['success'], success)
+        if error_code is not None:
+            assert_equal(result[0]['error']['code'], error_code)
+            assert_equal(result[0]['error']['message'], error_message)
+
+    def run_test(self):
+        self.log.info('Setting up wallets')
+        self.nodes[0].createwallet(
+            wallet_name='w0',
+            disable_private_keys=False)
+        w0 = self.nodes[0].get_wallet_rpc('w0')
+
+        self.nodes[1].createwallet(
+            wallet_name='w1',
+            disable_private_keys=True,
+            blank=True,
+            descriptors=True)
+        w1 = self.nodes[1].get_wallet_rpc('w1')
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
+
+        self.nodes[1].createwallet(
+            wallet_name="wpriv",
+            disable_private_keys=False,
+            blank=True,
+            descriptors=True)
+        wpriv = self.nodes[1].get_wallet_rpc("wpriv")
+        assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
+
+        self.log.info('Mining coins')
+        w0.generatetoaddress(101, w0.getnewaddress())
+
+        # RPC importdescriptors -----------------------------------------------
+
+        # # Test import fails if no descriptor present
+        key = get_generate_key()
+        self.log.info("Import should fail if a descriptor is not provided")
+        self.test_importdesc({"timestamp": "now"},
+                             success=False,
+                             error_code=-8,
+                             error_message='Descriptor not found.')
+
+        # # Test importing of a P2PKH descriptor
+        key = get_generate_key()
+        self.log.info("Should import a p2pkh descriptor")
+        self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
+                              "timestamp": "now",
+                              "label": "Descriptor import test"},
+                             success=True)
+        test_address(w1,
+                     key.p2pkh_addr,
+                     solvable=True,
+                     ismine=True,
+                     labels=["Descriptor import test"])
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
+
+        self.log.info("Internal addresses cannot have labels")
+        self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
+                              "timestamp": "now",
+                              "internal": True,
+                              "label": "Descriptor import test"},
+                             success=False,
+                             error_code=-8,
+                             error_message="Internal addresses should not have a label")
+
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
+
+        test_address(w1,
+                     key.p2pkh_addr,
+                     ismine=True,
+                     solvable=True)
+
+        # # Test importing of a multisig descriptor
+        key1 = get_generate_key()
+        key2 = get_generate_key()
+        self.log.info("Should import a 1-of-2 bare multisig from descriptor")
+        self.test_importdesc({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
+                              "timestamp": "now"},
+                             success=True)
+        self.log.info(
+            "Should not treat individual keys from the imported bare multisig as watchonly")
+        test_address(w1,
+                     key1.p2pkh_addr,
+                     ismine=False)
+
+        # # Test ranged descriptors
+        xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
+        xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H"
+        addresses = [
+            "2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf",
+            "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"]  # hdkeypath=m/0'/0'/0' and 1'
+        # wpkh subscripts corresponding to the above addresses
+        addresses += ["bchreg:prvn9ycvgr5atuyh49sua3mapskh2mnnzg34lqtyst",
+                      "bchreg:pp3n087yx0njv2e5wcvltahfxqst7l66ruyuaun8qt"]
+        desc = "sh(pkh(" + xpub + "/0/0/*" + "))"
+
+        self.log.info("Ranged descriptors cannot have labels")
+        self.test_importdesc({"desc": descsum_create(desc),
+                              "timestamp": "now",
+                              "range": [0, 100],
+                              "label": "test"},
+                             success=False,
+                             error_code=-8,
+                             error_message='Ranged descriptors should not have a label')
+
+        self.log.info("Private keys required for private keys enabled wallet")
+        self.test_importdesc({"desc": descsum_create(desc),
+                              "timestamp": "now",
+                              "range": [0, 100]},
+                             success=False,
+                             error_code=-4,
+                             error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',
+                             wallet=wpriv)
+
+        self.log.info(
+            "Ranged descriptor import should warn without a specified range")
+        self.test_importdesc({"desc": descsum_create(desc),
+                              "timestamp": "now"},
+                             success=True,
+                             warnings=['Range not given, using default keypool range'])
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
+
+        # # Test importing of a ranged descriptor with xpriv
+        self.log.info(
+            "Should not import a ranged descriptor that includes xpriv into a watch-only wallet")
+        desc = "sh(pkh(" + xpriv + "/0'/0'/*'" + "))"
+        self.test_importdesc({"desc": descsum_create(desc),
+                              "timestamp": "now",
+                              "range": 1},
+                             success=False,
+                             error_code=-4,
+                             error_message='Cannot import private keys to a wallet with private keys disabled')
+
+        for address in addresses:
+            test_address(w1,
+                         address,
+                         ismine=False,
+                         solvable=False)
+
+        self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
+                             success=False, error_code=-8, error_message='End of range is too high')
+
+        self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]},
+                             success=False, error_code=-8, error_message='Range should be greater or equal than 0')
+
+        self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]},
+                             success=False, error_code=-8, error_message='End of range is too high')
+
+        self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]},
+                             success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end')
+
+        self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
+                             success=False, error_code=-8, error_message='Range is too large')
+
+        # Make sure ranged imports import keys in order
+        w1 = self.nodes[1].get_wallet_rpc('w1')
+        self.log.info('Key ranges should be imported in order')
+        xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
+        addresses = [
+            'bchreg:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svryj39hul',  # m/0'/0'/0
+            'bchreg:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgzxm0035q',  # m/0'/0'/1
+            'bchreg:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgk64mmwew',  # m/0'/0'/2
+            'bchreg:qrhn24tegn04cptfv4ldhtkduxq55zcwryhvnfcm3r',  # m/0'/0'/3
+            'bchreg:qzpqhett2uwltq803vrxv7zkqhft5vsnmca8ds9jjp',  # m/0'/0'/4
+        ]
+
+        self.test_importdesc({'desc': descsum_create('sh(pkh([abcdef12/0h/0h]' + xpub + '/*))'),
+                              'active': True,
+                              'range': [0, 2],
+                              'timestamp': 'now'
+                              },
+                             success=True)
+        self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
+                              'active': True,
+                              'range': [0, 2],
+                              'timestamp': 'now'
+                              },
+                             success=True)
+
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 5)
+        for i, expected_addr in enumerate(addresses):
+            pkh_addr = w1.getnewaddress('')
+            assert_raises_rpc_error(-4,
+                                    'This wallet has no available keys',
+                                    w1.getrawchangeaddress)
+
+            assert_equal(pkh_addr, expected_addr)
+            pkh_addr_info = w1.getaddressinfo(pkh_addr)
+            assert_equal(pkh_addr_info['desc'][:22],
+                         'pkh([12345678/0\'/0\'/{}]'.format(i))
+
+            # After retrieving a key, we don't refill the keypool again, so
+            # it's one less for each address type
+            assert_equal(w1.getwalletinfo()['keypoolsize'], 4)
+        w1.keypoolrefill()
+        assert_equal(w1.getwalletinfo()['keypoolsize'], 5)
+
+        # Check active=False default
+        self.log.info('Check imported descriptors are not active by default')
+        self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
+                              'range': [0, 2],
+                              'timestamp': 'now',
+                              'internal': True
+                              },
+                             success=True)
+        assert_raises_rpc_error(-4,
+                                'This wallet has no available keys',
+                                w1.getrawchangeaddress)
+
+        # # Test importing a descriptor containing a WIF private key
+        wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
+        address = "bchreg:ppn85zpvym8cdccmgw8km6e48jfhnpa435c0djwhs6"
+        desc = "sh(pkh(" + wif_priv + "))"
+        self.log.info(
+            "Should import a descriptor with a WIF private key as spendable")
+        self.test_importdesc({"desc": descsum_create(desc),
+                              "timestamp": "now"},
+                             success=True,
+                             wallet=wpriv)
+        test_address(wpriv,
+                     address,
+                     solvable=True,
+                     ismine=True)
+        txid = w0.sendtoaddress(address, 49.99999600)
+        w0.generatetoaddress(6, w0.getnewaddress())
+        self.sync_blocks()
+        tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], {
+                                        w0.getnewaddress(): 49.999})
+        signed_tx = wpriv.signrawtransactionwithwallet(tx)
+        w1.sendrawtransaction(signed_tx['hex'])
+
+        # Make sure that we can use import and use multisig as addresses
+        self.log.info(
+            'Test that multisigs can be imported, signed for, and getnewaddress\'d')
+        self.nodes[1].createwallet(
+            wallet_name="wmulti_priv",
+            disable_private_keys=False,
+            blank=True,
+            descriptors=True)
+        wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv")
+        assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0)
+
+        self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#f5nqn4ax",
+                              "active": True,
+                              "range": 1000,
+                              "next_index": 0,
+                              "timestamp": "now"},
+                             success=True,
+                             wallet=wmulti_priv)
+        self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#m4e4s5de",
+                              "active": True,
+                              "internal": True,
+                              "range": 1000,
+                              "next_index": 0,
+                              "timestamp": "now"},
+                             success=True,
+                             wallet=wmulti_priv)
+
+        # Range end (1000) is inclusive, so 1001 addresses generated
+        assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001)
+        addr = wmulti_priv.getnewaddress('')
+        # Derived at m/84'/0'/0'/0
+        assert_equal(
+            addr,
+            'bchreg:pzkcf26dw7np58jcspnpxaupgz9csnc3wsx25fa5q3')
+        change_addr = wmulti_priv.getrawchangeaddress()
+        assert_equal(
+            change_addr,
+            'bchreg:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vuj8tutcn')
+
+        assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000)
+        txid = w0.sendtoaddress(addr, 10)
+        self.nodes[0].generate(6)
+
+        self.nodes[0].generate(6)
+        self.sync_all()
+
+        self.nodes[1].createwallet(
+            wallet_name="wmulti_pub",
+            disable_private_keys=True,
+            blank=True,
+            descriptors=True)
+        wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
+        assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0)
+
+        self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#x75vpsak",
+                              "active": True,
+                              "range": 1000,
+                              "next_index": 0,
+                              "timestamp": "now"},
+                             success=True,
+                             wallet=wmulti_pub)
+        self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#v0t48ucu",
+                              "active": True,
+                              "internal": True,
+                              "range": 1000,
+                              "next_index": 0,
+                              "timestamp": "now"},
+                             success=True,
+                             wallet=wmulti_pub)
+
+        # The first one was already consumed by previous import and is detected
+        # as used
+        assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000)
+        addr = wmulti_pub.getnewaddress('')
+        # Derived at m/84'/0'/0'/1
+        assert_equal(
+            addr,
+            'bchreg:pr5xql8r03jp5dvrep22dns59vf7hhykr5u98cj6hh')
+        change_addr = wmulti_pub.getrawchangeaddress()
+        assert_equal(
+            change_addr,
+            'bchreg:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vuj8tutcn')
+
+        assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
+        txid = w0.sendtoaddress(addr, 10)
+        vout = find_vout_for_address(self.nodes[0], txid, addr)
+        self.nodes[0].generate(6)
+        self.sync_all()
+        assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
+
+        self.log.info("Multisig with distributed keys")
+        self.nodes[1].createwallet(
+            wallet_name="wmulti_priv1",
+            descriptors=True)
+        wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
+        res = wmulti_priv1.importdescriptors([
+            {
+                "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
+                "active": True,
+                "range": 1000,
+                "next_index": 0,
+                "timestamp": "now"
+            },
+            {
+                "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
+                "active": True,
+                "internal": True,
+                "range": 1000,
+                "next_index": 0,
+                "timestamp": "now"
+            }])
+        assert_equal(res[0]['success'], True)
+        assert_equal(
+            res[0]['warnings'][0],
+            'Not all private keys provided. Some wallet functionality may return unexpected errors')
+        assert_equal(res[1]['success'], True)
+        assert_equal(
+            res[1]['warnings'][0],
+            'Not all private keys provided. Some wallet functionality may return unexpected errors')
+
+        self.nodes[1].createwallet(
+            wallet_name='wmulti_priv2',
+            blank=True,
+            descriptors=True)
+        wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2')
+        res = wmulti_priv2.importdescriptors([
+            {
+                "desc": descsum_create("sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
+                "active": True,
+                "range": 1000,
+                "next_index": 0,
+                "timestamp": "now"
+            },
+            {
+                "desc": descsum_create("sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
+                "active": True,
+                "internal": True,
+                "range": 1000,
+                "next_index": 0,
+                "timestamp": "now"
+            }])
+        assert_equal(res[0]['success'], True)
+        assert_equal(
+            res[0]['warnings'][0],
+            'Not all private keys provided. Some wallet functionality may return unexpected errors')
+        assert_equal(res[1]['success'], True)
+        assert_equal(
+            res[1]['warnings'][0],
+            'Not all private keys provided. Some wallet functionality may return unexpected errors')
+
+        rawtx = self.nodes[1].createrawtransaction(
+            [{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9.999})
+        tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx)
+        assert_equal(tx_signed_1['complete'], False)
+        tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(
+            tx_signed_1['hex'])
+        assert_equal(tx_signed_2['complete'], True)
+        self.nodes[1].sendrawtransaction(tx_signed_2['hex'])
+
+        self.log.info("Combo descriptors cannot be active")
+        self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
+                              "active": True,
+                              "range": 1,
+                              "timestamp": "now"},
+                             success=False,
+                             error_code=-4,
+                             error_message="Combo descriptors cannot be set to active")
+
+        self.log.info("Descriptors with no type cannot be active")
+        self.test_importdesc({"desc": descsum_create("pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
+                              "active": True,
+                              "range": 1,
+                              "timestamp": "now"},
+                             success=True,
+                             warnings=["Unknown output type, cannot set descriptor to active."])
+
+
+if __name__ == '__main__':
+    ImportDescriptorsTest().main()