miniscript: convert non-critical asserts to CHECK_NONFATAL

The Miniscript code contains assertions to prevent ending up in an insane state or prevent UB, but
also to enforce logical invariants. For the latter it is not necessary to crash the program if they
are broken. Raising an exception suffices, especially as this code is often called through the RPC
interface which can in turn handle the exception and the user can report it to developers.

This is based on previous work from Pieter Wuille.
This commit is contained in:
Antoine Poinsot
2025-01-23 10:48:23 -05:00
parent 5acf12bafe
commit ff0194a7ce
2 changed files with 54 additions and 51 deletions

View File

@@ -19,20 +19,20 @@ namespace internal {
Type SanitizeType(Type e) {
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
assert(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o
assert(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z
assert(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W
assert(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d
assert(!(e << "K"_mst) || (e << "u"_mst)); // K implies u
assert(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u
assert(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f
assert(!(e << "e"_mst) || (e << "d"_mst)); // e implies d
assert(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e
assert(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f
assert(!(e << "V"_mst) || (e << "f"_mst)); // V implies f
assert(!(e << "K"_mst) || (e << "s"_mst)); // K implies s
assert(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
CHECK_NONFATAL(num_types == 1); // K, V, B, W all conflict with each other
CHECK_NONFATAL(!(e << "z"_mst) || !(e << "o"_mst)); // z conflicts with o
CHECK_NONFATAL(!(e << "n"_mst) || !(e << "z"_mst)); // n conflicts with z
CHECK_NONFATAL(!(e << "n"_mst) || !(e << "W"_mst)); // n conflicts with W
CHECK_NONFATAL(!(e << "V"_mst) || !(e << "d"_mst)); // V conflicts with d
CHECK_NONFATAL(!(e << "K"_mst) || (e << "u"_mst)); // K implies u
CHECK_NONFATAL(!(e << "V"_mst) || !(e << "u"_mst)); // V conflicts with u
CHECK_NONFATAL(!(e << "e"_mst) || !(e << "f"_mst)); // e conflicts with f
CHECK_NONFATAL(!(e << "e"_mst) || (e << "d"_mst)); // e implies d
CHECK_NONFATAL(!(e << "V"_mst) || !(e << "e"_mst)); // V conflicts with e
CHECK_NONFATAL(!(e << "d"_mst) || !(e << "f"_mst)); // d conflicts with f
CHECK_NONFATAL(!(e << "V"_mst) || (e << "f"_mst)); // V implies f
CHECK_NONFATAL(!(e << "K"_mst) || (e << "s"_mst)); // K implies s
CHECK_NONFATAL(!(e << "z"_mst) || (e << "m"_mst)); // z implies m
return e;
}
@@ -40,46 +40,46 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx) {
// Sanity check on data
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
assert(data_size == 32);
CHECK_NONFATAL(data_size == 32);
} else if (fragment == Fragment::RIPEMD160 || fragment == Fragment::HASH160) {
assert(data_size == 20);
CHECK_NONFATAL(data_size == 20);
} else {
assert(data_size == 0);
CHECK_NONFATAL(data_size == 0);
}
// Sanity check on k
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
assert(k >= 1 && k < 0x80000000UL);
CHECK_NONFATAL(k >= 1 && k < 0x80000000UL);
} else if (fragment == Fragment::MULTI || fragment == Fragment::MULTI_A) {
assert(k >= 1 && k <= n_keys);
CHECK_NONFATAL(k >= 1 && k <= n_keys);
} else if (fragment == Fragment::THRESH) {
assert(k >= 1 && k <= n_subs);
CHECK_NONFATAL(k >= 1 && k <= n_subs);
} else {
assert(k == 0);
CHECK_NONFATAL(k == 0);
}
// Sanity check on subs
if (fragment == Fragment::AND_V || fragment == Fragment::AND_B || fragment == Fragment::OR_B ||
fragment == Fragment::OR_C || fragment == Fragment::OR_I || fragment == Fragment::OR_D) {
assert(n_subs == 2);
CHECK_NONFATAL(n_subs == 2);
} else if (fragment == Fragment::ANDOR) {
assert(n_subs == 3);
CHECK_NONFATAL(n_subs == 3);
} else if (fragment == Fragment::WRAP_A || fragment == Fragment::WRAP_S || fragment == Fragment::WRAP_C ||
fragment == Fragment::WRAP_D || fragment == Fragment::WRAP_V || fragment == Fragment::WRAP_J ||
fragment == Fragment::WRAP_N) {
assert(n_subs == 1);
CHECK_NONFATAL(n_subs == 1);
} else if (fragment != Fragment::THRESH) {
assert(n_subs == 0);
CHECK_NONFATAL(n_subs == 0);
}
// Sanity check on keys
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
assert(n_keys == 1);
CHECK_NONFATAL(n_keys == 1);
} else if (fragment == Fragment::MULTI) {
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTISIG);
assert(!IsTapscript(ms_ctx));
CHECK_NONFATAL(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTISIG);
CHECK_NONFATAL(!IsTapscript(ms_ctx));
} else if (fragment == Fragment::MULTI_A) {
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTI_A);
assert(IsTapscript(ms_ctx));
CHECK_NONFATAL(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTI_A);
CHECK_NONFATAL(IsTapscript(ms_ctx));
} else {
assert(n_keys == 0);
CHECK_NONFATAL(n_keys == 0);
}
// Below is the per-fragment logic for computing the expression types.