private string GetAddressFromScript(string script) { if (string.IsNullOrEmpty(script)) { return(string.Empty); } string firstOP = script.Substring(0, 2); string remove = string.Empty; if (firstOP == NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_DUP, 1)) { remove = NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_DUP, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_HASH160, 1) + NumberConversions.IntToHex(40 / 2, 1); } else if (firstOP == NumberConversions.IntToHex(0x01, 1)) { remove = NumberConversions.IntToHex(0x01, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_INVALIDOPCODE, 1) + NumberConversions.IntToHex(0x16, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_PUBKEYHASH, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_0, 1); } string addressHash = script.Substring(remove.Length, 40); return(BitcoinConversions.Hash160ToBase58(addressHash)); }
/// <summary> /// Makes ScriptSig based on wallet type. /// </summary> /// <param name="hash">Hes string to put inside of ScriptSig.</param> /// <param name="type">Type of wallet that is supposed to sign this transaction.</param> /// <returns>ScriptSig</returns> public static string BuildScript(string hash, WalletType type) { string result = ""; if (type == WalletType.Normal) { //76 a9 14 - 88 ac result = NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_DUP, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_HASH160, 1) + NumberConversions.IntToHex((UInt64)hash.Length / 2, 1) + hash + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_EQUALVERIFY, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_CHECKSIG, 1); } else if (type == WalletType.Electrum) { //01 ff 16 fd 00 result = NumberConversions.IntToHex(0x01, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_INVALIDOPCODE, 1) + NumberConversions.IntToHex(0x16, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_PUBKEYHASH, 1) + NumberConversions.IntToHex((int)OPCodes.opcodetype.OP_0, 1) + hash; } else if (type == WalletType.Core) { result = ""; } return(result); }
/// <summary> /// Creates a Raw Unsigned bitcoin transaction Transaction. /// <para/> Sources: /// <para/> 1. https://bitcoin.stackexchange.com/questions/32628/redeeming-a-raw-transaction-step-by-step-example-required /// <para/> 2. https://bitcoin.org/en/developer-reference#raw-transaction-format /// <para/> 3. https://bitcoin.org/en/developer-examples#simple-raw-transaction /// </summary> /// <param name="txToSpend">List of UTXOs to spend.</param> /// <param name="receiveAddr">List of receiving addresses and the amount to be paid to each.</param> /// <param name="wallet">Type of the wallet (Electrum does not recognize normal type of scriptSig placeholder).</param> /// <returns>Rat unsigned transaction string.</returns> public static string CreateRawTx(List <UTXO> txToSpend, List <ReceivingAddress> receiveAddr, UInt32 lockTime, WalletType wallet) { StringBuilder rawTx = new StringBuilder(); // 1) 4 byte - version UInt32 version = 1; rawTx.Append(NumberConversions.IntToHex(version, 4)); // 2) ? byte - tx_in count (compactSize uint) int txInCount = txToSpend.Count; rawTx.Append(NumberConversions.MakeCompactSize((UInt64)txInCount)); for (int i = 0; i < txInCount; i++) { // 3) 32 byte - TX hash (reverse) string txToSend = txToSpend[i].TxHash; rawTx.Append(ReverseTx(txToSend)); // 4) 4 byte - output Index UInt32 outputIndex = txToSpend[i].OutIndex; rawTx.Append(NumberConversions.IntToHex(outputIndex, 4)); // 5) ? byte - scriptSig length (compactSize uint) (Maximum value is 10,000 bytes) // Can be from 0 and up. Will be replaced by actual scriptSig with another length. // Format can be different based on the client which will be used for signing. if (string.IsNullOrEmpty(txToSpend[i].AddressHash160) && wallet != WalletType.Core) { throw new Exception(string.Format("No address was found. Set wallet type to Core.")); } string scriptSig = BuildScript(txToSpend[i].AddressHash160, wallet); rawTx.Append(NumberConversions.IntToHex((UInt64)(scriptSig.Length / 2), 1)); // 6) ? byte - scriptSig which is filled with scriptPubkey temporarily (20 is half the length of address hash160) rawTx.Append(scriptSig); //7) 4 byte - sequence - max is 0xffffffff - can change for RBF transactions // Ref: // https://bitcoin.stackexchange.com/questions/2025/what-is-txins-sequence UInt32 sequence = UInt32.MaxValue; rawTx.Append(NumberConversions.IntToHex(sequence, 4)); } //8) ? byte - tx_out count (compactSize uint) int txOutCount = receiveAddr.Count; rawTx.Append(NumberConversions.MakeCompactSize((UInt64)txOutCount)); foreach (var item in receiveAddr) { //9) 8 byte - amout to transfer UInt64 amount = item.PaymentSatoshi; rawTx.Append(NumberConversions.IntToHex(amount, 8)); //10) ? byte - pk_script length (compactSize uint) string itemHash = BitcoinConversions.Base58ToHash160(item.Address); string outputScript = BuildScript(itemHash, WalletType.Normal); rawTx.Append(NumberConversions.MakeCompactSize((UInt64)outputScript.Length / 2)); //11) ? byte - pk_script rawTx.Append(outputScript); } //12) 4 byte - lock time // * If less than 500 million, locktime is parsed as a block height. The transaction can be added to any block which has this height or higher. // * If greater than or equal to 500 million, locktime is parsed using the Unix epoch time format (the number of seconds elapsed since 1970-01-01T00:00 UTC—currently over 1.395 billion). The transaction can be added to any block whose block time is greater than the locktime. rawTx.Append(NumberConversions.IntToHex(lockTime, 4)); return(rawTx.ToString()); }