From d4b0107d68a91ed4d1a5c78c8ca76251329d3f3c Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Sun, 4 Aug 2019 23:26:01 +0200 Subject: rpc: send: support external signer --- src/util/error.cpp | 4 +++ src/util/error.h | 2 ++ src/wallet/external_signer.cpp | 51 ++++++++++++++++++++++++++++ src/wallet/external_signer.h | 7 ++++ src/wallet/external_signer_scriptpubkeyman.h | 2 ++ src/wallet/rpcwallet.cpp | 6 ++-- src/wallet/scriptpubkeyman.cpp | 1 + src/wallet/wallet.cpp | 1 + 8 files changed, 72 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/util/error.cpp b/src/util/error.cpp index 76fac4d391..48c81693f3 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -31,6 +31,10 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("Specified sighash value does not match value stored in PSBT"); case TransactionError::MAX_FEE_EXCEEDED: return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"); + case TransactionError::EXTERNAL_SIGNER_NOT_FOUND: + return Untranslated("External signer not found"); + case TransactionError::EXTERNAL_SIGNER_FAILED: + return Untranslated("External signer failed to sign"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/util/error.h b/src/util/error.h index 6633498d2b..4cc35eb1fd 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -30,6 +30,8 @@ enum class TransactionError { PSBT_MISMATCH, SIGHASH_MISMATCH, MAX_FEE_EXCEEDED, + EXTERNAL_SIGNER_NOT_FOUND, + EXTERNAL_SIGNER_FAILED, }; bilingual_str TransactionErrorString(const TransactionError error); diff --git a/src/wallet/external_signer.cpp b/src/wallet/external_signer.cpp index baf97700e7..3396111760 100644 --- a/src/wallet/external_signer.cpp +++ b/src/wallet/external_signer.cpp @@ -3,6 +3,10 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include +#include +#include +#include #include 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) {} @@ -65,4 +69,51 @@ 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 diff --git a/src/wallet/external_signer.h b/src/wallet/external_signer.h index 198a939d3d..4b9711107b 100644 --- a/src/wallet/external_signer.h +++ b/src/wallet/external_signer.h @@ -10,6 +10,8 @@ #include #include +struct PartiallySignedTransaction; + class ExternalSignerException : public std::runtime_error { public: using std::runtime_error::runtime_error; @@ -60,6 +62,11 @@ public: //! @param[out] UniValue see doc/external-signer.md UniValue GetDescriptors(int account); + //! Sign PartiallySignedTransaction on the device. + //! Calls ` signtransaction` and passes the PSBT via stdin. + //! @param[in,out] psbt PartiallySignedTransaction to be signed + bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error); + #endif }; diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h index 1cde143ef9..e60d7b8004 100644 --- a/src/wallet/external_signer_scriptpubkeyman.h +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -26,6 +26,8 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan static ExternalSigner GetExternalSigner(); bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const; + + TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; }; #endif diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 9d61f6fbe2..bfc42ac1b0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4195,8 +4195,10 @@ static RPCHelpMan send() // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); - // Fill transaction with our data and sign - bool complete = true; + // First fill transaction with our data without signing, + // so external signers are not asked sign more than once. + bool complete; + pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true); const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 4630603f8e..efb408c163 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include //! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details. diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7c15438067..08e480225d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include