mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-10 03:47:29 -03:00
rpc: signerdisplayaddress
This commit is contained in:
parent
7ebc7c0215
commit
245b4457cf
9 changed files with 134 additions and 0 deletions
|
@ -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));
|
||||
|
|
|
@ -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'`)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,6 +25,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
|
|||
|
||||
static ExternalSigner GetExternalSigner();
|
||||
|
||||
bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue