Skip to content

refactor: remove SortedMultiVec and use Terminal::SortedMulti#915

Open
trevarj wants to merge 1 commit intorust-bitcoin:masterfrom
trevarj:refactor-sortedmulti
Open

refactor: remove SortedMultiVec and use Terminal::SortedMulti#915
trevarj wants to merge 1 commit intorust-bitcoin:masterfrom
trevarj:refactor-sortedmulti

Conversation

@trevarj
Copy link
Copy Markdown
Contributor

@trevarj trevarj commented Apr 4, 2026

  • Utilize the Miniscript parsing to handle sortedmulti as a Terminal.
  • Deleted sortedmulti.rs (SortedMultiVec)
  • Refactor Wsh to only wrap a Miniscript now that SortedMultiVec isn't used.
  • Refactor ShInner to remove SortedMulti variant and only use the Ms variant for
    sortedmulti scripts

Now that sortedmulti_a is supported as a Terminal, I think it makes sense to
move sortedmulti over in the same way by following what multi does and applying
the sorting functions that were introduced in eba1aff.

Like sortedmulti_a, sortedmulti is sorted upon encoding and cannot be decoded
into from Script.

@trevarj trevarj force-pushed the refactor-sortedmulti branch from 2c77295 to 9b0b1c0 Compare April 4, 2026 13:14
@apoelstra
Copy link
Copy Markdown
Member

