Skip to content
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

Remove BIP 69 sorting #534

Closed
TheBlueMatt opened this issue Jan 26, 2022 · 19 comments · Fixed by #1487
Closed

Remove BIP 69 sorting #534

TheBlueMatt opened this issue Jan 26, 2022 · 19 comments · Fixed by #1487
Labels
bug Something isn't working good first issue Good for newcomers
Milestone

Comments

@TheBlueMatt
Copy link

BIP 69 input+output sorting was a very bad idea from day one (it relies on the assumption that all wallets sort their inputs+outputs according to BIP 69 - something that will never happen - in order to provide privacy). It has no place in BDK. Instead, transaction input and output ordering should simply be randomized, making it much harder for a transaction classifier to sort transactions.

@TheBlueMatt TheBlueMatt added the bug Something isn't working label Jan 26, 2022
@LLFourn
Copy link
Contributor

LLFourn commented Jan 27, 2022

+1. Thanks for bringing this up. Before depreciating BIP69 I think we should make it possible to set the rng for shuffling so you still have a deterministic sorting option. I'd like this so it's still easy to have two wallets come up with the same input/output ordering.

@notmandatory
Copy link
Member

For context BDK currently defaults to Shuffle for input and output ordering, but based on the discussion from rust-bitcoin/rust-bitcoin#810 I agree we should remove the BIP69 option.

/// Ordering of the transaction's inputs and outputs
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum TxOrdering {
/// Randomized (default)
Shuffle,
/// Unchanged
Untouched,
/// BIP69 / Lexicographic
Bip69Lexicographic,
}
impl Default for TxOrdering {
fn default() -> Self {
TxOrdering::Shuffle
}
}

@notmandatory
Copy link
Member

... Before depreciating BIP69 I think we should make it possible to set the rng for shuffling so you still have a deterministic sorting option. I'd like this so it's still easy to have two wallets come up with the same input/output ordering.

@LLFourn Do you need to have deterministic random sorting for testing or are there other uses for it? If only for #[cfg(test)] the sort_tx function already uses a deterministic rng , but if there are other use cases then should we add an optional rng parameter to the TxOrdering.Shuffle variant which seems kind of obtrusive.. maybe better to create a new variant of TxOrdering.ShuffleWIthRng with the extra param or something like that?

impl TxOrdering {
/// Sort transaction inputs and outputs by [`TxOrdering`] variant
pub fn sort_tx(&self, tx: &mut Transaction) {
match self {
TxOrdering::Untouched => {}
TxOrdering::Shuffle => {
use rand::seq::SliceRandom;
#[cfg(test)]
use rand::SeedableRng;
#[cfg(not(test))]
let mut rng = rand::thread_rng();
#[cfg(test)]
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
tx.output.shuffle(&mut rng);
}
TxOrdering::Bip69Lexicographic => {
tx.input.sort_unstable_by_key(|txin| {
(txin.previous_output.txid, txin.previous_output.vout)
});
tx.output
.sort_unstable_by_key(|txout| (txout.value, txout.script_pubkey.clone()));
}
}
}
}

@TheBlueMatt
Copy link
Author

I'm not sure what @LLFourn was specifically looking for, but that makes sense - if you have some two-party protocol (like lightning, but lightning has a different, worse, way of doing this) where two nodes have to agree on a transaction and just want to exchange signatures, you need some way to sort the inputs+outputs such that they both agree, without having to bother communicating. You can do something deterministic (like BIP 69) but its much better to instead take some random secret value that both parties already have and use that to seed your RNG. Of course you have to ensure your RNG then generates deterministic output given the seed (I'm not sure if the rand crate provides that as an API guarantee or not, but its easy to write a shuffler without it).

@LLFourn
Copy link
Contributor

LLFourn commented Jan 27, 2022

... Before depreciating BIP69 I think we should make it possible to set the rng for shuffling so you still have a deterministic sorting option. I'd like this so it's still easy to have two wallets come up with the same input/output ordering.

