-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Miniscript integration #24147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Miniscript integration #24147
Conversation
4c172cb
to
52165e4
Compare
52165e4
to
74d3f9b
Compare
Arg, the unit test ( Note: this was later fixed by having a Aside: this shouldn't be labeled as "Consensus" (neither should #24148 be) :) |
74d3f9b
to
86076cb
Compare
Concept ACK. I wonder if it makes sense to start out with a small subset of miniscript, so reviewers can focus on the implementation rather than on completeness. |
86076cb
to
2b9f9f9
Compare
Strong concept ACK |
src/test/miniscript_tests.cpp
Outdated
g_testdata.reset(); | ||
} | ||
|
||
BOOST_AUTO_TEST_CASE(random_tests) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test, along with all the RandomNode logic, is probably much more useful if converted to a fuzz test (which doesn't need to construct a realistic/useful probability model, but can just choose based on inputs). Let me know if you want any help with that.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
nice. Concept ACK.
|
Concept ACK |
I have tried to split this work into as many PR as reasonable, to reduce the size of this first chunk as much as possible. Note that 21% (you can't make this up) of the diff here is tests, and even more so in the following PRs. I'm afraid that further splitting this doesn't make sense. Further, i'm currently simplifying a few things following Pieter's comments above. |
It's mostly 3a2c00d that introduces a rather large body of stuff. Conceptually This doesn't necessarily require more PR's, though it might: only a limited subset of the descriptor language is necessary to get the functionality currently in descriptors. So then you could go straight to #24148 and #24149, to finish the job of blending miniscript with descriptors, while using parallel PR's to expand the miniscript language. |
constexpr Type operator&(Type x) const { return Type(m_flags & x.m_flags); } | ||
|
||
//! Check whether the left hand's properties are superset of the right's (= left is a subtype of right). | ||
constexpr bool operator<<(Type x) const { return (x.m_flags & ~m_flags) == 0; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 3a2c00d "Miniscript: type system, script creation, text notation, tests"
<<
confused me for a while as I am used to it meaning bit shift when used in a flags like context, rather than a containment operator.
@stepansnigirev you might be interested in reviewing this. |
We'll address all comments that have been left here soon, but just FYI, there are a bunch of improvements still ongoing in the miniscript repo itself (https://github.com/sipa/miniscript/, most of which will eventually be moved here after this PR and the followups). |
We'll need it for Miniscript
It is used by Miniscript.
Some prep work for Miniscript. BuildScript is an efficient way to build Scripts in a generic manner (by concatenating OPs, data, and other Scripts). Co-Authored-By: Pieter Wuille <pieter@wuille.net>
More information about Miniscript can be found at https://bitcoin.sipa.be/miniscript/ (the website source is hosted at https://github.com/sipa/miniscript/). This commit defines all fragments, their composition, parsing from string representation and conversion to Script. Co-Authored-By: Antoine Poinsot <darosior@protonmail.com> Co-Authored-By: Sanket Kanjalkar <sanket1729@gmail.com> Co-Authored-By: Samuel Dobson <dobsonsa68@gmail.com>
Co-Authored-By: Antoine Poinsot <darosior@protonmail.com> Co-Authored-By: Samuel Dobson <dobsonsa68@gmail.com>
Co-Authored-By: Antoine Poinsot <darosior@protonmail.com>
Rebased. This is now ready for review. There are still refactorings to the fuzz targets that have not landed in the Miniscript repo yet, i'll either add them here eventually or we'll open them as followup PRs. See sipa/miniscript#98 for details. Changes since last push:
|
cdd630e
to
2da94a4
Compare
ACK 2da94a4 I've been running the miniscript_decode fuzzer for awhile and it looks good. looking forward to testing the smarter fuzzers after this is merged. |
int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst); | ||
if (num_types == 0) return ""_mst; // No valid type, don't care about the rest | ||
assert(num_types == 1); // K, V, B, W all conflict with each other | ||
bool ok = // Work around a GCC 4.8 bug that breaks user-defined literals in macro calls. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: we require gcc 8.1 according to doc/dependencies.md
. There's no need to work around gcc 4.8 bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll address than in #24148 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Light code review ACK 2da94a4 (mostly reviewed the changes to the existing code and build system) |
Reviewers, before the integration into output descriptors, i've made a follow-up PR with the final updates and cleanups we made in the Miniscript repo. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally completed the review. Sorry for the delay. Did a line-by-line comparison of all miniscript code with rust-miniscript. Did not review tests/fuzzer.
Found a couple of issues that can be addressed in followups. ACK for the rest of it
|
||
/** Return the maximum number of stack elements needed to satisfy this script non-malleably, including | ||
* the script push. */ | ||
uint32_t GetStackSize() const { return ss.sat.value + 1; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above for the opcodes
public: | ||
//! Return the size of the script for this expression (faster than ToScript().size()). | ||
size_t ScriptSize() const { return scriptlen; } | ||
|
||
//! Return the maximum number of ops needed to satisfy this script non-malleably. | ||
uint32_t GetOps() const { return ops.count + ops.sat.value; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should we do when there is a script that cannot be satisfied? For example, thresh(1,0,a:0,a:pk(A))
is a valid miniscript that can never be satisfied. Should GetOps
return 0
or some constant?
Maybe something like?
uint32_t GetOps() const { return op.sat.valid ? ops.count + ops.sat.value : 0; }
This is really a nit, as I don't see any useful miniscripts being constructed this way, but still good to check the validity of the variable before accessing it's value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but i'm not sure it's worth "fixing".
In this case returning 0
would be as wrong as any other value. We could probably make the return value std::optional
but that sounds a bit overkill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but i'm not sure it's worth "fixing".
Agreed. I don't think it is worth fixing. Just wanted to highlight this.
case Fragment::SHA256: | ||
case Fragment::RIPEMD160: | ||
case Fragment::HASH256: | ||
case Fragment::HASH160: return {1, {}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be {1, 0}. All hash types can be dis-satisfied with a random 32-byte pre-image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's malleable, so we don't want to account for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why be different while calculating stack size and opcodes? In calculating executed op code count, we count the correct opcodes when this expression is dissatisfied. But we don't do so while considering stack size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hashing fragments return {4, 0, {}}
in CalcOps, and {1, {}}
in CalcStackSize? In both cases, no value ({}
) is returned for the dissatisfaction case, because the non-malleable signing algorithm will never use the dissatisfaction path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, error on my part. You are correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sipa @darosior Revisiting this:
I am considering should malleability rules even come into play over here? Any witness malleability cannot change the max opcode count. Though, it can potentially change the witness stack element count.
An example test-case in rust-miniscript broke of this,
c:and_v(or_c(sha256(H),v:multi(1,A)),pk_k(B))
Outputs maxops=8
because it does not count the malleable solution we dissatisfy sha256
. Just trying to stay consistent with the definition of OpsCount with rust-miniscript.
If we are ignoring malleable solutions in Ops/StackElems
, how about we change GetOps/GetStackSize
to output Error/Option/-1 when it operates on the malleable script?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sanket1729 That's probably reasonable, but I'm not sure it's needed.
Really what these functions (as currently written) answer is: "what is the maximum stacksize/opcode needed for non-malleable satisfactions". If you have a script with branches that cannot be satisfied non-malleable, you probably don't care about the numbers of just the non-malleable ones, but you also probably don't care about these functions in general. Their purpose is really only contributing to determining IsSane(TopLevel), and in case of not IsNonMalleable(), IsSane() will already be false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sipa Agreed. It might not be needed. I was translating the test vector from this PR to rust-miniscript when I noted the difference between the implementations.
c:and_v(or_c(sha256(H),v:multi(1,A)),pk_k(B)) with MaxOps() = 8
but rust-miniscript outputs 9.
I was also using them for cross-testing outputs with rust-miniscript to check why the value mismatches here. I agree that from an end-user perspective this is not really needed.
It might not be worth it to change the tests for future miniscript implementations(if any).
} | ||
} | ||
assert(false); | ||
return {{}, {}}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of return after an assert? Is this normal in c++? Same comment for the above function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's not necessary, Marco mentioned it to me elsewhere. I can remove it in #24860
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in #24860
//! Compute the type with the intersection of properties. | ||
constexpr Type operator&(Type x) const { return Type(m_flags & x.m_flags); } | ||
|
||
//! Check whether the left hand's properties are superset of the right's (= left is a subtype of right). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it the other way around, where if true
then the right hand's properties are a superset of the left's?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry of course right after posting I see that the order of m_flags
and x.m_flags
is switched up for this operator compared to the other operators, which makes the docstring correct so please disregard my previous comment. The inconsistent order is maybe a bit confusing but no problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the comment is correct I believe.
(x.m_flags & ~m_flags) is zero when every flag set in x.m_flags is also set in m_flags. In other words, where this's flags (=left hand) are a superset of x's flags.
ffc79b8 qa: functional test Miniscript watchonly support (Antoine Poinsot) bfb0367 Miniscript support in output descriptors (Antoine Poinsot) 4a08288 qa: better error reporting on descriptor parsing error (Antoine Poinsot) d25d58b miniscript: add a helper to find the first insane sub with no child (Antoine Poinsot) c38c7c5 miniscript: don't check for top level validity at parsing time (Antoine Poinsot) Pull request description: This adds Miniscript support for Output Descriptors without any signing logic (yet). See the OP of #24147 for a description of Miniscript and a rationale of having it in Bitcoin Core. On its own, this PR adds "watchonly" support for Miniscript descriptors in the descriptor wallet. A follow-up adds signing support. A minified corpus of Miniscript Descriptors for the `descriptor_parse` fuzz target is available at bitcoin-core/qa-assets#92. The Miniscript descriptors used in the unit tests here and in #24149 were cross-tested against the Rust implementation at https://github.com/rust-bitcoin/rust-miniscript. This PR contains code and insights from Pieter Wuille. ACKs for top commit: Sjors: re-utACK ffc79b8 achow101: ACK ffc79b8 w0xlt: reACK ffc79b8 Tree-SHA512: 02d919d38bb626d3c557eca3680ce71117739fa161b7a92cfdb6c9c432ed88870b1ed127ba24248574c40c7428217d7e9bdd986fd8cd7c51fae8c776e1271fb9
ffc79b8 qa: functional test Miniscript watchonly support (Antoine Poinsot) bfb0367 Miniscript support in output descriptors (Antoine Poinsot) 4a08288 qa: better error reporting on descriptor parsing error (Antoine Poinsot) d25d58b miniscript: add a helper to find the first insane sub with no child (Antoine Poinsot) c38c7c5 miniscript: don't check for top level validity at parsing time (Antoine Poinsot) Pull request description: This adds Miniscript support for Output Descriptors without any signing logic (yet). See the OP of bitcoin#24147 for a description of Miniscript and a rationale of having it in Bitcoin Core. On its own, this PR adds "watchonly" support for Miniscript descriptors in the descriptor wallet. A follow-up adds signing support. A minified corpus of Miniscript Descriptors for the `descriptor_parse` fuzz target is available at bitcoin-core/qa-assets#92. The Miniscript descriptors used in the unit tests here and in bitcoin#24149 were cross-tested against the Rust implementation at https://github.com/rust-bitcoin/rust-miniscript. This PR contains code and insights from Pieter Wuille. ACKs for top commit: Sjors: re-utACK ffc79b8 achow101: ACK ffc79b8 w0xlt: reACK bitcoin@ffc79b8 Tree-SHA512: 02d919d38bb626d3c557eca3680ce71117739fa161b7a92cfdb6c9c432ed88870b1ed127ba24248574c40c7428217d7e9bdd986fd8cd7c51fae8c776e1271fb9
Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way.
Miniscript permits:
Miniscript guarantees:
For more details around Miniscript (including the specifications), please refer to the website.
Miniscript was designed by Pieter Wuille, Andrew Poelstra and Sanket Kanjalkar.
This PR is an updated and rebased version of #16800. See the commit history of the Miniscript repository for details about the changes made since September 2019 (TL;DR: bugfixes, introduction of timelock conflicts in the type system,
pk()
andpkh()
aliases,thresh_m
renamed tomulti
, all recursive algorithms were made non-recursive).This PR is also the first in a series of 3:
Note to reviewers:
VerifyScript
. I think it could be further improved by having custom mutators as we now have for multisig (see How to fuzz stateful logic? #23105). A minified corpus of Miniscript Scripts is available at miniscript_decode: add initial corpus bitcoin-core/qa-assets#85.[0] We call "valid" any correctly-typed Miniscript. And "safe" any sane Miniscript, ie one whose satisfaction isn't malleable, which requires a key for any spending path, etc..