@@ -45,8 +45,8 @@ namespace miniscript {
45
45
* - When satisfied, pushes nothing.
46
46
* - Cannot be dissatisfied.
47
47
* - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode
48
- * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY
49
- * and OP_EQUAL), or by combining a V fragment under some conditions.
48
+ * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY,
49
+ * OP_NUMEQUAL and OP_EQUAL), or by combining a V fragment under some conditions.
50
50
* - For example vc:pk_k(key) = <key> OP_CHECKSIGVERIFY
51
51
* - "K" Key:
52
52
* - Takes its inputs from the top of the stack.
@@ -217,7 +217,8 @@ enum class Fragment {
217
217
OR_I, // !< OP_IF [X] OP_ELSE [Y] OP_ENDIF
218
218
ANDOR, // !< [X] OP_NOTIF [Z] OP_ELSE [Y] OP_ENDIF
219
219
THRESH, // !< [X1] ([Xn] OP_ADD)* [k] OP_EQUAL
220
- MULTI, // !< [k] [key_n]* [n] OP_CHECKMULTISIG
220
+ MULTI, // !< [k] [key_n]* [n] OP_CHECKMULTISIG (only available within P2WSH context)
221
+ MULTI_A, // !< [key_0] OP_CHECKSIG ([key_n] OP_CHECKSIGADD)* [k] OP_NUMEQUAL (only within Tapscript ctx)
221
222
// AND_N(X,Y) is represented as ANDOR(X,Y,0)
222
223
// WRAP_T(X) is represented as AND_V(X,1)
223
224
// WRAP_L(X) is represented as OR_I(0,X)
@@ -637,6 +638,14 @@ struct Node {
637
638
}
638
639
return BuildScript (std::move (script), node.keys .size (), verify ? OP_CHECKMULTISIGVERIFY : OP_CHECKMULTISIG);
639
640
}
641
+ case Fragment::MULTI_A: {
642
+ CHECK_NONFATAL (is_tapscript);
643
+ CScript script = BuildScript (ctx.ToPKBytes (*node.keys .begin ()), OP_CHECKSIG);
644
+ for (auto it = node.keys .begin () + 1 ; it != node.keys .end (); ++it) {
645
+ script = BuildScript (std::move (script), ctx.ToPKBytes (*it), OP_CHECKSIGADD);
646
+ }
647
+ return BuildScript (std::move (script), node.k , verify ? OP_NUMEQUALVERIFY : OP_NUMEQUAL);
648
+ }
640
649
case Fragment::THRESH: {
641
650
CScript script = std::move (subs[0 ]);
642
651
for (size_t i = 1 ; i < subs.size (); ++i) {
@@ -740,6 +749,16 @@ struct Node {
740
749
}
741
750
return std::move (str) + " )" ;
742
751
}
752
+ case Fragment::MULTI_A: {
753
+ CHECK_NONFATAL (is_tapscript);
754
+ auto str = std::move (ret) + " multi_a(" + ::ToString (node.k );
755
+ for (const auto & key : node.keys ) {
756
+ auto key_str = ctx.ToString (key);
757
+ if (!key_str) return {};
758
+ str += " ," + std::move (*key_str);
759
+ }
760
+ return std::move (str) + " )" ;
761
+ }
743
762
case Fragment::THRESH: {
744
763
auto str = std::move (ret) + " thresh(" + ::ToString (node.k );
745
764
for (auto & sub : subs) {
@@ -805,6 +824,7 @@ struct Node {
805
824
return {count, sat, dsat};
806
825
}
807
826
case Fragment::MULTI: return {1 , (uint32_t )keys.size (), (uint32_t )keys.size ()};
827
+ case Fragment::MULTI_A: return {(uint32_t )keys.size () + 1 , 0 , 0 };
808
828
case Fragment::WRAP_S:
809
829
case Fragment::WRAP_C:
810
830
case Fragment::WRAP_N: return {1 + subs[0 ]->ops .count , subs[0 ]->ops .sat , subs[0 ]->ops .dsat };
@@ -857,6 +877,7 @@ struct Node {
857
877
case Fragment::OR_D: return {subs[0 ]->ss .sat | (subs[0 ]->ss .dsat + subs[1 ]->ss .sat ), subs[0 ]->ss .dsat + subs[1 ]->ss .dsat };
858
878
case Fragment::OR_I: return {(subs[0 ]->ss .sat + 1 ) | (subs[1 ]->ss .sat + 1 ), (subs[0 ]->ss .dsat + 1 ) | (subs[1 ]->ss .dsat + 1 )};
859
879
case Fragment::MULTI: return {k + 1 , k + 1 };
880
+ case Fragment::MULTI_A: return {keys.size (), keys.size ()};
860
881
case Fragment::WRAP_A:
861
882
case Fragment::WRAP_N:
862
883
case Fragment::WRAP_S: return subs[0 ]->ss ;
@@ -907,6 +928,7 @@ struct Node {
907
928
case Fragment::OR_D: return {subs[0 ]->ws .sat | (subs[0 ]->ws .dsat + subs[1 ]->ws .sat ), subs[0 ]->ws .dsat + subs[1 ]->ws .dsat };
908
929
case Fragment::OR_I: return {(subs[0 ]->ws .sat + 1 + 1 ) | (subs[1 ]->ws .sat + 1 ), (subs[0 ]->ws .dsat + 1 + 1 ) | (subs[1 ]->ws .dsat + 1 )};
909
930
case Fragment::MULTI: return {k * (1 + 72 ) + 1 , k + 1 };
931
+ case Fragment::MULTI_A: return {k * (1 + 65 ) + static_cast <uint32_t >(keys.size ()) - k, static_cast <uint32_t >(keys.size ())};
910
932
case Fragment::WRAP_A:
911
933
case Fragment::WRAP_N:
912
934
case Fragment::WRAP_S:
@@ -947,6 +969,34 @@ struct Node {
947
969
Availability avail = ctx.Sign (node.keys [0 ], sig);
948
970
return {ZERO + InputStack (key), (InputStack (std::move (sig)).SetWithSig () + InputStack (key)).SetAvailable (avail)};
949
971
}
972
+ case Fragment::MULTI_A: {
973
+ // sats[j] represents the best stack containing j valid signatures (out of the first i keys).
974
+ // In the loop below, these stacks are built up using a dynamic programming approach.
975
+ std::vector<InputStack> sats = Vector (EMPTY);
976
+ for (size_t i = 0 ; i < node.keys .size (); ++i) {
977
+ // Get the signature for the i'th key in reverse order (the signature for the first key needs to
978
+ // be at the top of the stack, contrary to CHECKMULTISIG's satisfaction).
979
+ std::vector<unsigned char > sig;
980
+ Availability avail = ctx.Sign (node.keys [node.keys .size () - 1 - i], sig);
981
+ // Compute signature stack for just this key.
982
+ auto sat = InputStack (std::move (sig)).SetWithSig ().SetAvailable (avail);
983
+ // Compute the next sats vector: next_sats[0] is a copy of sats[0] (no signatures). All further
984
+ // next_sats[j] are equal to either the existing sats[j] + ZERO, or sats[j-1] plus a signature
985
+ // for the current (i'th) key. The very last element needs all signatures filled.
986
+ std::vector<InputStack> next_sats;
987
+ next_sats.push_back (sats[0 ] + ZERO);
988
+ for (size_t j = 1 ; j < sats.size (); ++j) next_sats.push_back ((sats[j] + ZERO) | (std::move (sats[j - 1 ]) + sat));
989
+ next_sats.push_back (std::move (sats[sats.size () - 1 ]) + std::move (sat));
990
+ // Switch over.
991
+ sats = std::move (next_sats);
992
+ }
993
+ // The dissatisfaction consists of as many empty vectors as there are keys, which is the same as
994
+ // satisfying 0 keys.
995
+ auto & nsat{sats[0 ]};
996
+ assert (node.k != 0 );
997
+ assert (node.k <= sats.size ());
998
+ return {std::move (nsat), std::move (sats[node.k ])};
999
+ }
950
1000
case Fragment::MULTI: {
951
1001
// sats[j] represents the best stack containing j valid signatures (out of the first i keys).
952
1002
// In the loop below, these stacks are built up using a dynamic programming approach.
@@ -1281,6 +1331,7 @@ struct Node {
1281
1331
case Fragment::PK_K:
1282
1332
case Fragment::PK_H:
1283
1333
case Fragment::MULTI:
1334
+ case Fragment::MULTI_A:
1284
1335
case Fragment::AFTER:
1285
1336
case Fragment::OLDER:
1286
1337
case Fragment::HASH256:
@@ -1502,6 +1553,41 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1502
1553
1503
1554
to_parse.emplace_back (ParseContext::WRAPPED_EXPR, -1 , -1 );
1504
1555
1556
+ // Parses a multi() or multi_a() from its string representation. Returns false on parsing error.
1557
+ const auto parse_multi_exp = [&](Span<const char >& in, const bool is_multi_a) -> bool {
1558
+ const auto max_keys{is_multi_a ? MAX_PUBKEYS_PER_MULTI_A : MAX_PUBKEYS_PER_MULTISIG};
1559
+ const auto required_ctx{is_multi_a ? MiniscriptContext::TAPSCRIPT : MiniscriptContext::P2WSH};
1560
+ if (ctx.MsContext () != required_ctx) return false ;
1561
+ // Get threshold
1562
+ int next_comma = FindNextChar (in, ' ,' );
1563
+ if (next_comma < 1 ) return false ;
1564
+ int64_t k;
1565
+ if (!ParseInt64 (std::string (in.begin (), in.begin () + next_comma), &k)) return false ;
1566
+ in = in.subspan (next_comma + 1 );
1567
+ // Get keys. It is compatible for both compressed and x-only keys.
1568
+ std::vector<Key> keys;
1569
+ while (next_comma != -1 ) {
1570
+ next_comma = FindNextChar (in, ' ,' );
1571
+ int key_length = (next_comma == -1 ) ? FindNextChar (in, ' )' ) : next_comma;
1572
+ if (key_length < 1 ) return false ;
1573
+ auto key = ctx.FromString (in.begin (), in.begin () + key_length);
1574
+ if (!key) return false ;
1575
+ keys.push_back (std::move (*key));
1576
+ in = in.subspan (key_length + 1 );
1577
+ }
1578
+ if (keys.size () < 1 || keys.size () > max_keys) return false ;
1579
+ if (k < 1 || k > (int64_t )keys.size ()) return false ;
1580
+ if (is_multi_a) {
1581
+ // (push + xonly-key + CHECKSIG[ADD]) * n + k + OP_NUMEQUAL(VERIFY), minus one.
1582
+ script_size += (1 + 32 + 1 ) * keys.size () + BuildScript (k).size ();
1583
+ constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext (), Fragment::MULTI_A, std::move (keys), k));
1584
+ } else {
1585
+ script_size += 2 + (keys.size () > 16 ) + (k > 16 ) + 34 * keys.size ();
1586
+ constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext (), Fragment::MULTI, std::move (keys), k));
1587
+ }
1588
+ return true ;
1589
+ };
1590
+
1505
1591
while (!to_parse.empty ()) {
1506
1592
if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {};
1507
1593
@@ -1646,27 +1732,9 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1646
1732
in = in.subspan (arg_size + 1 );
1647
1733
script_size += 1 + (num > 16 ) + (num > 0x7f ) + (num > 0x7fff ) + (num > 0x7fffff );
1648
1734
} else if (Const (" multi(" , in)) {
1649
- if (IsTapscript (ctx.MsContext ())) return {};
1650
- // Get threshold
1651
- int next_comma = FindNextChar (in, ' ,' );
1652
- if (next_comma < 1 ) return {};
1653
- if (!ParseInt64 (std::string (in.begin (), in.begin () + next_comma), &k)) return {};
1654
- in = in.subspan (next_comma + 1 );
1655
- // Get keys
1656
- std::vector<Key> keys;
1657
- while (next_comma != -1 ) {
1658
- next_comma = FindNextChar (in, ' ,' );
1659
- int key_length = (next_comma == -1 ) ? FindNextChar (in, ' )' ) : next_comma;
1660
- if (key_length < 1 ) return {};
1661
- auto key = ctx.FromString (in.begin (), in.begin () + key_length);
1662
- if (!key) return {};
1663
- keys.push_back (std::move (*key));
1664
- in = in.subspan (key_length + 1 );
1665
- }
1666
- if (keys.size () < 1 || keys.size () > 20 ) return {};
1667
- if (k < 1 || k > (int64_t )keys.size ()) return {};
1668
- script_size += 2 + (keys.size () > 16 ) + (k > 16 ) + 34 * keys.size ();
1669
- constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext (), Fragment::MULTI, std::move (keys), k));
1735
+ if (!parse_multi_exp (in, /* is_multi_a = */ false )) return {};
1736
+ } else if (Const (" multi_a(" , in)) {
1737
+ if (!parse_multi_exp (in, /* is_multi_a = */ true )) return {};
1670
1738
} else if (Const (" thresh(" , in)) {
1671
1739
int next_comma = FindNextChar (in, ' ,' );
1672
1740
if (next_comma < 1 ) return {};
@@ -1843,8 +1911,8 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx)
1843
1911
* Construct a vector with one element per opcode in the script, in reverse order.
1844
1912
* Each element is a pair consisting of the opcode, as well as the data pushed by
1845
1913
* the opcode (including OP_n), if any. OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY,
1846
- * and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL
1847
- * respectively, plus OP_VERIFY.
1914
+ * OP_NUMEQUALVERIFY and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG,
1915
+ * OP_EQUAL and OP_NUMEQUAL respectively, plus OP_VERIFY.
1848
1916
*/
1849
1917
std::optional<std::vector<Opcode>> DecomposeScript (const CScript& script);
1850
1918
@@ -2023,6 +2091,36 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx)
2023
2091
constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext (), Fragment::MULTI, std::move (keys), *k));
2024
2092
break ;
2025
2093
}
2094
+ // Tapscript's equivalent of multi
2095
+ if (last - in >= 4 && in[0 ].first == OP_NUMEQUAL) {
2096
+ if (!IsTapscript (ctx.MsContext ())) return {};
2097
+ // The necessary threshold of signatures.
2098
+ const auto k = ParseScriptNumber (in[1 ]);
2099
+ if (!k) return {};
2100
+ if (*k < 1 || *k > MAX_PUBKEYS_PER_MULTI_A) return {};
2101
+ if (last - in < 2 + *k * 2 ) return {};
2102
+ std::vector<Key> keys;
2103
+ keys.reserve (*k);
2104
+ // Walk through the expected (pubkey, CHECKSIG[ADD]) pairs.
2105
+ for (int pos = 2 ;; pos += 2 ) {
2106
+ if (last - in < pos + 2 ) return {};
2107
+ // Make sure it's indeed an x-only pubkey and a CHECKSIG[ADD], then parse the key.
2108
+ if (in[pos].first != OP_CHECKSIGADD && in[pos].first != OP_CHECKSIG) return {};
2109
+ if (in[pos + 1 ].second .size () != 32 ) return {};
2110
+ auto key = ctx.FromPKBytes (in[pos + 1 ].second .begin (), in[pos + 1 ].second .end ());
2111
+ if (!key) return {};
2112
+ keys.push_back (std::move (*key));
2113
+ // Make sure early we don't parse an arbitrary large expression.
2114
+ if (keys.size () > MAX_PUBKEYS_PER_MULTI_A) return {};
2115
+ // OP_CHECKSIG means it was the last one to parse.
2116
+ if (in[pos].first == OP_CHECKSIG) break ;
2117
+ }
2118
+ if (keys.size () < (size_t )*k) return {};
2119
+ in += 2 + keys.size () * 2 ;
2120
+ std::reverse (keys.begin (), keys.end ());
2121
+ constructed.push_back (MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext (), Fragment::MULTI_A, std::move (keys), *k));
2122
+ break ;
2123
+ }
2026
2124
/* * In the following wrappers, we only need to push SINGLE_BKV_EXPR rather
2027
2125
* than BKV_EXPR, because and_v commutes with these wrappers. For example,
2028
2126
* c:and_v(X,Y) produces the same script as and_v(X,c:Y). */
0 commit comments