btcd - Construct transaction with Golang
Prologue
Participating in the Bitcoin ecosystem inevitably involves on-chain operations. Compared to the transaction building mechanism on Ethereum, constructing a Bitcoin transaction requires some programming skills.
Prerequisite knowledge for this post: UTxO, transaction structure, and scripting language,you can learn these on [Learn me a bitcoin](https://learnmeabitcoin.com/. In addition:
- The visualized testnet transaction pool explorer:https://mempool.space/testnet
- Test bitcoin faucet:https://bitcoinfaucet.uo1.net/
- Private key generator(avoid to use it on mainnet):https://iancoleman.io/bip39/
The Bitcoin tool repo: github.com/btcsuite/btcd
Consturct A Simple Transaction
Generate the private key and address
Private key
The private key
Private key have many different forms. The most common form is Wallet Import Format (WIF), and also form in hex. These forms of private key are interchangeable.
Actually, most Chrome wallet extensions not support private key import in WIF format.
You can generate private key and derive private key in WIF format with Golang. It also can generate WIF private key from the online BIP-39 website (remember avoid to use it on mainnet).
1 | // The code in the post all defaults to the test network |
Address
The bitcoin address also have many different format, you can learn it in Script on Learn me a bitcoin.
Taproot address is the most frequently used type, which is proposed to support the Taproot protocol and other different Pay-To methods.
The code for generate private key in WIF form is:
1 | taprootAddr, err := btcutil.NewAddressTaproot( |
There are four nested functions here, from innermost to outermost:
- wif.PrivKey.PubKey():obtian the public key from WIF private key
- txscript.ComputeTaprootKeyNoScript:calculate a public key that used to Schnorr signature from the public key
- schnorr.SerializePubKey:serialize public key to bytecodes
- btcutil.NewAddressTaproot:generate the address from public key in bytecodes format
Build transaction
The most simplest transaction in Bitcoin is a transaction that just has one input and one output. It transfer the BTC in the input to another address (the receiver).
Generate a random receiver address:tb1pvwak065fek4y0mup9p4l7t03ey2nu8as7zgcrlgm9mdfl8gs5rzss490qd
Although it is said to be a 'simple' transaction, the transaction under Taproot is the most complex transaction in Bitcoin. The simpler transaction type is P2PKH.
Before building a transaction, we need to obtain the available UTxO in wallet. Here is the function GetUnspent(address string)
be used to obtain the UTxO. We just fill in manually and returns the required UTxO information.
1 | func GetUnspent(address string) (*wire.OutPoint, *txscript.MultiPrevOutFetcher){ |
It returns a output point and the previous output fetcher.
- The output point records the transaction hash of the UTxO and the index of the output within the transaction;
- The fetcher records a mapping that indicates what kind of output corresponds to a given output point
In addition, it is necessary to decode the address and generate a PayToAddress script under Taproot before build transaction, the implementation code as follows:
1 | func DecodeTaprootAddress(strAddr string, cfg *chaincfg.Params) ([]byte, |
We can start constructing a simple transaction now. Unlike Ethereal, where a single JSON object can complete the transaction construction. We need to initialize a empty transaction and fill it in manually.
The input (wire.TxIn) for a new transaction need three parameters: previous output point, signature and witeness script. When constructing the transaction, both of the latter fields are initially set to nil and will be filled in only after the signing is complete.
signature and witeness script
Generally, the witness script and the signature script are independent of each other.
Alternatively, the witness script acts as a type of signature; it exists independently outside the transaction body and can be pruned by nodes after some time.
1 | // default version = 1 |
Next, the transaction needs to be signed. The signature is applied to all inputs of the transaction. It is necessary to fill correct unlocking script to the signature or witness field. In Taproot transcation, fill in the witness unlocking script.
1 | // obtain the previous transaction |
This section does not cover the code-level details of sending transactions. In simple terms, broadcasting a transaction means publishing it to any blockchain node.
So, we submit the transaction to Broadcast Transaction
The transaction is https://mempool.space/testnet/tx/f11f3edccb9988729ba4896e1da82b799a7b4e70cca82aa212058076dd49d76f
The complete code for this section:Simple Bitcoin Transaction - Github Gist