Skip to content

Conversation

@afilini
Copy link
Member

@afilini afilini commented Apr 20, 2021

Description

Unless explicitly asked not to (by enabling a signer option), we will now by default require the presence of the non_witness_utxo when signing SegWit transactions as well, to mitigate the risk of an attacker tricking the wallet into paying more fees than it expects.

The force_non_witness_utxo option which was off by default has been replaced with only_witness_utxo which is also disabled by default, but since it's basically the opposite of the one we had previously, it effectively inverts the default behavior from not including the non_witness_utxo to providing it.

Notes to the reviewers

This needed a bit of refactoring in the signing part to introduce some options that can be provided to tweak the behavior of the signer. I don't particularly like having that extra method on our Signer trait, but I couldn't come up with a better alternative.

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature
  • I've updated CHANGELOG.md
  • This pull request breaks the existing API

@LLFourn
Copy link
Collaborator

LLFourn commented Apr 22, 2021

Concept ACK. I think there might be a simpler approach.

  1. Add another argument to Wallet::sign which indicates whether it should check for missing non_witness_utxo. Turning on this check should be decided dynamically by the caller because it depends on the context of the signing; it shouldn't be on the Signer I don't think. Consider adding a struct SignOptions passed to Wallet::sign with assume_height and trust_witness_utxo which is default false. Just do the check in Wallet::sign.
  2. The "SegWit bug" only applies to v0 outputs -- it's fixed in v1 I think. So really you probably want three possible values here: (i) don't force, (ii) force v0 (iii) force all. I guess you need force all since some wallets may be stubborn about this or not upgraded.

@afilini
Copy link
Member Author

afilini commented Apr 22, 2021

Consider adding a struct SignOptions passed to Wallet::sign with assume_height and trust_witness_utxo which is default false

This was more or less how I implemented it initially, but there was the issue that signers like hardware wallets would basically ignore the options. I mean, I hope there's no way to ask an HW to sign a transaction while asking it to trust some parts without complaining 😅 So I came to the conclusion that this is more of a Signer-specific thing, we can control it for our signers but not for all of them. Maybe a good compromise is to have a set_options() method on Signer that returns a Result, so you could try to set the options and it would work fine on our signers but fail on HWs.

The "SegWit bug" only applies to v0 outputs -- it's fixed in v1 I think.

That's a good point

@LLFourn
Copy link
Collaborator

LLFourn commented Apr 26, 2021

Consider adding a struct SignOptions passed to Wallet::sign with assume_height and trust_witness_utxo which is default false

This was more or less how I implemented it initially, but there was the issue that signers like hardware wallets would basically ignore the options. I mean, I hope there's no way to ask an HW to sign a transaction while asking it to trust some parts without complaining 😅 So I came to the conclusion that this is more of a Signer-specific thing, we can control it for our signers but not for all of them.

I think you misunderstood my suggestion: put the check in Wallet::sign before any signer is called to sign anything. To be clear:

    pub fn sign(&self, mut psbt: PSBT, sign_options: SignOptions) -> Result<(PSBT, bool), Error> {
        // this helps us doing our job later
        self.add_input_hd_keypaths(&mut psbt)?;
        
        if !sign_options.trust_witness_utxo {
             for input in psbt.input {
                 if input.non_witness_utxo.is_none() {
                     return Err(Error::MissingNonWitnessUtxo)
                  }
             }
        }

        for signer in self
            .signers
            .signers()
            .iter()
            .chain(self.change_signers.signers().iter())
        {
            if signer.sign_whole_tx() {
                signer.sign(&mut psbt, None, &self.secp)?;
            } else {
                for index in 0..psbt.inputs.len() {
                    signer.sign(&mut psbt, Some(index), &self.secp)?;
                }
            }
        }

This means it doesn't matter whether they are hardware wallets at all.

@afilini afilini force-pushed the fix/signer-witness-utxo branch 2 times, most recently from 933b2c6 to f23de96 Compare May 5, 2021 14:34
@afilini afilini force-pushed the fix/signer-witness-utxo branch 2 times, most recently from 2207755 to 89b5245 Compare May 5, 2021 15:22
afilini added 2 commits May 6, 2021 08:58
Instead of providing an opt-in option to force the addition of the
`non_witness_utxo`, we will now add them by default and provide the
option to disable them when they aren't considered necessary.
@afilini afilini force-pushed the fix/signer-witness-utxo branch from 89b5245 to 17bcd8e Compare May 6, 2021 06:58
@afilini afilini force-pushed the fix/signer-witness-utxo branch from b1b1694 to 3c7bae9 Compare May 6, 2021 09:39
@afilini afilini mentioned this pull request May 6, 2021
3 tasks
@afilini afilini merged commit 3c7bae9 into bitcoindevkit:master May 6, 2021
@afilini afilini deleted the fix/signer-witness-utxo branch May 6, 2021 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants