Skip to content

Conversation

Sjors
Copy link
Member

@Sjors Sjors commented Jul 2, 2025

While testing MuSig2 integration in #29675 I ran into a Ledger bug LedgerHQ/app-bitcoin-new#329 that was triggered because we signed for a script path in addition to the key path. The bug itself will be fixed, but it seems useful in general to be able to skip script path signing.

For example, you may have a 2-of-2 musig() descriptor with some sort of fallback after coins are N blocks old. For both on chain privacy and fee reasons, you don't want to use this path unless absolutely necessary. But our wallet can't know if our co-signer still has their key, so we sign the script path at all times. This could lead to accidental broadcast.

This PR introduces a keypath_only option (default false for now) to:

  • the send RPC
  • the walletprocesspsbt RPC.

The resulting PSBT won't include taproot_script_path_sigs. It does still have taproot_bip32_derivs and musig2_participant_pubkeys.

The wallet_taproot.py tests are expanded to cover this behavior.

It can also be tested with Sjors#91 and a slightly modified Moosig. See LedgerHQ/app-bitcoin-new#329 for the rough steps.

Based on:

TODO:

  • maybe refactor MutableTransactionSignatureCreator
  • add test coverage for send

Potential followups:

@DrahtBot DrahtBot added the Wallet label Jul 2, 2025
@DrahtBot
Copy link
Contributor

