/ src / external_signer.cpp
external_signer.cpp
  1  // Copyright (c) 2018-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <external_signer.h>
  6  
  7  #include <chainparams.h>
  8  #include <common/run_command.h>
  9  #include <core_io.h>
 10  #include <psbt.h>
 11  #include <util/strencodings.h>
 12  #include <util/subprocess.h>
 13  
 14  #include <algorithm>
 15  #include <stdexcept>
 16  #include <string>
 17  #include <vector>
 18  
 19  ExternalSigner::ExternalSigner(std::vector<std::string> command, std::string chain, std::string fingerprint, std::string name)
 20      : m_command{std::move(command)}, m_chain{std::move(chain)}, m_fingerprint{std::move(fingerprint)}, m_name{std::move(name)} {}
 21  
 22  std::vector<std::string> ExternalSigner::NetworkArg() const
 23  {
 24      return {"--chain", m_chain};
 25  }
 26  
 27  bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, const std::string& chain)
 28  {
 29      // Call <command> enumerate
 30      std::vector<std::string> cmd_args = Cat(subprocess::util::split(command), {"enumerate"});
 31  
 32      const UniValue result = RunCommandParseJSON(cmd_args, "");
 33      if (!result.isArray()) {
 34          throw std::runtime_error(strprintf("'%s' received invalid response, expected array of signers", command));
 35      }
 36      for (const UniValue& signer : result.getValues()) {
 37          // Check for error
 38          const UniValue& error = signer.find_value("error");
 39          if (!error.isNull()) {
 40              if (!error.isStr()) {
 41                  throw std::runtime_error(strprintf("'%s' error", command));
 42              }
 43              throw std::runtime_error(strprintf("'%s' error: %s", command, error.getValStr()));
 44          }
 45          // Check if fingerprint is present
 46          const UniValue& fingerprint = signer.find_value("fingerprint");
 47          if (fingerprint.isNull()) {
 48              throw std::runtime_error(strprintf("'%s' received invalid response, missing signer fingerprint", command));
 49          }
 50          const std::string& fingerprintStr{fingerprint.get_str()};
 51          // Skip duplicate signer
 52          bool duplicate = false;
 53          for (const ExternalSigner& signer : signers) {
 54              if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
 55          }
 56          if (duplicate) break;
 57          std::string name;
 58          const UniValue& model_field = signer.find_value("model");
 59          if (model_field.isStr() && model_field.getValStr() != "") {
 60              name += model_field.getValStr();
 61          }
 62          signers.emplace_back(subprocess::util::split(command), chain, fingerprintStr, name);
 63      }
 64      return true;
 65  }
 66  
 67  UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
 68  {
 69      return RunCommandParseJSON(Cat(m_command, Cat(Cat({"--fingerprint", m_fingerprint}, NetworkArg()), {"displayaddress", "--desc", descriptor})), "");
 70  }
 71  
 72  UniValue ExternalSigner::GetDescriptors(const int account)
 73  {
 74      return RunCommandParseJSON(Cat(m_command, Cat(Cat({"--fingerprint", m_fingerprint}, NetworkArg()), {"getdescriptors", "--account", strprintf("%d", account)})), "");
 75  }
 76  
 77  bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
 78  {
 79      // Serialize the PSBT
 80      DataStream ssTx{};
 81      ssTx << psbtx;
 82      // parse ExternalSigner master fingerprint
 83      std::vector<unsigned char> parsed_m_fingerprint = ParseHex(m_fingerprint);
 84      // Check if signer fingerprint matches any input master key fingerprint
 85      auto matches_signer_fingerprint = [&](const PSBTInput& input) {
 86          for (const auto& entry : input.hd_keypaths) {
 87              if (std::ranges::equal(parsed_m_fingerprint, entry.second.fingerprint)) return true;
 88          }
 89          for (const auto& entry : input.m_tap_bip32_paths) {
 90              if (std::ranges::equal(parsed_m_fingerprint, entry.second.second.fingerprint)) return true;
 91          }
 92          return false;
 93      };
 94  
 95      if (!std::any_of(psbtx.inputs.begin(), psbtx.inputs.end(), matches_signer_fingerprint)) {
 96          error = "Signer fingerprint " + m_fingerprint + " does not match any of the inputs:\n" + EncodeBase64(ssTx.str());
 97          return false;
 98      }
 99  
100      const std::vector<std::string> command = Cat(m_command, Cat({"--stdin", "--fingerprint", m_fingerprint}, NetworkArg()));
101      const std::string stdinStr = "signtx " + EncodeBase64(ssTx.str());
102  
103      const UniValue signer_result = RunCommandParseJSON(command, stdinStr);
104  
105      if (signer_result.find_value("error").isStr()) {
106          error = signer_result.find_value("error").get_str();
107          return false;
108      }
109  
110      if (!signer_result.find_value("psbt").isStr()) {
111          error = "Unexpected result from signer";
112          return false;
113      }
114  
115      PartiallySignedTransaction signer_psbtx;
116      std::string signer_psbt_error;
117      if (!DecodeBase64PSBT(signer_psbtx, signer_result.find_value("psbt").get_str(), signer_psbt_error)) {
118          error = strprintf("TX decode failed %s", signer_psbt_error);
119          return false;
120      }
121  
122      psbtx = signer_psbtx;
123  
124      return true;
125  }