(CI is failing because the integration tests don't compile)

Hmmmm, okay, this seems reasonable enough to me. Does Core allow sortedmulti in arbitrary positions or only as a top-level descriptor?

@trevarj
Copy link
Copy Markdown
Contributor Author

trevarj commented Apr 4, 2026

(CI is failing because the integration tests don't compile)

Hmmmm, okay, this seems reasonable enough to me. Does Core allow sortedmulti in arbitrary positions or only as a top-level descriptor?

Working on fixing the integration tests now! Will ping you when it's ready, my bad.

@trevarj trevarj force-pushed the refactor-sortedmulti branch from 9b0b1c0 to 816620a Compare April 4, 2026 14:54
Descriptor::Sh(sh) => match sh.as_inner() {
miniscript::descriptor::ShInner::Wsh(wsh) => match wsh.as_inner() {
miniscript::descriptor::WshInner::SortedMulti(ref smv) => {
let ms = Miniscript::from_ast(smv.sorted_node()).unwrap();
Copy link
Copy Markdown
Contributor Author

@trevarj trevarj Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key point here was to call sorted_node() and now we don't have something so convenient. See the HACK in find_sks_ms

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. So, the issue is that we want to get all the keys in the descriptor, in the order they'd be needed, and we're using pk_iter for this. Since pk_iter can't sort the keys, we have to sort them after the fact.

This is a bit of a weird thing to desire -- does this test actually fail without this? It seems like we never use the ordering of sks_reqd. In fact, we lose the ordering because we iterate over it, produce signatures, then stick them into the BTreeMap psbt.inputs[0].partial_sigs.

Also, since #910 I think the logic in this example is actually wrong, or at least inconsistent -- it would sort sortedmulti but not sortedmulti_a. And your hack doesn't change this.

So can we just drop the sortedmulti logic here and treat sortedmulti like multi?

ShInner::Wsh(ref wsh) => {
let ms = wsh.as_inner();
if let Terminal::SortedMulti(ref thresh) = ms.node {
PkIter::from_sortedmulti(thresh.data())
Copy link
Copy Markdown
Contributor Author

@trevarj trevarj Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot call thresh.into_sorted_bip67() due to lacking ToPublicKey on Pk here. It would clean up the integration test changes (HACK). I'm sure it would be useful for users as well.

Copy link
Copy Markdown
Member

@apoelstra apoelstra Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right -- I get the desire that pk_iter return the keys in the correct order. But as you note, without a ToPublicKey bound, we don't actually know the correct order (and the user might not either, e.g. if there are wildcards in the keys).

We also can't make this do the right thing for ToPublicKey keys because Rust lacks specialization. (There are tons of places in this crate where we suffer from this, and you'll find that if you keep trying to clean stuff up, you will run into missing language features basically every single time.)

I think we need to just live with this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are tons of places in this crate where we suffer from this, and you'll find that if you keep trying to clean stuff up, you will run into missing language features basically every single time.

@apoelstra don't worry, in 2035 when specialization stabilizes we can get to it!

}

#[test]
fn too_many_pubkeys_for_p2sh() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: migrated from deleted sortedmulti.rs file.

let mut witness = match self.inner {
WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?,
WshInner::Ms(ref ms) => ms.satisfy_malleable(satisfier)?,
let mut witness = if let Terminal::SortedMulti(..) = self.ms.node {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the if let Terminal::SortedMulti(..)'s are kind of bothering me.

Does it make sense to add a function on Miniscript called is_sortedmulti() or something?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably yes, but I'm curious what the heck is going on here. Why are we calling the non-malleable satisfier for sortedmultis? (What I suspect is: the old SortedMultiVec type had a satisfy method but no satisfy_malleable method, presumably because for multisigs, malleability is irrelevant. We needed to branch because SortedMultiVec is a distinct type, and then the branch wound up looking sus with one side saying satisfy and the other side saying satisfy_malleable, just because of this API gap.)

SO I think that in this PR, we just always want to call self.ms.satisfy_malleable, and not branch at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I based it off of this, which is from the deleted SortedMultiVec.

-    /// Attempt to produce a satisfying witness for the
-    /// witness script represented by the parse tree
-    pub fn satisfy<S>(&self, satisfier: S) -> Result<Vec<Vec<u8>>, Error>
-    where
-        Pk: ToPublicKey,
-        S: Satisfier<Pk>,
-    {
-        let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck");
-        ms.satisfy(satisfier)

We needed to branch because SortedMultiVec is a distinct type, and then the branch wound up looking sus with one side saying satisfy and the other side saying satisfy_malleable, just because of this API gap.

I don't think I could have guessed that lol...trusting you and changing to only call satisfy_malleable

@trevarj
Copy link
Copy Markdown
Contributor Author

trevarj commented Apr 5, 2026

@apoelstra did a self-review with some questions. Let me know what you think, thanks!

@apoelstra
Copy link
Copy Markdown
Member

816620a needs rebase

@trevarj trevarj force-pushed the refactor-sortedmulti branch from 816620a to f9d0bdd Compare April 9, 2026 17:02
@apoelstra
Copy link
Copy Markdown
Member

f9d0bdd needs rebase

@trevarj trevarj force-pushed the refactor-sortedmulti branch 3 times, most recently from b4d73ec to 366974f Compare April 9, 2026 17:32
ShInner::Wsh(ref wsh) => {
let ms = wsh.as_inner();
if let Terminal::SortedMulti(ref thresh) = ms.node {
PkIter::from_sortedmulti(thresh.data())
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are tons of places in this crate where we suffer from this, and you'll find that if you keep trying to clean stuff up, you will run into missing language features basically every single time.

@apoelstra don't worry, in 2035 when specialization stabilizes we can get to it!

let mut witness = match self.inner {
WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?,
WshInner::Ms(ref ms) => ms.satisfy_malleable(satisfier)?,
let mut witness = if let Terminal::SortedMulti(..) = self.ms.node {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I based it off of this, which is from the deleted SortedMultiVec.

-    /// Attempt to produce a satisfying witness for the
-    /// witness script represented by the parse tree
-    pub fn satisfy<S>(&self, satisfier: S) -> Result<Vec<Vec<u8>>, Error>
-    where
-        Pk: ToPublicKey,
-        S: Satisfier<Pk>,
-    {
-        let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck");
-        ms.satisfy(satisfier)

We needed to branch because SortedMultiVec is a distinct type, and then the branch wound up looking sus with one side saying satisfy and the other side saying satisfy_malleable, just because of this API gap.

I don't think I could have guessed that lol...trusting you and changing to only call satisfy_malleable

Comment on lines +35 to +38
| Terminal::Multi(_)
| Terminal::SortedMulti(_)
| Terminal::MultiA(_)
| Terminal::SortedMultiA(_) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saw SortedMultiA was also missing from this.

match (&self.node, n) {
(Terminal::PkK(key), 0) | (Terminal::PkH(key), 0) => Some(key.clone()),
(Terminal::Multi(thresh), _) => thresh.data().get(n).cloned(),
(Terminal::SortedMulti(thresh), _) => thresh.data().get(n).cloned(),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this on the first iteration since type-driven development failed me with the _ => fallback.

@apoelstra It resolves the HACK that was mentioned in this comment.

Comment on lines +663 to +665
Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh)
if !thresh.iter().all(&mut pred) =>
{
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After rebasing one of the tests from 1062144 caught that I missed this! Thanks @febyeji

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

- Utilize the Miniscript parsing to handle sortedmulti as a Terminal.
- Deleted sortedmulti.rs (SortedMultiVec)
- Refactor Wsh to only wrap a Miniscript now that SortedMultiVec isn't used.
- Refactor ShInner to remove SortedMulti variant and only use the Ms variant for
  sortedmulti scripts
@trevarj trevarj force-pushed the refactor-sortedmulti branch from 366974f to 941205b Compare April 9, 2026 18:01
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