From e653c25589ad8cd197d10e120c4e7a0404350a86 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 22 May 2019 12:37:25 +0900 Subject: [PATCH 1/8] Prepare parser for scripting --- NBitcoin/Scripting/Parser/ParseException.cs | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 NBitcoin/Scripting/Parser/ParseException.cs diff --git a/NBitcoin/Scripting/Parser/ParseException.cs b/NBitcoin/Scripting/Parser/ParseException.cs new file mode 100644 index 0000000000..9b2fa79672 --- /dev/null +++ b/NBitcoin/Scripting/Parser/ParseException.cs @@ -0,0 +1,49 @@ +using System; + +namespace NBitcoin.Scripting.Parser +{ + /// + /// Represents an error that occurs during parsing. + /// + public class ParseException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public ParseException() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public ParseException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and the position where the error occured. + /// + /// The message that describes the error. + /// The position where the error occured. + public ParseException(string message, int position) : base(message) + { + Position = position; + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, + /// or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public ParseException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Gets the position of the parsing failure if one is available; otherwise, null. + /// + public int Position + { + get; + } + } +} From a93720923c4dc313c234e161e762314119db061d Mon Sep 17 00:00:00 2001 From: joemphilips Date: Thu, 23 May 2019 14:37:54 +0900 Subject: [PATCH 2/8] Rename ParseException -> ParsingException and inherit from FormatException --- NBitcoin/Scripting/Parser/ParseException.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/NBitcoin/Scripting/Parser/ParseException.cs b/NBitcoin/Scripting/Parser/ParseException.cs index 9b2fa79672..4c50b5e6f1 100644 --- a/NBitcoin/Scripting/Parser/ParseException.cs +++ b/NBitcoin/Scripting/Parser/ParseException.cs @@ -5,18 +5,18 @@ namespace NBitcoin.Scripting.Parser /// /// Represents an error that occurs during parsing. /// - public class ParseException : Exception + public class ParsingException : FormatException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ParseException() { } + public ParsingException() { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. - public ParseException(string message) : base(message) { } + public ParsingException(string message) : base(message) { } /// /// Initializes a new instance of the class with a specified error message @@ -24,7 +24,7 @@ public ParseException(string message) : base(message) { } /// /// The message that describes the error. /// The position where the error occured. - public ParseException(string message, int position) : base(message) + public ParsingException(string message, int position) : base(message) { Position = position; } @@ -36,7 +36,7 @@ public ParseException(string message, int position) : base(message) /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception, /// or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ParseException(string message, Exception innerException) : base(message, innerException) { } + public ParsingException(string message, Exception innerException) : base(message, innerException) { } /// /// Gets the position of the parsing failure if one is available; otherwise, null. From e7629a614aee4d1a4b23ffd466a7a9b12f593966 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 24 May 2019 08:30:50 +0900 Subject: [PATCH 3/8] fixup! Rename ParseException -> ParsingException and inherit from FormatException --- NBitcoin/Scripting/Parser/ParseException.cs | 49 --------------------- 1 file changed, 49 deletions(-) delete mode 100644 NBitcoin/Scripting/Parser/ParseException.cs diff --git a/NBitcoin/Scripting/Parser/ParseException.cs b/NBitcoin/Scripting/Parser/ParseException.cs deleted file mode 100644 index 4c50b5e6f1..0000000000 --- a/NBitcoin/Scripting/Parser/ParseException.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; - -namespace NBitcoin.Scripting.Parser -{ - /// - /// Represents an error that occurs during parsing. - /// - public class ParsingException : FormatException - { - /// - /// Initializes a new instance of the class. - /// - public ParsingException() { } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public ParsingException(string message) : base(message) { } - - /// - /// Initializes a new instance of the class with a specified error message - /// and the position where the error occured. - /// - /// The message that describes the error. - /// The position where the error occured. - public ParsingException(string message, int position) : base(message) - { - Position = position; - } - - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, - /// or a null reference (Nothing in Visual Basic) if no inner exception is specified. - public ParsingException(string message, Exception innerException) : base(message, innerException) { } - - /// - /// Gets the position of the parsing failure if one is available; otherwise, null. - /// - public int Position - { - get; - } - } -} From f820e3be849e1e3193bf2b24bf94a9745c522f2c Mon Sep 17 00:00:00 2001 From: joemphilips Date: Wed, 22 May 2019 12:37:25 +0900 Subject: [PATCH 4/8] Prepare Parser for scripting --- NBitcoin/Scripting/Parser/Parse.Char.cs | 2 +- NBitcoin/Scripting/Parser/ParserResult.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NBitcoin/Scripting/Parser/Parse.Char.cs b/NBitcoin/Scripting/Parser/Parse.Char.cs index e3fb633e6d..38ba053ca7 100644 --- a/NBitcoin/Scripting/Parser/Parse.Char.cs +++ b/NBitcoin/Scripting/Parser/Parse.Char.cs @@ -96,7 +96,7 @@ public static Parser Text(this Parser> cha { return characters.Select(chs => new string(chs.ToArray())); } - + /// /// Parse a number. /// diff --git a/NBitcoin/Scripting/Parser/ParserResult.cs b/NBitcoin/Scripting/Parser/ParserResult.cs index 24339ce451..355b8ac4b1 100644 --- a/NBitcoin/Scripting/Parser/ParserResult.cs +++ b/NBitcoin/Scripting/Parser/ParserResult.cs @@ -37,7 +37,7 @@ public ParserResult IfSuccess(Func, P return next(this); return ParserResult.Failure(this.Rest, this.Expected, this.Description); - } + } public ParserResult IfFailure(Func, ParserResult> next) { From 7049841631f5195125d9e422d367842382179ec9 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Sun, 21 Apr 2019 10:10:13 +0900 Subject: [PATCH 5/8] Support Miniscript --- .../Generators/AbstractPolicyGenerator.cs | 170 +++ NBitcoin.Tests/Generators/CryptoGenerator.cs | 3 + NBitcoin.Tests/Generators/ScriptGenerator.cs | 3 + NBitcoin.Tests/Generators/StringGenerator.cs | 18 +- NBitcoin.Tests/Generators/Utils.cs | 4 + NBitcoin.Tests/Helpers/PrimitiveUtils.cs | 43 + NBitcoin.Tests/MiniscriptTests.cs | 482 ++++++ NBitcoin.Tests/transaction_tests.cs | 27 +- NBitcoin/BIP174/PSBTInput.cs | 27 - .../BuilderExtensions/BuilderExtension.cs | 7 +- .../MiniscriptBuilderExtension.cs | 63 + NBitcoin/BuilderExtensions/OPTrueExtension.cs | 8 +- .../P2MultiSigBuilderExtension.cs | 8 +- .../BuilderExtensions/P2PKBuilderExtension.cs | 8 +- .../P2PKHBuilderExtension.cs | 8 +- NBitcoin/Scripting/AbstractPolicy.cs | 329 ++++ NBitcoin/Scripting/AstElem.cs | 1339 +++++++++++++++++ NBitcoin/Scripting/CompiledNodeContent.cs | 141 ++ NBitcoin/Scripting/Compiler.cs | 581 +++++++ NBitcoin/Scripting/Cost.cs | 310 ++++ NBitcoin/Scripting/Miniscript.cs | 108 ++ NBitcoin/Scripting/MiniscriptDSLParsers.cs | 157 ++ NBitcoin/Scripting/MiniscriptScriptParser.cs | 390 +++++ NBitcoin/Scripting/OutputDescriptor.cs | 135 ++ NBitcoin/Scripting/OutputDescriptorParser.cs | 63 + NBitcoin/Scripting/Parser/Parse.Char.cs | 16 +- NBitcoin/Scripting/Parser/Parse.cs | 15 - NBitcoin/Scripting/Parser/ParserResult.cs | 2 +- NBitcoin/Scripting/Parser/README.md | 3 +- NBitcoin/Scripting/Satisfy.cs | 674 +++++++++ NBitcoin/Scripting/SatisfyException.cs | 62 + NBitcoin/TransactionBuilder.cs | 77 +- 32 files changed, 5179 insertions(+), 102 deletions(-) create mode 100644 NBitcoin.Tests/Generators/AbstractPolicyGenerator.cs create mode 100644 NBitcoin.Tests/Helpers/PrimitiveUtils.cs create mode 100644 NBitcoin.Tests/MiniscriptTests.cs create mode 100644 NBitcoin/BuilderExtensions/MiniscriptBuilderExtension.cs create mode 100644 NBitcoin/Scripting/AbstractPolicy.cs create mode 100644 NBitcoin/Scripting/AstElem.cs create mode 100644 NBitcoin/Scripting/CompiledNodeContent.cs create mode 100644 NBitcoin/Scripting/Compiler.cs create mode 100644 NBitcoin/Scripting/Cost.cs create mode 100644 NBitcoin/Scripting/Miniscript.cs create mode 100644 NBitcoin/Scripting/MiniscriptDSLParsers.cs create mode 100644 NBitcoin/Scripting/MiniscriptScriptParser.cs create mode 100644 NBitcoin/Scripting/OutputDescriptor.cs create mode 100644 NBitcoin/Scripting/OutputDescriptorParser.cs create mode 100644 NBitcoin/Scripting/Satisfy.cs create mode 100644 NBitcoin/Scripting/SatisfyException.cs 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