public void CashAddressDecodeTest() { foreach (var testCase in _cashAddressTestCases) { // decode the cash address var decoded = CashAddress.DecodeCashAddress(testCase.Key); // ensure all bytes of the hash output are correct CollectionAssert.AreEqual(decoded.Hash, testCase.Value.Hash); // ensure type output is correct Assert.AreEqual(decoded.Type, testCase.Value.Type); // ensure prefix output is correct Assert.AreEqual(decoded.Prefix, testCase.Value.Prefix); } }
/// <summary> /// Handles json messages: /// {"op": "block"} /// {"op": "transaction"} /// {"op": "address", "address": "[CASH_ADDRESS]"} /// {"op": "opreturn", "prefix": "[PREFIX(HEX)]"} /// {"op": "rm_block"} /// {"op": "rm_transaction"} /// {"op": "rm_address", "address": "[CASH_ADDRESS]"} /// {"op": "rm_opreturn", "prefix": "[PREFIX(HEX)]"} /// - Validates json /// - Validates parameters /// - Sends response (error or ok) /// - Adds valid subscriptions to subscription handler /// </summary> /// <param name="socket"></param> /// <param name="message"></param> /// <param name="subscriptionHandler"></param> public static void HandleMessage(IWebsocketConnection socket, string message, SubscriptionHandler subscriptionHandler) { message = message.Trim(); // check if the message is valid json if ((!message.StartsWith("{") || !message.EndsWith("}")) && (!message.StartsWith("[") || !message.EndsWith("]"))) { return; } JToken jToken; try { jToken = JToken.Parse(message); } catch (JsonReaderException jex) { socket.Send("{ \"op\": \"error\", \"error\": \"Unable to parse JSON request. " + JsonConvert.ToString(jex.Message) + "\" }"); return; } catch (Exception ex) { socket.Send("{ \"op\": \"error\", \"error\": \"Exception while parsing JSON request. " + JsonConvert.ToString(ex.Message) + "\" }"); return; } if (jToken.Type != JTokenType.Object) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected Object, but encountered " + jToken.Type + "\" }"); return; } var jObject = jToken.ToObject <JObject>(); if (!jObject.ContainsKey("op") || jObject["op"].Type != JTokenType.String) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected 'op' parameter as string.\" }"); return; } // block subscriptions if (jObject["op"].ToString().Equals("block", StringComparison.CurrentCultureIgnoreCase)) { subscriptionHandler.AddSubscription(socket, new BlockSubscription()); socket.Send("{ \"op\": \"block\", \"result\": \"ok\" }"); } // block un-subscribe else if (jObject["op"].ToString().Equals("rm_block", StringComparison.CurrentCultureIgnoreCase)) { socket.Send(subscriptionHandler.RemoveSubscription(socket, new BlockSubscription()) ? "{ \"op\": \"rm_block\", \"result\": \"ok\" }" : "{ \"op\": \"rm_block\", \"result\": \"failed\" }"); } // transaction subscriptions else if (jObject["op"].ToString().Equals("transaction", StringComparison.CurrentCultureIgnoreCase)) { subscriptionHandler.AddSubscription(socket, new TransactionSubscription()); socket.Send("{ \"op\": \"transaction\", \"result\": \"ok\" }"); } // transaction un-subscribe else if (jObject["op"].ToString().Equals("rm_transaction", StringComparison.CurrentCultureIgnoreCase)) { socket.Send(subscriptionHandler.RemoveSubscription(socket, new TransactionSubscription()) ? "{ \"op\": \"rm_transaction\", \"result\": \"ok\" }" : "{ \"op\": \"rm_transaction\", \"result\": \"failed\" }"); } // address subscriptions else if (jObject["op"].ToString().Equals("address", StringComparison.CurrentCultureIgnoreCase) || jObject["op"].ToString().Equals("rm_address", StringComparison.CurrentCultureIgnoreCase)) { if (!jObject.ContainsKey("address") || jObject["address"].Type != JTokenType.String) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected 'address' parameter as string.\" }"); return; } // attempt to decode cash address try { var decoded = CashAddress.DecodeCashAddress(jObject["address"].ToString()); if (jObject["op"].ToString().Equals("address", StringComparison.CurrentCultureIgnoreCase)) { subscriptionHandler.AddSubscription(socket, new AddressSubscription(decoded)); socket.Send("{ \"op\": \"address\", \"result\": \"ok\" }"); } else if (jObject["op"].ToString().Equals("rm_address", StringComparison.CurrentCultureIgnoreCase)) { socket.Send(!subscriptionHandler.RemoveSubscription(socket, new AddressSubscription(decoded)) ? "{ \"op\": \"rm_address\", \"result\": \"failed\" }" : "{ \"op\": \"rm_address\", \"result\": \"ok\" }"); } } catch (CashAddress.CashAddressException e) { socket.Send("{ \"op\": \"error\", \"error\": \"" + JsonConvert.ToString(e.Message) + " ... " + JsonConvert.ToString(e.InnerException.Message) + "\" }"); } } // op return subscriptions else if (jObject["op"].ToString().Equals("opreturn", StringComparison.CurrentCultureIgnoreCase) || jObject["op"].ToString().Equals("rm_opreturn", StringComparison.CurrentCultureIgnoreCase)) { if (!jObject.ContainsKey("prefix") || jObject["prefix"].Type != JTokenType.String) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected 'prefix' parameter as string.\" }"); return; } if (!IsValidHexString(jObject["prefix"].ToString())) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected 'prefix' parameter to be valid hex.\" }"); return; } if (jObject["prefix"].ToString().Length > 32) { socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected 'prefix' parameter to be less than 32 hex characters long.\" }"); return; } var prefix = ByteHexConverter.StringToByteArray(jObject["prefix"].ToString()); if (jObject["op"].ToString().Equals("opreturn", StringComparison.CurrentCultureIgnoreCase)) { subscriptionHandler.AddSubscription(socket, new OpReturnSubscription(prefix)); socket.Send("{ \"op\": \"opreturn\", \"result\": \"ok\" }"); } else if (subscriptionHandler.RemoveSubscription(socket, new OpReturnSubscription(prefix))) { socket.Send(!subscriptionHandler.RemoveSubscription(socket, new OpReturnSubscription(prefix)) ? "{ \"op\": \"rm_opreturn\", \"result\": \"failed\" }" : "{ \"op\": \"rm_opreturn\", \"result\": \"ok\" }"); } } else { // unrecognized op command socket.Send("{ \"op\": \"error\", \"error\": \"Error while parsing JSON request. Expected valid 'op' parameter.\" }"); } }
public static void Main(string[] args) { Console.WriteLine("Creating output script for 'bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a'..."); // input cash address and produce an output script var decoded = CashAddress.DecodeCashAddress("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"); // so far, cash addresses have two defined types // let's print the readable output script for the address switch (decoded.Type) { // our demo happens to be a P2PKH cash address... case ScriptType.P2PKH: // use ByteHexConverter to convert raw byte data for output script to readable hex Console.WriteLine("Output script: " + "OP_DUP OP_HASH160 " + ByteHexConverter.ByteArrayToHex(decoded.Hash) + " OP_EQUALVERIFY OP_CHECKSIG"); break; // if it was a P2SH cash address... case ScriptType.P2SH: // use ByteHexConverter to convert raw byte data for output script to readable hex Console.WriteLine("Output script: " + "OP_HASH160 " + ByteHexConverter.ByteArrayToHex(decoded.Hash) + " OP_CHECKSIG"); break; // whoa... another type? default: Console.WriteLine("This shouldn't happen! That's an unknown cash address type!"); break; } // let's use script builder now var outputScript = ScriptBuilder.CreateOutputScript(decoded.Type, decoded.Hash); // same thing if we go straight from a Cash Address outputScript = ScriptBuilder.CreateOutputScript("bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"); Console.WriteLine("Output script from ScriptBuilder: " + outputScript); Console.WriteLine("Output script RAW from ScriptBuilder: " + ByteHexConverter.ByteArrayToHex(outputScript.ScriptBytes)); // What about if we want to create an OP_RETURN output? // ASCII encode "My Bitcoin OP_RETURN!" and create an output script var opReturn = ScriptBuilder.CreateOpReturn(Encoding.ASCII.GetBytes("My Bitcoin OP_RETURN!")); Console.WriteLine("OP_RETURN script from ScriptBuilder: " + opReturn); Console.WriteLine("OP_RETURN script RAW from ScriptBuilder: " + ByteHexConverter.ByteArrayToHex(opReturn.ScriptBytes)); // encode a hash160 from an output script as a cash address (demo script is P2PKH) Console.WriteLine("Encoding output script 'OP_DUP OP_HASH160 76a04053bda0a88bda5177b86a15c3b29f559873 OP_EQUALVERIFY OP_CHECKSIG'..."); // use ByteHexConverter to convert the readable hex to raw byte data (as it would actually be encoded in an output script) var encoded = CashAddress.EncodeCashAddress(AddressPrefix.bitcoincash, ScriptType.P2PKH, ByteHexConverter.StringToByteArray("76a04053bda0a88bda5177b86a15c3b29f559873")); Console.WriteLine("Cash Address: " + encoded); // let's try decoding a raw transaction! var txHex = "020000000113b15104613103365466d9c1773a2c60c3dec7ab6ea41f7f2824f6b00556bd98370000006b483045022100bda8b53dcffbcbf3c005b7c55a923cd04eb3d3abd7632dd260f97d15cc2982ed02202dc15d4a9ad826f4b3a0781693050fe8c1cdeb919903ba11385f0b5e83c1ea5641210384dd3ad997f2e10980e755236b474f986c519599946027876cdeb4eb5a30a09fffffffff0110270000000000001976a91476a04053bda0a88bda5177b86a15c3b29f55987388ac00000000"; var tx = new Transaction(ByteHexConverter.StringToByteArray(txHex)); Console.WriteLine("Decoded transaction. TXID: " + tx.TXIDHex + ". Inputs: " + tx.Inputs.Length + ". Outputs: " + tx.Outputs.Length + ". Output Scripts:"); foreach (var output in tx.Outputs) { Console.WriteLine(output); } // wait Console.ReadKey(); }