Skip to content

Conversation

@theStack
Copy link
Contributor

@theStack theStack commented Aug 24, 2023

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.

@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 24, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK stratospher, achow101, Eunovo, davidgumberg
Concept ACK sipa
Stale ACK kevkevinpal

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

No conflicts as of last run.

@sipa
Copy link
Member

sipa commented Aug 24, 2023

Concept ACK

Copy link
Contributor

@kevkevinpal kevkevinpal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 194b4e6

added one comment but feel free to ignore

src/rpc/util.cpp Outdated
Comment on lines +184 to +186
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: not sure how often this HexToPubKey is called but I'm pretty sure
hex_in.length() != 66 && hex_in.length() != 130
will fail faster than
!IsHex(hex_in)
so it would be preferred to do that first

feel free to ignore this comment if that efficiency bump isn't a concern

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply. I think the exact order of the checks doesn't really matter here from a performance perspective, since in the typical case (if the passed pubkey is valid) all three of them are executed anyway. Also, if users pass in something that has a different format like e.g. a Bitcoin address, the error message "pubkey has to be in hex" seems to be more useful than "pubkey has the wrong length".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup makes sense and I agree!

@theStack theStack force-pushed the 202308-rpc-consistent_legacy_pubkey_parsing_errors branch from 194b4e6 to 0f7a113 Compare January 22, 2024 12:41
@theStack
Copy link
Contributor Author

Rebased on master (to fix CI).

Copy link
Contributor

@stratospher stratospher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK! liked how the error handling is done in a consitent manner now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b8ee41b: is it impossible to have valid bitcoin addresses which are just hex characters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. For legacy addresses on mainnet, it's very unlikely, but indeed theoretically possible to have a valid address that IsHex, i.e. consists of an even number of hex characters. One example for this is 111111111111111111126C31DfD9, found with the following Python script using our test framework:

#!/usr/bin/env python3
from test_framework.address import keyhash_to_p2pkh

for i in range(100000):
    addr = keyhash_to_p2pkh(i.to_bytes(20, 'big'), main=True)
    try:
        bytes.fromhex(addr)
    except:
        continue
    print(f"mainnet legacy address in hex format found: {addr}")
    break

If we only consider pubkey-hashes constructed from valid pubkeys (the example above is not), I'd assume that the chance to get a hex-address is negligible. Still, to be on the safe side and to avoid changing behaviour, I restored the length-check conditions after the IsHex call in addmultisigaddress in the first commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting script! sounds good to be on safer side.

nit: only if you have to retouch, you could update commit message in 100e8a7 to not mention addmultisigaddress

In the helper `HexToPubKey`, check for three different causes of legacy
public key parsing errors (in this order):

    - pubkey is not a hex string
    - pubkey doesn't have a valid length (33 or 65 bytes) [NEW]
    - pubkey is cryptographically invalid, i.e. not on curve
      (`IsFullyValid` check)

and throw a specific error message for each one. Note that the error
code is identical for all of them (-5), so this doesn't break RPC API
compatibility.

The helper is currently used for the RPCs `createmultisig` and
`addmultisigaddress`. The length checks can be removed from the
call-sites and error message checks in the functional tests are adapted.
This deduplicates code and leads to more consistent and detailed error
messages. Affected are legacy import RPCs (`importpubkey`,
`importmulti`) and other ones where solving data can be provided
(`fundrawtransaction`, `walletcreatefundedpsbt`, `send`, `sendall`).
@theStack theStack force-pushed the 202308-rpc-consistent_legacy_pubkey_parsing_errors branch from 0f7a113 to 98570fe Compare February 9, 2024 12:43
@stratospher
Copy link
Contributor

tested ACK 98570fe.

@DrahtBot DrahtBot requested review from kevkevinpal and sipa February 9, 2024 14:00
@achow101 achow101 requested review from achow101 and josibake and removed request for kevkevinpal April 9, 2024 15:46
Copy link
Member

@achow101 achow101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 98570fe

{
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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 100e8a7 "rpc: check and throw specific pubkey parsing errors in HexToPubKey"

nit: The error message reads a little bit weirdly to me. I think it would make more sense to use "is not" rather than "must be", e.g. "Pubkey abcd is not a hex string".

@DrahtBot DrahtBot requested a review from kevkevinpal April 15, 2024 17:51
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: IMO, the length check should happen before the IsHex check, since a string with an odd-numbered length of valid hex characters will fail with the slightly confusing error message that it "must be a hex string." For example, 67 zeroes:

$ bitcoin-cli importpubkey 0000000000000000000000000000000000000000000000000000000000000000000
error code: -5
error message:
Pubkey "0000000000000000000000000000000000000000000000000000000000000000000" must be a hex string


# 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"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit-question: Should there be a test added here that checks for pubkeys that are not valid points as in 98570fe?

Or, since this PR deduplicates pubkey parsing, is this test of the length check redundant and worthy of being removed?

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, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["01234567890a0b0c0d0e0f"]})
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, 'Pubkey "01234567890a0b0c0d0e0f" must have a length of either 33 or 65 bytes', wallet.fundrawtransaction, raw_tx, solving_data={"pubkeys":["01234567890a0b0c0d0e0f"]})
Copy link
Contributor

@davidgumberg davidgumberg May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit-question as in 100e8a7

@davidgumberg
Copy link
Contributor

davidgumberg commented May 3, 2024

ACK 98570fe
The deduplication of pubkey parsing here is a big improvement, and granular pubkey parsing error messages make the rpc interface easier to use. As far as I could find, no instances of pubkey parsing in the rpc code were missed here.

I tested this PR manually by compiling with legacy wallet support and passing bad pubkeys with the importpubkey rpc.

@Eunovo
Copy link
Contributor

Eunovo commented May 4, 2024

Tested ACK 98570fe
Verified that all checks are covered in tests

@achow101 achow101 merged commit 4ff4276 into bitcoin:master May 8, 2024
@theStack theStack deleted the 202308-rpc-consistent_legacy_pubkey_parsing_errors branch May 8, 2024 22:57
@bitcoin bitcoin locked and limited conversation to collaborators May 8, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants