aboutsummaryrefslogtreecommitdiff
path: root/src/external_signer.cpp
diff options
context:
space:
mode:
authorSjors Provoost <sjors@sprovoost.nl>2021-03-18 14:17:39 +0100
committerSjors Provoost <sjors@sprovoost.nl>2021-04-08 17:56:00 +0200
commitb54b2e7b1a171203404bd41853372c73f2c64532 (patch)
treef6d0854fa8a582e536bf8cdd8c0cf51ab51e8250 /src/external_signer.cpp
parent6664211be2b664dd471d7aeea12fcf2859dba860 (diff)
downloadbitcoin-b54b2e7b1a171203404bd41853372c73f2c64532.tar.xz
Move external signer out of wallet module
This commit moves the ExternalSigner class and RPC methods out of the wallet module. The enumeratesigners RPC can be used without a wallet since #21417. With additional modifications external signers could be used without a wallet in general, e.g. via signrawtransaction. The signerdisplayaddress RPC is ranamed to walletdisplayaddress because it requires wallet context. A future displayaddress RPC call without wallet context could take a descriptor argument. This commit fixes a rpc_help.py failure when configured with --disable-wallet.
Diffstat (limited to 'src/external_signer.cpp')
-rw-r--r--src/external_signer.cpp119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/external_signer.cpp b/src/external_signer.cpp
new file mode 100644
index 0000000000..b82dcc503d
--- /dev/null
+++ b/src/external_signer.cpp
@@ -0,0 +1,119 @@
+// Copyright (c) 2018-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparams.h>
+#include <core_io.h>
+#include <psbt.h>
+#include <util/strencodings.h>
+#include <util/system.h>
+#include <external_signer.h>
+
+ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint, std::string chain, std::string name): m_command(command), m_fingerprint(fingerprint), m_chain(chain), m_name(name) {}
+
+const std::string ExternalSigner::NetworkArg() const
+{
+ return " --chain " + m_chain;
+}
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+
+bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors)
+{
+ // Call <command> enumerate
+ const UniValue result = RunCommandParseJSON(command + " enumerate");
+ if (!result.isArray()) {
+ if (ignore_errors) return false;
+ throw ExternalSignerException(strprintf("'%s' received invalid response, expected array of signers", command));
+ }
+ for (UniValue signer : result.getValues()) {
+ // Check for error
+ const UniValue& error = find_value(signer, "error");
+ if (!error.isNull()) {
+ if (ignore_errors) return false;
+ if (!error.isStr()) {
+ throw ExternalSignerException(strprintf("'%s' error", command));
+ }
+ throw ExternalSignerException(strprintf("'%s' error: %s", command, error.getValStr()));
+ }
+ // Check if fingerprint is present
+ const UniValue& fingerprint = find_value(signer, "fingerprint");
+ if (fingerprint.isNull()) {
+ if (ignore_errors) return false;
+ throw ExternalSignerException(strprintf("'%s' received invalid response, missing signer fingerprint", command));
+ }
+ std::string fingerprintStr = fingerprint.get_str();
+ // Skip duplicate signer
+ bool duplicate = false;
+ for (ExternalSigner signer : signers) {
+ if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
+ }
+ if (duplicate) break;
+ std::string name = "";
+ const UniValue& model_field = find_value(signer, "model");
+ if (model_field.isStr() && model_field.getValStr() != "") {
+ name += model_field.getValStr();
+ }
+ signers.push_back(ExternalSigner(command, fingerprintStr, chain, name));
+ }
+ return true;
+}
+
+UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
+{
+ return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " displayaddress --desc \"" + descriptor + "\"");
+}
+
+UniValue ExternalSigner::GetDescriptors(int account)
+{
+ return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
+}
+
+bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
+{
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ // Check if signer fingerprint matches any input master key fingerprint
+ bool match = false;
+ for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
+ const PSBTInput& input = psbtx.inputs[i];
+ for (auto entry : input.hd_keypaths) {
+ if (m_fingerprint == strprintf("%08x", ReadBE32(entry.second.fingerprint))) match = true;
+ }
+ }
+
+ if (!match) {
+ error = "Signer fingerprint " + m_fingerprint + " does not match any of the inputs:\n" + EncodeBase64(ssTx.str());
+ return false;
+ }
+
+ std::string command = m_command + " --stdin --fingerprint \"" + m_fingerprint + "\"" + NetworkArg();
+ std::string stdinStr = "signtx \"" + EncodeBase64(ssTx.str()) + "\"";
+
+ const UniValue signer_result = RunCommandParseJSON(command, stdinStr);
+
+ if (find_value(signer_result, "error").isStr()) {
+ error = find_value(signer_result, "error").get_str();
+ return false;
+ }
+
+ if (!find_value(signer_result, "psbt").isStr()) {
+ error = "Unexpected result from signer";
+ return false;
+ }
+
+ PartiallySignedTransaction signer_psbtx;
+ std::string signer_psbt_error;
+ if (!DecodeBase64PSBT(signer_psbtx, find_value(signer_result, "psbt").get_str(), signer_psbt_error)) {
+ error = strprintf("TX decode failed %s", signer_psbt_error);
+ return false;
+ }
+
+ psbtx = signer_psbtx;
+
+ return true;
+}
+
+#endif