Descriptors: rule out unspendable miniscript descriptors #27997

pull darosior wants to merge 4 commits into bitcoin:master from darosior:miniscript_non_satisfiable changing 5 files +48 −14
  1. darosior commented at 10:36 am on June 29, 2023: member

    IsSane() in Miniscript does not ensure a Script is actually spendable. This is an issue as we would accept any sane Miniscript when parsing a descriptor. Fix this by explicitly checking a Miniscript descriptor is both sane and spendable when parsing it.

    This bug was exposed due to a check added in #22838 (https://github.com/bitcoin/bitcoin/pull/22838#discussion_r1226859880) that triggered a fuzz crash (https://github.com/bitcoin/bitcoin/pull/22838#issuecomment-1612510057).

  2. miniscript: make GetStackSize() and GetOps() return optionals
    The value is only set for satisfiable nodes, so it was undefined for
    non-satisfiable nodes. Make it clear in the interface by returning
    std::nullopt if the node isn't satisfiable instead of an undefined
    value.
    e3280eae1b
  3. DrahtBot commented at 10:36 am on June 29, 2023: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK sipa, achow101

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #22838 (descriptors: Be able to specify change and receiving in a single descriptor string by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. DrahtBot added the label Descriptors on Jun 29, 2023
  5. darosior renamed this:
    Miniscript: always treat unsatisfiables scripts as insane
    Miniscript: always treat unsatisfiable scripts as insane
    on Jun 29, 2023
  6. in src/script/miniscript.h:1165 in d4d3fcc16c outdated
    1160@@ -1161,6 +1161,9 @@ struct Node {
    1161         return true;
    1162     }
    1163 
    1164+    //! Whether no satisfaction exists for this node.
    1165+    bool IsNotSatisfiable() const { return !GetStackSize(); }
    


    achow101 commented at 4:44 pm on June 29, 2023:

    In d4d3fcc16ccae4755604bb3a9a6a45542b231dcc “miniscript: treat all unsatisfiable miniscripts as insane”

    Since this is only used negated, I think it would be better to make this IsSatisfiable

    0    bool IsSatisfiable() const { return GetStackSize().has_value(); }
    
  7. darosior commented at 5:01 pm on June 29, 2023: member

    This is on purpose, because:

    1. Whether a script is satisfiable depends on the material available, whereas we can tell whether a script will never be satisfiable.
    2. IsSatisfiable already exists and implements the above (needs to be told what challenges are available).

    So to avoid the confusion i opted for the negative. But i could make it MayBeSatisfiable if that’s clearer? ——– Original Message ——– On Jun 29, 2023, 6:45 PM, Andrew Chow wrote:

    @achow101 commented on this pull request.


    In src/script/miniscript.h:

    @@ -1161,6 +1161,9 @@ struct Node { return true; }

    • //! Whether no satisfaction exists for this node.
    • bool IsNotSatisfiable() const { return !GetStackSize(); }

    In d4d3fcc “miniscript: treat all unsatisfiable miniscripts as insane”

    Since this is only used negated, I think it would be better to make this IsSatisfiable

    ⬇️ Suggested change

    • bool IsNotSatisfiable() const { return !GetStackSize(); }
    • bool IsSatisfiable() const { return GetStackSize().has_value(); }

    — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

  8. apoelstra commented at 6:35 pm on June 29, 2023: contributor

    0 is sane according to https://github.com/rust-bitcoin/rust-miniscript/blob/master/src/miniscript/analyzable.rs#L66

    If we are going to change the definition can we please have some reference document somewhere about what “sane” actually means?

  9. sipa commented at 7:33 pm on June 29, 2023: member

    So I think the definition for “sane” that we want is “does the apparent policy of this miniscript match its actual semantics”. If you drop all wrappers and naively replace all fragments with their corresponding policy, is that guaranteed to match the policy of the actual script?

    Arguably, this is the case for “0”, despite not having any keys, because its apparent policy is that it’s never spendable, and that is also what it actually implements. Of course, we could expand the definition and explicitly treat lack of keys as automatically non-sane, but it seems more correct to instead say that only spendable miniscripts need key to be sane.

  10. darosior commented at 11:57 am on June 30, 2023: member

    Sure, i’ll just move the check for satifiability to the descriptor parsing logic.

    Just one comment on the definition of “sane”.

    I think the definition for “sane” that we want is “does the apparent policy of this miniscript match its actual semantics” - if you drop all wrappers and naively replace all fragments with their corresponding policy, is that guaranteed to match the policy of the actual script.

    It’s not what we have at the moment though? For instance older(4) meets this criterion but isn’t “sane” because it does not have the s property. It seems to me what you describe is checking that all satisfactions are within limits and there is no timelock mixes, but without the requirement for a signature? ——– Original Message ——– On Jun 29, 2023, 9:33 PM, Pieter Wuille wrote:

    So I think the definition for “sane” that we want is “does the apparent policy of this miniscript match its actual semantics” - if you drop all wrappers and naively replace all fragments with their corresponding policy, is that guaranteed to match the policy of the actual script.

    Arguably, this is the case for “0”, despite not having any keys, because its apparent policy is that it’s never spendable, and that is also what it actually implements. Of course, we could expand the definition and explicitly treat lack of keys as automatically non-sane, but it seems more correct to instead say that only spendable miniscripts need key to be sane.

    — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

  11. sipa commented at 3:29 pm on June 30, 2023: member

    The requirement for a signature actually stems from the same question, though it’s not as exact.

    If a script can be satisfied without signatures, it means there may be no signatures at all in the whole transaction or input, and thus nothing that actually commits to the nLockTime or nSequence values respectively, and so an attacker could just modify those values without invalidating anything.

  12. descriptor: refuse to parse unspendable miniscript descriptors
    It's possible for some unsatisfiable miniscripts to be considered sane.
    Make sure we refuse to import those, as they would be unspendable.
    639e3b6c97
  13. qa: make sure we don't let unspendable Miniscript descriptors be imported a49402a9ec
  14. descriptor: assert we never parse a sane miniscript with no pubkey c7db88af71
  15. darosior force-pushed on Jul 1, 2023
  16. darosior commented at 12:43 pm on July 1, 2023: member

    I know, my point is the definition as spelled out before would still hold (anybody would be able to spend it but the “apparent policy of this miniscript match its actual semantics”). What i was trying to get at is that the rationale for requiring sane miniscripts to contain a signature check isn’t to make the actual semantics match the apparent policy, but that it’s unusable otherwise. And it’s the same for unsatisfiable miniscripts.

    Anyways, i was just nitpicking. I pushed an update to leave intact the definition of IsSane in Miniscript but instead check we don’t parse unsatisfiable miniscripts in the descriptors. Added a functional test in a separate commit to easily check it fails on master.

  17. darosior renamed this:
    Miniscript: always treat unsatisfiable scripts as insane
    Descriptors: rule out unspendable miniscript descriptors
    on Jul 1, 2023
  18. apoelstra commented at 4:27 pm on July 1, 2023: contributor

    What i was trying to get at is that the rationale for requiring sane miniscripts to contain a signature check isn’t to make the actual semantics match the apparent policy, but that it’s unusable otherwise

    No, the rationale was definitely about transaction malleability, and about transactions being spendable in surprising ways. Transactions not being spendable, given a policy which indicates non-spendability, has never been something we’ve tried to prevent or even thought much about.

    I’d be fine changing our definitions or rationale, and I agree that it’d be sensible to forbid top-level unspendable policies in a wallet, but their semantics are exactly what it says on the tin, so this would be a change in the way that we’ve been thinking about these things.

  19. darosior commented at 4:53 pm on July 17, 2023: member

    No, the rationale was definitely about transaction malleability

    Malleability is orthogonal. I was responding to the definition given above of “does the apparent policy of this miniscript match its actual semantics”. An older(42) script can well be malleated, its apparent policy will match its semantics.

    But anyways, let’s switch to ##miniscript if we want to continue discussing this? It’s not what this PR is implementing anymore.

  20. darosior requested review from achow101 on Jul 17, 2023
  21. sipa commented at 7:45 pm on July 17, 2023: member

    utACK c7db88af71b3204171f33399aa4f33b40a4f7cd9

    Longer term, and not for this PR, I think it would make sense to make a clearer separation between “miniscripts (and descriptors in general) which have undesirable properties so they shouldn’t be importable without warning”, and “miniscripts that are so deficient we cannot reason about them at all”. For example, if one has a malleable (or non-IsSane(), for certain reasons) miniscript, it’d be a bad idea to import it, as assessment of its properties may be wrong. However, it may still be worth signing for if one happens to have coins encumbered by such a miniscript despite those reasons.

  22. achow101 commented at 11:06 pm on July 17, 2023: member
    ACK c7db88af71b3204171f33399aa4f33b40a4f7cd9
  23. DrahtBot removed review request from achow101 on Jul 17, 2023
  24. achow101 merged this on Jul 17, 2023
  25. achow101 closed this on Jul 17, 2023

  26. jonatack commented at 11:37 pm on July 17, 2023: contributor
    Post-merge ACK
  27. sidhujag referenced this in commit 036a5dfa46 on Jul 19, 2023
  28. darosior deleted the branch on Jul 20, 2023
  29. darosior commented at 1:04 pm on July 20, 2023: member

    I forgot also checking this when parsing from Script..

    EDIT: actually i don’t think we need it since we don’t let users import a descriptor from Script. And it’s always better to parse a Miniscript in some utilities (such as decodescript), even though unspendable, than to display the raw Script.

  30. darosior referenced this in commit 3dc840635f on Jul 20, 2023

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-08 22:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me