aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSjors Provoost <sjors@sprovoost.nl>2020-02-19 14:33:37 +0100
committerSjors Provoost <sjors@sprovoost.nl>2021-02-23 14:34:31 +0100
commit245b4457cf9265190a05529a0a97e1cb258cca8a (patch)
treedb47c62fb253880cbc1ed7b4284ac39b9782d5bc
parent7ebc7c0215979c53b92a436acc8b5b607b8d735a (diff)
rpc: signerdisplayaddress
-rw-r--r--src/wallet/external_signer.cpp5
-rw-r--r--src/wallet/external_signer.h5
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.cpp35
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.h1
-rw-r--r--src/wallet/rpcsigner.cpp37
-rw-r--r--src/wallet/wallet.cpp19
-rw-r--r--src/wallet/wallet.h3
-rwxr-xr-xtest/functional/mocks/signer.py18
-rwxr-xr-xtest/functional/wallet_signer.py11
9 files changed, 134 insertions, 0 deletions
diff --git a/src/wallet/external_signer.cpp b/src/wallet/external_signer.cpp
index ec915d5c5e..baf97700e7 100644
--- a/src/wallet/external_signer.cpp
+++ b/src/wallet/external_signer.cpp
@@ -55,6 +55,11 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
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));
diff --git a/src/wallet/external_signer.h b/src/wallet/external_signer.h
index 944dd7d5ef..198a939d3d 100644
--- a/src/wallet/external_signer.h
+++ b/src/wallet/external_signer.h
@@ -49,6 +49,11 @@ public:
//! @param[out] success Boolean
static bool Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors = false);
+ //! Display address on the device. Calls `<command> displayaddress --desc <descriptor>`.
+ //! @param[in] descriptor Descriptor specifying which address to display.
+ //! Must include a public key or xpub, as well as key origin.
+ UniValue DisplayAddress(const std::string& descriptor) const;
+
//! Get receive and change Descriptor(s) from device for a given account.
//! Calls `<command> getdescriptors --account <account>`
//! @param[in] account which BIP32 account to use (e.g. `m/44'/0'/account'`)
diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp
index b8bd69f941..a2071e521a 100644
--- a/src/wallet/external_signer_scriptpubkeyman.cpp
+++ b/src/wallet/external_signer_scriptpubkeyman.cpp
@@ -43,4 +43,39 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
return signers[0];
}
+bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const
+{
+ // TODO: avoid the need to infer a descriptor from inside a descriptor wallet
+ auto provider = GetSolvingProvider(scriptPubKey);
+ auto descriptor = InferDescriptor(scriptPubKey, *provider);
+
+ signer.DisplayAddress(descriptor->ToString());
+ // TODO inspect result
+ return true;
+}
+
+// If sign is true, transaction must previously have been filled
+TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
+{
+ if (!sign) {
+ return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed);
+ }
+
+ // Already complete if every input is now signed
+ bool complete = true;
+ for (const auto& input : psbt.inputs) {
+ // TODO: for multisig wallets, we should only care if all _our_ inputs are signed
+ complete &= PSBTInputSigned(input);
+ }
+ if (complete) return TransactionError::OK;
+
+ std::string strFailReason;
+ if(!GetExternalSigner().SignTransaction(psbt, strFailReason)) {
+ tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
+ return TransactionError::EXTERNAL_SIGNER_FAILED;
+ }
+ FinalizePSBT(psbt); // This won't work in a multisig setup
+ return TransactionError::OK;
+}
+
#endif
diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h
index cef0a18075..1cde143ef9 100644
--- a/src/wallet/external_signer_scriptpubkeyman.h
+++ b/src/wallet/external_signer_scriptpubkeyman.h
@@ -25,6 +25,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
static ExternalSigner GetExternalSigner();
+ bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
};
#endif
diff --git a/src/wallet/rpcsigner.cpp b/src/wallet/rpcsigner.cpp
index 76f4f3c6aa..607b778c68 100644
--- a/src/wallet/rpcsigner.cpp
+++ b/src/wallet/rpcsigner.cpp
@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparamsbase.h>
+#include <key_io.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/strencodings.h>
@@ -57,6 +58,41 @@ static RPCHelpMan enumeratesigners()
};
}
+static RPCHelpMan signerdisplayaddress()
+{
+ return RPCHelpMan{
+ "signerdisplayaddress",
+ "Display address on an external signer for verification.\n",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, /* default_val */ "", "bitcoin address to display"},
+ },
+ RPCResult{RPCResult::Type::NONE,"",""},
+ RPCExamples{""},
+ [](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+ CWallet* const pwallet = wallet.get();
+
+ LOCK(pwallet->cs_wallet);
+
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+
+ // Make sure the destination is valid
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
+
+ if (!pwallet->DisplayAddress(dest)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Failed to display address");
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", request.params[0].get_str());
+ return result;
+ }
+ };
+}
+
Span<const CRPCCommand> GetSignerRPCCommands()
{
@@ -65,6 +101,7 @@ static const CRPCCommand commands[] =
{ // category actor (function)
// --------------------- ------------------------
{ "signer", &enumeratesigners, },
+ { "signer", &signerdisplayaddress, },
};
// clang-format on
return MakeSpan(commands);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 795299d103..7c15438067 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -3587,6 +3587,25 @@ ExternalSigner CWallet::GetExternalSigner()
}
#endif
+bool CWallet::DisplayAddress(const CTxDestination& dest)
+{
+#ifdef ENABLE_EXTERNAL_SIGNER
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
+ if (spk_man == nullptr) {
+ return false;
+ }
+ auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan*>(spk_man);
+ if (signer_spk_man == nullptr) {
+ return false;
+ }
+ ExternalSigner signer = GetExternalSigner(); // TODO: move signer in spk_man
+ return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+#else
+ return false;
+#endif
+}
+
void CWallet::LockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 3c27aee488..eb797938cd 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -842,6 +842,9 @@ public:
#ifdef ENABLE_EXTERNAL_SIGNER
ExternalSigner GetExternalSigner() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
#endif
+ /** Display address on an external signer. Returns false if external signer support is not compiled */
+ bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
diff --git a/test/functional/mocks/signer.py b/test/functional/mocks/signer.py
index aedab14cee..4036c785b3 100755
--- a/test/functional/mocks/signer.py
+++ b/test/functional/mocks/signer.py
@@ -37,6 +37,20 @@ def getdescriptors(args):
}))
+def displayaddress(args):
+ # Several descriptor formats are acceptable, so allowing for potential
+ # changes to InferDescriptor:
+ if args.fingerprint != "00000001":
+ return sys.stdout.write(json.dumps({"error": "Unexpected fingerprint", "fingerprint": args.fingerprint}))
+
+ expected_desc = [
+ "wpkh([00000001/84'/1'/0'/0/0]02c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#0yneg42r"
+ ]
+ if args.desc not in expected_desc:
+ return sys.stdout.write(json.dumps({"error": "Unexpected descriptor", "desc": args.desc}))
+
+ return sys.stdout.write(json.dumps({"address": "bcrt1qm90ugl4d48jv8n6e5t9ln6t9zlpm5th68x4f8g"}))
+
parser = argparse.ArgumentParser(prog='./signer.py', description='External signer mock')
parser.add_argument('--fingerprint')
parser.add_argument('--chain', default='main')
@@ -51,6 +65,10 @@ parser_getdescriptors = subparsers.add_parser('getdescriptors')
parser_getdescriptors.set_defaults(func=getdescriptors)
parser_getdescriptors.add_argument('--account', metavar='account')
+parser_displayaddress = subparsers.add_parser('displayaddress', help='display address on signer')
+parser_displayaddress.add_argument('--desc', metavar='desc')
+parser_displayaddress.set_defaults(func=displayaddress)
+
args = parser.parse_args()
perform_pre_checks()
diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py
index abbffe24d1..b39f1b4d9b 100755
--- a/test/functional/wallet_signer.py
+++ b/test/functional/wallet_signer.py
@@ -123,5 +123,16 @@ class SignerTest(BitcoinTestFramework):
assert_equal(address_info['ismine'], True)
assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0")
+ self.log.info('Test signerdisplayaddress')
+ result = hww.signerdisplayaddress(address1)
+ assert_equal(result, {"address": address1})
+
+ # Handle error thrown by script
+ self.set_mock_result(self.nodes[1], "2")
+ assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
+ hww.signerdisplayaddress, address1
+ )
+ self.clear_mock_result(self.nodes[1])
+
if __name__ == '__main__':
SignerTest().main()