private static string BuildScriptPub(string addr) { string hash160 = BitcoinConversions.Base58ToHash160(addr); return(ByteArray.ConcatArrays( new byte[] { (byte)OPCodes.OP_DUP, (byte)OPCodes.OP_HASH160 }, new byte[] { (byte)(hash160.Length / 2) }, Base16.ToByteArray(hash160), new byte[] { (byte)OPCodes.OP_EQUALVERIFY, (byte)OPCodes.OP_CHECKSIG }).ToBase16()); }
private void MakeTx() { List <UTXO> uList = new List <UTXO>(); foreach (var item in TxInList) { UTXO u = new UTXO(); u.TxHash = item.TxId; u.OutIndex = item.OutIndex; if (BTx.Status == BitcoinTransaction.TxStatus.Signed) { int pubKeyLength = 65; string pubKey = item.ScriptSig.Substring((item.ScriptSigLength * 2) - (pubKeyLength + 1)); u.Address = BitcoinConversions.PubKeyToBase58(pubKey); u.AddressHash160 = BitcoinConversions.ByteArrayToHex(BitcoinConversions.PubKeyToHash160(pubKey)); } else { var addr = GetAddressFromScript(item.ScriptSig); if (string.IsNullOrEmpty(addr)) { u.Address = string.Empty; u.AddressHash160 = string.Empty; } else { u.Address = addr; u.AddressHash160 = BitcoinConversions.Base58ToHash160(addr); } } uList.Add(u); } try { UInt32 lockTime = 0; RawTx = Transaction.CreateRawTx(uList, ReceiveList.ToList(), lockTime, SelectedWalletType); } catch (Exception ex) { RawTx = string.Empty; MessageBox.Show(ex.Message); } }
/// <summary> /// Makes ScriptSig based on wallet type. /// <para/> INCOMPLETE! /// </summary> /// <param name="addr">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 BuildScriptSig(string addr, WalletType type) { string hash160 = BitcoinConversions.Base58ToHash160(addr); switch (type) { case WalletType.Electrum: //01 ff 16 fd 00 return(ByteArray.ConcatArrays( new byte[] { 1, (byte)OPCodes.OP_INVALIDOPCODE, 16, (byte)OPCodes.OP_PUBKEYHASH, 0 }, Base16.ToByteArray(hash160) ).ToBase16()); case WalletType.Normal: case WalletType.Core: default: return(BuildScriptPub(addr)); } }
/// <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()); }