diff options
author | Sjors Provoost <sjors@sprovoost.nl> | 2020-02-19 14:33:37 +0100 |
---|---|---|
committer | Sjors Provoost <sjors@sprovoost.nl> | 2021-02-23 14:34:31 +0100 |
commit | 245b4457cf9265190a05529a0a97e1cb258cca8a (patch) | |
tree | db47c62fb253880cbc1ed7b4284ac39b9782d5bc | |
parent | 7ebc7c0215979c53b92a436acc8b5b607b8d735a (diff) |
rpc: signerdisplayaddress
-rw-r--r-- | src/wallet/external_signer.cpp | 5 | ||||
-rw-r--r-- | src/wallet/external_signer.h | 5 | ||||
-rw-r--r-- | src/wallet/external_signer_scriptpubkeyman.cpp | 35 | ||||
-rw-r--r-- | src/wallet/external_signer_scriptpubkeyman.h | 1 | ||||
-rw-r--r-- | src/wallet/rpcsigner.cpp | 37 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 19 | ||||
-rw-r--r-- | src/wallet/wallet.h | 3 | ||||
-rwxr-xr-x | test/functional/mocks/signer.py | 18 | ||||
-rwxr-xr-x | test/functional/wallet_signer.py | 11 |
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() |