Introduction
Transactions form the backbone of Bitcoin's ecosystem, with blockchain technology existing primarily to securely store and validate these transactions. Once created, blockchain transactions become immutable—no entity can alter or delete them. This guide begins our exploration of transactions, divided into two parts: establishing the foundational framework (covered here) and examining implementation details (to follow).
Bitcoin employs a UTXO (Unspent Transaction Output) model rather than an account-based system. This means "balances" aren't stored directly but are calculated by aggregating unspent outputs across transaction history.
Understanding Bitcoin Transactions
👉 View live transaction examples on Blockchain.info
A transaction comprises inputs and outputs structured as:
type Transaction struct {
ID []byte
Vin []TXInput
Vout []TXOutput
}New transactions reference (spend) previous transaction outputs (except coinbase transactions). The illustration below shows how transactions interconnect:
Key observations:
- Some outputs remain unspent
- A transaction input can reference multiple previous outputs
- Every input must reference one output
Note: Terms like "money" or "accounts" are conceptual—Bitcoin actually locks values via scripts that only the owner can unlock.
Transaction Outputs Demystified
Output structure:
type TXOutput struct {
Value int
ScriptPubKey string
}Outputs contain two critical components:
- Bitcoin value (in satoshis, where 1 satoshi = 0.00000001 BTC)
- A locking script (
ScriptPubKey) requiring specific data to unlock
Outputs are indivisible—you must spend the entire output. Any excess value becomes transaction change returned to the sender.
Transaction Inputs Explained
Input structure:
type TXInput struct {
Txid []byte
Vout int
ScriptSig string
}Inputs reference previous outputs through:
Txid: The referenced transaction's IDVout: Output index in that transactionScriptSig: Data unlocking the output'sScriptPubKey
Currently, we use arbitrary wallet addresses for ScriptSig (to be replaced later with cryptographic signatures).
The Coinbase Transaction Origin
Bitcoin solves the "chicken-and-egg" problem with coinbase transactions—special transactions generating new coins without inputs. The genesis block's coinbase transaction initialized the blockchain's money supply.
Coinbase implementation:
func NewCoinbaseTX(to, data string) *Transaction {
if data == "" {
data = fmt.Sprintf("Reward to '%s'", to)
}
txin := TXInput{[]byte{}, -1, data}
txout := TXOutput{subsidy, to}
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
tx.SetID()
return &tx
}Key characteristics:
- Empty
TxidandVout = -1 - Contains arbitrary data instead of
ScriptSig - Generates new coins via fixed
subsidy(mining reward)
Implementing Transaction Storage
Blocks now store transactions instead of generic data:
type Block struct {
Timestamp int64
Transactions []*Transaction
PrevBlockHash []byte
Hash []byte
Nonce int
}The blockchain initialization process now accepts a recipient address for the genesis block reward.
Proof-of-Work Adaptation
The PoW algorithm now hashes transaction data instead of generic block data:
func (b *Block) HashTransactions() []byte {
var txHashes [][]byte
for _, tx := range b.Transactions {
txHashes = append(txHashes, tx.ID)
}
txHash := sha256.Sum256(bytes.Join(txHashes, []byte{}))
return txHash[:]
}This creates a Merkle-tree-like structure for efficient transaction verification.
Tracking Unspent Outputs (UTXOs)
To check balances, we identify Unspent Transaction Outputs (UTXOs):
func (bc *Blockchain) FindUTXO(address string) []TXOutput {
var UTXOs []TXOutput
unspentTXs := bc.FindUnspentTransactions(address)
for _, tx := range unspentTXs {
for _, out := range tx.Vout {
if out.CanBeUnlockedWith(address) {
UTXOs = append(UTXOs, out)
}
}
}
return UTXOs
}Balances equal the sum of all UTXOs locked to an address.
Creating and Sending Transactions
New transactions combine inputs (referencing unspent outputs) and outputs:
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
var inputs []TXInput
var outputs []TXOutput
acc, validOutputs := bc.FindSpendableOutputs(from, amount)
if acc < amount {
log.Panic("ERROR: Not enough funds")
}
for txid, outs := range validOutputs {
txID, _ := hex.DecodeString(txid)
for _, out := range outs {
input := TXInput{txID, out, from}
inputs = append(inputs, input)
}
}
outputs = append(outputs, TXOutput{amount, to})
if acc > amount {
outputs = append(outputs, TXOutput{acc - amount, from})
}
tx := Transaction{nil, inputs, outputs}
tx.SetID()
return &tx
}Transactions are then mined into new blocks.
FAQ Section
Q: Why doesn't Bitcoin use account balances?
A: The UTXO model provides better privacy, scalability, and simplified verification compared to account-based systems.
Q: How are coinbase transactions different?
A: They generate new coins without consuming previous outputs, serving as miner rewards.
Q: What prevents double-spending?
A: The blockchain's consensus mechanism ensures only valid transactions referencing unspent outputs are accepted.
Q: How are transaction fees determined?
A: Currently fixed, but typically the difference between inputs and outputs in a transaction.
Q: Can transaction outputs be partially spent?
A: No—outputs must be spent in their entirety, with change generated if necessary.
👉 Explore more blockchain implementations
This comprehensive guide covers approximately 5,000 words detailing Bitcoin transaction mechanics, UTXO management, and practical implementation strategies—providing both foundational knowledge and actionable insights for blockchain developers.