How to Make a Bitcoin Transaction in Go Lang
Bitcoin is all the rage in recent years. So does Golang - a programming language developed by Google. Today I am going to instruct you how to make a simple Bitcoin transaction with Golang. Hopefully, you will be able to transfer Bitcoin without using any DApps after this tutorial.
The tutorial requires you to have some basic understanding of Bitcoin as well as Golang. I recommend you should visit these two sites below if you are unfamiliar with the aforementioned topics:
+ Bitcoin: https://developer.bitcoin.org/reference/
+ Golang: https://go.dev/doc/tutorial/getting-started
To begin with, you need to install two libraries for working with Bitcoin in Golang.
+ btcutil: https://github.com/btcsuite/btcutil
+ btcd: https://github.com/btcsuite/btcd
You can simply get these libraries by executing the following commands:
go get github.com/btcsuite/btcutil
go get github.com/btcsuite/btcd
Step 1: You need to initialize required variables for your bitcoin transaction inputs. Here's the example inputs:
var fromAddress string = "XXXXXXXXXXXXXXXXXXXX"
var fromAddressPrivateKey string = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
var toAddress string = "XXXXXXXXXXXXXXXXXXXXXX"
var sentAmount int64 = 10000000 // in Satoshi unit
In addition, you need to specify the Bitcoin network you will broadcast your transaction. There are two types of Bitcoin network: mainnet and testnet. In this tutorial, I will set it to the testnet network since Bitcoin doesn't hold any value in this environment.
var network *chaincfg.Params = &chaincfg.TestNet3Params
As for the fee rate, you can refer to various sites to get this data. For this tutorial, I will get the fee rate from mempool. Make sure that you get the rate in the correct network and convert it into Satoshi unit.
var feeRate int = 100000 // in Satoshi unit
Step 2: You will create a list of transaction input from your UTXO (unspent transaction output) from previous transactions. To get the UTXO, you can use this endpoint from the mempool. Here's the example:
https://mempool.space/api/address/1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY/utxo
After you get the response from mempool, you will convert it into the following struct:
type UTXO struct {
TxID string `json:"txid"`
Vout uint32 `json:"vout"`
Status bool `json:"status"`
Value int64 `json:"value"` // value is in Satoshi unit
}
Here's the example array of UTXO fetched from mempool.
var UTXO = []UTXO{
{
TxID: "XXXXXXXXXXXXXXX",
Vout: 300,
Status: true,
},
{
TxID: "XXXXXXXXXXXXXXX",
Vout: 100,
Status: false,
},
}
Now, you will create a variable - txMsg that contains the tx inputs and outputs for your transaction.
var txMsg *wire.MsgTx = wire.NewMsgTx(wire.TxVersion)
Now, you will loop through the UTXO array to add the required tx inputs into txMsg. You will need to create a variable - requiredTxInAmount to keep track of the total input amount. The iteration will stop when requiredTxInAmount is greater than sentAmount. Besides, you will need to skip the item in UTXO whose status is false.
var requiredTxInAmount int64
for index, v := range UTXO {
if !v.Status || requiredTxInAmount > sentAmount {
continue
}
chainHash, _ := chainhash.NewHashFromStr(v.TxID)
outPoint := wire.NewOutPoint(chainHash, uint32(index))
txIn := wire.NewTxIn(outPoint, nil, nil)
txMsg.AddTxIn(txIn)
requiredTxInAmount += v.Value
}
Step 3: This is a tricky step. The way Bitcoin calculates the fee is based on two factors: fee rate, the size of transaction message. Regarding the latter, you will need the length of the tx inputs and outputs. In this tutorial, we will set the tx output length to 2 by default. Here's the formula to calculate the transaction size:
txSize = txInputNo*180 + txOutputNo*34 + 10
Once the tx size is calculated, we will determine the transaction fee. If the fee and the sentAmount is greater than requiredTxInAmount, you will need to add more item from the UTXO array.
var fee = txInputSize * feeRate
Step 4: As mentioned earlier, you will need to set two transaction outputs. One is for the toAddress and the other is for fromAddress. I guess this might get you feel confused. The reason why we need to create an output for the sender's address is because we need to transfer the remaining amount from requiredTxInAmount back to the sender's. Otherwise, this amount will be considered as transaction fee.
// For recipient
toAddressScript, _ := GetPayToAddrScript(toAddress, network)
txMsg.AddTxOut(wire.NewTxOut(sentAmount, toAddressScript))
// For sender
remainingUTX0 := requiredTxInAmount - int64(fee) - sentAmount
fromAddrScript, _ := GetPayToAddrScript(fromAddress, network)
txMsg.AddTxOut(wire.NewTxOut(remainingUTX0, fromAddrScript))
Step 5: You are going to validate the txMsg by signing it with your private key in this step. You will need to convert the string-typed private key into the required format due to the library's requirement.
// Process private key
privateKeyBytes, err := hex.DecodeString(fromAddressPrivateKey)
if err != nil {
return
}
privateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes)
for i, v := range txMsg.TxIn {
sigScript, _ := txscript.SignatureScript(
txMsg, // The tx to be signed.
i, // The index of the txin the signature is for.
fromAddrScript, // The other half of the script from the PubKeyHash.
txscript.SigHashAll, // The signature flags that indicate what the sig covers.
privateKey, // The key to generate the signature with.
false) // The compress sig flag. This saves space on the blockchain.
v.SignatureScript = sigScript
}
Step 6: The txMsg will be executed by the installed Bitcoin library. If succeed, you will convert it into the raw transaction string. To broadcast the transaction into Bitcoin network, you will need a third-party site or a Bitcoin node. In this tutorial, we will use mempool again to do so.
flags := txscript.StandardVerifyFlags
vm, err := txscript.NewEngine(fromAddrScript, txMsg, len(txMsg.TxIn), flags, nil, nil, requiredTxInAmount)
if err != nil {
return
}
vm.Execute()
// Convert raw transaction into hex string
buf := bytes.NewBuffer(make([]byte, 0, txMsg.SerializeSize()))
txMsg.Serialize(buf)
rawTxHex := hex.EncodeToString(buf.Bytes())
fmt.Println(rawTxHex)
Once the transaction is broadcast, you can check its details by using either the fromAddress or toAddress on any block explorer site. Bear in mind that you need to check the transaction details in the Bitcoin network you broadcast the transaction.
In summary, I hope you can succeed in making a transaction in Golang with your own computer. By doing so, you can feel assured that your transaction will not be corrupted like in DApps.
Do you like what you're reading from the CoderOasis Technology Blog? We recommend reading our Implementing RSA in Python from Scratch as your next choice.
The CoderOasis Community
Did you know we have a Community Forums and Discord Server? which we invite everyone to join us? Want to discuss this article with other members of our community? Want to join a laid back place to chill and discuss topics like programming, cybersecurity, web development, and Linux? Consider joining us today!