-
Notifications
You must be signed in to change notification settings - Fork 38.7k
wallet: warn against accidental unsafe older() import #33135
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
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33135. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please copy-paste 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. |
brunoerg
left a comment
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've reviewed the code and it looks great - Just a question: Instead of having this new field and prevent the import (any user have reported an accidental import?), couldn't we just throw a warning?
|
@brunoerg perhaps a warning is good enough yes. In that case we wouldn't need the |
fac503e to
8c6b70b
Compare
|
I switched to emitting a warning. I'll look into modifying miniscript / descriptor classes to avoid the string parsing. Though unless people really don't like it, I think it could be done as a followup. |
8c6b70b to
3aa611d
Compare
|
Rebased. Fixed bug in the functional test found by an AI overlord, apparently using
Done, and also added a unit test. It's a bit more code and complexity, but more robust and extendable than parsing strings. Since c1e6a9f1e7331fa2c1c73a0dd17056570283e443 is no longer relevant for this PR I dropped it. |
2f684f2 to
fe06b92
Compare
brunoerg
left a comment
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.
ACK fe06b92429206aee66d33c843195235f4bb27181 - This new approach of just warning is better than the previous one, nice! I haven't reviewed the code in depth, just did a light code review but tested the behavior in practice and worked fine.
Also, I ran a mutation analysis on these changes and the only uncaught mutant is:
// Traverse miniscript tree for unsafe use of older()
miniscript::ForEachNode(*m_node, [&](const miniscript::Node<uint32_t>& node) {
- if (node.fragment == miniscript::Fragment::OLDER) {
+ if (1==1) {
const uint32_t raw = node.k;
const uint32_t value_part = raw & ~CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG;
if (value_part > CTxIn::SEQUENCE_LOCKTIME_MASK) {it means we're not really testing that the warning is produced exclusively for the older() fragment, nothing critical and can be ignored but I think a simple test case could solve it. e.g.:
diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 9e06e78fe9..fb35dfd05a 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -1260,6 +1260,15 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
BOOST_AUTO_TEST_CASE(descriptor_older_warnings)
{
+ {
+ FlatSigningProvider keys;
+ std::string err;
+ // Using after() with a large timestamp (> 65535)
+ auto descs = Parse("wsh(and_v(v:pk(0379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3),after(1000000)))", keys, err, /*require_checksum=*/false);
+ BOOST_REQUIRE(!descs.empty());
+ BOOST_CHECK(descs[0]->Warnings().empty()); // Ensure the older() warning is really being applied only for this fragment
+ }
+9431dc3 to
5e9737f
Compare
brunoerg
left a comment
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.
reACK 5e9737ff493a96566f4fc11b1733b4b5a5393f6b
| @@ -7,8 +7,10 @@ | |||
|
|
|||
| #include <algorithm> | |||
| #include <compare> | |||
| #include <concepts> | |||
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.
nit: not sure if those includes are stricly required?
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.
There's some sort of linter command that can check this, but I forgot which.
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.
build success even w/o those 2 (added) includes on my side
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.
That doesn't really say much, because indirect includes will succeed but we try to avoid those. Though maybe not for std stuff?
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 Tidy CI job runs IWYU and doesn't complain: https://github.com/bitcoin/bitcoin/actions/runs/17605378583/job/50015040929?pr=33135#step:9:6218
| @@ -58,6 +59,7 @@ def test_importdesc(self, req, success, error_code=None, error_message=None, war | |||
| if 'warnings' in result[0]: | |||
| observed_warnings = result[0]['warnings'] | |||
| assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings))) | |||
| self.log.debug(result) | |||
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.
nit: should we keep this debug log?
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.
Yes, it's only printed when you set the log level to debug and it's useful for debugging if the test breaks.
|
tACK 5e9737ff49 code review + test importing few "Liana" descriptor with timelocks > 65535 |
src/script/descriptor.cpp
Outdated
| if (value_part > CTxIn::SEQUENCE_LOCKTIME_MASK) { | ||
| const bool is_time = (raw & CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) != 0; | ||
| if (is_time) { | ||
| m_warnings.push_back(strprintf("older(%u - 1<<22) > 65535 is unsafe", raw)); |
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 5e9737ff493a96566f4fc11b1733b4b5a5393f6b "wallet: warn against accidental unsafe older() import"
This warning is basically incomprehensible. It would be better to actually write out what it means, i.e. "time based relative locktime > 65535 is unsafe"
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 went for a slight variant: relative locktime > 65535 * 512 seconds is unsafe
|
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
|
Lost the first commit in that rebase, let's try again... |
6edd8d4 to
bbfa35d
Compare
c0bfe72 Change Parse descriptor argument to string_view (Sjors Provoost) Pull request description: While investigating a silent merge conflict in #33135 I noticed that #32983 changed the descriptor `Parse` function signature from `const std::string& descriptor` to `std::span<const char> descriptor`. Calling that new version of `Parse` with a string literal will trigger a confusing "Invalid characters in payload" due to the trailing "\0". It can be worked around by having (the test) wrap string literals in `std::string()`, but that's easy to forget. Using `string_view` is easier and more compact than (as a previous version of this PR did) checking for trailing `\0`. Also add a test. ACKs for top commit: maflcko: review ACK c0bfe72 🍨 enirox001: tACK c0bfe72 stickies-v: ACK c0bfe72 rkrux: crACK c0bfe72 Tree-SHA512: 6b20307f834dae66826c8763f6c2ba0071f4e369375184cb5ff8543b85220fcaf33a47ddb065e418d1af3ed9a3fac401a7854f8924f52aab2b000b1f65328f2c
bbfa35d to
189437a
Compare
|
Rebased after #33914 so the test should pass again. |
rkrux
left a comment
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.
lgtm ACK 189437a3dedefce3945ab2a8c05785eb283c2c6f
Shared a non-blocking nit.
brunoerg
left a comment
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.
reACK 189437a3dedefce3945ab2a8c05785eb283c2c6f
189437a to
45eb536
Compare
BIP 379 allows height and time locks that have no consensus meaning in BIP 68 / BIP 112. This is used by some protocols like Lightning to encode extra data, but is unsafe when used unintentionally. E.g. older(65536) is equivalent to older(1). This commit emits a warning when importing such a descriptor. It introduces a helper ForEachNode to traverse all miniscript nodes.
45eb536 to
76c092f
Compare
rkrux
left a comment
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.
lgtm re-ACK 76c092f
|
@brunoerg @pythcoiner can you re-review the latest push? |
brunoerg
left a comment
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.
reACK 76c092f
|
reACK 76c092f |
|
ACK 76c092f |
…followup item from PR bitcoin#33135 by refactoring descriptor classes to avoid string parsing instead of emitting warnings.
BIP 379 (Miniscript) allows relative height and time locks that have no consensus meaning in BIP 68 (relative timelocks) / BIP 112 (
CHECKSEQUENCEVERIFY). This is (ab)used by some protocols, e.g. by Lightning to encode extra data, but is unsafe when used unintentionally:older(65536)is equivalent toolder(1).This PR emits a warning when
importdescriptorscontains such a descriptor.The first commit makes
SEQUENCE_LOCKTIMEflags reusable by other tests.The main commit adds the
ForEachNodehelper tominiscript.hwhich is then used in theMiniscriptDescriptorconstructor to check forFragment::OLDERwith unsafe values. These are stored inm_warnings, which the RPC code then collects viaWarnings().It adds both a unit and functional test.
A previous version of this PR prevented the import, unless the user opted in with an
unsafeflag. It also used string parsing in the RPC code.Based on: