From 989cb175f1049883a5abb2b2e98a94be9c76c2fc Mon Sep 17 00:00:00 2001 From: Hennadii Stepanov <32963518+hebasto@users.noreply.github.com> Date: Sun, 24 Oct 2021 23:04:11 +0300 Subject: [PATCH 1/2] util: Add "importfromcoldcard" command to bitcoin-wallet tool Coldcard firmware v4.1.3+ supports wallet export via descriptors. Github-Pull: #23362 Rebased-From: 8076f8d4c2abaf74ab7eaacf9cd47027b9faddd1 --- src/bitcoin-wallet.cpp | 1 + src/wallet/rpc/backup.cpp | 2 +- src/wallet/wallettool.cpp | 81 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 1ebe98b920..687d56b8c7 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -49,6 +49,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman) argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental."); argsman.AddCommand("dump", "Print out all of the wallet key-value records"); argsman.AddCommand("createfromdump", "Create new wallet file from dumped records"); + argsman.AddCommand("importfromcoldcard", "Create new wallet file and import descriptors from Coldcard wallet"); } static std::optional WalletAppInit(ArgsManager& args, int argc, char* argv[]) diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index d9712f05d2..d5a137875c 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1443,7 +1443,7 @@ RPCHelpMan importmulti() }; } -static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index acd817a311..a0645b8681 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -8,6 +8,8 @@ #include +#include +#include #include #include #include @@ -16,7 +18,15 @@ #include #include +#include +#include +#include + namespace wallet { + +UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) + EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet); + namespace WalletTool { // The standard wallet deleter function blocks on the validation interface @@ -36,6 +46,10 @@ static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flag wallet_instance->SetMinVersion(FEATURE_LATEST); wallet_instance->InitWalletFlags(wallet_creation_flags); + if (wallet_instance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) { + return; + } + if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan(); spk_man->SetupGeneration(false); @@ -113,14 +127,40 @@ static void WalletShowInfo(CWallet* wallet_instance) tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size()); } +static bool ReadAndParseColdcardFile(const fs::path& path, UniValue& decriptors) +{ + std::ifstream file; + file.open(path); + if (!file.is_open()) { + tfm::format(std::cerr, "%s. Please check permissions.\n", fs::PathToString(path)); + return false; + } + + std::string line; + while (std::getline(file, line)) { + if (line.substr(0, 22) == "importdescriptors \'[{\"") break; + } + + file.close(); + + decriptors.clear(); + if (!decriptors.read(line.substr(19, line.size() - 20))) { + tfm::format(std::cerr, "Unable to parse %s\n", fs::PathToString(path)); + return false; + } + + assert(decriptors.isArray()); + return true; +} + bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) { if (args.IsArgSet("-format") && command != "createfromdump") { tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n"); return false; } - if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump") { - tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\" and \"createfromdump\" commands.\n"); + if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump" && command != "importfromcoldcard") { + tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\", \"createfromdump\" and \"importfromcoldcard\" commands.\n"); return false; } if (args.IsArgSet("-descriptors") && command != "create") { @@ -215,6 +255,43 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) tfm::format(std::cerr, "%s\n", error.original); } return ret; + } else if (command == "importfromcoldcard") { + std::string filename = gArgs.GetArg("-dumpfile", ""); + if (filename.empty()) { + tfm::format(std::cerr, "To use importfromcoldcard, -dumpfile= must be provided.\n"); + return false; + } + + const fs::path import_file_path{fs::absolute(fs::PathFromString(filename))}; + if (!fs::exists(import_file_path)) { + tfm::format(std::cerr, "File %s does not exist.\n", fs::PathToString(import_file_path)); + return false; + } + + UniValue descriptors; + if (!ReadAndParseColdcardFile(import_file_path, descriptors)) { + return false; + } + + DatabaseOptions options; + options.require_create = true; + options.create_flags |= WALLET_FLAG_DESCRIPTORS; + options.create_flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; + options.create_flags |= WALLET_FLAG_BLANK_WALLET; + options.require_format = DatabaseFormat::SQLITE; + std::shared_ptr wallet_instance = MakeWallet(name, path, options); + if (!wallet_instance) { + return false; + } + + LOCK(wallet_instance->cs_wallet); + for (const UniValue& descriptor : descriptors.getValues()) { + const UniValue result = ProcessDescriptorImport(*wallet_instance, descriptor, 0); + tfm::format(std::cerr, "%s\n", result.write(2)); + } + + WalletShowInfo(wallet_instance.get()); + wallet_instance->Close(); } else { tfm::format(std::cerr, "Invalid command: %s\n", command); return false; From 69dec40189519eca713024332ab17fbf6fa2fb89 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 30 Oct 2021 05:44:33 +0000 Subject: [PATCH 2/2] wallettool: Add experimental warning to importfromcoldcard command --- src/wallet/wallettool.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index a0645b8681..08d2b48137 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -256,6 +256,8 @@ bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command) } return ret; } else if (command == "importfromcoldcard") { + tfm::format(std::cerr, "WARNING: The \"importfromcoldcard\" command is experimental and will likely be removed or changed incompatibly in a future version.\n"); + std::string filename = gArgs.GetArg("-dumpfile", ""); if (filename.empty()) { tfm::format(std::cerr, "To use importfromcoldcard, -dumpfile= must be provided.\n");