DrahtBot commented Jul 2, 2025

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

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32857.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32964 (descriptor: don't underestimate the size of a Taproot spend (instead, overestimate it) by w0xlt)
  • #32958 (wallet/refactor: change PSBTError to PSBTResult and remove std::optionalcommon::PSBTResult and return common::PSBTResult by kevkevinpal)
  • #32896 (wallet, rpc: add v3 transaction creation and wallet support by ishaanam)
  • #32876 (refactor: use options struct for signing and PSBT operations by Sjors)
  • #29675 (wallet: Be able to receive and spend inputs involving MuSig2 aggregate keys by achow101)
  • #21283 (Implement BIP 370 PSBTv2 by achow101)

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.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jul 2, 2025

🚧 At least one of the CI tasks failed.
Task ARM, unit tests, no functional tests: https://github.com/bitcoin/bitcoin/runs/45219351756
LLM reason (✨ experimental): Compilation error due to passing a bool where an int* is expected in fillPSBT function.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@fanquake
Copy link
Member

fanquake commented Jul 2, 2025

@Sjors
Copy link
Member Author

Sjors commented Jul 2, 2025

@fanquake because it's in the fuzzer code, I incorrectly changed one of the function signatures there.

@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 967931f to 22013ba Compare July 2, 2025 16:44
@Sjors
Copy link
Member Author

Sjors commented Jul 2, 2025

From the original description:

Under the hood it adds m_hide_script_paths to the HidingSigningProvider which triggers an early return in GetTaprootSpendData() when set.

It's also missing taproot_scripts which might be a bit too much. At least in initial testing Ledger wouldn't sign it.

I probably need to implement this at the SigningProvider level rather than HidingSigningProvider. The early return needs to be inside SignTaproot() right before // Try script path spending rather than in GetTaprootSpendData(). The goal isn't to hide leaf scripts from co-signers, only to make sure we don't sign them ourselves.

@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 22013ba to 370081a Compare July 2, 2025 18:30
@Sjors
Copy link
Member Author

Sjors commented Jul 2, 2025

Shoehorning this into SigningProvider got ugly real fast. So instead I endup up passing avoid_script_path along the call stack from SignPSBTInput to SignTaproot. Which is also ugly...

Well, it compiles and works. Thoughts on how to implement this elegantly?

@Sjors
Copy link
Member Author

Sjors commented Jul 3, 2025

The walletprocesspsbt RPC now also has this option. Added test coverage by expanding wallet_taproot.py.

@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 1c4dfcb to 5542074 Compare July 3, 2025 13:55
@Sjors
Copy link
Member Author

Sjors commented Jul 3, 2025

I split off a non-behavior-changing commit that adds avoid_script_path to FillPSBT. I think it makes sense to pass this option here, although in general we may want to refactor FillPSBT to take an options struct instead of this ever expanding argument list. That could be an independent or prerequisite PR.

I then did the same for SignPSBTInput, where I also think struct would be better.

Rinse and repeat for ::SignTransaction.

Will continue later to further split 09334ae. It might make sense to add a property to BaseSignatureCreator or it subclasses?

@DrahtBot DrahtBot removed the CI failed label Jul 3, 2025
Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 4, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments that are passed to FillPSBT() and SignPSBTInput() with a PSBTOptions struct.

This makes it easier to add additional options later without large code churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the use of default boolean options safer compared to positional arguments that can easily get mixed up.
Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 4, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments that are passed to FillPSBT() and SignPSBTInput() with a PSBTOptions struct.

This makes it easier to add additional options later without large code churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the use of default boolean options safer compared to positional arguments that can easily get mixed up.
Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 4, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments that are passed to FillPSBT() and SignPSBTInput() with a PSBTOptions struct.

This makes it easier to add additional options later without large code churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the use of default boolean options safer compared to positional arguments that can easily get mixed up.
@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 5542074 to 4c6a1c7 Compare July 4, 2025 13:45
@Sjors
Copy link
Member Author

Sjors commented Jul 4, 2025

I opened #32876 and rebased this PR on top of it. That allowed me to drop 1486f05 + efc34a5.

@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 4c6a1c7 to 5192880 Compare July 4, 2025 16:02
@Sjors
Copy link
Member Author

Sjors commented Jul 4, 2025

I expanded #32876 with SignOptions. This shrinks 93fb607 to just e2a151c which is very small ... but very ugly: casting BaseSignatureCreator to MutableTransactionSignatureCreator to access its avoid_script_path option.

@Sjors
Copy link
Member Author

Sjors commented Jul 7, 2025

Sidenote: currently it's unlikely you'll accidentally spend via an older() or after() script path. This is because you need to manually set the input sequence field in the send (etc.) RPC (and manually adjust the fee rate). Found out the hard way while testing 🤦 (there's currently no useful feedback when this happens).

Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 8, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments that are passed to FillPSBT() and SignPSBTInput() with a PSBTOptions struct.

This makes it easier to add additional options later without large code churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the use of default boolean options safer compared to positional arguments that can easily get mixed up.
@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 5192880 to b0a162f Compare July 8, 2025 07:16
Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 8, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments which
are passed to FillPSBT() and SignPSBTInput() with a PSBTFillOptions struct.

This makes it easier to add additional options later without large code
churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the
use of default boolean options safer compared to positional arguments
that can easily get mixed up.
@Sjors Sjors force-pushed the 2025/07/no_script_path branch from b0a162f to 33eaf87 Compare July 8, 2025 11:30
Sjors added a commit to Sjors/bitcoin that referenced this pull request Jul 11, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments which
are passed to FillPSBT() and SignPSBTInput() with a PSBTFillOptions struct.

This makes it easier to add additional options later without large code
churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the
use of default boolean options safer compared to positional arguments
that can easily get mixed up.
@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 33eaf87 to 50023a9 Compare July 14, 2025 09:02
Sjors added 7 commits July 29, 2025 20:44
Replace the sign, finalize , bip32derivs and sighash_type arguments which
are passed to FillPSBT() and SignPSBTInput() with a PSBTFillOptions struct.

This makes it easier to add additional options later without large code
churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the
use of default boolean options safer compared to positional arguments
that can easily get mixed up.
Expand taproot tests to cover avoid_script_path in  walletprocesspsbt.

When avoiding script paths, there's no need for the workaround that increases fee_rate to compensate for the wallet's inability to estimate fees for script path spends. We use this to indirectly test that key path was used.

We also check that taproot_script_path_sigs is not set.

Finally, for transactions that can't be signed using their key path, we try again by allowing the script path.

Additional test extended private keys were extracted from other tests.
@Sjors Sjors force-pushed the 2025/07/no_script_path branch from 50023a9 to db07d06 Compare July 29, 2025 18:52
Sjors added a commit to Sjors/bitcoin that referenced this pull request Aug 1, 2025
Replace the sign, finalize , bip32derivs and sighash_type arguments which
are passed to FillPSBT() and SignPSBTInput() with a PSBTFillOptions struct.

This makes it easier to add additional options later without large code
churn, such as avoid_script_path proposed in bitcoin#32857. It also makes the
use of default boolean options safer compared to positional arguments
that can easily get mixed up.
@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

@Sjors
Copy link
Member Author

Sjors commented Aug 25, 2025

Holding off on rebasing until #32876 is merged or I have to touch it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants