rpc: signer: add enumeratesigners to list external signers

This commit is contained in:
Sjors Provoost 2019-02-15 12:54:29 +01:00
parent 07b7c940a7
commit 2700f09c41
No known key found for this signature in database
GPG key ID: 57FF9BDBCC301009
5 changed files with 157 additions and 16 deletions

View file

@ -2,7 +2,57 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <wallet/external_signer.h>
#include <util/system.h>
ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint): m_command(command), m_fingerprint(fingerprint) {}
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) {}
const std::string ExternalSigner::NetworkArg() const
{
return " --chain " + m_chain;
}
#ifdef ENABLE_EXTERNAL_SIGNER
bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors)
{
// Call <command> enumerate
const UniValue result = RunCommandParseJSON(command + " enumerate");
if (!result.isArray()) {
if (ignore_errors) return false;
throw ExternalSignerException(strprintf("'%s' received invalid response, expected array of signers", command));
}
for (UniValue signer : result.getValues()) {
// Check for error
const UniValue& error = find_value(signer, "error");
if (!error.isNull()) {
if (ignore_errors) return false;
if (!error.isStr()) {
throw ExternalSignerException(strprintf("'%s' error", command));
}
throw ExternalSignerException(strprintf("'%s' error: %s", command, error.getValStr()));
}
// Check if fingerprint is present
const UniValue& fingerprint = find_value(signer, "fingerprint");
if (fingerprint.isNull()) {
if (ignore_errors) return false;
throw ExternalSignerException(strprintf("'%s' received invalid response, missing signer fingerprint", command));
}
std::string fingerprintStr = fingerprint.get_str();
// Skip duplicate signer
bool duplicate = false;
for (ExternalSigner signer : signers) {
if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
}
if (duplicate) break;
std::string name = "";
const UniValue& model_field = find_value(signer, "model");
if (model_field.isStr() && model_field.getValStr() != "") {
name += model_field.getValStr();
}
signers.push_back(ExternalSigner(command, fingerprintStr, chain, name));
}
return true;
}
#endif

View file

@ -8,6 +8,7 @@
#include <stdexcept>
#include <string>
#include <univalue.h>
#include <util/system.h>
class ExternalSignerException : public std::runtime_error {
public:
@ -25,10 +26,30 @@ private:
public:
//! @param[in] command the command which handles interaction with the external signer
//! @param[in] fingerprint master key fingerprint of the signer
ExternalSigner(const std::string& command, const std::string& fingerprint);
//! @param[in] chain "main", "test", "regtest" or "signet"
//! @param[in] name device name
ExternalSigner(const std::string& command, const std::string& fingerprint, std::string chain, std::string name);
//! Master key fingerprint of the signer
std::string m_fingerprint;
//! Bitcoin mainnet, testnet, etc
std::string m_chain;
//! Name of signer
std::string m_name;
const std::string NetworkArg() const;
#ifdef ENABLE_EXTERNAL_SIGNER
//! Obtain a list of signers. Calls `<command> enumerate`.
//! @param[in] command the command which handles interaction with the external signer
//! @param[in,out] signers vector to which new signers (with a unique master key fingerprint) are added
//! @param chain "main", "test", "regtest" or "signet"
//! @param[out] success Boolean
static bool Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors = false);
#endif
};
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_H

View file

@ -2,36 +2,69 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparamsbase.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/strencodings.h>
#include <wallet/rpcsigner.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
#ifdef ENABLE_EXTERNAL_SIGNER
// CRPCCommand table won't compile with an empty array
static RPCHelpMan dummy()
static RPCHelpMan enumeratesigners()
{
return RPCHelpMan{"dummy",
"\nDoes nothing.\n"
"",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
return NullUniValue;
},
return RPCHelpMan{
"enumeratesigners",
"Returns a list of external signers from -signer.",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::ARR, "signers", /* optional */ false, "",
{
{RPCResult::Type::STR_HEX, "masterkeyfingerprint", "Master key fingerprint"},
{RPCResult::Type::STR, "name", "Device name"},
},
}
}
},
RPCExamples{""},
[](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
const std::string command = gArgs.GetArg("-signer", "");
if (command == "") throw JSONRPCError(RPC_WALLET_ERROR, "Error: restart bitcoind with -signer=<cmd>");
std::string chain = gArgs.GetChainName();
UniValue signers_res = UniValue::VARR;
try {
std::vector<ExternalSigner> signers;
ExternalSigner::Enumerate(command, signers, chain);
for (ExternalSigner signer : signers) {
UniValue signer_res = UniValue::VOBJ;
signer_res.pushKV("fingerprint", signer.m_fingerprint);
signer_res.pushKV("name", signer.m_name);
signers_res.push_back(signer_res);
}
} catch (const ExternalSignerException& e) {
throw JSONRPCError(RPC_WALLET_ERROR, e.what());
}
UniValue result(UniValue::VOBJ);
result.pushKV("signers", signers_res);
return result;
}
};
}
Span<const CRPCCommand> GetSignerRPCCommands()
{
// clang-format off
static const CRPCCommand commands[] =
{ // category actor (function)
// --------------------- ------------------------
{ "signer", &dummy, },
{ "signer", &enumeratesigners, },
};
// clang-format on
return MakeSpan(commands);

View file

@ -17,10 +17,16 @@ def perform_pre_checks():
sys.stdout.write(mock_result[2:])
sys.exit(int(mock_result[0]))
def enumerate(args):
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
parser = argparse.ArgumentParser(prog='./signer.py', description='External signer mock')
subparsers = parser.add_subparsers(description='Commands', dest='command')
subparsers.required = True
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
parser_enumerate.set_defaults(func=enumerate)
args = parser.parse_args()
perform_pre_checks()

View file

@ -48,5 +48,36 @@ class SignerTest(BitcoinTestFramework):
def run_test(self):
self.log.debug(f"-signer={self.mock_signer_path()}")
assert_raises_rpc_error(-4, 'Error: restart bitcoind with -signer=<cmd>',
self.nodes[0].enumeratesigners
)
# Handle script missing:
assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
self.nodes[2].enumeratesigners
)
# Handle error thrown by script
self.set_mock_result(self.nodes[1], "2")
assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
self.set_mock_result(self.nodes[1], '0 [{"type": "trezor", "model": "trezor_t", "error": "fingerprint not found"}]')
assert_raises_rpc_error(-4, 'fingerprint not found',
self.nodes[1].enumeratesigners
)
self.clear_mock_result(self.nodes[1])
# Create new wallets with private keys disabled:
self.nodes[1].createwallet(wallet_name='hww', disable_private_keys=True, descriptors=True)
hww = self.nodes[1].get_wallet_rpc('hww')
result = hww.enumeratesigners()
assert_equal(len(result['signers']), 2)
assert_equal(result['signers'][0]["fingerprint"], "00000001")
assert_equal(result['signers'][0]["name"], "trezor_t")
if __name__ == '__main__':
SignerTest().main()