/// <summary> /// Decode - Decodes a raw bitcoin block header /// </summary> private void Decode() { // block version number BlockVersion = ReadUInt(); // previous block hash PrevBlockHash = ByteHexConverter.ByteArrayToHex(ReadSlice(32).Reverse().ToArray()); // merkle root hash MerkleRootHash = ReadSlice(32); // block timestamp (seconds since 1970-01-01T00:00 UTC) TimeStamp = ReadUInt(); // difficulty target in compact format DiffTarget = ReadUInt(); // nonce Nonce = ReadUInt(); // strict validation - we should be at the end of the header LengthMatch = Offset == ByteData.Length; // don't store the extra data; useful when serializing multiple block headers if (!LengthMatch) { ByteData = ByteData.Take(Offset).ToArray(); } // block hash = sha256(sha256(header_data)) -> reverse byte data -> convert to hex SHA256 sha256 = new SHA256Managed(); BlockHashHex = ByteHexConverter.ByteArrayToHex(sha256.ComputeHash(sha256.ComputeHash(ByteData)).Reverse().ToArray()); }
/// <summary> /// Gets raw transaction data for a given txid /// </summary> /// <param name="txid">transaction id in hex</param> /// <returns>raw transaction byte data</returns> public byte[] GetRawTransaction(string txid) { var ret = SendCommand("getrawtransaction", txid); var transactionHex = ret.GetValue("result").ToObject <string>(); return(ByteHexConverter.StringToByteArray(transactionHex)); }
/// <inheritdoc /> /// <summary> /// Constructor /// - decodes the transaction data /// </summary> /// <param name="txBytes">raw transaction as byte array</param> public Transaction(IEnumerable <byte> txBytes) : base(txBytes) { var sha256 = new SHA256Managed(); // double sha256 hash, reverse bytes, then convert to hex TXID = sha256.ComputeHash(sha256.ComputeHash(ByteData)).Reverse().ToArray(); TXIDHex = ByteHexConverter.ByteArrayToHex(TXID); Decode(); }
/// <summary> /// Calculates double sha256 hash of two merkle nodes /// </summary> /// <param name="a">merkle node 1 hash as hex</param> /// <param name="b">merkle node 2 hash as hex</param> /// <returns></returns> private static string CalculateParentHash(string a, string b) { // convert string to byte arrays, reverse each, and combine var mergeHash = ByteHexConverter.StringToByteArray(a).Reverse().Concat(ByteHexConverter.StringToByteArray(b).Reverse()).ToArray(); // compute and return lowercase double sha256 hash of merged hashes, reversed, in hex var sha256 = SHA256.Create(); return(ByteHexConverter.ByteArrayToHex(sha256.ComputeHash(sha256.ComputeHash(mergeHash)).Reverse().ToArray()).ToLower()); }
/// <summary> /// Creates a human readable script with data as hex from the bitcoin script /// </summary> /// <returns>human readable script</returns> public override string ToString() { var ret = string.Join(" ", OpCodes); foreach (var data in DataChunks) { ret = ret.ReplaceFirst("OP_DATA", ByteHexConverter.ByteArrayToHex(data)); } return(ret); }
/// <summary> /// Gets and decodes the transaction data for a given txid /// </summary> /// <param name="txid">transaction id in hex</param> /// <returns>decoded transaction</returns> public Transaction.Transaction GetTransactionById(string txid) { var ret = SendCommand("tx/" + txid + ".hex").Trim(); if (ret.Contains("not found")) { return(null); } return(WrapDecodeException(() => new Transaction.Transaction(ByteHexConverter.StringToByteArray(ret)))); }
/// <summary> /// Gets raw block headers starting from a given block hash /// </summary> /// <returns>raw block data as hex</returns> public IEnumerable <BlockHeader> GetBlockHeadersStartingAt(string blockHash, int count) { var ret = SendCommand("headers/" + count + "/" + blockHash + ".hex"); var headerBytes = WrapDecodeException(() => ByteHexConverter.StringToByteArray(ret.Trim())); // decode headers (80 bytes each) for (var headerStart = 0; headerStart < headerBytes.Length; headerStart += 80) { yield return(new BlockHeader(headerBytes.Skip(headerStart).Take(80))); } }
/// <inheritdoc /> /// <summary> /// Constructor /// - creates a transaction object given transaction properties /// - used for transactions found in blocks /// </summary> public Transaction(IEnumerable <byte> txBytes, string inclusionBlockHex, uint txVersion, Input[] inputs, Output[] outputs, uint lockTime) : base(txBytes) { var sha256 = new SHA256Managed(); TXID = sha256.ComputeHash(sha256.ComputeHash(ByteData)).Reverse().ToArray(); TXIDHex = ByteHexConverter.ByteArrayToHex(TXID); IncludedInBlockHex = inclusionBlockHex; TXVersion = txVersion; Inputs = inputs; Outputs = outputs; LockTime = lockTime; LengthMatch = true; }
/// <summary> /// ReadScript /// </summary> /// <param name="script"></param> /// <exception cref="InvalidSLPScriptException"></exception> public static void ValidateSLPScriptHeader(Script.Script script) { /* * <lokad_id: 'SLP\x00'> (4 bytes, ascii) * <token_type: 1> (1 to 2 byte integer) */ if (script.OpCodes.Count < 1 || !script.OpCodes[0].Equals(OpCodeType.OP_RETURN)) { throw new InvalidSLPScriptException("Script is not an op_return"); } if (script.OpCodes.Count < 2 || !script.OpCodes[1].Equals(OpCodeType.OP_DATA) || script.DataChunks.Count < 1) { throw new InvalidSLPScriptException( "Script is not an SLP op_return. Header is missing."); } if (script.DataChunks[0].Length != 4) { throw new InvalidSLPScriptException( "Script is not an SLP op_return. Wrong header - expected 4 bytes, but received " + script.DataChunks[0].Length + "."); } if (!script.DataChunks[0].SequenceEqual(OpReturnPrefix)) { throw new InvalidSLPScriptException( "Script is not an SLP op_return. Wrong header - expected 0x504c5300, but received " + ByteHexConverter.ByteArrayToHex(script.DataChunks[0]) + "."); } if (script.DataChunks.Count == 1) { throw new InvalidSLPScriptException("Script is missing SLP token_type."); } if (script.DataChunks[1].Length != 1 && script.DataChunks[1].Length != 2) { throw new InvalidSLPScriptException("SLP version violates spec - expected 1 or 2 bytes, but received " + script.DataChunks[1].Length + "."); } // make sure the script contains only push opcodes, data, and the op_return if (script.OpCodes.Where(x => x != OpCodeType.OP_DATA).Count() > 1) { throw new InvalidSLPScriptException("Script contains invalid op_codes! Invalid codes found: " + string.Join(", ", script.OpCodes.Where(x => x != OpCodeType.OP_DATA))); } }
/// <summary> /// Processes raw bitcoin transactions /// - decodes the transaction, output scripts, and output addresses /// </summary> /// <param name="data">raw transaction byte array</param> protected override Transaction DoWork(byte[] data) { // hex version of the transaction var txHex = ByteHexConverter.ByteArrayToHex(data); // attempt to decode the transaction (also decodes output scripts and addresses) Transaction transaction; try { transaction = new Transaction(data); } catch (Exception e) { Console.WriteLine("Unable to decode transaction: \n" + txHex + "\n" + e.Message); return(null); } return(transaction); }
/// <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.\" }"); } }
/// <summary> /// Gets and decodes block data /// </summary> /// <returns>decoded block</returns> public Block.Block GetBlockByHash(string blockHash) { var ret = SendCommand("block/" + blockHash + ".hex").Trim(); return(WrapDecodeException(() => new Block.Block(ByteHexConverter.StringToByteArray(ret)))); }
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(); }