Skip to content

Commit ce735e8

Browse files
committed
Reject oversized multisig miniscripts
1 parent fbece75 commit ce735e8

3 files changed

Lines changed: 62 additions & 3 deletions

File tree

NBitcoin.Tests/MiniscriptTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,29 @@ public void CanGenerateScript(string miniscript, string expected)
332332
Assert.Equal(expected, parsed.ToScriptCodeString());
333333
}
334334

335+
[Theory]
336+
[InlineData("multi")]
337+
[InlineData("sortedmulti")]
338+
public void RejectsCheckMultiSigWithMoreThanTwentyKeys(string fragment)
339+
{
340+
var settings = new MiniscriptParsingSettings(Network.Main, KeyType.Classic)
341+
{
342+
Dialect = MiniscriptDialect.Strict,
343+
AllowedParameters = ParameterTypeFlags.All
344+
};
345+
var valid = $"{fragment}(2,{string.Join(",", GeneratePubKeys(20))})";
346+
var invalid = $"{fragment}(2,{string.Join(",", GeneratePubKeys(21))})";
347+
348+
var parsed = Miniscript.Parse(valid, settings);
349+
Assert.Equal(valid, parsed.ToString());
350+
parsed.ToScripts();
351+
352+
Assert.False(Miniscript.TryParse(invalid, settings, out var error, out _));
353+
Assert.IsType<MiniscriptError.TooManyKeys>(error);
354+
var exception = Assert.Throws<MiniscriptFormatException>(() => Miniscript.Parse(invalid, settings));
355+
Assert.IsType<MiniscriptError.TooManyKeys>(exception.Error);
356+
}
357+
335358
[Fact]
336359
public void CanParseMusigExpression()
337360
{
@@ -537,6 +560,16 @@ private static HDKeyNode[] GenerateKeys(int count)
537560
}).ToArray();
538561
}
539562

