From 59d1acc2b7076f410f3910a4dfd9d9116c3a56e8 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 8 Nov 2019 00:54:51 +0900 Subject: [PATCH 1/4] Pass ConcretePolicy parser test --- NBitcoin.Tests/MiniscriptTests.cs | 18 +++++++- .../MiniscriptDSLParsers.ConcretePolicy.cs | 46 ------------------- 2 files changed, 16 insertions(+), 48 deletions(-) diff --git a/NBitcoin.Tests/MiniscriptTests.cs b/NBitcoin.Tests/MiniscriptTests.cs index 4f13817029..3f30847f25 100644 --- a/NBitcoin.Tests/MiniscriptTests.cs +++ b/NBitcoin.Tests/MiniscriptTests.cs @@ -214,8 +214,22 @@ public void PolicyParserTest() var strOr = $"or(99@pk({PubKeys[0]}),pk({PubKeys[1]}))"; // var strOr = $"or(pk({PubKeys[0]}),pk({PubKeys[1]}))"; - var msRealOr = ConcretePolicy.Parse(strOr); - Assert.True(msRealOr.IsValid()); + var orRes = ConcretePolicy.Parse(strOr); + Assert.True(orRes.IsValid()); + + var strNestedOr = $"or(after(3),{strOr})"; + var nestedOrRes = ConcretePolicy.Parse(strNestedOr); + Assert.True(nestedOrRes.IsValid()); + + var strAnd = $"and(older(3),{strNestedOr})"; + var andRes = ConcretePolicy.Parse(strAnd); + Assert.True(andRes.IsValid()); + + var hash256 = Crypto.Hashes.Hash256(PubKeys[2].ToBytes()).ToString(); + var hash160 = Crypto.Hashes.Hash160(PubKeys[2].ToBytes()).ToString(); + var strThresh = $"thresh(2,hash256({hash256}),{strAnd},hash160({hash160}))"; + var threshRes = ConcretePolicy.Parse(strThresh); + Assert.True(threshRes.IsValid()); } [Fact] diff --git a/NBitcoin/Scripting/Miniscript/MiniscriptDSLParsers.ConcretePolicy.cs b/NBitcoin/Scripting/Miniscript/MiniscriptDSLParsers.ConcretePolicy.cs index 150a637700..f0ec9049aa 100644 --- a/NBitcoin/Scripting/Miniscript/MiniscriptDSLParsers.ConcretePolicy.cs +++ b/NBitcoin/Scripting/Miniscript/MiniscriptDSLParsers.ConcretePolicy.cs @@ -20,52 +20,6 @@ from x in Parse.CharExcept(')').Many().Text() from rightB in Parse.Char(')').Token() select x; - private static string[] SafeSplit(string s) - { - var parenthCount = 0; - var items = new List(); - var charSoFar = new List(); - var length = s.Length; - for (int i = 0; i < length; i++) - { - var c = s[i]; - if (c == '(') - { - parenthCount++; - charSoFar.Add(c); - } - else if (c == ')') - { - parenthCount--; - charSoFar.Add(c); - } - else if (parenthCount != 0) - { - charSoFar.Add(c); - } - - if (parenthCount == 0) - { - if (i == length - 1) - { - charSoFar.Add(c); - } - if (c == ',' || i == length - 1) - { - var charsCopy = new List(charSoFar); - charSoFar = new List(); - var item = new String(charsCopy.ToArray()).Trim(); - items.Add(item); - } - else - { - charSoFar.Add(c); - } - } - } - return items.ToArray(); - } - internal static Parser TryConvert(string str, Func converter) { return i => From 3107b484e8621d54c1b2bbf80c3606f0fb548991 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 8 Nov 2019 21:42:43 +0900 Subject: [PATCH 2/4] Modify GetChild --- NBitcoin/Scripting/Miniscript/Types/Property.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/NBitcoin/Scripting/Miniscript/Types/Property.cs b/NBitcoin/Scripting/Miniscript/Types/Property.cs index c4febbeea5..52329fd91d 100644 --- a/NBitcoin/Scripting/Miniscript/Types/Property.cs +++ b/NBitcoin/Scripting/Miniscript/Types/Property.cs @@ -28,14 +28,10 @@ private static T TypeCheckCore(Terminal fragment,Func child) { T GetChild(Terminal sub, int n) { - try - { - return child(n); - } - catch - { - return TypeCheck(sub, _ => null); - } + var r = child(n); + if (r is null) + return TypeCheck(sub, _ => null); + return r; } switch (fragment.Tag) { From 07a1606d201793a2887c2f0bae5fef60114dda27 Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 8 Nov 2019 21:49:42 +0900 Subject: [PATCH 3/4] Update Terminal Parser --- NBitcoin.Tests/MiniscriptTests.cs | 4 +- NBitcoin/Scripting/Miniscript/AstElem.cs | 28 ++++++++-- .../MiniscriptDSLParser.Terminal.cs | 53 +++++++++++-------- .../Scripting/Miniscript/Types/Property.cs | 4 ++ 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/NBitcoin.Tests/MiniscriptTests.cs b/NBitcoin.Tests/MiniscriptTests.cs index 3f30847f25..e0d3afd2f5 100644 --- a/NBitcoin.Tests/MiniscriptTests.cs +++ b/NBitcoin.Tests/MiniscriptTests.cs @@ -238,11 +238,11 @@ public void MiniscriptParserTest() { var pkStr = $"c:pk({PubKeys[0]})"; var orStr = $"or_b(c:pk({PubKeys[0]}),sc:pk({PubKeys[1]}))"; - var andStr = $"and(time(3),{orStr})"; + // var andStr = $""; // var orMs = Miniscript.Parse(orStr); var pkRes = MiniscriptDSLParser.ParseTerminal(pkStr); var orRes = MiniscriptDSLParser.ParseTerminal(orStr); - var andRes = MiniscriptDSLParser.ParseTerminal(andStr); + // var andRes = MiniscriptDSLParser.ParseTerminal(andStr); } } } diff --git a/NBitcoin/Scripting/Miniscript/AstElem.cs b/NBitcoin/Scripting/Miniscript/AstElem.cs index 5cd21d73bf..6108002b34 100644 --- a/NBitcoin/Scripting/Miniscript/AstElem.cs +++ b/NBitcoin/Scripting/Miniscript/AstElem.cs @@ -314,7 +314,14 @@ public ThreshM(uint item1, TPk[] item2): base(Tags.ThreshM) public static Terminal NewTrue() => Terminal.True; public static Terminal NewFalse() => Terminal.False; - public static Terminal NewPk(TPk item) => new Pk(item); + public static Terminal NewPk(TPk item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); + + return new Pk(item); + } + public static Terminal NewPkH(TPKh item) => new PkH(item); public static Terminal NewAfter(uint item) => new After(item); public static Terminal NewOlder(uint item) => new Older(item); @@ -328,7 +335,14 @@ public static Terminal NewHash160(uint160 item) => new Hash160(item); public static Terminal NewAlt(Miniscript item) => new Terminal.Alt(item); public static Terminal NewSwap(Miniscript item) => new Terminal.Swap(item); - public static Terminal NewCheck(Miniscript item) => new Terminal.Check(item); + public static Terminal NewCheck(Miniscript item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); + + return new Check(item); + } + public static Terminal NewDupIf(Miniscript item) => new DupIf(item); public static Terminal NewVerify(Miniscript item) => new Verify(item); public static Terminal NewNonZero(Miniscript item) => new NonZero(item); @@ -344,7 +358,15 @@ public static Terminal NewAndOr(Miniscript item1, Miniscri => new AndOr(item1, item2, item3); public static Terminal NewOrB(Miniscript item1, Miniscript item2) - => new OrB(item1, item2); + { + if (item1 is null) + throw new ArgumentNullException(nameof(item1)); + + if (item2 is null) + throw new ArgumentNullException(nameof(item2)); + + return new OrB(item1, item2); + } public static Terminal NewOrD(Miniscript item1, Miniscript item2) => new OrD(item1, item2); diff --git a/NBitcoin/Scripting/Miniscript/MiniscriptDSLParser.Terminal.cs b/NBitcoin/Scripting/Miniscript/MiniscriptDSLParser.Terminal.cs index 3c0e644c18..6c40863ad4 100644 --- a/NBitcoin/Scripting/Miniscript/MiniscriptDSLParser.Terminal.cs +++ b/NBitcoin/Scripting/Miniscript/MiniscriptDSLParser.Terminal.cs @@ -53,16 +53,20 @@ from t in ExprP("hash160").Then(s => TryConvert(s, uint160.Parse)) private static Parser> PWrapper(char identifier, Func, Terminal> construct) => - from _t in Parse.Char(identifier).Then(_ => Parse.Char(':')) - from inner in Parse.Ref(() => TerminalDSLParser) - .Except(PWrapper(identifier, construct)) // should not have the same wrapper twice - select construct(Miniscript.FromAst(inner)); + (from _t in Parse.Char(identifier).Then(_ => Parse.Char(':')) + from inner in Parse.Ref(PNonWrappers) + select construct(Miniscript.FromAst(inner))) + .Or( + from _t in Parse.Char(identifier) + from x in Parse.Ref(() => PWrappers).Except(PWrapper(identifier, construct)) + select x + ); private static Parser> PBinary( string identifier, Func, Miniscript, Terminal> constructor ) => - from s in PSubExprs(identifier, () => TerminalDSLParser) + from s in PSubExprs(identifier, TerminalDSLParser) where s.Count() == 2 select constructor(Miniscript.FromAst(s.ElementAt(0)), Miniscript.FromAst(s.ElementAt(1))); @@ -71,7 +75,7 @@ private static Parser> PTernary( string identifier, Func, Miniscript, Miniscript, Terminal> constructor ) => - from s in PSubExprs(identifier, () => TerminalDSLParser) + from s in PSubExprs(identifier, TerminalDSLParser) where s.Count() == 3 select constructor( Miniscript.FromAst(s.ElementAt(0)), @@ -82,30 +86,31 @@ select constructor( PThresh( "thresh", Terminal.NewThresh, - () => TerminalDSLParser.Select(t => Miniscript.FromAst(t))); + () => TerminalDSLParser().Select(t => Miniscript.FromAst(t))); private static Parser> PTerminalThreshM = PThresh("thresh_m", Terminal.NewThreshM, () => (from pk in ExprP("pk").Then(s => TryParseMiniscriptKey(s)) select pk)); - private static readonly Parser> TerminalDSLParser = - // ------ leafs ------ - Parse.Ref(() => PTerminalPk) - .Or(Parse.Ref(() => PTerminalPkH)) - .Or(Parse.Ref(() => PTerminalAfter)) - .Or(Parse.Ref(() => PTerminalOlder)) - .Or(Parse.Ref(() => PTerminalSha256)) - .Or(Parse.Ref(() => PTerminalHash256)) - .Or(Parse.Ref(() => PTerminalRipemd160)) - .Or(Parse.Ref(() => PTerminalHash160)) - // ------- wrappers -------- - .Or(PWrapper('a', Terminal.NewAlt)) - .Or(PWrapper('s', Terminal.NewSwap)) + private static readonly Parser> PWrappers = + PWrapper('a', Terminal.NewAlt) .Or(PWrapper('c', Terminal.NewCheck)) + .Or(PWrapper('s', Terminal.NewSwap)) .Or(PWrapper('d', Terminal.NewDupIf)) .Or(PWrapper('v', Terminal.NewVerify)) - .Or(PWrapper('j', Terminal.NewZeroNotEqual)) + .Or(PWrapper('j', Terminal.NewZeroNotEqual)); + + private static Parser> PNonWrappers() => + // ------ leafs ------ + PTerminalPk + .Or(PTerminalPkH) + .Or(PTerminalAfter) + .Or(PTerminalOlder) + .Or(PTerminalSha256) + .Or(PTerminalHash256) + .Or(PTerminalRipemd160) + .Or(PTerminalHash160) // ------- Conjunctions ----- .Or(PBinary("and_v", Terminal.NewAndV)) .Or(PBinary("and_b", Terminal.NewAndB)) @@ -118,8 +123,12 @@ select constructor( // ------- Thresholds ------ .Or(Parse.Ref(() => PTerminalThresh)) .Or(Parse.Ref(() => PTerminalThreshM)); + private static Parser> TerminalDSLParser() => + Parse.Ref(() => PWrappers) + .Or(PNonWrappers()); + // ------- wrappers -------- public static Terminal ParseTerminal(string input) - => Parse.Ref(() => TerminalDSLParser).Parse(input); + => TerminalDSLParser().Parse(input); } } diff --git a/NBitcoin/Scripting/Miniscript/Types/Property.cs b/NBitcoin/Scripting/Miniscript/Types/Property.cs index 52329fd91d..718adb9043 100644 --- a/NBitcoin/Scripting/Miniscript/Types/Property.cs +++ b/NBitcoin/Scripting/Miniscript/Types/Property.cs @@ -26,6 +26,9 @@ internal static T TypeCheck(Terminal fragment) private static T TypeCheckCore(Terminal fragment,Func child) { + if (fragment is null) + throw new ArgumentNullException(nameof(fragment)); + T GetChild(Terminal sub, int n) { var r = child(n); @@ -33,6 +36,7 @@ T GetChild(Terminal sub, int n) return TypeCheck(sub, _ => null); return r; } + switch (fragment.Tag) { case Terminal.Tags.True: From e99259b7bccb678bc3f6fa9092abc95cea8d8cdb Mon Sep 17 00:00:00 2001 From: joemphilips Date: Fri, 8 Nov 2019 21:56:53 +0900 Subject: [PATCH 4/4] Add assertion to check contents after parsing --- NBitcoin.Tests/MiniscriptTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NBitcoin.Tests/MiniscriptTests.cs b/NBitcoin.Tests/MiniscriptTests.cs index e0d3afd2f5..388bd826df 100644 --- a/NBitcoin.Tests/MiniscriptTests.cs +++ b/NBitcoin.Tests/MiniscriptTests.cs @@ -241,7 +241,10 @@ public void MiniscriptParserTest() // var andStr = $""; // var orMs = Miniscript.Parse(orStr); var pkRes = MiniscriptDSLParser.ParseTerminal(pkStr); + Assert.True(pkRes is Terminal.Check c && (c.Item.Node is Terminal.Pk)); + var orRes = MiniscriptDSLParser.ParseTerminal(orStr); + Assert.True(orRes is Terminal.OrB); // var andRes = MiniscriptDSLParser.ParseTerminal(andStr); } }