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
test: fix keys_to_multisig_script
(P2MS) helper for n/k > 16
#28312
base: master
Are you sure you want to change the base?
test: fix keys_to_multisig_script
(P2MS) helper for n/k > 16
#28312
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. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
73c2def
to
85c01bd
Compare
Maybe add a functional test that would have failed too? |
Yeah, seems like a good idea. Added one in |
The helper assumes that the n and k values have to be provided as a single byte push operation, which is only possible for values up to 16. Fix that by passing the numbers directly to the CScript list, where it's automatically converted to minimally-encoded pushes (see class method `CScript.__coerce_instance`, branch `isinstance(other, int)`). In case of 17..20, this means that the data-pushes are done with two bytes using OP_PUSH1 (0x01), e.g. for n=20: 0x01,0x14
16269ef
to
d113709
Compare
self.assertEqual(len(normal_ms_script), 1 + 16*34 + 1 + 1) | ||
self.assertTrue(normal_ms_script.startswith(bytes([OP_15]))) | ||
self.assertTrue(normal_ms_script.endswith(bytes([OP_16, OP_CHECKMULTISIG]))) | ||
|
||
# check correct encoding of P2MS script with n,k > 16 | ||
max_ms_script = keys_to_multisig_script([fake_pubkey]*20, k=19) | ||
self.assertEqual(len(max_ms_script), 2 + 20*34 + 2 + 1) |
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.
Should we add a comment explaining the logic behind the math in these script length assert calls?
Or maybe refer an existing doc?
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.
Trying to understand why is there this else
block in coerce_instance
here that encodes using pushdata.
@theStack Do you know the reason by chance?
https://github.com/bitcoin/bitcoin/blob/master/test/functional/test_framework/script.py#L451-L452
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.
@rkrux Numbers between -1 and 16 inclusive can use the 1-byte OP_n
opcodes. Larger numbers require a script push (which is 2 bytes for numbers up to 127).
self.log.info('Check correct encoding of multisig script for all n (1..20)') | ||
for nkeys in range(1, 20+1): |
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: Checking for integers between 15 and 17 would also suffice, right?
# note that the 'legacy' address type fails for n values larger than 15 | ||
# due to exceeding the P2SH size limit (520 bytes), so we use 'bech32' instead | ||
# (for the purpose of this encoding test, we don't care about the resulting address) |
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.
Thanks for adding the comment, helpful!
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 d113709
Testing Methodology
- Checked out PR
- Built core
- Ran make check
- Ran test/functional/test_runner.py
self.log.info('Check correct encoding of multisig script for all n (1..20)') | ||
for nkeys in range(1, 20+1): | ||
keys = [self.pub[0]]*nkeys | ||
expected_ms_script = keys_to_multisig_script(keys, k=nkeys) # simply use n-of-n |
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.
Curious: Is it common to test the correctness of a test utility function against the response from the bitcoin node?
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.
Great work catching the bug and making testing more robust.
ACK for d113709.
One inline recommendation to cover an additional related edge case.
# (for the purpose of this encoding test, we don't care about the resulting address) | ||
res = self.nodes[0].createmultisig(nrequired=nkeys, keys=keys, address_type='bech32') | ||
assert_equal(res['redeemScript'], expected_ms_script.hex()) | ||
|
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.
Since > 20 keys are not allowed when creating a multisig address, recommend adding a test case to check this.
Something like:
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index 4bbd2a526d..88f4e44a65 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -129,6 +129,9 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
res = self.nodes[0].createmultisig(nrequired=nkeys, keys=keys, address_type='bech32')
assert_equal(res['redeemScript'], expected_ms_script.hex())
+ self.log.info('Check that number of keys greater than 20 is not allowed')
+ assert_raises_rpc_error(-8, "Number of keys involved in the multisignature address creation > 20", self.nodes[0].createmultisig, 20, [self.pub[0]]*21, "bech32")
+
def check_addmultisigaddress_errors(self):
if self.options.descriptors:
return
🐙 This pull request conflicts with the target branch and needs rebase. |
While reviewing #28307, I noticed that the test framework's
key_to_multisig_script
helper (introduced in #23305) is broken for pubkey count (n) and threshold (k) values larger than 16. This is due to the implementation currently enforcing a direct single-byte data push (usingCScriptOp.encode_op_n
), which obviously fails for values 17+. Fix that by passing the numbers directly to the CScript list, where it's automatically converted to minimally-encoded pushes (see class methodCScript.__coerce_instance
, branchisinstance(other, int)
).The second commit adds a unit test to ensure that the encoding is correct.