Skip to content

Commit 695ca64

Browse files
committed
Merge bitcoin#24860: Miniscript integration follow-ups
f3a50c9 miniscript: rename IsSane and IsSaneSubexpression to prevent misuse (Antoine Poinsot) c5fe516 miniscript: nit: don't return after assert(false) (Antoine Poinsot) 7bbaca9 miniscript: explicit the threshold size computation in multi() (Antoine Poinsot) 8323e42 miniscript: add an OpCode typedef for readability (Antoine Poinsot) 7a549c6 miniscript: mark nodes with duplicate keys as insane (Antoine Poinsot) 8c0f8bf fuzz: add a Miniscript target for string representation roundtripping (Antoine Poinsot) be34d50 fuzz: rename and improve the Miniscript Script roundtrip target (Antoine Poinsot) 7eb70f0 miniscript: tiny doc fixups (Antoine Poinsot) 5cea85f miniscript: split ValidSatisfactions from IsSane (Antoine Poinsot) a0f064d miniscript: introduce a CheckTimeLocksMix helper (Antoine Poinsot) ed45ee3 miniscript: use optional instead of bool/outarg (Antoine Poinsot) 1ab8d89 miniscript: make equality operator non-recursive (Antoine Poinsot) 5922c66 scripted-diff: miniscript: rename 'nodetype' variables to 'fragment' (Antoine Poinsot) c5f65db miniscript: remove a workaround for a GCC 4.8 bug (Antoine Poinsot) Pull request description: The Miniscript repository and the Miniscript integration PR here have been a moving target for the past months, and some final cleanups were done there that were not included here. I initially intended to add some small followup commits to bitcoin#24148 but i think there are enough of them to be worth a followup PR on its own. Some parts of the code did not change since it was initially written in 2019, and the code could use some modernization. (Use std::optional instead of out args, remove old compiler workarounds). We refactored the helpers to be more meaningful, and also did some renaming. A new fuzz target was also added and both were merged in a single file. 2 more will be added in bitcoin#24149 that will be contained in this file too. The only behaviour change in this PR is to rule out Miniscript with duplicate keys from sane Miniscripts. In a P2WSH context, signatures can be rebounded (Miniscript does not use CODESEPARATOR) and it's reasonable to assume that reusing keys across the Script drops the malleability guarantees. It was previously assumed such Miniscript would never exist in the first place since a compiler should never create them. We finally agreed that if one were to exist (say, written by hand or from a buggy compiler) it would be very confusing if an imported Miniscript descriptor (after bitcoin#24148) with duplicate keys was deemed sane (ie, "safe to use") by Bitcoin Core. We now check for duplicate keys in the constructor. This is (still) joint work with Pieter Wuille. (Actually he entirely authored the cleanups and code modernization.) ACKs for top commit: sipa: utACK f3a50c9 (with the caveat that a lot of it is my own code) sanket1729: code review ACK f3a50c9. Did not review the fuzz tests. Tree-SHA512: c043325e4936fe25e8ece4266b46119e000c6745f88cea530fed1edf01c80f03ee6f9edc83b6e9d42ca01688d184bad16bfd967c5bb8037744e726993adf3deb
2 parents aac9c25 + f3a50c9 commit 695ca64

File tree

6 files changed

+481
-306
lines changed

6 files changed

+481
-306
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ test_fuzz_fuzz_SOURCES = \
270270
test/fuzz/locale.cpp \
271271
test/fuzz/merkleblock.cpp \
272272
test/fuzz/message.cpp \
273-
test/fuzz/miniscript_decode.cpp \
273+
test/fuzz/miniscript.cpp \
274274
test/fuzz/minisketch.cpp \
275275
test/fuzz/muhash.cpp \
276276
test/fuzz/multiplication_overflow.cpp \

src/script/miniscript.cpp

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,69 +17,67 @@ Type SanitizeType(Type e) {
1717
int num_types = (e << "K"_mst) + (e << "V"_mst) + (e << "B"_mst) + (e << "W"_mst);
1818
if (num_types == 0) return ""_mst; // No valid type, don't care about the rest
1919
assert(num_types == 1); // K, V, B, W all conflict with each other
20-
bool ok = // Work around a GCC 4.8 bug that breaks user-defined literals in macro calls.
21-
(!(e << "z"_mst) || !(e << "o"_mst)) && // z conflicts with o
22-
(!(e << "n"_mst) || !(e << "z"_mst)) && // n conflicts with z
23-
(!(e << "n"_mst) || !(e << "W"_mst)) && // n conflicts with W
24-
(!(e << "V"_mst) || !(e << "d"_mst)) && // V conflicts with d
25-
(!(e << "K"_mst) || (e << "u"_mst)) && // K implies u
26-
(!(e << "V"_mst) || !(e << "u"_mst)) && // V conflicts with u
27-
(!(e << "e"_mst) || !(e << "f"_mst)) && // e conflicts with f
28-
(!(e << "e"_mst) || (e << "d"_mst)) && // e implies d
29-
(!(e << "V"_mst) || !(e << "e"_mst)) && // V conflicts with e
30-
(!(e << "d"_mst) || !(e << "f"_mst)) && // d conflicts with f
31-
(!(e << "V"_mst) || (e << "f"_mst)) && // V implies f
32-
(!(e << "K"_mst) || (e << "s"_mst)) && // K implies s
33-
(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
34-
assert(ok);
20+
assert(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o
21+
assert(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z
22+
assert(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W
23+
assert(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d
24+
assert(!(e << "K"_mst) || (e << "u"_mst)); // K implies u
25+
assert(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u
26+
assert(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f
27+
assert(!(e << "e"_mst) || (e << "d"_mst)); // e implies d
28+
assert(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e
29+
assert(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f
30+
assert(!(e << "V"_mst) || (e << "f"_mst)); // V implies f
31+
assert(!(e << "K"_mst) || (e << "s"_mst)); // K implies s
32+
assert(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
3533
return e;
3634
}
3735

38-
Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
36+
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
3937
// Sanity check on data
40-
if (nodetype == Fragment::SHA256 || nodetype == Fragment::HASH256) {
38+
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
4139
assert(data_size == 32);
42-
} else if (nodetype == Fragment::RIPEMD160 || nodetype == Fragment::HASH160) {
40+
} else if (fragment == Fragment::RIPEMD160 || fragment == Fragment::HASH160) {
4341
assert(data_size == 20);
4442
} else {
4543
assert(data_size == 0);
4644
}
4745
// Sanity check on k
48-
if (nodetype == Fragment::OLDER || nodetype == Fragment::AFTER) {
46+
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
4947
assert(k >= 1 && k < 0x80000000UL);
50-
} else if (nodetype == Fragment::MULTI) {
48+
} else if (fragment == Fragment::MULTI) {
5149
assert(k >= 1 && k <= n_keys);
52-
} else if (nodetype == Fragment::THRESH) {
50+
} else if (fragment == Fragment::THRESH) {
5351
assert(k >= 1 && k <= n_subs);
5452
} else {
5553
assert(k == 0);
5654
}
5755
// Sanity check on subs
58-
if (nodetype == Fragment::AND_V || nodetype == Fragment::AND_B || nodetype == Fragment::OR_B ||
59-
nodetype == Fragment::OR_C || nodetype == Fragment::OR_I || nodetype == Fragment::OR_D) {
56+
if (fragment == Fragment::AND_V || fragment == Fragment::AND_B || fragment == Fragment::OR_B ||
57+
fragment == Fragment::OR_C || fragment == Fragment::OR_I || fragment == Fragment::OR_D) {
6058
assert(n_subs == 2);
61-
} else if (nodetype == Fragment::ANDOR) {
59+
} else if (fragment == Fragment::ANDOR) {
6260
assert(n_subs == 3);
63-
} else if (nodetype == Fragment::WRAP_A || nodetype == Fragment::WRAP_S || nodetype == Fragment::WRAP_C ||
64-
nodetype == Fragment::WRAP_D || nodetype == Fragment::WRAP_V || nodetype == Fragment::WRAP_J ||
65-
nodetype == Fragment::WRAP_N) {
61+
} else if (fragment == Fragment::WRAP_A || fragment == Fragment::WRAP_S || fragment == Fragment::WRAP_C ||
62+
fragment == Fragment::WRAP_D || fragment == Fragment::WRAP_V || fragment == Fragment::WRAP_J ||
63+
fragment == Fragment::WRAP_N) {
6664
assert(n_subs == 1);
67-
} else if (nodetype != Fragment::THRESH) {
65+
} else if (fragment != Fragment::THRESH) {
6866
assert(n_subs == 0);
6967
}
7068
// Sanity check on keys
71-
if (nodetype == Fragment::PK_K || nodetype == Fragment::PK_H) {
69+
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
7270
assert(n_keys == 1);
73-
} else if (nodetype == Fragment::MULTI) {
71+
} else if (fragment == Fragment::MULTI) {
7472
assert(n_keys >= 1 && n_keys <= 20);
7573
} else {
7674
assert(n_keys == 0);
7775
}
7876

79-
// Below is the per-nodetype logic for computing the expression types.
77+
// Below is the per-fragment logic for computing the expression types.
8078
// It heavily relies on Type's << operator (where "X << a_mst" means
8179
// "X has all properties listed in a").
82-
switch (nodetype) {
80+
switch (fragment) {
8381
case Fragment::PK_K: return "Konudemsxk"_mst;
8482
case Fragment::PK_H: return "Knudemsxk"_mst;
8583
case Fragment::OLDER: return
@@ -247,11 +245,10 @@ Type ComputeType(Fragment nodetype, Type x, Type y, Type z, const std::vector<Ty
247245
}
248246
}
249247
assert(false);
250-
return ""_mst;
251248
}
252249

253-
size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
254-
switch (nodetype) {
250+
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
251+
switch (fragment) {
255252
case Fragment::JUST_1:
256253
case Fragment::JUST_0: return 1;
257254
case Fragment::PK_K: return 34;
@@ -262,7 +259,7 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_
262259
case Fragment::SHA256: return 4 + 2 + 33;
263260
case Fragment::HASH160:
264261
case Fragment::RIPEMD160: return 4 + 2 + 21;
265-
case Fragment::MULTI: return 3 + (n_keys > 16) + (k > 16) + 34 * n_keys;
262+
case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys;
266263
case Fragment::AND_V: return subsize;
267264
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
268265
case Fragment::WRAP_S:
@@ -280,19 +277,17 @@ size_t ComputeScriptLen(Fragment nodetype, Type sub0typ, size_t subsize, uint32_
280277
case Fragment::THRESH: return subsize + n_subs + BuildScript(k).size();
281278
}
282279
assert(false);
283-
return 0;
284280
}
285281

286-
bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, std::vector<unsigned char>>>& out)
282+
std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script)
287283
{
288-
out.clear();
284+
std::vector<Opcode> out;
289285
CScript::const_iterator it = script.begin(), itend = script.end();
290286
while (it != itend) {
291287
std::vector<unsigned char> push_data;
292288
opcodetype opcode;
293289
if (!script.GetOp(it, opcode, push_data)) {
294-
out.clear();
295-
return false;
290+
return {};
296291
} else if (opcode >= OP_1 && opcode <= OP_16) {
297292
// Deal with OP_n (GetOp does not turn them into pushes).
298293
push_data.assign(1, CScript::DecodeOP_N(opcode));
@@ -309,30 +304,28 @@ bool DecomposeScript(const CScript& script, std::vector<std::pair<opcodetype, st
309304
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
310305
opcode = OP_VERIFY;
311306
} else if (IsPushdataOp(opcode)) {
312-
if (!CheckMinimalPush(push_data, opcode)) return false;
307+
if (!CheckMinimalPush(push_data, opcode)) return {};
313308
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) {
314309
// Rule out non minimal VERIFY sequences
315-
return false;
310+
return {};
316311
}
317312
out.emplace_back(opcode, std::move(push_data));
318313
}
319314
std::reverse(out.begin(), out.end());
320-
return true;
315+
return out;
321316
}
322317

323-
bool ParseScriptNumber(const std::pair<opcodetype, std::vector<unsigned char>>& in, int64_t& k) {
318+
std::optional<int64_t> ParseScriptNumber(const Opcode& in) {
324319
if (in.first == OP_0) {
325-
k = 0;
326-
return true;
320+
return 0;
327321
}
328322
if (!in.second.empty()) {
329-
if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return false;
323+
if (IsPushdataOp(in.first) && !CheckMinimalPush(in.second, in.first)) return {};
330324
try {
331-
k = CScriptNum(in.second, true).GetInt64();
332-
return true;
325+
return CScriptNum(in.second, true).GetInt64();
333326
} catch(const scriptnum_error&) {}
334327
}
335-
return false;
328+
return {};
336329
}
337330

338331
int FindNextChar(Span<const char> sp, const char m)

0 commit comments

Comments
 (0)