563+
private static string[] GeneratePubKeys(int count)
564+
{
565+
return Enumerable.Range(1, count).Select(i =>
566+
{
567+
var data = new byte[32];
568+
data[31] = (byte)i;
569+
return new Key(data).PubKey.ToHex();
570+
}).ToArray();
571+
}
572+
540573
[Fact]
541574
public void CanManipulateKeyExpression()
542575
{

NBitcoin/WalletPolicies/Miniscript.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ private static bool TryParseExpressions(ParsingContext ctx, [MaybeNullWhen(true)
176176
return TryParseExpression(ctx, out error, out node);
177177
}
178178
}
179-
private static bool TryParsePubKeys<T>(ParsingContext ctx, [MaybeNullWhen(true)] out MiniscriptError error, [MaybeNullWhen(false)] out MiniscriptNode node)
179+
private static bool TryParseCheckMultiSigPubKeys(ParsingContext ctx, [MaybeNullWhen(true)] out MiniscriptError error, [MaybeNullWhen(false)] out MiniscriptNode node) =>
180+
TryParsePubKeys<Value.PubKeyValue>(ctx, FragmentDescriptor.MaxCheckMultiSigPubKeys, out error, out node);
181+
182+
private static bool TryParsePubKeys<T>(ParsingContext ctx, [MaybeNullWhen(true)] out MiniscriptError error, [MaybeNullWhen(false)] out MiniscriptNode node) =>
183+
TryParsePubKeys<T>(ctx, null, out error, out node);
184+
185+
private static bool TryParsePubKeys<T>(ParsingContext ctx, int? maxPubKeys, [MaybeNullWhen(true)] out MiniscriptError error, [MaybeNullWhen(false)] out MiniscriptNode node)
180186
{
181187
node = null;
182188
error = null;
@@ -200,6 +206,12 @@ private static bool TryParsePubKeys<T>(ParsingContext ctx, [MaybeNullWhen(true)]
200206
}
201207
else
202208
{
209+
var parsedPubKeyCount = ctx.CurrentFrame.Parameters.Count - 1;
210+
if (maxPubKeys is { } max && parsedPubKeyCount >= max)
211+
{
212+
error = new MiniscriptError.TooManyKeys(ctx.Offset, max);
213+
return false;
214+
}
203215
if (ctx.CurrentFrame.ExpectedParameterCount == ctx.CurrentFrame.Parameters.Count + 1)
204216
ctx.CurrentFrame.ExpectedParameterCount = -1;
205217
return TryParseKey(ctx, out error, out node);
@@ -556,8 +568,8 @@ private static bool TryParseExpression(ParsingContext ctx, [MaybeNullWhen(true)]
556568
"or_d" => TryParseParameters(ctx, 2, TryParseExpression, out error, out var p) ? FragmentTwoParameters.or_d(p[0], p[1]) : null,
557569
"or_i" => TryParseParameters(ctx, 2, TryParseExpression, out error, out var p) ? FragmentTwoParameters.or_i(p[0], p[1]) : null,
558570
"thresh" => TryParseParameters(ctx, 1, TryParseExpressions, out error, out var p) ? FragmentUnboundedParameters.thresh(p) : null,
559-
"sortedmulti" => TryParseParameters(ctx, 1, TryParsePubKeys<Value.PubKeyValue>, out error, out var p) ? FragmentUnboundedParameters.sortedmulti(p) : null,
560-
"multi" => TryParseParameters(ctx, 1, TryParsePubKeys<Value.PubKeyValue>, out error, out var p) ? FragmentUnboundedParameters.multi(p) : null,
571+
"sortedmulti" => TryParseParameters(ctx, 1, TryParseCheckMultiSigPubKeys, out error, out var p) ? FragmentUnboundedParameters.sortedmulti(p) : null,
572+
"multi" => TryParseParameters(ctx, 1, TryParseCheckMultiSigPubKeys, out error, out var p) ? FragmentUnboundedParameters.multi(p) : null,
561573
"multi_a" => ctx.Network.Consensus.SupportTaproot && TryParseParameters(ctx, 1, TryParsePubKeys<Value.TaprootPubKeyValue>, out error, out var p) ? FragmentUnboundedParameters.multi_a(p) : null,
562574
"sortedmulti_a" => ctx.Network.Consensus.SupportTaproot && TryParseParameters(ctx, 1, TryParsePubKeys<Value.TaprootPubKeyValue>, out error, out var p) ? FragmentUnboundedParameters.sortedmulti_a(p) : null,
563575
_ => null
@@ -924,6 +936,10 @@ public record TooManyParameters(int Index, int Expected) : MiniscriptError
924936
{
925937
public override string ToString() => $"Too many parameters at index {Index}, expected {Expected}";
926938
}
939+
public record TooManyKeys(int Index, int Maximum) : MiniscriptError
940+
{
941+
public override string ToString() => $"Too many keys at index {Index}, maximum {Maximum}";
942+
}
927943
public record TooFewParameters(int Index, int Expected) : MiniscriptError
928944
{
929945
public override string ToString() => $"Too few parameters at index {Index}, expected {Expected}";

NBitcoin/WalletPolicies/MiniscriptNode.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ namespace NBitcoin.WalletPolicies
2525
{
2626
public class FragmentDescriptor
2727
{
28+
internal const int MaxCheckMultiSigPubKeys = 20;
29+
2830
public bool IsOr() =>
2931
this == or_b ||
3032
this == or_c ||
@@ -40,6 +42,12 @@ public bool IsHash() =>
4042
this == hash256 ||
4143
this == hash160;
4244
private static Op HASH160(List<Op> node) => Op.GetPushOp(Hashes.Hash160(node[0].PushData).ToBytes());
45+
private static void AssertCheckMultiSigPubKeyCount(List<Op>[] v)
46+
{
47+
var pubKeyCount = v.Length - 1;
48+
if (pubKeyCount > MaxCheckMultiSigPubKeys)
49+
throw new InvalidOperationException($"CHECKMULTISIG supports at most {MaxCheckMultiSigPubKeys} public keys.");
50+
}
4351
FragmentDescriptor(string name,
4452
Action<List<Op>[], List<Op>> addOps)
4553
{
@@ -172,6 +180,7 @@ public bool IsHash() =>
172180
"multi",
173181
(v, ops) =>
174182
{
183+
AssertCheckMultiSigPubKeyCount(v);
175184
int i = 0;
176185
while (i < v.Length)
177186
{
@@ -184,6 +193,7 @@ public bool IsHash() =>
184193
"sortedmulti",
185194
(v, ops) =>
186195
{
196+
AssertCheckMultiSigPubKeyCount(v);
187197
var pks = new byte[v.Length - 1][];
188198
for (int i = 1; i < v.Length; i++)
189199
{

0 commit comments

Comments
 (0)