rust-bitcoin / rust-bitcoin/rust-miniscript
Satisfier: track non-removable signatures when assessing timelock malleability
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 426
- Forks
- 200
- Avg merge
- 7d 17h
- Merged PRs (30d)
- 8
Description
Requested by this comment.
PR #1037 introduces a new regression_1037 test that demonstrates this in src/miniscript/satisfy/mod.rs.
The following text is from GPT-6.
Problem
The satisfier passes a single root_has_sig boolean down to every timelock
fragment. This is intended to indicate that every satisfaction requires a
signature. When a timelock check fails:
- With
root_has_sig, the branch isWitness::Impossible: the signature
constrains the transaction's timelock fields, so a third party cannot change
them to make the branch available. - Without it, the branch is
Witness::Unavailable: the satisfier cannot use
it, but a third party might be able to change the transaction's timelocks.
This is a coarse approximation of whether the witness being constructed
contains a signature that a third party cannot remove. It can reject a
non-malleable witness because a timelocked alternative is treated as a
possible malleation.
The malleability enum makes this approximation more conservative: expressions
classified as Malleability::Malleable no longer retain a signature property,
even when every spend requires a signature. Consequently,
non_malleable_and_requires_sig() returns false for those expressions.
Reproduction
Consider the following Miniscript, parsed with from_str_insane:
and_v(v:pk(K),or_i(after(100),after(200)))
Provide a signature for K and an absolute height locktime of 100. Every
spend requires that signature. Only the first timelock is satisfied, and a
third party cannot change the transaction's locktime to select the other
branch without invalidating the signature.
Expected: satisfy returns the first branch's witness, [1, sig(K)].
Actual: the root is classified as malleable, so root_has_sig is false.
The unmet after(200) becomes Unavailable, and the two unsigned or_i
alternatives cause satisfy to return Error::CouldNotSatisfy.
The same problem occurs with
and_v(v:pk(K),or_i(older(100),older(200))) and a relative height locktime
of 100.
regression_1037 in src/miniscript/satisfy/mod.rs covers both cases. It
also demonstrates that satisfy_malleable returns the expected witness and
that explicitly passing root_has_sig = true to the internal satisfier
recovers that witness through the non-malleable algorithm.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read the satisfier logic in src/miniscript/satisfy/mod.rs and run the regression_1037 test, which covers both absolute and relative timelocks. Done means the reported cases return the expected witness through satisfy while the satisfy_malleable and explicit root_has_sig checks continue to demonstrate the expected behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- blockchain, cryptography
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100