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..f8318e4453 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) => @@ -94,5 +97,23 @@ from raw in Gen.NonEmptyListOf(PrimitiveGenerator.RandomBytes(4)) select NBitcoin.KeyPath.FromBytes(flattenBytes); public static Gen ExtPubKey() => ExtKey().Select(ek => ek.Neuter()); + public static Gen BitcoinExtPubKey() => + from extKey in ExtPubKey() + from network in ChainParamsGenerator.NetworkGen() + select new BitcoinExtPubKey(extKey, network); + + public static Gen BitcoinExtKey() => + from extKey in ExtKey() + from network in ChainParamsGenerator.NetworkGen() + select new BitcoinExtKey(extKey, network); + + public static Gen RootedKeyPath() => + from parentFingerPrint in HDFingerPrint() + from kp in KeyPath() + select new RootedKeyPath(parentFingerPrint, kp); + + public static Gen HDFingerPrint() => + from x in PrimitiveGenerator.UInt32() + select new HDFingerprint(x); } } \ No newline at end of file diff --git a/NBitcoin.Tests/Generators/OutputDescriptorGenerator.cs b/NBitcoin.Tests/Generators/OutputDescriptorGenerator.cs new file mode 100644 index 0000000000..63590e3926 --- /dev/null +++ b/NBitcoin.Tests/Generators/OutputDescriptorGenerator.cs @@ -0,0 +1,88 @@ +using FsCheck; +using NBitcoin.Scripting; + +namespace NBitcoin.Tests.Generators +{ + public class OutputDescriptorGenerator + { + public static Arbitrary OutputDescriptorArb() => + Arb.From(OutputDescriptorGen()); + + public static Gen OutputDescriptorGen() => + Gen.OneOf( + AddrOutputDescriptorGen(), + RawOutputDescriptorGen(), + PKOutputDescriptorGen(), + PKHOutputDescriptorGen(), + WPKHOutputDescriptorGen(), + ComboOutputDescriptorGen(), + MultisigOutputDescriptorGen(), + SHOutputDescriptorGen(), + WSHOutputDescriptorGen() + ); + private static Gen AddrOutputDescriptorGen() => + from addr in AddressGenerator.RandomAddress() + select OutputDescriptor.NewAddr(addr); + + private static Gen RawOutputDescriptorGen() => + from addr in ScriptGenerator.RandomScriptSig() + select OutputDescriptor.NewRaw(addr); + private static Gen PKOutputDescriptorGen() => + from pkProvider in PubKeyProviderGen() + select OutputDescriptor.NewPK(pkProvider); + + private static Gen PKHOutputDescriptorGen() => + from pkProvider in PubKeyProviderGen() + select OutputDescriptor.NewPKH(pkProvider); + + private static Gen WPKHOutputDescriptorGen() => + from pkProvider in PubKeyProviderGen() + select OutputDescriptor.NewWPKH(pkProvider); + + private static Gen ComboOutputDescriptorGen() => + from pkProvider in PubKeyProviderGen() + select OutputDescriptor.NewCombo(pkProvider); + + private static Gen MultisigOutputDescriptorGen() => + from m in Gen.Choose(1, 20).Select(i => (uint)i) + from pkProviders in Gen.NonEmptyListOf(PubKeyProviderGen()) + select OutputDescriptor.NewMulti(m, pkProviders); + + private static Gen InnerOutputDescriptorGen() => + Gen.OneOf( + PKOutputDescriptorGen(), + PKHOutputDescriptorGen(), + WPKHOutputDescriptorGen(), + MultisigOutputDescriptorGen() + ); + private static Gen SHOutputDescriptorGen() => + from inner in Gen.OneOf(InnerOutputDescriptorGen(), WSHOutputDescriptorGen()) + select OutputDescriptor.NewSH(inner); + + private static Gen WSHOutputDescriptorGen() => + from inner in InnerOutputDescriptorGen() + select OutputDescriptor.NewWSH(inner); + + #region pubkey providers + + private static Gen PubKeyProviderGen() => + Gen.OneOf(OriginPubKeyProviderGen(), ConstPubKeyProviderGen(), HDPubKeyProviderGen()); + + private static Gen OriginPubKeyProviderGen() => + from keyOrigin in CryptoGenerator.RootedKeyPath() + from inner in Gen.OneOf(ConstPubKeyProviderGen(), HDPubKeyProviderGen()) + select PubKeyProvider.NewOrigin(keyOrigin, inner); + + private static Gen ConstPubKeyProviderGen() => + from pk in CryptoGenerator.PublicKey() + select PubKeyProvider.NewConst(pk); + + private static Gen HDPubKeyProviderGen() => + from extPk in CryptoGenerator.BitcoinExtPubKey() + from kp in CryptoGenerator.KeyPath() + from t in Arb.Generate() + select PubKeyProvider.NewHD(extPk, kp, t); + + # endregion + } +} 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