diff --git a/NBitcoin.Tests/AltcoinTests.cs b/NBitcoin.Tests/AltcoinTests.cs index 15ba329942..c470835963 100644 --- a/NBitcoin.Tests/AltcoinTests.cs +++ b/NBitcoin.Tests/AltcoinTests.cs @@ -38,7 +38,7 @@ public void NoCrashQuickTest() foreach (var n in new[] { network.Mainnet, network.Testnet, network.Regtest }) { - n.Parse(new Key().PubKey.GetAddress(n).ToString()); + n.Parse(new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, n).ToString()); } } } @@ -116,7 +116,7 @@ public void CanSignTransactions() var rpc = node.CreateRPCClient(); var alice = new Key().GetBitcoinSecret(builder.Network); - BitcoinAddress aliceAddress = alice.GetAddress(); + BitcoinAddress aliceAddress = alice.GetAddress(ScriptPubKeyType.Legacy); var txid = rpc.SendToAddress(aliceAddress, Money.Coins(1.0m)); var tx = rpc.GetRawTransaction(txid); var coin = tx.Outputs.AsCoins().First(c => c.ScriptPubKey == aliceAddress.ScriptPubKey); @@ -172,7 +172,7 @@ public void CanParseAddress() var addr2 = BitcoinAddress.Create(addr, builder.Network).ToString(); Assert.Equal(addr, addr2); - var address = (BitcoinAddress)new Key().PubKey.GetAddress(builder.Network); + var address = (BitcoinAddress)new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, builder.Network); // Test normal address var isValid = ((JObject)node.CreateRPCClient().SendCommand("validateaddress", address.ToString()).Result)["isvalid"].Value(); diff --git a/NBitcoin.Tests/BIP38Tests.cs b/NBitcoin.Tests/BIP38Tests.cs index 31c30dc192..4ab80329fd 100644 --- a/NBitcoin.Tests/BIP38Tests.cs +++ b/NBitcoin.Tests/BIP38Tests.cs @@ -111,7 +111,7 @@ public void EncryptedSecretECmultiplyNoLot() Assert.Null(encryptedKey.LotSequence); var actualKey = encryptedKey.GetKey(test.Passphrase); Assert.Equal(test.Unencrypted, actualKey.GetBitcoinSecret(Network.Main).ToString()); - Assert.Equal(test.Address, actualKey.PubKey.GetAddress(Network.Main).ToString()); + Assert.Equal(test.Address, actualKey.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ToString()); Assert.Equal(test.Compressed, actualKey.IsCompressed); @@ -163,7 +163,7 @@ public void EncryptedSecretECmultiplyLotSequence() AssertSequenceEquals(test.LotSequence, encryptedKey.LotSequence); var actualKey = encryptedKey.GetKey(test.Passphrase); Assert.Equal(test.Unencrypted, actualKey.GetBitcoinSecret(Network.Main).ToString()); - Assert.Equal(test.Address, actualKey.PubKey.GetAddress(Network.Main).ToString()); + Assert.Equal(test.Address, actualKey.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ToString()); Assert.Equal(test.Compressed, actualKey.IsCompressed); @@ -214,10 +214,10 @@ public void EncryptedSecretECmultiplyNoLotSimple() var result = code.GenerateEncryptedSecret(compressed); Assert.True(result.ConfirmationCode.Check("test", result.GeneratedAddress)); Assert.False(result.ConfirmationCode.Check("toto", result.GeneratedAddress)); - Assert.False(result.ConfirmationCode.Check("test", new Key().PubKey.GetAddress(Network.Main))); + Assert.False(result.ConfirmationCode.Check("test", new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main))); var decryptedKey = result.EncryptedKey.GetKey("test"); - Assert.Equal(result.GeneratedAddress.ToString(), decryptedKey.PubKey.GetAddress(Network.Main).ToString()); + Assert.Equal(result.GeneratedAddress.ToString(), decryptedKey.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ToString()); Assert.Throws(() => result.EncryptedKey.GetKey("wrong")); diff --git a/NBitcoin.Tests/ColoredCoinsTests.cs b/NBitcoin.Tests/ColoredCoinsTests.cs index 0d1394a85b..25b38be71c 100644 --- a/NBitcoin.Tests/ColoredCoinsTests.cs +++ b/NBitcoin.Tests/ColoredCoinsTests.cs @@ -70,7 +70,7 @@ class AssetKey public AssetKey() { Key = new Key(); - ScriptPubKey = Key.PubKey.GetAddress(Network.Main).ScriptPubKey; + ScriptPubKey = Key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ScriptPubKey; Id = ScriptPubKey.Hash.ToAssetId(); } public Key Key @@ -126,8 +126,8 @@ public void CanColorizeSpecScenario() var a1 = new AssetKey(); var a2 = new AssetKey(); var h = new AssetKey(); - var sender = new Key().PubKey.GetAddress(Network.Main); - var receiver = new Key().PubKey.GetAddress(Network.Main); + var sender = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main); + var receiver = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main); colored.Marker = new ColorMarker(new ulong[] { 0, 10, 6, 0, 7, 3 }); colored.Inputs.Add(new ColoredEntry(0, new AssetMoney(a1.Id, 3UL))); @@ -445,7 +445,7 @@ public void CanCreateAssetAddress() //The issuer first generates a private key: 18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725. var key = new Key(TestUtils.ParseHex("18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725")); //He calculates the corresponding address: 16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM. - var address = key.PubKey.Decompress().GetAddress(Network.Main); + var address = key.PubKey.Decompress().GetAddress(ScriptPubKeyType.Legacy, Network.Main); Assert.Equal("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", address.ToString()); //Next, he builds the Pay-to-PubKey-Hash script associated to that address: OP_DUP OP_HASH160 010966776006953D5567439E5E39F86A0D273BEE OP_EQUALVERIFY OP_CHECKSIG diff --git a/NBitcoin.Tests/Generators/AddressGenerator.cs b/NBitcoin.Tests/Generators/AddressGenerator.cs index e77d3d47ad..4ef91391a6 100644 --- a/NBitcoin.Tests/Generators/AddressGenerator.cs +++ b/NBitcoin.Tests/Generators/AddressGenerator.cs @@ -33,7 +33,7 @@ from addr in P2PKHAddress(network) public static Gen P2PKHAddress(Network network) => from pk in PublicKey() - select (BitcoinAddress) pk.GetAddress(network); + select (BitcoinAddress) pk.GetAddress(ScriptPubKeyType.Legacy, network); public static Gen P2SHAddress() => from network in NetworkGen() diff --git a/NBitcoin.Tests/ParserTests.cs b/NBitcoin.Tests/ParserTests.cs new file mode 100644 index 0000000000..9d55599842 --- /dev/null +++ b/NBitcoin.Tests/ParserTests.cs @@ -0,0 +1,43 @@ +using NBitcoin.Scripting.Parser; +using Xunit; + +namespace NBitcoin.Tests +{ + /// + /// Super basic parser just for checking sanity of parser combinator + /// + internal static class ParserForTest + { + internal enum TokenType + { + Foo, + Bar + } + + public static Parser PToken() => + from _prefix in Parse.String("prefix") + from _l in Parse.Char('(').Token() + from foo in Parse.String("foo").Return(TokenType.Foo) + from _r in Parse.Char(')').Token() + select foo; + } + public class ParserTests + { + [Fact] + [Trait("UnitTest", "UnitTest")] + public void BasicParserTest() + { + ParserForTest.PToken().Parse("prefix(foo)"); + ParserForTest.PToken().Parse("prefix (foo)"); + ParserForTest.PToken().Parse("prefix (foo) "); + ParserForTest.PToken().Parse("prefix ( foo ) "); + + // input must be enumerable + foreach (var p in new StringInput("123")) + Assert.Contains(p, "123".ToCharArray()); + + foreach (var p in new ScriptInput(new Script("OP_ADD OP_EQUALVERIFY"))) + Assert.NotNull(p); + } + } +} \ No newline at end of file diff --git a/NBitcoin.Tests/key_tests.cs b/NBitcoin.Tests/key_tests.cs index edd224945f..d6d10b0b5a 100644 --- a/NBitcoin.Tests/key_tests.cs +++ b/NBitcoin.Tests/key_tests.cs @@ -165,7 +165,7 @@ public void CanGeneratePubKeysAndAddress() var address = (BitcoinPubKeyAddress)Network.Main.CreateBitcoinAddress(test.Address); Assert.Equal(new KeyId(test.Hash160), address.Hash); Assert.Equal(new KeyId(test.Hash160), secret.PrivateKey.PubKey.Hash); - Assert.Equal(address.Hash, secret.PrivateKey.PubKey.GetAddress(Network.Main).Hash); + Assert.Equal(address, secret.PrivateKey.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main)); var compressedSec = secret.Copy(true); diff --git a/NBitcoin/BIP38/BitcoinConfirmationCode.cs b/NBitcoin/BIP38/BitcoinConfirmationCode.cs index bf6531d285..6a146ca977 100644 --- a/NBitcoin/BIP38/BitcoinConfirmationCode.cs +++ b/NBitcoin/BIP38/BitcoinConfirmationCode.cs @@ -122,7 +122,7 @@ public bool Check(string passphrase, BitcoinAddress expectedAddress) //and hash it into address using either compressed or uncompressed public key methodology as specifid in flagbyte. pubkey = IsCompressed ? pubkey.Compress() : pubkey.Decompress(); - var actualhash = BitcoinEncryptedSecretEC.HashAddress(pubkey.GetAddress(Network)); + var actualhash = BitcoinEncryptedSecretEC.HashAddress(pubkey.GetAddress(ScriptPubKeyType.Legacy, Network)); var expectedhash = BitcoinEncryptedSecretEC.HashAddress(expectedAddress); return Utils.ArrayEqual(actualhash, expectedhash); diff --git a/NBitcoin/BIP38/BitcoinEncryptedSecret.cs b/NBitcoin/BIP38/BitcoinEncryptedSecret.cs index 618fdfe5b9..349a327209 100644 --- a/NBitcoin/BIP38/BitcoinEncryptedSecret.cs +++ b/NBitcoin/BIP38/BitcoinEncryptedSecret.cs @@ -36,7 +36,7 @@ private static string GenerateWif(Key key, string password, Network network) { var vch = key.ToBytes(); //Compute the Bitcoin address (ASCII), - var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(network).ToString()); + var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(ScriptPubKeyType.Legacy, network).ToString()); // and take the first four bytes of SHA256(SHA256()) of it. Let's call this "addresshash". var addresshash = Hashes.Hash256(addressBytes).ToBytes().SafeSubarray(0, 4); @@ -91,7 +91,7 @@ public override Key GetKey(string password) var key = new Key(bitcoinprivkey, fCompressedIn: IsCompressed); - var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(Network).ToString()); + var addressBytes = Encoders.ASCII.DecodeData(key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network).ToString()); var salt = Hashes.Hash256(addressBytes).ToBytes().SafeSubarray(0, 4); if(!Utils.ArrayEqual(salt, AddressHash)) @@ -199,7 +199,7 @@ public override Key GetKey(string password) var key = new Key(keyBytes, fCompressedIn: IsCompressed); - var generatedaddress = key.PubKey.GetAddress(Network); + var generatedaddress = key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network); var addresshash = HashAddress(generatedaddress); if(!Utils.ArrayEqual(addresshash, AddressHash)) diff --git a/NBitcoin/BIP38/BitcoinPassphraseCode.cs b/NBitcoin/BIP38/BitcoinPassphraseCode.cs index d3a2d84f2e..65a1139d1b 100644 --- a/NBitcoin/BIP38/BitcoinPassphraseCode.cs +++ b/NBitcoin/BIP38/BitcoinPassphraseCode.cs @@ -233,7 +233,7 @@ public EncryptedKeyResult GenerateEncryptedSecret(bool isCompressed = true, byte pubKey = isCompressed ? pubKey.Compress() : pubKey.Decompress(); //call it generatedaddress. - var generatedaddress = pubKey.GetAddress(Network); + var generatedaddress = pubKey.GetAddress(ScriptPubKeyType.Legacy, Network); //Take the first four bytes of SHA256(SHA256(generatedaddress)) and call it addresshash. var addresshash = BitcoinEncryptedSecretEC.HashAddress(generatedaddress); diff --git a/NBitcoin/Scripting/Parser/IInput.cs b/NBitcoin/Scripting/Parser/IInput.cs new file mode 100644 index 0000000000..a673384436 --- /dev/null +++ b/NBitcoin/Scripting/Parser/IInput.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace NBitcoin.Scripting.Parser +{ + internal interface IInput : IEnumerable + { + IInput Advance(); + + T GetCurrent(); + + bool AtEnd { get; } + int Position { get; } + IDictionary Memos { get; } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/Parse.Char.cs b/NBitcoin/Scripting/Parser/Parse.Char.cs new file mode 100644 index 0000000000..e3fb633e6d --- /dev/null +++ b/NBitcoin/Scripting/Parser/Parse.Char.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace NBitcoin.Scripting.Parser +{ + internal static partial class Parse + { + private const string ValidHex = "0123456789abcdef"; + private const string ValidBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private readonly static char[] ValidHexChars; + private readonly static char[] ValidBase58Chars; + + static Parse() + { + ValidHexChars = ValidHex.ToCharArray(); + ValidBase58Chars = ValidBase58.ToCharArray(); + } + public static Parser Char(Func predicate, string expected) + { + if (predicate == null) + throw new ArgumentNullException(nameof(predicate)); + + return i => + { + if (i.AtEnd) + { + return ParserResult.Failure(i, new [] {expected}, "Unexpected end of input"); + } + + if (predicate(i.GetCurrent())) + return ParserResult.Success(i.Advance(), i.GetCurrent()); + + return ParserResult.Failure(i, new [] {expected}, $"Unexpected '{i.GetCurrent()}'"); + }; + } + public static Parser CharExcept(Func predicate, string description) + => Char(c => !predicate(c), $"any character except {description}"); + + public static Parser Char(char c) + => Char(ch => c == ch, char.ToString(c)); + + public static Parser Chars(params char[] c) + => Char(c.Contains, string.Join("|", c)); + + public static Parser Chars(string c) + => Char(c.ToCharArray().Contains, string.Join("|", c)); + + public static Parser CharExcept(char c) + => CharExcept(ch => c == ch, c.ToString()); + + public static readonly Parser AnyChar = Char(c => true, "any charactor"); + public static readonly Parser WhiteSpace = Char(char.IsWhiteSpace, "whitespace"); + public static readonly Parser Digit = Char(char.IsDigit, "digit"); + public static readonly Parser Letter = Char(char.IsLetter, "letter"); + public static readonly Parser LetterOrDigit = Char(char.IsLetterOrDigit, "letter or digit"); + public static readonly Parser Hex = + Char(c => ValidHexChars.Any(ca => c == ca), "hex"); + public static readonly Parser Base58 = + Char(c => ValidBase58Chars.Any(ca => c == ca), "hex"); + + public static readonly Parser Numeric = Char(char.IsNumber, "numeric character"); + + /// + /// Parse a string of characters. + /// + /// + /// + public static Parser> String(string s) + { + if (s == null) throw new ArgumentNullException(nameof(s)); + + return s + .ToCharArray() + .Select(Char) + .Sequence(); + } + + public static Parser Token(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return from leading in WhiteSpace.Many() + from item in parser + from trailing in WhiteSpace.Many() + select item; + } + + /// + /// Convert a stream of characters to a string. + /// + /// + /// + public static Parser Text(this Parser> characters) + { + return characters.Select(chs => new string(chs.ToArray())); + } + + /// + /// Parse a number. + /// + public static readonly Parser Number = Numeric.AtLeastOnce().Text(); + + static Parser DecimalWithoutLeadingDigits(CultureInfo ci = null) + { + return from nothing in Return("") + // dummy so that CultureInfo.CurrentCulture is evaluated later + from dot in String((ci ?? CultureInfo.InvariantCulture).NumberFormat.NumberDecimalSeparator).Text() + from fraction in Number + select dot + fraction; + } + + static Parser DecimalWithLeadingDigits(CultureInfo ci = null) + { + return Number.Then(n => DecimalWithoutLeadingDigits(ci).XOr(Return("")).Select(f => n + f)); + } + + /// + /// Parse a decimal number with separator '.'. + /// + public static readonly Parser Decimal = DecimalWithLeadingDigits() + .XOr(DecimalWithoutLeadingDigits()); + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/Parse.ScriptToken.cs b/NBitcoin/Scripting/Parser/Parse.ScriptToken.cs new file mode 100644 index 0000000000..bc7133c684 --- /dev/null +++ b/NBitcoin/Scripting/Parser/Parse.ScriptToken.cs @@ -0,0 +1,30 @@ +using System; + +namespace NBitcoin.Scripting.Parser +{ + internal static partial class Parse + { + public static Parser ScriptToken(Func predicate, string expected) + { + return i => + { + if (i.AtEnd) + { + return ParserResult.Failure(i, new [] {expected}, "Unexpected end of input"); + } + + if (predicate(i.GetCurrent().Tag)) + return ParserResult.Success(i.Advance(), i.GetCurrent()); + return ParserResult.Failure(i, $"Unexpected {i.GetCurrent()}"); + }; + } + + public static Parser ScriptToken(ScriptToken sct, string expected) + => ScriptToken((tag) => tag == sct.Tag, expected); + + public static Parser ScriptToken(int tag) + => ScriptToken(actualTag => actualTag == tag, $"ScriptToken for tag: {tag}"); + public static Parser ScriptToken(ScriptToken sct) + => ScriptToken(sct, sct.ToString()); + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/Parse.cs b/NBitcoin/Scripting/Parser/Parse.cs new file mode 100644 index 0000000000..ea0d0544f6 --- /dev/null +++ b/NBitcoin/Scripting/Parser/Parse.cs @@ -0,0 +1,617 @@ +using System.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace NBitcoin.Scripting.Parser +{ + internal static partial class Parse + { + + + public static Parser Then(this Parser first, Func> second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return i => first(i).IfSuccess(s => second(s.Value)(s.Rest)); + } + + public static Parser> Many(this Parser parser) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + + return i => + { + var rest = i; + var result = new List(); + var r = parser(i); + while (r.IsSuccess) + { + if (rest.Equals(r.Rest)) + break; + result.Add(r.Value); + rest = r.Rest; + r = parser(rest); + } + + return ParserResult>.Success(rest, result); + }; + } + + /// + /// Parse a stream of elements, failing if any element is only partially parsed. + /// + /// + /// The type of element to parse. + /// A parser that matches a single element. + /// A that matches the sequence. + /// + /// + /// Using may be preferable to + /// where the first character of each match identified by + /// is sufficient to determine whether the entire match should succeed. The X* + /// methods typically give more helpful errors and are easier to debug than their + /// unqualified counterparts. + /// + /// + /// + public static Parser> XMany(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Many().Then(m => parser.Once().XOr(Return>(m))); + } + + /// + /// TryParse a stream of elements with at least one item. + /// + /// + /// + /// + /// + public static Parser> AtLeastOnce(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts))); + } + + public static Parser> Once(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + return parser.Select(r => (IEnumerable)new[] { r }); + } + + public static Parser> AtLeastOne (this Parser parser) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + + return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts))); + } + + /// + /// TryParse a stream of elements with at least one item. Except the first + /// item, all other items will be matched with the XMany operator. + /// + /// + /// + /// + /// + public static Parser> XAtLeastOnce(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return parser.Once().Then(t1 => parser.XMany().Select(ts => t1.Concat(ts))); + } + + /// + /// Lift to a parser monad world + /// + /// + /// + /// + /// + public static Parser Return(T v) + => i => ParserResult.Success(i, v); + + /// + /// Take the result of parsing, and project it onto a different domain. + /// + /// + /// + /// + /// + /// + /// + public static Parser Select(this Parser parser, Func convert) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (convert == null) throw new ArgumentNullException(nameof(convert)); + + return parser.Then(t => Return(convert(t))); + } + + public static Parser> Sequence(this IEnumerable> parserList) + { + return + parserList + .Aggregate(Return>(Enumerable.Empty()), (a, p) => a.Concat(p.Once())); + } + + /// + /// Refer to another parser indirectly. This allows circular compile-time dependency between parsers. + /// + /// + /// + /// + /// + public static Parser Ref(Func> reference) + { + if (reference == null) throw new ArgumentNullException(nameof(reference)); + + Parser p = null; + + return i => + { + if (p == null) + p = reference(); + + if (i.Memos.ContainsKey(p)) + throw new ParsingException(i.Memos[p].ToString()); + + i.Memos[p] = ParserResult.Failure(i, + new string[0], + "Left recursion in the grammar." + ); + var result = p(i); + i.Memos[p] = result; + return result; + }; + } + + + public static Parser Or(this Parser first, Parser second) + { + if (first == null) + throw new ArgumentNullException(nameof(first)); + if (second == null) + throw new ArgumentNullException(nameof(second)); + + return i => + { + var fr = first(i); + if (!fr.IsSuccess) + return second(i).IfFailure(sf => DetermineBestError(fr, sf)); + + if (fr.Rest.Equals(i)) + return second(i).IfFailure(sf => fr); + + return fr; + }; + } + + + /// + /// Parse first, if it succeeds, return first, otherwise try second. + /// Assumes that the first parsed character will determine the parser chosen (see Try). + /// + /// + /// + /// + /// + /// + public static Parser XOr (this Parser first, Parser second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return i => + { + var fr = first(i); + if (!fr.IsSuccess) + { + // The 'X' part + if (!fr.Rest.Equals(i)) + return fr; + + return second(i).IfFailure(sf => DetermineBestError(fr, sf)); + } + + // This handles a zero-length successful application of first. + if (fr.Rest.Equals(i)) + return second(i).IfFailure(sf => fr); + + return fr; + }; + } + + /// + /// Concatenate two streams of elements. + /// + /// + /// + /// + /// + /// + public static Parser> Concat(this Parser> first, Parser> second) + { + if (first == null) throw new ArgumentNullException(nameof(first)); + if (second == null) throw new ArgumentNullException(nameof(second)); + + return first.Then(f => second.Select(f.Concat)); + } + + public static ParserResult DetermineBestError( + ParserResult firstF, + ParserResult secondF + ) + { + if (secondF.Rest.Position > firstF.Rest.Position) + return secondF; + + if (secondF.Rest.Position == firstF.Rest.Position) + return ParserResult.Failure( + firstF.Rest, + firstF.Expected.Union(secondF.Expected), + firstF.Description + ); + + return firstF; + } + + + /// + /// Version of Return with simpler inline syntax. + /// + /// + /// + /// + /// + /// + /// + public static Parser Return(this Parser parser, U value) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + return parser.Select(t => value); + } + + + /// + /// Attempt parsing only if the parser fails. + /// + /// + /// + /// + /// + /// + /// + public static Parser Except(this Parser parser, Parser except) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (except == null) throw new ArgumentNullException(nameof(except)); + + // Could be more like: except.Then(s => s.Fail("..")).XOr(parser) + return i => + { + var r = except(i); + if (r.IsSuccess) + return ParserResult.Failure(i, new[] { "other than the excepted input"} , "Excepted parser succeeded." ); + return parser(i); + }; + } + + public static Parser End(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return i => parser(i).IfSuccess(s => + s.Rest.AtEnd ? s : ParserResult.Failure( + s.Rest, + new[] {"end of input"}, + string.Format("unexpected '{0}'", s.Rest.GetCurrent()) + ) + ); + } + /// + /// Parse a sequence of items until a terminator is reached. + /// Returns the sequence, discarding the terminator. + /// + /// + /// + /// + /// + /// + /// + public static Parser> Until(this Parser parser, Parser until) + { + return parser.Except(until).Many().Then(r => until.Return(r)); + } + + + /// + /// Succeed if the parsed value matches predicate. + /// + /// + /// + /// + /// + /// + public static Parser Where(this Parser parser, Func predicate) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + + return i => parser(i).IfSuccess(s => + predicate(s.Value) ? s : ParserResult.Failure(i, + new string[0], + string.Format("Unexpected {0}.", s.Value) + ) + ); + } + + + /// + /// Monadic combinator Then, adapted for Linq comprehension syntax. + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser SelectMany( + this Parser parser, + Func> selector, + Func projector) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); + if (projector == null) throw new ArgumentNullException(nameof(projector)); + + return parser.Then(t => selector(t).Select(u => projector(t, u))); + } + + public static Parser Bind( + this Parser parser, + Func> selector + ) + => parser.SelectMany(selector, (t, u) => u); + + /// + /// Chain a left-associative operator. + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser ChainOperator( + Parser op, + Parser operand, + Func apply) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return operand.Then(first => ChainOperatorRest(first, op, operand, apply, Or)); + } + + /// + /// Chain a left-associative operator. + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser XChainOperator( + Parser op, + Parser operand, + Func apply) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return operand.Then(first => ChainOperatorRest(first, op, operand, apply, XOr)); + } + + static Parser ChainOperatorRest( + T firstOperand, + Parser op, + Parser operand, + Func apply, + Func, Parser, Parser> or) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return or(op.Then(opvalue => + operand.Then(operandValue => + ChainOperatorRest(apply(opvalue, firstOperand, operandValue), op, operand, apply, or))), + Return(firstOperand)); + } + + /// + /// Chain a right-associative operator. + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser ChainRightOperator( + Parser op, + Parser operand, + Func apply) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, Or)); + } + + /// + /// Chain a right-associative operator. + /// + /// + /// + /// + /// + /// + /// + /// + public static Parser XChainRightOperator( + Parser op, + Parser operand, + Func apply) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, XOr)); + } + + static Parser ChainRightOperatorRest( + T lastOperand, + Parser op, + Parser operand, + Func apply, + Func, Parser, Parser> or) + { + if (op == null) throw new ArgumentNullException(nameof(op)); + if (operand == null) throw new ArgumentNullException(nameof(operand)); + if (apply == null) throw new ArgumentNullException(nameof(apply)); + return or(op.Then(opvalue => + operand.Then(operandValue => + ChainRightOperatorRest(operandValue, op, operand, apply, or)).Then(r => + Return(apply(opvalue, lastOperand, r)))), + Return(lastOperand)); + } + + + # region sequence + public static Parser> DelimitedBy ( + this Parser parser, + Parser delimiter + ) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + if (delimiter == null) + throw new ArgumentNullException(nameof(delimiter)); + + return + from head in parser.Once() + from tail in + ( + from seprattor in delimiter + from item in parser + select item + ).Many() + select head.Concat(tail); + } + + public static Parser> XDelimitedBy( + this Parser itemParser, + Parser delimiter + ) + { + if (itemParser == null) + throw new ArgumentNullException(nameof(itemParser)); + + if (delimiter == null) + throw new ArgumentNullException(nameof(delimiter)); + + return + from head in itemParser.Once() + from tail in + ( + from seprator in delimiter + from item in itemParser + select item + ).XMany() + select head.Concat(tail); + } + + public static Parser> Repeat( + this Parser parser, + int count + ) + => Repeat(parser, count, count); + + public static Parser> Repeat ( + this Parser parser, + int minimumCount, + int maximumCount + ) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + + return i => + { + var remainder = i; + var result = new List(); + + for (var n = 0; n < maximumCount; ++n) + { + var r = parser(remainder); + + if (!r.IsSuccess && n < minimumCount) + { + var what = r.Rest.AtEnd + ? "end of input" + : r.Rest.GetCurrent().ToString(); + + var msg = $"Unexpected '{what}'"; + var exp = minimumCount == maximumCount + ? $"'{string.Join(", ", r.Expected)}' {minimumCount} times, but found {n}" + : $"'{string.Join(", ", r.Expected)}' between {minimumCount} and {maximumCount} times, but found {n}"; + + return ParserResult>.Failure(i, new[] { exp }, msg); + } + + if (!ReferenceEquals(remainder, r.Rest)) + { + result.Add(r.Value); + } + + remainder = r.Rest; + } + + return ParserResult>.Success(remainder, result); + }; + } + + internal static Parser TryConvert(string str, Func converter) + { + return i => + { + try + { + return ParserResult.Success(i, converter(str)); + } + catch (FormatException) + { + return ParserResult.Failure(i, $"Failed to parse {str}"); + } + }; + } + + #endregion + } + +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/Parser.cs b/NBitcoin/Scripting/Parser/Parser.cs new file mode 100644 index 0000000000..a791e8e312 --- /dev/null +++ b/NBitcoin/Scripting/Parser/Parser.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace NBitcoin.Scripting.Parser +{ + internal delegate ParserResult Parser(IInput input); + + internal delegate ParserResult Parser(IInput input); + internal static class ParserExtension + { + /// + /// Tries to parse the input without throwing an exception. + /// + /// The type of the result. + /// The parser. + /// The input. + /// The result of the parser + internal static ParserResult TryParse(this Parser parser, string input) + { + if (parser == null) + throw new System.ArgumentNullException(nameof(parser)); + + if (input == null) + throw new System.ArgumentNullException(nameof(input)); + + return parser(new StringInput(input)); + } + + /// + /// Parses the specified input string. + /// + /// The type of the result. + /// The parser. + /// The input. + /// The result of the parser. + /// It contains the details of the parsing error. + internal static T Parse(this Parser parser, string input) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (input == null) throw new ArgumentNullException(nameof(input)); + + var result = parser.TryParse(input); + + if (!result.IsSuccess) + throw new ParsingException(result.ToString(), result.Rest.Position); + return result.Value; + } + + internal static ParserResult TryParse(this Parser parser, Script input) + { + if (parser == null) + throw new System.ArgumentNullException(nameof(parser)); + + if (input == null) + throw new System.ArgumentNullException(nameof(input)); + + try + { + return parser(new ScriptInput(input)); + } + // Catching exception here is bit ugly, but converting `Script` to `ScriptToken` is itself unsafe + // so this is good for assuring purity of this method. + catch (ParsingException e) + { + return ParserResult.Failure(new ScriptInput(new ScriptToken[]{}), e.Message); + } + } + + internal static T Parse(this Parser parser, Script input) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + if (input == null) throw new ArgumentNullException(nameof(input)); + + var result = parser.TryParse(input); + + if (!result.IsSuccess) + throw new ParsingException(result.ToString(), result.Rest.Position); + return result.Value; + } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/ParserResult.cs b/NBitcoin/Scripting/Parser/ParserResult.cs new file mode 100644 index 0000000000..24339ce451 --- /dev/null +++ b/NBitcoin/Scripting/Parser/ParserResult.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NBitcoin.Scripting.Parser +{ + internal class ParserResult + { + public readonly TValue Value; + public readonly IInput Rest; + + private ParserResult(IInput rest, TValue value) + { + Rest = rest; + Value = value; + } + + public static ParserResult Success(IInput rest, TValue v) + => new ParserResult(rest, v) { IsSuccess = true }; + + public static ParserResult Failure(IInput rest, string description) => + Failure(rest, new string[]{}, description); + public static ParserResult Failure(IInput rest, IEnumerable expected, string description) => + new ParserResult(rest, default(TValue)) + { + IsSuccess = false, + Description = description, + Expected = expected + }; + + public ParserResult IfSuccess(Func, ParserResult> next) + { + if (next == null) + throw new ArgumentNullException(nameof(next)); + + if (this.IsSuccess) + return next(this); + + return ParserResult.Failure(this.Rest, this.Expected, this.Description); + } + + public ParserResult IfFailure(Func, ParserResult> next) + { + if (next == null) + throw new ArgumentNullException(nameof(next)); + + return this.IsSuccess ? this : next(this); + } + public IEnumerable Expected { get; private set; } = null; + public string Description { get; private set; } + + public bool IsSuccess { get; private set; } + + public override string ToString() + { + if (IsSuccess) + return string.Format("Successful parsing of {0}.", Value); + + var expMsg = ""; + + if (Expected.Any()) + expMsg = " expected " + Expected.Aggregate((e1, e2) => e1 + " or " + e2); + + return string.Format("Parsing failure: {0};{1} ({2});", Description, expMsg, Rest); + } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/ParsingException.cs b/NBitcoin/Scripting/Parser/ParsingException.cs new file mode 100644 index 0000000000..9c6a2f81ca --- /dev/null +++ b/NBitcoin/Scripting/Parser/ParsingException.cs @@ -0,0 +1,49 @@ +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; + } + } +} diff --git a/NBitcoin/Scripting/Parser/README.md b/NBitcoin/Scripting/Parser/README.md new file mode 100644 index 0000000000..ba670e9c93 --- /dev/null +++ b/NBitcoin/Scripting/Parser/README.md @@ -0,0 +1,7 @@ +Minimal parser combinator library for + +1. Miniscript DSL parsing +2. Miniscript Decompilaiton (Deserializing from Script) +3. OutputDescriptor parsing + +This is simplified version of [Sprache](https://github.com/sprache/Sprache) diff --git a/NBitcoin/Scripting/Parser/ScriptInput.cs b/NBitcoin/Scripting/Parser/ScriptInput.cs new file mode 100644 index 0000000000..e4f0f1c907 --- /dev/null +++ b/NBitcoin/Scripting/Parser/ScriptInput.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System; +using System.Collections; + +namespace NBitcoin.Scripting.Parser +{ + internal class ScriptInput : IInput + { + public ScriptInput(Script source) : this(source.ToTokens(), 0) { } + + public ScriptInput(ScriptToken[] source) : this(source, 0) { } + + public ScriptToken[] Source { get; } + public int Position { get; } + + internal ScriptInput(ScriptToken[] source, int position) + { + if (source == null) + throw new System.ArgumentNullException(nameof(source)); + Source = source; + Position = position; + Memos = new Dictionary(); + } + + public bool AtEnd { get { return Position == Source.Length; } } + public ScriptToken GetCurrent() => Source[Position]; + + public IInput Advance() + { + if (AtEnd) + throw new InvalidOperationException("The input is already at the end of the source"); + return new ScriptInput(Source, Position + 1); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Source).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.Source.GetEnumerator(); + } + + public IDictionary Memos { get; } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/Parser/StringInput.cs b/NBitcoin/Scripting/Parser/StringInput.cs new file mode 100644 index 0000000000..1d7afbde44 --- /dev/null +++ b/NBitcoin/Scripting/Parser/StringInput.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace NBitcoin.Scripting.Parser +{ + internal class StringInput : IInput + { + public StringInput(string source) : this(source, 0) { } + public string Source { get; } + public int Position { get; } + + internal StringInput(string source, int position) + { + if (source == null) + throw new System.ArgumentNullException(nameof(source)); + Source = source; + Position = position; + Memos = new Dictionary(); + } + + public bool AtEnd { get { return Position == Source.Length; } } + public char GetCurrent() => Source[Position]; + + public IInput Advance() + { + if (AtEnd) + throw new InvalidOperationException("The input is already at the end of the source"); + return new StringInput(Source, Position + 1); + } + + public IEnumerator GetEnumerator() + { + var arr = (IEnumerable)(Source).ToCharArray(); + return arr.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return (Source).ToCharArray().GetEnumerator(); + } + + public IDictionary Memos { get; } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/ParserUtil.cs b/NBitcoin/Scripting/ParserUtil.cs new file mode 100644 index 0000000000..a219a5edd4 --- /dev/null +++ b/NBitcoin/Scripting/ParserUtil.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using NBitcoin.Scripting.Parser; +using System; + +namespace NBitcoin.Scripting +{ + internal static class ParserUtil + { + internal static readonly Parser SurroundedByBrackets = + from leftB in Parse.Char('(').Token() + from x in Parse.CharExcept(')').Many().Text() + from rightB in Parse.Char(')').Token() + select x; + + internal 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 ExprP(string name) + => + from identifier in Parse.String(name) + from x in SurroundedByBrackets + select x; + + internal static Parser ExprPMany(string name) + => + from x in ExprP(name) + select SafeSplit(x); + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/ScriptExtensions.cs b/NBitcoin/Scripting/ScriptExtensions.cs new file mode 100644 index 0000000000..256c23da73 --- /dev/null +++ b/NBitcoin/Scripting/ScriptExtensions.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NBitcoin.Scripting.Parser; + +namespace NBitcoin.Scripting +{ + public static class ScriptExtensions + { + internal static ScriptToken[] ToTokens(this Script sc) + { + var result = new List(); + foreach (Op op in sc.ToOps()) + { + switch (op.Code) + { + case OpcodeType.OP_BOOLAND: + result.Add(ScriptToken.BoolAnd); + break; + case OpcodeType.OP_BOOLOR: + result.Add(ScriptToken.BoolOr); + break; + case OpcodeType.OP_EQUAL: + result.Add(ScriptToken.Equal); + break; + case OpcodeType.OP_EQUALVERIFY: + result.Add(ScriptToken.EqualVerify); + break; + case OpcodeType.OP_CHECKSIG: + result.Add(ScriptToken.CheckSig); + break; + case OpcodeType.OP_CHECKSIGVERIFY: + result.Add(ScriptToken.CheckSigVerify); + break; + case OpcodeType.OP_CHECKMULTISIG: + result.Add(ScriptToken.CheckMultiSig); + break; + case OpcodeType.OP_CHECKMULTISIGVERIFY: + result.Add(ScriptToken.CheckMultiSigVerify); + break; + case OpcodeType.OP_CHECKSEQUENCEVERIFY: + result.Add(ScriptToken.CheckSequenceVerify); + break; + case OpcodeType.OP_FROMALTSTACK: + result.Add(ScriptToken.FromAltStack); + break; + case OpcodeType.OP_TOALTSTACK: + result.Add(ScriptToken.ToAltStack); + break; + case OpcodeType.OP_DROP: + result.Add(ScriptToken.Drop); + break; + case OpcodeType.OP_DUP: + result.Add(ScriptToken.Dup); + break; + case OpcodeType.OP_IF: + result.Add(ScriptToken.If); + break; + case OpcodeType.OP_IFDUP: + result.Add(ScriptToken.IfDup); + break; + case OpcodeType.OP_NOTIF: + result.Add(ScriptToken.NotIf); + break; + case OpcodeType.OP_ELSE: + result.Add(ScriptToken.Else); + break; + case OpcodeType.OP_ENDIF: + result.Add(ScriptToken.EndIf); + break; + case OpcodeType.OP_0NOTEQUAL: + result.Add(ScriptToken.ZeroNotEqual); + break; + case OpcodeType.OP_SIZE: + result.Add(ScriptToken.Size); + break; + case OpcodeType.OP_SWAP: + result.Add(ScriptToken.Swap); + break; + case OpcodeType.OP_VERIFY: + result.Add(ScriptToken.Verify); + break; + case OpcodeType.OP_HASH160: + result.Add(ScriptToken.Hash160); + break; + case OpcodeType.OP_SHA256: + result.Add(ScriptToken.Sha256); + break; + case OpcodeType.OP_ADD: + result.Add(ScriptToken.Add); + break; + case OpcodeType.OP_0: + result.Add(new ScriptToken.Number(0u)); + break; + case OpcodeType.OP_1: + result.Add(new ScriptToken.Number(1u)); + break; + case OpcodeType.OP_2: + result.Add(new ScriptToken.Number(2u)); + break; + case OpcodeType.OP_3: + result.Add(new ScriptToken.Number(3u)); + break; + case OpcodeType.OP_4: + result.Add(new ScriptToken.Number(4u)); + break; + case OpcodeType.OP_5: + result.Add(new ScriptToken.Number(5u)); + break; + case OpcodeType.OP_6: + result.Add(new ScriptToken.Number(6u)); + break; + case OpcodeType.OP_7: + result.Add(new ScriptToken.Number(7u)); + break; + case OpcodeType.OP_8: + result.Add(new ScriptToken.Number(8u)); + break; + case OpcodeType.OP_9: + result.Add(new ScriptToken.Number(9u)); + break; + case OpcodeType.OP_10: + result.Add(new ScriptToken.Number(10u)); + break; + case OpcodeType.OP_11: + result.Add(new ScriptToken.Number(11u)); + break; + case OpcodeType.OP_12: + result.Add(new ScriptToken.Number(12u)); + break; + case OpcodeType.OP_13: + result.Add(new ScriptToken.Number(13u)); + break; + case OpcodeType.OP_14: + result.Add(new ScriptToken.Number(14u)); + break; + case OpcodeType.OP_15: + result.Add(new ScriptToken.Number(15u)); + break; + case OpcodeType.OP_16: + result.Add(new ScriptToken.Number(16u)); + break; + default: + if ((byte)0x01 <= (byte)op.Code && (byte)op.Code < (byte)0x48) + result.Add(GetItem(op)); + else if ((byte)0x48 <= (byte)op.Code) + throw new ParsingException($"Miniscript does not support pushdata bigger than 33. Got {op}"); + else + throw new ParsingException($"Unknown Opcode to Miniscript {op.Name}"); + break; + } + } + result.Reverse(); + return result.ToArray(); + } + private static ScriptToken GetItem(Op op) + { + if (op.PushData.Length == 20) + { + return new ScriptToken.Hash160Hash(new uint160(op.PushData, false)); + } + if (op.PushData.Length == 32) + { + return new ScriptToken.Sha256Hash(new uint256(op.PushData, false)); + } + if (op.PushData.Length == 33) + { + try + { + return new ScriptToken.Pk(new PubKey(op.PushData)); + } + catch (FormatException ex) + { + throw new ParsingException("Invalid Public Key", ex); + } + } + var i = op.GetInt(); + if (i.HasValue) + { + return new ScriptToken.Number((UInt32)i.Value); + } + else + { + throw new ParsingException($"Invalid push with Opcode {op}"); + } + } + } +} \ No newline at end of file diff --git a/NBitcoin/Scripting/ScriptToken.cs b/NBitcoin/Scripting/ScriptToken.cs new file mode 100644 index 0000000000..fdf54c6f1d --- /dev/null +++ b/NBitcoin/Scripting/ScriptToken.cs @@ -0,0 +1,327 @@ +using System; + +namespace NBitcoin.Scripting +{ + internal class ScriptToken : IEquatable + { + internal static class Tags + { + public const int BoolAnd = 0; + + public const int BoolOr = 1; + + public const int Add = 2; + + public const int Equal = 3; + + public const int EqualVerify = 4; + + public const int CheckSig = 5; + + public const int CheckSigVerify = 6; + + public const int CheckMultiSig = 7; + + public const int CheckMultiSigVerify = 8; + + public const int CheckSequenceVerify = 9; + + public const int FromAltStack = 10; + + public const int ToAltStack = 11; + + public const int Drop = 12; + + public const int Dup = 13; + + public const int If = 14; + + public const int IfDup = 15; + + public const int NotIf = 16; + + public const int Else = 17; + + public const int EndIf = 18; + + public const int ZeroNotEqual = 19; + + public const int Size = 20; + + public const int Swap = 21; + + public const int Verify = 23; + + public const int Hash160 = 24; + + public const int Sha256 = 25; + + public const int Number = 26; + + public const int Hash160Hash = 27; + + public const int Sha256Hash = 28; + + public const int Pk = 29; + + } + + private ScriptToken(int tag) => Tag = tag; + + internal int Tag { get; } + + internal static readonly ScriptToken _unique_BoolAnd = new ScriptToken(0); + internal static ScriptToken BoolAnd { get { return _unique_BoolAnd; } } + internal static readonly ScriptToken _unique_BoolOr = new ScriptToken(1); + internal static ScriptToken BoolOr { get { return _unique_BoolOr; } } + + internal static readonly ScriptToken _unique_Add = new ScriptToken(2); + internal static ScriptToken Add { get { return _unique_Add; } } + + internal static readonly ScriptToken _unique_Equal = new ScriptToken(3); + internal static ScriptToken Equal { get { return _unique_Equal; } } + + internal static readonly ScriptToken _unique_EqualVerify = new ScriptToken(4); + internal static ScriptToken EqualVerify { get { return _unique_EqualVerify; } } + + internal static readonly ScriptToken _unique_CheckSig = new ScriptToken(5); + internal static ScriptToken CheckSig { get { return _unique_CheckSig; } } + + internal static readonly ScriptToken _unique_CheckSigVerify = new ScriptToken(6); + internal static ScriptToken CheckSigVerify { get { return _unique_CheckSigVerify; } } + internal static readonly ScriptToken _unique_CheckMultiSig = new ScriptToken(7); + internal static ScriptToken CheckMultiSig { get { return _unique_CheckMultiSig; } } + internal static readonly ScriptToken _unique_CheckMultiSigVerify = new ScriptToken(8); + internal static ScriptToken CheckMultiSigVerify { get { return _unique_CheckMultiSigVerify; } } + + internal static readonly ScriptToken _unique_CheckSequenceVerify = new ScriptToken(9); + internal static ScriptToken CheckSequenceVerify { get { return _unique_CheckSequenceVerify; } } + internal static readonly ScriptToken _unique_FromAltStack = new ScriptToken(10); + internal static ScriptToken FromAltStack { get { return _unique_FromAltStack; } } + internal static readonly ScriptToken _unique_ToAltStack = new ScriptToken(11); + internal static ScriptToken ToAltStack { get { return _unique_ToAltStack; } } + + internal static readonly ScriptToken _unique_Drop = new ScriptToken(12); + internal static ScriptToken Drop { get { return _unique_Drop; } } + + internal static readonly ScriptToken _unique_Dup = new ScriptToken(13); + internal static ScriptToken Dup { get { return _unique_Dup; } } + + internal static readonly ScriptToken _unique_If = new ScriptToken(14); + internal static ScriptToken If { get { return _unique_If; } } + internal static readonly ScriptToken _unique_IfDup = new ScriptToken(15); + internal static ScriptToken IfDup { get { return _unique_IfDup; } } + + internal static readonly ScriptToken _unique_NotIf = new ScriptToken(16); + internal static ScriptToken NotIf { get { return _unique_NotIf; } } + + internal static readonly ScriptToken _unique_Else = new ScriptToken(17); + internal static ScriptToken Else { get { return _unique_Else; } } + + internal static readonly ScriptToken _unique_EndIf = new ScriptToken(18); + internal static ScriptToken EndIf { get { return _unique_EndIf; } } + internal static readonly ScriptToken _unique_ZeroNotEqual = new ScriptToken(19); + internal static ScriptToken ZeroNotEqual { get { return _unique_ZeroNotEqual; } } + + internal static readonly ScriptToken _unique_Size = new ScriptToken(20); + internal static ScriptToken Size { get { return _unique_Size; } } + + internal static readonly ScriptToken _unique_Swap = new ScriptToken(21); + internal static ScriptToken Swap { get { return _unique_Swap; } } + internal static readonly ScriptToken _unique_Verify = new ScriptToken(23); + internal static ScriptToken Verify { get { return _unique_Verify; } } + internal static readonly ScriptToken _unique_Hash160 = new ScriptToken(24); + internal static ScriptToken Hash160 { get { return _unique_Hash160; } } + internal static readonly ScriptToken _unique_Sha256 = new ScriptToken(25); + internal static ScriptToken Sha256 { get { return _unique_Sha256; } } + internal class Number : ScriptToken + { + internal uint Item { get; } + internal Number(uint item) : base(26) => Item = item; + } + + internal class Hash160Hash : ScriptToken + { + internal uint160 Item { get; } + internal Hash160Hash(uint160 item) : base(27) + { + Item = item; + } + } + internal class Sha256Hash : ScriptToken + { + internal uint256 Item { get; } + internal Sha256Hash(uint256 item) : base(28) => Item = item; + } + internal class Pk : ScriptToken + { + internal PubKey Item { get; } + internal Pk(PubKey item) : base(29) => Item = item; + } + + public override string ToString() + { + switch (this.Tag) + { + case Tags.BoolAnd: + return "BoolAnd"; + case Tags.BoolOr: + return "BoolAnd"; + case Tags.Add: + return "Add"; + case Tags.Equal: + return "Equal"; + case Tags.EqualVerify: + return "EqualVerify"; + case Tags.CheckSig: + return "CheckSig"; + case Tags.CheckSigVerify: + return "CheckSigVerify"; + case Tags.CheckMultiSig: + return "CheckMultiSig"; + case Tags.CheckMultiSigVerify: + return "CheckMultiSigVerify"; + case Tags.CheckSequenceVerify: + return "CheckSequenceVerify"; + case Tags.FromAltStack: + return "FromAltStack"; + case Tags.ToAltStack: + return "ToAltStack"; + case Tags.Drop: + return "Drop"; + case Tags.Dup: + return "Dup"; + case Tags.If: + return "If"; + case Tags.IfDup: + return "IfDup"; + case Tags.NotIf: + return "NotIf"; + case Tags.Else: + return "Else"; + case Tags.EndIf: + return "EndIf"; + case Tags.ZeroNotEqual: + return "ZeroNotEqual"; + case Tags.Size: + return "Size"; + case Tags.Swap: + return "Swap"; + case Tags.Verify: + return "Verify"; + case Tags.Hash160: + return "Hash160"; + case Tags.Sha256: + return "Sha256"; + case Tags.Number: + var n = ((Number)this).Item; + return $"Number({n})"; + case Tags.Hash160Hash: + var hash160 = ((Hash160Hash)this).Item; + return $"Hash160Hash({hash160})"; + case Tags.Sha256Hash: + var sha256 = ((Sha256Hash)this).Item; + return $"Sha256Hash({sha256})"; + case Tags.Pk: + var pk = ((Pk)this).Item; + return $"Pk({pk})"; + } + throw new Exception("Unreachable"); + } + + public sealed override int GetHashCode() + { + if (this != null) + { + int num = 0; + switch (Tag) + { + case 26: + { + Number number = (Number)this; + num = 26; + return -1640531527 + ((int)number.Item + ((num << 6) + (num >> 2))); + } + case 27: + { + Hash160Hash hash160Hash = (Hash160Hash)this; + num = 27; + return -1640531527 + (hash160Hash.Item.GetHashCode()) + ((num << 6) + (num >> 2)); + } + case 28: + { + Sha256Hash sha256Hash = (Sha256Hash)this; + num = 28; + return -1640531527 + (sha256Hash.Item.GetHashCode()) + ((num << 6) + (num >> 2)); + } + case 29: + { + Pk pk = (Pk)this; + num = 29; + return -1640531527 + (pk.Item.GetHashCode()) + ((num << 6) + (num >> 2)); + } + default: + return Tag; + } + } + return 0; + } + + public sealed override bool Equals(object obj) + { + ScriptToken token = obj as ScriptToken; + if (token != null) + { + return Equals(token); + } + return false; + } + public bool Equals(ScriptToken obj) + { + if (this != null) + { + if (obj != null) + { + int tag = Tag; + int tag2 = obj.Tag; + if (tag == tag2) + { + switch (Tag) + { + case 26: + { + Number number = (Number)this; + Number number2 = (Number)obj; + return number.Item == number2.Item; + } + case 27: + { + Hash160Hash hash160Hash = (Hash160Hash)this; + Hash160Hash hash160Hash2 = (Hash160Hash)obj; + return hash160Hash.Item == hash160Hash2.Item; + } + case 28: + { + Sha256Hash sha256Hash = (Sha256Hash)this; + Sha256Hash sha256Hash2 = (Sha256Hash)obj; + return sha256Hash.Item == sha256Hash2.Item; + } + case 29: + { + Pk pk = (Pk)this; + Pk pk2 = (Pk)obj; + return pk.Item == pk2.Item; + } + default: + return true; + } + } + return false; + } + return false; + } + return obj == null; + } + + } +} \ No newline at end of file