Bitcoin: Create, Sign And Push Transaction

Aniket Divekar
4 min readNov 13, 2019

--

One of the issues faced by blockchain integrators today is to get more control over creating, signing and pushing the cryptocurrency transactions.

Photo by André François McKenzie on Unsplash

In this blog, I’ll share a simple example to create, sign and push a Bitcoin transaction. You can extend the example based on your needs.

Note: If you don’t know how bitcoin transactions work, I would suggest you go through this documentation.

For the purpose, I’m going to use a bitcoinjs-lib which is written in javascript and possibly one of the best out there.

Let’s get started! 💣

Install the library:

yarn add bitcoinjs-lib

I’m using yarn as the package manager. You can use the appropriate installation command based on the package manager you are using.

Create the transaction:

In bitcoin transactions, you don’t actually spend the balance of the address, instead, you spend transactions received by your address, also referred to as UTXO(Unspent Transaction Outputs)

To create a transaction, we’ll need such UTXO to use as input to our next transaction.

Before we start, just have a look at below raw hex of the transaction:

020000000001013cbc4fead3215b0a2823a4099cee55ac4b50b1151aed0fc73a994692550aa0e801000000171600144410fc594ae65be4bb4e6ed3c79b55602a338674ffffffff02cb4700000000000017a914ef1b74580a73c2e2ee52cb7a20febacd7050a2f187ed7b02000000000017a914d82952189f437226406ec17c8c397a34af177cd1870247304402203425f7f4bd699caf880db6e9489440f65cd3a6191933038974d201fa7d5897e00220526449a4aa0555b8098f470d1c6443480f8e44b269a96427c423de4ee2a23b57012103a0bea7dca18179c56a2819c575fa1b68bdf8bad87d8c12b1008daed2285ef08200000000

If the flag(formed using 4th and 5th byte of raw hex) is 0001, then the transaction is a Segwit transaction otherwise, it’s a Non-Segwit transaction. This knowledge is needed to use the library for creating the transaction.

Here is how you will create the transaction using bitcionjs-lib:

const bitcoin = require('bitcoinjs-lib')
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.testnet }) // For Mainnet: const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin })
// example transaction
const unspentOutput = {
"txid": "11e5b7005a76c8a53f9a0036bc1a2745ebd73ad40017b3169894aa2c19e789d7",
"vout": 1,
"address": "2NCxBSjMaVeBFyxmGcD2X428v6k5n3pCQKN",
"label": "payment",
"redeemScript": "001495a994a417c45f97c87d03efb21997452d4c782f",
"scriptPubKey": "a914d82952189f437226406ec17c8c397a34af177cd187",
"amount": 1.00000000,
"confirmations": 24,
"spendable": true,
"solvable": true,
"desc": "sh(wpkh([7fc5659a/0'/0'/1']02284916d8cd4fdf35574d5f0aaea0c93607254b06601ef126e73d8fb075b7169f))#6k9sssw6",
"safe": true
}
// get transaction hex
// You can use any public API to get it...
// https://testnet-api.smartbit.com.au/v1/blockchain/transaction/11e5b7005a76c8a53f9a0036bc1a2745ebd73ad40017b3169894aa2c19e789d7/hex
const rawTransaction = getRawHex(unspentOutput.txid) //haven’t shared this method’s implementation as it’s trivial// check if it's a Segwit or Non-Segwit transaction
const isSegwit = rawTransaction.substring(8, 12) === '0001'
if (isSegwit) {
// add segwit transaction input

psbt.addInput({
hash: unspentOutput.txid,
index: unspentOutput.vout,
witnessUtxo: {
script: Buffer.from(unspentOutput.scriptPubKey, 'hex'),
value: unspentOutput.amount * 100000000 // value in satoshi
},
redeemScript: Buffer.from(unspentOutput.redeemScript, 'hex')
})
} else {
// add non-segwit transaction input
psbt.addInput({
hash: unspentOutput.txid,
index: unspentOutput.vout,
nonWitnessUtxo: Buffer.from(rawTransaction, 'hex'),
redeemScript: Buffer.from(unspentOutput.redeemScript, 'hex')
})
}
// add output - destination address and the amount to transfer topsbt.addOutput({
address: '2NF3WNhdXJzgChaAZgdYjHWaAvYG25Nhz58’, // destination address
value: 0.5 * 100000000 // value in satoshi (0.5 BTC)
})
// If we look closely, We have input of 1 BTC and we are trying to send 0.5 BTC
// If we just use these configurations to send the transaction, it will consume remaining 0.5 BTC as fees
// which we wouldn't want
// So we'll leave some fee for the transaction, let's say 0.001 BTC and send the remaining amount to change address
// change address is the address you own where change from the transaction can be sent to
psbt.addOutput({
address: '2MzaZzn4cuAByJrNRpDHEgE8Z55Y7dsi3Gq’, // change address
value: 0.499 * 100000000 // value in satoshi (0.499 BTC)
})
// create transaction end

Sign the transaction:

To sign the transaction, we’ll need the private keys of all the addresses whose UTXOs have been used. In our example, we have used only one input, thus we’ll need only one private key(of address to which input transaction belongs to).

Code will look like below:

// first parameter in below method is the index of the input to be signed.
psbt.signInput(0, bitcoin.ECPair.fromWIF(<PRIVATE_KEY>, bitcoin.networks.testnet))
// you can use validate signature method provided by library to make sure generated signature is validpsbt.validateSignaturesOfAllInputs() // if this returns false, then you can throw the errorpsbt.finalizeAllInputs()// signed transaction hexconst transaction = psbt.extractTransaction()
const signedTransaction = transaction.toHex()
const transactionId = transaction.getId()
// sign transaction end

Push the transaction:

After you have the signed transaction, you can now publish it on the bitcoin network.
If you have your own node, you can use sendrawtransaction RPC to publish it to the network

const http = require("http");const options = {
"method": "POST",
"hostname": "<host>",
"port": "<port>",
"headers": {
"Content-Type": "application/json",
}
};
const req = http.request(options, function (res) {
const chunks = []
res.on("data", function (chunk) {
chunks.push(chunk)
})
res.on("end", function () {
const body = Buffer.concat(chunks)
})
})
req.write(JSON.stringify({ method: 'sendrawtransaction',
params: [signedTransaction],
id: '1' }))
req.end()

OR

You can use Blockcypher, Blockchain.com or Blockchair to publish your transaction.

⚠️ Final Notes:

Be very careful while building and signing the transaction on your own. A small mistake in the amount can cost you a lot. Test your code well by mocking input values before using your code on Mainnet.

If you have any doubts or find any mistakes, do let me know in the comments.
Suggestions are welcome! :)

Thank you!!! 🤟🏽

--

--

Aniket Divekar
Aniket Divekar

Written by Aniket Divekar

0.1x Engineer. Building Lusio - Your web3 gaming sidekick (lusio.gg). Previously @WazirXIndia. Fluent in Sarcasm. Interested in #web3 #defi. F1 & Ferrari fan.

Responses (1)