diff --git a/NBitcoin.Tests/Generators/AbstractPolicyGenerator.cs b/NBitcoin.Tests/Generators/AbstractPolicyGenerator.cs
new file mode 100644
index 0000000000..efdb43ed81
--- /dev/null
+++ b/NBitcoin.Tests/Generators/AbstractPolicyGenerator.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FsCheck;
+using NBitcoin.Scripting;
+
+namespace NBitcoin.Tests.Generators
+{
+ public class AbstractPolicyGenerator
+ {
+
+ ///
+ /// Ideally This should be able to specify the size from the callers side. But who cares.
+ ///
+ ///
+ public static Arbitrary AbstractPolicyArb()
+ => new ArbitraryAbstractPolicy();
+
+ public class ArbitraryAbstractPolicy : Arbitrary
+ {
+ public override Gen Generator { get { return Gen.Sized(s => AbstractPolicyGen(s)); } }
+ public override IEnumerable Shrinker(AbstractPolicy parent)
+ {
+ switch (parent)
+ {
+ case AbstractPolicy.And p:
+ {
+ yield return p.Item1;
+ yield return p.Item2;
+ foreach (var t in Shrinker(p.Item1).SelectMany(shrinkedItem1 => Shrinker(p.Item2), (i1, i2) => Tuple.Create(i1, i2)))
+ yield return AbstractPolicy.NewAnd(t.Item1, t.Item2);
+ foreach (var subShrinked in Shrinker(p.Item1).Select(shrinkedItem1 => AbstractPolicy.NewAnd(shrinkedItem1, p.Item2)))
+ yield return subShrinked;
+ foreach (var subShrinked in Shrinker(p.Item2).Select(shrinkedItem2 => AbstractPolicy.NewAnd(p.Item1, shrinkedItem2)))
+ yield return subShrinked;
+ break;
+ }
+ case AbstractPolicy.Or p:
+ {
+ yield return p.Item1;
+ yield return p.Item2;
+ foreach (var t in Shrinker(p.Item1).SelectMany(shrinkedItem1 => Shrinker(p.Item2), (i1, i2) => Tuple.Create(i1, i2)))
+ yield return AbstractPolicy.NewOr(t.Item1, t.Item2);
+ foreach (var subShrinked in Shrinker(p.Item1).Select(shrinkedItem1 => AbstractPolicy.NewOr(shrinkedItem1, p.Item2)))
+ yield return subShrinked;
+ foreach (var subShrinked in Shrinker(p.Item2).Select(shrinkedItem2 => AbstractPolicy.NewOr(p.Item1, shrinkedItem2)))
+ yield return subShrinked;
+ break;
+ }
+ case AbstractPolicy.AsymmetricOr p:
+ {
+ yield return p.Item1;
+ yield return p.Item2;
+ foreach (var t in Shrinker(p.Item1).SelectMany(shrinkedItem1 => Shrinker(p.Item2), (i1, i2) => Tuple.Create(i1, i2)))
+ yield return AbstractPolicy.NewAsymmetricOr(t.Item1, t.Item2);
+ foreach (var subShrinked in Shrinker(p.Item1).Select(shrinkedItem1 => AbstractPolicy.NewAsymmetricOr(shrinkedItem1, p.Item2)))
+ yield return subShrinked;
+ foreach (var subShrinked in Shrinker(p.Item2).Select(shrinkedItem2 => AbstractPolicy.NewAsymmetricOr(p.Item1, shrinkedItem2)))
+ yield return subShrinked;
+ break;
+ }
+ case AbstractPolicy.Threshold p:
+ {
+ foreach (var subP in p.Item2)
+ {
+ yield return subP;
+ }
+ foreach (var i in Arb.Shrink(p.Item2).Select(subs => subs.Select(sub => Shrinker(sub))))
+ {
+ foreach (var i2 in i)
+ {
+ if (1 < i2.Count())
+ yield return AbstractPolicy.NewThreshold(1, i2.ToArray());
+ }
+ }
+
+ if (p.Item2.Length == 2)
+ yield break;
+
+ foreach (var i in Arb.Shrink(p.Item2))
+ {
+ if (1 < i.Length)
+ yield return AbstractPolicy.NewThreshold(1, i);
+ }
+ yield break;
+ }
+ case AbstractPolicy.Multi p:
+ {
+ yield return AbstractPolicy.NewCheckSig(p.Item2[0]);
+ if (p.Item2.Length > 2)
+ yield return AbstractPolicy.NewMulti(2, p.Item2.Take(2).ToArray());
+ foreach (var i in Arb.Shrink(p.Item2))
+ {
+ if (i.Length > 2)
+ yield return AbstractPolicy.NewMulti(2, i.ToArray());
+ }
+ break;
+ }
+ default:
+ {
+ yield break;
+ }
+ }
+ }
+
+ }
+
+ private static Gen AbstractPolicyGen(int size)
+ {
+ if (size == 0)
+ {
+ return NonRecursivePolicyGen();
+ }
+ else
+ {
+ return Gen.Frequency
+ (
+ Tuple.Create(3, NonRecursivePolicyGen()),
+ Tuple.Create(2, RecursivePolicyGen(AbstractPolicyGen(size / 2)))
+ );
+ }
+ }
+
+ private static Gen NonRecursivePolicyGen()
+ =>
+ Gen.OneOf(
+ new[]{
+ CheckSigGen(),
+ MultiSigGen(),
+ TimeGen(),
+ HashGen()
+ }
+ );
+ private static Gen CheckSigGen()
+ => CryptoGenerator.PublicKey().Select(pk => AbstractPolicy.NewCheckSig(pk));
+
+ private static Gen MultiSigGen()
+ =>
+ from m in Gen.Choose(2, 16)
+ from numKeys in Gen.Choose(m, 16)
+ from pks in Gen.ArrayOf(numKeys, CryptoGenerator.PublicKey())
+ select AbstractPolicy.NewMulti((uint)m, pks);
+
+ private static Gen TimeGen()
+ =>
+ from t in Gen.Choose(0, 65535)
+ select AbstractPolicy.NewTime((uint)t);
+
+ private static Gen HashGen()
+ =>
+ from t in CryptoGenerator.Hash256()
+ select AbstractPolicy.NewHash(t);
+
+ private static Gen RecursivePolicyGen(Gen subGen)
+ => Gen.OneOf
+ (
+ subGen.Two().Select(t => AbstractPolicy.NewAnd(t.Item1, t.Item2)),
+ subGen.Two().Select(t => AbstractPolicy.NewOr(t.Item1, t.Item2)),
+ subGen.Two().Select(t => AbstractPolicy.NewAsymmetricOr(t.Item1, t.Item2)),
+ ThresholdContentsGen(subGen).Select(t => AbstractPolicy.NewThreshold(t.Item1, t.Item2))
+ );
+
+ private static Gen> ThresholdContentsGen(Gen subGen)
+ =>
+ from num in Gen.Choose(1, 6)
+ from actualNum in num == 1 ? Gen.Choose(2, 6) : Gen.Choose(num ,6)
+ from subPolicies in Gen.ArrayOf(actualNum, subGen)
+ select Tuple.Create((uint)num, subPolicies);
+ }
+}
\ No newline at end of file
diff --git a/NBitcoin.Tests/Generators/CryptoGenerator.cs b/NBitcoin.Tests/Generators/CryptoGenerator.cs
index f3f8f2bfa8..d1c5a1fa5e 100644
--- a/NBitcoin.Tests/Generators/CryptoGenerator.cs
+++ b/NBitcoin.Tests/Generators/CryptoGenerator.cs
@@ -23,6 +23,9 @@ public static Arbitrary> KeysListArb() =>
public static Arbitrary ExtPathArb() =>
Arb.From(KeyPath());
+ public static Arbitrary PubKeyArb() =>
+ Arb.From(PublicKey());
+
public static Gen PrivateKey() => Gen.Fresh(() => new Key());
public static Gen> PrivateKeys(int n) =>
diff --git a/NBitcoin.Tests/Generators/ScriptGenerator.cs b/NBitcoin.Tests/Generators/ScriptGenerator.cs
index 57e6568597..443d5fb73b 100644
--- a/NBitcoin.Tests/Generators/ScriptGenerator.cs
+++ b/NBitcoin.Tests/Generators/ScriptGenerator.cs
@@ -9,6 +9,9 @@ namespace NBitcoin.Tests.Generators
public class ScriptGenerator
{
+ public static Arbitrary