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 is Witness::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.
Requested by this comment.
PR #1037 introduces a new
regression_1037test that demonstrates this insrc/miniscript/satisfy/mod.rs.The following text is from GPT-6.
Problem
The satisfier passes a single
root_has_sigboolean down to every timelockfragment. This is intended to indicate that every satisfaction requires a
signature. When a timelock check fails:
root_has_sig, the branch isWitness::Impossible: the signatureconstrains the transaction's timelock fields, so a third party cannot change
them to make the branch available.
Witness::Unavailable: the satisfier cannot useit, 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::Malleableno 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:Provide a signature for
Kand an absolute height locktime of 100. Everyspend 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:
satisfyreturns the first branch's witness,[1, sig(K)].Actual: the root is classified as malleable, so
root_has_sigis false.The unmet
after(200)becomesUnavailable, and the two unsignedor_ialternatives cause
satisfyto returnError::CouldNotSatisfy.The same problem occurs with
and_v(v:pk(K),or_i(older(100),older(200)))and a relative height locktimeof 100.
regression_1037insrc/miniscript/satisfy/mod.rscovers both cases. Italso demonstrates that
satisfy_malleablereturns the expected witness andthat explicitly passing
root_has_sig = trueto the internal satisfierrecovers that witness through the non-malleable algorithm.