@LLFourn Do you need to have deterministic random sorting for testing or are there other uses for it?

I need it for the scenario @TheBlueMatt mentioned.

You can do something deterministic (like BIP 69) but its much better to instead take some random secret value that both parties already have and use that to seed your RNG. Of course you have to ensure your RNG then generates deterministic output given the seed (I'm not sure if the rand crate provides that as an API guarantee or not, but its easy to write a shuffler without it).

Right I currently use BIP69 in https://gun.fun because it's a simple way of agreeing on an order for a dual funded transaction (privacy is not relevant here atm since tx is distinguishable for other reasons). Good point on ensuring it's API stable. Looking at the current implementation of shuffle it's 3 lines of code so should be easy enough to roll our own.

Just looking around it looks like this deterministic sorting of transactions based on secret data is something that people have been wanting: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-October/016457.html. Perhaps if the person who takes this issue feels so inspired they could come up with something that might be easily specifiable (might not be the way rand does it).

@afilini
Copy link
Member

afilini commented Jan 27, 2022

Agreed that we can deprecate it to show a warning that it should not be used, but I'm not sure I would remove it completely anytime soon. The way I see BDK is not just as a library to build wallets today, but also as a "swiss army knife" that can do anything. Like, if you have a 5 yr old project and you want to port it to BDK, you should be able to do that.

So in theory anything that has ever been used should be supported here (unless it's completely broken, but I don't think BIP69 is). But I agree that we should discourage new users from building something that relies on it.

For the "custom sorting" scenario, I'm wondering if instead of an Rng we could just take a "comparing" callback like the std sorting functions do, and then internally we just use that. I'm not particularly in favor or against any of the options, just wanted to remind you that this alternative exists.

@TheBlueMatt
Copy link
Author

So in theory anything that has ever been used should be supported here (unless it's completely broken, but I don't think BIP69 is)

Right, here is where we (strongly) disagree. BIP 69 is completely and totally broken, IMO. Absent use-cases like @LLFourn's, it has absolutely no place in a Bitcoin wallet whatsoever.

@LLFourn
Copy link
Contributor

LLFourn commented Jan 29, 2022

Agreed that we can deprecate it to show a warning that it should not be used, but I'm not sure I would remove it completely anytime soon. The way I see BDK is not just as a library to build wallets today, but also as a "swiss army knife" that can do anything. Like, if you have a 5 yr old project and you want to port it to BDK, you should be able to do that.

So in theory anything that has ever been used should be supported here (unless it's completely broken, but I don't think BIP69 is). But I agree that we should discourage new users from building something that relies on it.

Permanent depreciation sounds like a good solution. BIP69 can be useful in some odd cases but asking you to suppress a warning if you actually find yourself in a situation where it is useful seems reasonable.

For the "custom sorting" scenario, I'm wondering if instead of an Rng we could just take a "comparing" callback like the std sorting functions do, and then internally we just use that. I'm not particularly in favor or against any of the options, just wanted to remind you that this alternative exists.

I really like this idea. Actually a custom Rng doesn't even do the whole job because you'd still need to sort everything lexically first. At the cost of doing a sha256 call per input/output you could do the following sort function:

let shared_secret = ...;
let cmp_fn = |a,b| {
    sha256(shared_secret, a).cmp(sha256(shared_secret, b))
};

This will give the same ordering for both parties if they both have shared_secret but will look random to everyone else. I think this is better than lexically sorting and then having a custom rng to shiuffle again afterwards. It's a good candidate for a replacement to BIP69 too.

@notmandatory notmandatory moved this to Todo in BDK Maintenance Feb 1, 2022
@notmandatory notmandatory added the good first issue Good for newcomers label Feb 8, 2022
@Eunoia1729
Copy link
Contributor

Eunoia1729 commented Feb 10, 2022

Greetings everyone, 👋
I'd be happy to work on this issue.
I'm new to this project and I just started learning rust to contribute here. I hope this will be a good starting point for contributing.

Can I take this up ? I'll also be glad to be guided a bit.

@LLFourn
Copy link
Contributor

LLFourn commented Feb 10, 2022

@Eunoia1729 Go for it! Probably the best approach right now is to do what @afilini suggested and allowing a custom comparison function. So I guess this means adding a Custom variant to TxOrdering in tx_builder.rs (and deprecating BIP69). Something like:

pub enum TxOrdering {
    /// Randomized (default)
    Shuffle,
    /// Unchanged
    Untouched,
    /// BIP69 / Lexicographic
    #[derprecated = "BIP69 does not improve privacy as was the intention of the BIP"]
    Bip69Lexicographic,
    /// Provide custom comparison functions for sorting
    Custom { 
         inputs: Box<dyn Fn(&TxIn,&TxIn) -> std::cmp::Ordering>,
         outputs: Box<dyn Fn(&TxOut, &TxOut) -> std::cmp::Ordering>
     }
}

I think that makes sense but don't be surprised if you find a better way of doing it :)

@Yureien
Copy link
Contributor

Yureien commented Feb 26, 2022

Hey @LLFourn, I was trying to implement it the way you suggested but that would require changing the derived traits since the current ones (Eq/Ord/Hash/Copy straight up won't work, Clone might work but would be weird, Debug will be pointless?) won't work if it's a Fn. It gets really complicated to do it that way, and I don't think it's possible without some major changes.

What if we do it like say,

wallet.build_tx()
    .ordering(TxOrdering::Custom)
    .add_input_ordering(<comparison function here>)
    .add_output_ordering(<comparison function here>)
    ....

This way, either the input or output can be added (if they aren't added, it behaves like Untouched.

@LLFourn
Copy link
Contributor

LLFourn commented Feb 26, 2022

@fadedcoder do we actually need those derives for anything? I wouldn't have thought so. Try removing them. If we need Clone for some reason just make it an Arc rather than a Box. Also it makes more sense for it to be dyn FnMut rathan than dyn Fn.

What you suggested would work but it'd be suboptimal.

@Yureien
Copy link
Contributor

Yureien commented Feb 27, 2022

@LLFourn Yeah that sounds good. Also I think it would be better to use non immutable Fn since if we add mutability with an Arc, it'll become more complex (we'll need to add a Mutex too). Is it worth supporting FnMut against the extra complexity?

@LLFourn
Copy link
Contributor

LLFourn commented Feb 28, 2022

Is it worth supporting FnMut against the extra complexity?

Ok I see your point. If it does need to be an Arc because of the clone thing then yeah I guess Fn is the right answer for now.

@notmandatory
Copy link
Member

@Eunoia1729 have you started looking into this issue? If so can you help @fadedcoder with testing/review for #556 ?

@Eunoia1729
Copy link
Contributor

@notmandatory Yes, I was working on it but paused when a PR was opened. Sure, will be happy to help with testing/review !

@danielabrozzoni
Copy link
Member

I'm unassigning @Yureien as he hasn't updated #556 in a long while.

If someone else wants to take a shot at this, it's all yours! You should probably use #556's code, as it was almost done.

@nymius
Copy link
Contributor

nymius commented Jun 21, 2024

@rustaceanrob are you working on this?

@notmandatory notmandatory added this to the 1.0.0-alpha milestone Jun 21, 2024
@notmandatory notmandatory added this to BDK Jun 21, 2024
@notmandatory notmandatory moved this to Needs Review in BDK Jun 21, 2024
@rustaceanrob
Copy link
Contributor

All yours. I would pick up where #556 left off but just remove BIP69 entirely. Adding a closure for input and output is sufficient imo. FYI there might be merge conflicts on your feature branch soon if #1395 gets merged

@notmandatory notmandatory moved this from Needs Review to In Progress in BDK Jun 21, 2024
@notmandatory notmandatory moved this from In Progress to Needs Review in BDK Jul 1, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Done in BDK Maintenance Jul 5, 2024
@github-project-automation github-project-automation bot moved this from Needs Review to Done in BDK Jul 5, 2024
notmandatory added a commit to notmandatory/bdk that referenced this issue Jul 5, 2024
…e BIP69

3bee563 refactor(wallet)!: remove TxOrdering::Bip69Lexicographic (nymius)
e5cb7b2 feat(wallet): add TxOrdering::Custom (FadedCoder)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  Resolves bitcoindevkit#534.

  Resumes from the work in bitcoindevkit#556.

  Add custom sorting function for inputs and outputs through `TxOrdering::Custom` and deprecates `TxOrdering::Bip69Lexicographic`.

  <!-- Describe the purpose of this PR, what's being adding and/or fixed -->

  ### Notes to the reviewers

  I tried consider all discussions in bitcoindevkit#534 while implementing some changes to the original PR. I created a summary of the considerations I had while implementing this:

  ##### Why use smart pointers?
  The size of enums and structs should be known at compilation time. A struct whose fields implements some kind of trait cannot be specified without using a smart pointer because the size of the implementations of the trait cannot be known beforehand.

  ##### Why `Arc` or `Rc` instead of `Box`?
  The majority of the useful smart pointers that I know (`Arc`, `Box`, `Rc`) for this case implement `Drop` which rules out the implementation of `Copy`, making harder to manipulate a simple enum like `TxOrdering`. `Clone` can be used instead, implemented by `Arc` and `Rc`, but not implemented by `Box`.

  #####  Why `Arc` instead of `Rc`?
  Multi threading I guess.

  ##### Why using a type alias like `TxVecSort`?
  cargo-clippy was accusing a too complex type if using the whole type inlined in the struct inside the enum.

  ##### Why `Fn` and not `FnMut`?
  `FnMut` is not allowed inside `Arc`. I think this is due to the `&mut self` ocupies the first parameter of the `call` method when desugared (https://rustyyato.github.io/rust/syntactic/sugar/2019/01/17/Closures-Magic-Functions.html), which doesn't respects `Arc` limitation of not having mutable references to data stored inside `Arc`:
  Quoting the [docs](https://doc.rust-lang.org/std/sync/struct.Arc.html):
  > you cannot generally obtain a mutable reference to something inside an `Arc`.

  `FnOnce` > `FnMut` > `Fn`, where `>` stands for "is supertrait of", so, `Fn` can be used everywhere `FnMut` is expected.

  ##### Why not `&'a dyn FnMut`?
  It needs to include a lifetime parameter in `TxOrdering`, which will force the addition of a lifetime parameter in `TxParams`, which will require the addition of a lifetime parameter in a lot of places more. **Which one is preferable?**

  <!-- In this section you can include notes directed to the reviewers, like explaining why some parts
  of the PR were done in a specific way -->

  ### Changelog notice

  - Adds new `TxOrdering` variant: `TxOrdering::Custom`. A structure that stores the ordering functions to sort the inputs and outputs of a transaction.
  - Deprecates `TxOrdering::Bip69Lexicographic`.

  <!-- Notice the release manager should include in the release tag message changelog -->
  <!-- See https://keepachangelog.com/en/1.0.0/ for examples -->

  ### Checklists

  #### All Submissions:

  * [ ] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [ ] I've added docs for the new feature

Top commit has no ACKs.

Tree-SHA512: 0d3e3ea9aee3a6c9e9d5e1ae93215be84bd1bd99907a319976517819aeda768a7166860a48a8d24abb30c516e0129decb6a6aebd8f24783ea2230143e6dcd72a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good for newcomers
Projects
Archived in project
Status: Done
9 participants