-
Notifications
You must be signed in to change notification settings - Fork 38.7k
rpc: parse legacy pubkeys consistently with specific error messages #28336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rpc: parse legacy pubkeys consistently with specific error messages #28336
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsNo conflicts as of last run. |
|
Concept ACK |
kevkevinpal
left a comment
There was a problem hiding this 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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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!
194b4e6 to
0f7a113
Compare
|
Rebased on master (to fix CI). |
stratospher
left a comment
There was a problem hiding this 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.
src/wallet/rpc/addresses.cpp
Outdated
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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}")
breakIf 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.
There was a problem hiding this comment.
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`).
0f7a113 to
98570fe
Compare
|
tested ACK 98570fe. |
achow101
left a comment
There was a problem hiding this 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"); |
There was a problem hiding this comment.
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".
| 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) { |
There was a problem hiding this comment.
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"]) |
There was a problem hiding this comment.
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"]}) |
There was a problem hiding this comment.
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
|
ACK 98570fe I tested this PR manually by compiling with legacy wallet support and passing bad pubkeys with the |
|
Tested ACK 98570fe |
Parsing legacy public keys can fail for three reasons (in this order):
CPubKey.IsFullyValid()check)Many RPCs currently perform these checks manually with different error messages, even though we already have a
HexToPubKeyhelper. 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,sendandsendall.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.