Skip to content

Commit 79728c4

Browse files
committed
Add (sorted)multi_a descriptor and script derivation
1 parent 25e95f9 commit 79728c4

File tree

4 files changed

+84
-5
lines changed

4 files changed

+84
-5
lines changed

src/script/descriptor.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,30 @@ class MultisigDescriptor final : public DescriptorImpl
794794
bool IsSingleType() const final { return true; }
795795
};
796796

797+
/** A parsed (sorted)multi_a(...) descriptor. Always uses x-only pubkeys. */
798+
class MultiADescriptor final : public DescriptorImpl
799+
{
800+
const int m_threshold;
801+
const bool m_sorted;
802+
protected:
803+
std::string ToStringExtra() const override { return strprintf("%i", m_threshold); }
804+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override {
805+
CScript ret;
806+
std::vector<XOnlyPubKey> xkeys;
807+
for (const auto& key : keys) xkeys.emplace_back(key);
808+
if (m_sorted) std::sort(xkeys.begin(), xkeys.end());
809+
ret << ToByteVector(xkeys[0]) << OP_CHECKSIG;
810+
for (size_t i = 1; i < keys.size(); ++i) {
811+
ret << ToByteVector(xkeys[i]) << OP_CHECKSIGADD;
812+
}
813+
ret << m_threshold << OP_NUMEQUAL;
814+
return Vector(std::move(ret));
815+
}
816+
public:
817+
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
818+
bool IsSingleType() const final { return true; }
819+
};
820+
797821
/** A parsed sh(...) descriptor. */
798822
class SHDescriptor final : public DescriptorImpl
799823
{
@@ -1032,7 +1056,6 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
10321056
using namespace spanparsing;
10331057

10341058
auto expr = Expr(sp);
1035-
bool sorted_multi = false;
10361059
if (Func("pk", expr)) {
10371060
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
10381061
if (!pubkey) return nullptr;
@@ -1057,7 +1080,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
10571080
error = "Can only have combo() at top level";
10581081
return nullptr;
10591082
}
1060-
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
1083+
const bool multi = Func("multi", expr);
1084+
const bool sortedmulti = !multi && Func("sortedmulti", expr);
1085+
const bool multi_a = !(multi || sortedmulti) && Func("multi_a", expr);
1086+
const bool sortedmulti_a = !(multi || sortedmulti || multi_a) && Func("sortedmulti_a", expr);
1087+
if (((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && (multi || sortedmulti)) ||
1088+
(ctx == ParseScriptContext::P2TR && (multi_a || sortedmulti_a))) {
10611089
auto threshold = Expr(expr);
10621090
uint32_t thres;
10631091
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@@ -1078,9 +1106,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
10781106
providers.emplace_back(std::move(pk));
10791107
key_exp_index++;
10801108
}
1081-
if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
1109+
if ((multi || sortedmulti) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG)) {
10821110
error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
10831111
return nullptr;
1112+
} else if ((multi_a || sortedmulti_a) && (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTI_A)) {
1113+
error = strprintf("Cannot have %u keys in multi_a; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTI_A);
1114+
return nullptr;
10841115
} else if (thres < 1) {
10851116
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
10861117
return nullptr;
@@ -1101,10 +1132,17 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
11011132
return nullptr;
11021133
}
11031134
}
1104-
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
1105-
} else if (Func("sortedmulti", expr) || Func("multi", expr)) {
1135+
if (multi || sortedmulti) {
1136+
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sortedmulti);
1137+
} else {
1138+
return std::make_unique<MultiADescriptor>(thres, std::move(providers), sortedmulti_a);
1139+
}
1140+
} else if (multi || sortedmulti) {
11061141
error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
11071142
return nullptr;
1143+
} else if (multi_a || sortedmulti_a) {
1144+
error = "Can only have multi_a/sortedmulti_a inside tr()";
1145+
return nullptr;
11081146
}
11091147
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
11101148
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);

src/script/script.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ static const int MAX_OPS_PER_SCRIPT = 201;
2929
// Maximum number of public keys per multisig
3030
static const int MAX_PUBKEYS_PER_MULTISIG = 20;
3131

32+
/** The limit of keys in OP_CHECKSIGADD-based scripts. It is due to the stack limit in BIP342. */
33+
static constexpr unsigned int MAX_PUBKEYS_PER_MULTI_A = 999;
34+
3235
// Maximum script length in bytes
3336
static const int MAX_SCRIPT_SIZE = 10000;
3437

src/script/standard.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,40 @@ static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector
139139
return (it + 1 == script.end());
140140
}
141141

142+
std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script)
143+
{
144+
std::vector<Span<const unsigned char>> keyspans;
145+
146+
// Redundant, but very fast and selective test.
147+
if (script.size() == 0 || script[0] != 32 || script.back() != OP_NUMEQUAL) return {};
148+
149+
// Parse keys
150+
auto it = script.begin();
151+
while (script.end() - it >= 34) {
152+
if (*it != 32) return {};
153+
++it;
154+
keyspans.emplace_back(&*it, 32);
155+
it += 32;
156+
if (*it != (keyspans.size() == 1 ? OP_CHECKSIG : OP_CHECKSIGADD)) return {};
157+
++it;
158+
}
159+
if (keyspans.size() == 0 || keyspans.size() > MAX_PUBKEYS_PER_MULTI_A) return {};
160+
161+
// Parse threshold.
162+
opcodetype opcode;
163+
std::vector<unsigned char> data;
164+
if (!script.GetOp(it, opcode, data)) return {};
165+
if (it == script.end()) return {};
166+
if (*it != OP_NUMEQUAL) return {};
167+
++it;
168+
if (it != script.end()) return {};
169+
auto threshold = GetScriptNumber(opcode, data, 1, (int)keyspans.size());
170+
if (!threshold) return {};
171+
172+
// Construct result.
173+
return std::pair{*threshold, std::move(keyspans)};
174+
}
175+
142176
TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned char>>& vSolutionsRet)
143177
{
144178
vSolutionsRet.clear();

src/script/standard.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ CScript GetScriptForDestination(const CTxDestination& dest);
191191
/** Generate a P2PK script for the given pubkey. */
192192
CScript GetScriptForRawPubKey(const CPubKey& pubkey);
193193

194+
/** Determine if script is a "multi_a" script. Returns (threshold, keyspans) if so, and nullopt otherwise.
195+
* The keyspans refer to bytes in the passed script. */
196+
std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND);
197+
194198
/** Generate a multisig script. */
195199
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
196200

0 commit comments

Comments
 (0)