mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
Merge bitcoin/bitcoin#28336: rpc: parse legacy pubkeys consistently with specific error messages
98570fe29b
test: add coverage for parsing cryptographically invalid pubkeys (Sebastian Falbesoner)c740b154d1
rpc: use `HexToPubKey` helper for all legacy pubkey-parsing RPCs (Sebastian Falbesoner)100e8a75bf
rpc: check and throw specific pubkey parsing errors in `HexToPubKey` (Sebastian Falbesoner) Pull request description: Parsing legacy public keys can fail for three reasons (in this order): - pubkey is not in hex - pubkey has an invalid length (not 33 or 65 bytes for compressed/uncompressed, respectively) - pubkey is crytographically invalid, i.e. is not on curve (`CPubKey.IsFullyValid()` check) Many RPCs currently perform these checks manually with different error messages, even though we already have a `HexToPubKey` helper. This PR puts all three checks in this helper (the length check was done on the call-sites before), adds specific error messages for each case, and consequently uses it for all RPCs that parse legacy pubkeys. This leads to deduplicated code and also to more consistent and detailed error messages for the user. Affected RPC calls are `createmultisig`, `addmultisigaddress`, `importpubkey`, `importmulti`, `fundrawtransaction`, `walletcreatefundedpsbt`, `send` and `sendall`. Note that the error code (-5 a.k.a. `RPC_INVALID_ADDRESS_OR_KEY`) doesn't change in any of the causes, so the changes are not breaking RPC API compatibility. Only the messages are more specific. The last commits adds test coverage for the cryptographically invalid (not-on-curve) pubkey case which wasn't exercised before. ACKs for top commit: stratospher: tested ACK98570fe
. davidgumberg: ACK98570fe29b
Eunovo: Tested ACK98570fe29b
achow101: ACK98570fe29b
Tree-SHA512: cfa474176e95b5b18f3a9da28fdd9e87195cd58994c1331198f2840925fff322fd323a6371feab74a1b32e4b9ea58a6dc732fa751b4cdd45402c1029af609ece
This commit is contained in:
commit
4ff42762fd
7 changed files with 19 additions and 38 deletions
|
@ -124,11 +124,7 @@ static RPCHelpMan createmultisig()
|
||||||
const UniValue& keys = request.params[1].get_array();
|
const UniValue& keys = request.params[1].get_array();
|
||||||
std::vector<CPubKey> pubkeys;
|
std::vector<CPubKey> pubkeys;
|
||||||
for (unsigned int i = 0; i < keys.size(); ++i) {
|
for (unsigned int i = 0; i < keys.size(); ++i) {
|
||||||
if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
|
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
|
||||||
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
|
|
||||||
} else {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the output type
|
// Get the output type
|
||||||
|
|
|
@ -194,11 +194,14 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList&
|
||||||
CPubKey HexToPubKey(const std::string& hex_in)
|
CPubKey HexToPubKey(const std::string& hex_in)
|
||||||
{
|
{
|
||||||
if (!IsHex(hex_in)) {
|
if (!IsHex(hex_in)) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be a hex string");
|
||||||
|
}
|
||||||
|
if (hex_in.length() != 66 && hex_in.length() != 130) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must have a length of either 33 or 65 bytes");
|
||||||
}
|
}
|
||||||
CPubKey vchPubKey(ParseHex(hex_in));
|
CPubKey vchPubKey(ParseHex(hex_in));
|
||||||
if (!vchPubKey.IsFullyValid()) {
|
if (!vchPubKey.IsFullyValid()) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + hex_in + "\" must be cryptographically valid.");
|
||||||
}
|
}
|
||||||
return vchPubKey;
|
return vchPubKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -456,12 +456,7 @@ RPCHelpMan importpubkey()
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsHex(request.params[0].get_str()))
|
CPubKey pubKey = HexToPubKey(request.params[0].get_str());
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
|
|
||||||
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
|
|
||||||
CPubKey pubKey(data);
|
|
||||||
if (!pubKey.IsFullyValid())
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
|
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
@ -983,15 +978,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
|
||||||
import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
|
import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < pubKeys.size(); ++i) {
|
for (size_t i = 0; i < pubKeys.size(); ++i) {
|
||||||
const auto& str = pubKeys[i].get_str();
|
CPubKey pubkey = HexToPubKey(pubKeys[i].get_str());
|
||||||
if (!IsHex(str)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string");
|
|
||||||
}
|
|
||||||
auto parsed_pubkey = ParseHex(str);
|
|
||||||
CPubKey pubkey(parsed_pubkey);
|
|
||||||
if (!pubkey.IsFullyValid()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
|
|
||||||
}
|
|
||||||
pubkey_map.emplace(pubkey.GetID(), pubkey);
|
pubkey_map.emplace(pubkey.GetID(), pubkey);
|
||||||
ordered_pubkeys.push_back(pubkey.GetID());
|
ordered_pubkeys.push_back(pubkey.GetID());
|
||||||
}
|
}
|
||||||
|
|
|
@ -627,15 +627,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
|
||||||
const UniValue solving_data = options["solving_data"].get_obj();
|
const UniValue solving_data = options["solving_data"].get_obj();
|
||||||
if (solving_data.exists("pubkeys")) {
|
if (solving_data.exists("pubkeys")) {
|
||||||
for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) {
|
for (const UniValue& pk_univ : solving_data["pubkeys"].get_array().getValues()) {
|
||||||
const std::string& pk_str = pk_univ.get_str();
|
const CPubKey pubkey = HexToPubKey(pk_univ.get_str());
|
||||||
if (!IsHex(pk_str)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not hex", pk_str));
|
|
||||||
}
|
|
||||||
const std::vector<unsigned char> data(ParseHex(pk_str));
|
|
||||||
const CPubKey pubkey(data.begin(), data.end());
|
|
||||||
if (!pubkey.IsFullyValid()) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("'%s' is not a valid public key", pk_str));
|
|
||||||
}
|
|
||||||
coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
||||||
// Add witness script for pubkeys
|
// Add witness script for pubkeys
|
||||||
const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
|
const CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey));
|
||||||
|
|
|
@ -490,11 +490,11 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
addr2Obj = self.nodes[2].getaddressinfo(addr2)
|
addr2Obj = self.nodes[2].getaddressinfo(addr2)
|
||||||
|
|
||||||
# Tests for createmultisig and addmultisigaddress
|
# Tests for createmultisig and addmultisigaddress
|
||||||
assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"])
|
assert_raises_rpc_error(-5, 'Pubkey "01020304" must have a length of either 33 or 65 bytes', self.nodes[0].createmultisig, 1, ["01020304"])
|
||||||
# createmultisig can only take public keys
|
# createmultisig can only take public keys
|
||||||
self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
|
self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])
|
||||||
# addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here
|
# addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here
|
||||||
assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1])
|
assert_raises_rpc_error(-5, f'Pubkey "{addr1}" must be a hex string', self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1])
|
||||||
|
|
||||||
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
|
mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
|
||||||
|
|
||||||
|
|
|
@ -461,10 +461,13 @@ class WalletTest(BitcoinTestFramework):
|
||||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
|
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
|
||||||
|
|
||||||
# This will raise an exception for attempting to import a pubkey that isn't in hex
|
# This will raise an exception for attempting to import a pubkey that isn't in hex
|
||||||
assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
|
assert_raises_rpc_error(-5, 'Pubkey "not hex" must be a hex string', self.nodes[0].importpubkey, "not hex")
|
||||||
|
|
||||||
# This will raise an exception for importing an invalid pubkey
|
# This will raise exceptions for importing a pubkeys with invalid length / invalid coordinates
|
||||||
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
|
too_short_pubkey = "5361746f736869204e616b616d6f746f"
|
||||||
|
assert_raises_rpc_error(-5, f'Pubkey "{too_short_pubkey}" must have a length of either 33 or 65 bytes', self.nodes[0].importpubkey, too_short_pubkey)
|
||||||
|
not_on_curve_pubkey = bytes([4] + [0]*64).hex() # pubkey with coordinates (0,0) is not on curve
|
||||||
|
assert_raises_rpc_error(-5, f'Pubkey "{not_on_curve_pubkey}" must be cryptographically valid', self.nodes[0].importpubkey, not_on_curve_pubkey)
|
||||||
|
|
||||||
# Bech32m addresses cannot be imported into a legacy wallet
|
# Bech32m addresses cannot be imported into a legacy wallet
|
||||||
assert_raises_rpc_error(-5, "Bech32m addresses cannot be imported into legacy wallets", self.nodes[0].importaddress, "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6")
|
assert_raises_rpc_error(-5, "Bech32m addresses cannot be imported into legacy wallets", self.nodes[0].importaddress, "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6")
|
||||||
|
|
|
@ -1054,8 +1054,8 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
assert_raises_rpc_error(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]), wallet.fundrawtransaction, raw_tx)
|
assert_raises_rpc_error(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]), wallet.fundrawtransaction, raw_tx)
|
||||||
|
|
||||||
# Error conditions
|
# Error conditions
|
||||||
assert_raises_rpc_error(-5, "'not a pubkey' is not hex", wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["not a pubkey"]})
|
assert_raises_rpc_error(-5, 'Pubkey "not a pubkey" must be a hex string', wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["not a pubkey"]})
|
||||||
assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["01234567890a0b0c0d0e0f"]})
|
assert_raises_rpc_error(-5, 'Pubkey "01234567890a0b0c0d0e0f" must have a length of either 33 or 65 bytes', wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["01234567890a0b0c0d0e0f"]})
|
||||||
assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, solving_data={"scripts":["not a script"]})
|
assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, solving_data={"scripts":["not a script"]})
|
||||||
assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, solving_data={"descriptors":["not a descriptor"]})
|
assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, solving_data={"descriptors":["not a descriptor"]})
|
||||||
assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"]}])
|
assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", wallet.fundrawtransaction, raw_tx, input_weights=[{"txid": ext_utxo["txid"]}])
|
||||||
|
|
Loading…
Add table
Reference in a new issue