Follow BIP379 for the thresh "e" rule - #1032
Conversation
BIP379 computes the "e" (expressive) property of a threshold as "all are s"; this crate additionally requires every child to be "e", as Bitcoin Core did until bitcoin/bitcoin#36028. The three disagree in no other malleability or correctness rule: an independent implementation of the BIP379 tables agrees with this crate on all 36,370 expressions of the corpora in ms_tests.rs, except for the 20 thresholds this commit retypes. The extra condition cannot change any outcome a caller can act on. It is already part of `non_malleable` below, and non-malleability is conjunctive, so a threshold whose children are all "s" but not all "e" is malleable either way, as is every expression containing one. The difference is only visible in the type of an expression that is malleable to begin with. It does matter for exact type test vectors, which is where it surfaced: BIP379's test vectors are being contributed from these corpora (bitcoin/bips#2005), so the specification and the implementations have to agree on the type of a malleable expression as well. Note that the property description in the BIP ("a unique unconditional dissatisfaction") does not hold for these thresholds: `or_i(pk(A),pk(B))` is a legal thresh child which is "s" but not "e", and a third party can freely pick either of its two dissatisfactions. The BIP is being amended to note that the descriptions only bind for expressions which meet the malleability requirements.
Covers both sides of the rule the previous commit changed, on the smallest expression that discriminates: a threshold whose children are all "s" but not all "e" is "e" and malleable, and one with a child that is not "s" is neither.
|
Interesting. Okay, I think this is reasonable. The claim is that, assuming that all children are
Therefore we can just always set The alternative (fix the BIP to match the implementation) might work, but it'd be upholding a stronger promise than we intended to make and would require far more extensive analysis for little benefit. See bitcoin/bips#2267 (comment) |
|
Now I'm kinda musing about changing the pub struct Malleability {
pub dissat: Dissat,
pub signed: bool,
pub non_malleable: bool,
}we'd have pub enum Malleability {
Malleable,
NonMalleable {
dissat: Dissat,
signed: bool,
},
}But I guess doing this would break our test vectors for little benefit. If we ever provide a "user-facing API" for the type system maybe we can do something like this. |
| // A threshold all of whose children are signed, but not all of | ||
| // which have a unique dissatisfaction, is Unique here even though a | ||
| // third party can vary the dissatisfaction of such a child. This is | ||
| // BIP379's rule ("e=all are s") and it cannot mislead a caller: |
There was a problem hiding this comment.
In 439be85:
lmao "cannot mislead a caller"
Let's tighten up this comment to say "Intuitively, Dissat::Unique should require all children to be Dissat::Unique. But this does not match BIP 379. Notice that if any child is not Dissat::Unique, the entire thresh will be malleable so the dissat property has no meaning anyway. See PR 1032 and BIPs PR 2267 for more discussion."
Prompted by bitcoin/bips#2240 (comment).
Related: bitcoin/bitcoin#36028 and bitcoin/bips#2267.
Description
BIP379 computes the "e" (expressive) property of a threshold as
e=all are s.This crate additionally requires every child to be "e". Bitcoin Core did the
same until bitcoin/bitcoin#36028, which aligns it with the BIP; this does the
same for rust-miniscript.
Why it doesn't change anything a caller can act on
The extra condition is already part of
non_malleable, and non-malleability isconjunctive, so a threshold whose children are all "s" but not all "e" is
malleable either way — as is every expression containing one. Wherever an
mrule consumes a child's
eit also requires that child'sm, so nomvaluechanges anywhere, and
Descriptor::from_straccepts and rejects exactly what itdid before. Only the type of an expression that was already malleable changes.
Why it matters anyway
BIP379's test vectors are being contributed from the corpora in
ms_tests.rs(bitcoin/bips#2240), and exact type vectors mean the specification and the
implementations have to agree on the type of a malleable expression too.
To check that this is the only rule where they disagree, I implemented the
BIP379 correctness and malleability tables as an independent reference and ran
it over the five corpora with recorded types — 36,370 expressions, including the
malleable and timelock-conflicting ones:
threshe rulems_tests.rsall are s(BIP text)all are e and all are s(this crate, before)So this crate matched the BIP in every other correctness and malleability rule.
(Core additionally differed in
or_b's "e" and in two vacuousd:rules, whichis the rest of #36028.)
A note on the property description
BIP379 describes "e" as requiring "a unique unconditional dissatisfaction to
exist", and that does not hold under its own rule:
or_i(pk(A),pk(B))is a legalthresh child which is "s" but not "e", and spending
or_d(thresh(2,or_i(pk(A),pk(B)),a:or_i(pk(C),pk(D))),pk(E))through its secondbranch has four consensus-valid witnesses that differ only in the two
branch-selector bytes. The BIP is being amended to note that the property
descriptions only bind for expressions meeting the malleability requirements.