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 MiniWallet script-path spend (missing parity bit in leaf version) #30076
base: master
Are you sure you want to change the base?
test: fix MiniWallet script-path spend (missing parity bit in leaf version) #30076
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. |
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.
tACK e4abac2
Reviewing #29939 previously made it easy for me to review this one!
Make successful and all functional tests pass on this branch. Using the shared patch, I can see these 2 tests fails in master branch.
feature_versionbits_warning.py | ✖ Failed | 3 s
wallet_fundrawtransaction.py --descriptors | ✖ Failed | 17 s
ALL | ✖ Failed | 2752 s (accumulated)
@@ -187,7 +186,11 @@ def sign_tx(self, tx, fixed_length=True): | |||
elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE: | |||
tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin) | |||
for i in tx.wit.vtxinwit: | |||
i.scriptWitness.stack = [CScript([OP_TRUE]), bytes([LEAF_VERSION_TAPSCRIPT]) + self._internal_key] | |||
leaf_info = self._taproot_info.leaves["only-path"] |
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.
I feel we should tie this only-path
here to taproot_construct
call in address.py
, either by passing it as a parameter or creating it as a constant. Otherwise, it makes you wonder here at this line where did only-path
come from.
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. I think what we could do here is first asserting that there is only a single entry in the _taproot_info.leaves
dictionary and then using that entry, so we don't even need the "only-path" magic string. Will look into it.
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.
Yes, this is also good.
i.scriptWitness.stack = [ | ||
leaf_info.script, | ||
bytes([leaf_info.version | self._taproot_info.negflag]) + self._taproot_info.internal_pubkey, | ||
] |
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.
Would love to see the corresponding cpp reference code, share if you know?
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.
The corresponding C++ code in Core for creating the control block (second item on the witness stack) can be found in the signingprovider module, see function TaprootBuilder::GetSpendData()
:
bitcoin/src/script/signingprovider.cpp
Lines 414 to 417 in 2cedb42
std::vector<unsigned char> control_block; | |
control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size()); | |
control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0); | |
std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1); |
The actual witness stack, consisting of script and control block, is assembled in the SignTaproot
function:
Lines 379 to 380 in 2cedb42
result_stack.emplace_back(std::begin(script), std::end(script)); // Push the script | |
result_stack.push_back(*control_blocks.begin()); // Push the smallest control block |
The validation counter-part for taproot script-path spends is found here:
bitcoin/src/script/interpreter.cpp
Lines 1923 to 1932 in 2cedb42
// Script path spending (stack size is >1 after removing optional annex) | |
const valtype& control = SpanPopBack(stack); | |
const valtype& script = SpanPopBack(stack); | |
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) { | |
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE); | |
} | |
execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script); | |
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash)) { | |
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); | |
} |
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, thanks for sharing, I'm digging into this!
Rather than only returning the internal key from the P2TR anyone-can-spend address creation routine, provide the whole TaprootInfo object, which in turn contains a dictionary of TaprootLeafInfo object for named leaves. This data is used in MiniWallet for the default ADDRESS_OP_TRUE mode, in order to deduplicate the witness script and leaf version of the control block.
…rsion) This commit fixes a dormant bug in MiniWallet that exists since support for P2TR was initially added in bitcoin#23371 (see commit 041abfe). In the course of spending the output, the leaf version byte of the control block in the witness stack doesn't set the parity bit, i.e. we were so far just lucky that the used combinations of relevant data (internal pubkey, leaf script / version) didn't result in a tweaked pubkey with odd y-parity. If that was the case, we'd get the following validation error: `mandatory-script-verify-flag-failed (Witness program hash mismatch) (-26)` Since MiniWallets can now optionally be tagged (bitcoin#29939), resulting in different internal pubkeys, the issue is more prevalent now. Fix it by passing the parity bit, as specified in BIP341.
e4abac2
to
57eb590
Compare
Thanks for the review @rkrux! Addressed your comment #30076 (comment); the single taproot leaf is now accessed directly rather than by name, so there is no magic string involved anymore. Note that the leaf name (second parameter to |
This PR fixes a dormant bug in MiniWallet that exists since support for P2TR was initially added in #23371 (see commit 041abfe).
In the course of spending the output, the leaf version byte of the control block in the witness stack doesn't set the parity bit, i.e. we were so far just lucky that the used combinations of relevant data (internal pubkey, leaf script / version) didn't result in a tweaked pubkey with odd y-parity. If that was the case, we'd get the following validation error:
mandatory-script-verify-flag-failed (Witness program hash mismatch) (-26)
Since MiniWallets can now optionally be tagged (#29939), resulting in different internal pubkeys, the issue is more prevalent now. Fix it by passing the parity bit, as specified in BIP341.
Can be tested with the following patch (fails on master, succeeds on PR):