Understanding Btcd Part 4: Create and Sign a Bitcoin Transaction With Btcd
Of course, we don’t send real bitcoin, it’s real money right now. This tutorial will help you get some bitcoin testnet then create and sign the transaction with btcd.
These are steps we need to do:
- Create a private key and address
- Get some bitcoin testnet on this address
- Creating a raw transaction
- Sign transaction
- Broadcast that signed the transaction to the network
1.Create a private key and address
This is kind of simple since you know the package btcec of btcd.
func GenerateKeyAddress() ([]byte, string){
key, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
fmt.Printf("failed to make privKey for %s: %v" , err)
}
pk := (*btcec.PublicKey)(&key.PublicKey).
SerializeUncompressed()
address, err := btcutil.NewAddressPubKeyHash(
btcutil.Hash160(pk), &chaincfg.TestNet3Params)
keyBytes := key.Serialize()
//keyHex := hex.EncodeToString(keyBytes)
fmt.Printf("PrivateKey: %x \n", keyBytes)
fmt.Printf("Address: %q\n", address.EncodeAddress())
return keyBytes, address.EncodeAddress()
}
One thing to notice here is we are using chaincfg.TestNet3Params, which will generate for us a testnet-3 address, which will begin with letter ’m’ or ’n’. This will help to prevent sending real bitcoin into testnet or vice versa. Now you all you have to do is copy and save that PrivateKey and Adress into some file. We will need it for latter use. In my case, the pair will be:
const test3_privKey = "291ad7996fa06e26379ce3640ca8752ae9ed7b87e27d2143f44ec1510ede3d74"
const my_address = "myeZPsW8yv8u9yy4gEG2ADuiczpf2257Vt"
2. Getting Some Testnet Bitcoin
You can google search bitcoin testnet faucet then find a lot of places. For example: https://coinfaucet.eu/en/btc-testnet/ After getting some satoshi, please give time for the transaction got confirmed. You could go to explorer site to watch your address. In my case, the link was https://tchain.btc.com/myeZPsW8yv8u9yy4gEG2ADuiczpf2257Vt.
3.Creating Raw Transaction
So you want to send bitcoin, you must have a receiver address. You could generate another privkey-address to use, so the bitcoin will sending to you too. Or you can send it back to the faucet donation address:
const receiver_address = "mv4rnyY3Su5gjcDNzbMLKBQkBicCtHUtFB"
Please don’t waste any bitcoin :)
What information you need to send a transaction. First, you remember how a transaction is constructed:
type MsgTx struct {
Version int32
TxIn []*TxIn
TxOut []*TxOut
LockTime uint32
}
So we need to define TxIn and TxOut of the new transaction. We need to collect the info to build the TxIn first. That’s all lying in the transaction from faucet giving us testnet bitcoin. They are:
unspentTx := utxo{
Address: my_address,
TxID: "b8d84fbcce0914ddc6688db53ef0dc6509833bf292189eb245a791a8415f9a58",
OutputIndex: 0,
Script: GetPayToAddrScript(my_address),
Satoshis: 200000,
}
- Address: is the address of your generated privkey-address.
- TxID: is the transaction id that faucet sent you.
- OutputIndex: is the index of TxtId above, that sending to your address. It’s often 0.
- Script: ?
- Satoshis: this is the amount of btc you receive. But we put this the amount we will send in the new transaction, so it’s a lower number, it’s your choice.
You can see the first TxIn sent 0.00340000 btc to our address, so that the OutputIndex would be 0 and satoshi we choose would be a number less than 0.00340000.
What is the Script here?
It’s the output pubKeyHash in TxtOut of a bitcoin transaction. This pubkeyHash will encode a puzzle, which only the receiver has the ability to prove it and claim the money. In this case, the Script is from txtOut of the faucet transaction, sending to you. So it will be GetPayToAddrScript(my_address).
Btcd helps us create this script pretty simple:
func GetPayToAddrScript(address string) []byte{
rcvAddress, _ := btcutil.DecodeAddress(address, &chaincfg.TestNet3Params)
rcvScript, _ := txscript.PayToAddrScript(rcvAddress)
return rcvScript
}
Let’s build the raw transaction
redemTx := wire.NewMsgTx(wire.TxVersion)
Create TxIn
hash, err := chainhash.NewHashFromStr(unspentTx.TxID)
if err != nil {
log.Fatalf("could not get hash from transaction ID: %v", err)
}
outPoint := wire.NewOutPoint(hash, unspentTx.OutputIndex)
txIn := wire.NewTxIn(outPoint, nil, nil)
redemTx.AddTxIn(txIn)
A TxIn will refer to the previous UTXO transaction id, and the index of TxtOut sending btc to you.
Create TxOut
rcv_script := GetPayToAddrScript(receiver_address)
outCoin := unspentTx.Satoshis
txOut := wire.NewTxOut(outCoin, rcv_script)
redemTx.AddTxOut(txOut)
As mention above, the TxOut of a transaction will have the Script to lock payment to receiver_address, and the number of satoshi will be sent. That’s simple!
4.Signing transaction
sig, err := txscript.SignatureScript(
redemTx,
0,
unspentTx.Script,
txscript.SigHashAll,
myPrivateKey,
false)
if err != nil {
log.Fatalf("could not generate signature: %v", err)
}
redemTx.TxIn[0].SignatureScript = sig
This function, txscript.SignatureScript will take parameters and give us the signature of TxIn at index 0. These parameters are:
- redemTx: The raw transaction we just created
- OutputIndex: The output index of the TxOut we sent to receiver_address. In this case, it equals 0.
- unspentTx.Script: The Script which we got money from faucet.
- txtscript.SigHashAll: The type of signature
- myPrivateKey: Your privKey
- compress Flag - false: privKey is serialized in either a compressed or uncompressed format based on compress flag
Then you put back this signature into your TxIn.SignatureScript field.
5.Broadcast your signed transaction
One last thing you should do is verifying the signature of your transaction. Btcd could help you do that like below. You construct a txtScript.Engine to run the Signature Script and see if it pass.
//Validate signature
flags := txscript.StandardVerifyFlags
vm, err := txscript.NewEngine(unspentTx.Script, redemTx, 0, flags, nil, nil, outCoin)
if err != nil {
fmt.Printf("err != nil: %v\n",err)
}
if err := vm.Execute(); err != nil {
fmt.Printf("vm.Execute > err != nil: %v\n",err)
}
fmt.Printf("redeemTx: %v\n", txToHex(redemTx))
Now print our transaction in hex format:
func txToHex(tx *wire.MsgTx) string {
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
tx.Serialize(buf)
return hex.EncodeToString(buf.Bytes())
}
You can copy this hex string and go to https://tchain.btc.com/tools/tx/publish to broadcast this transaction. Then wait for the network to mine it.
Done!
6.References
You can play with the source code of this tutorial at https://github.com/hlongvu/golang_ecdsa/blob/master/transaction.go