In-depth introduction to the UTXO transaction model of the bitcoin blockchain

Two transaction models dominate the blockchain world today - Account-based model where transactions are modeled as transfers happening from or to per-user accounts, and “unspent transaction output” or UTXO-based transactions. This post dives into the UTXO model, its design, execution and properties.

Overview

Blockchain is not a buzz word now and many implementations have been proposed as whitepapers. It is high time to bring those implementation proposals into the real world so that the technology can reach in the hands of normal people. For that, core knowledge of blockchain is very necessary. When I say core, I am referring to none other than “The Bitcoin Blockchain”.

The reason I give high importance to the Bitcoin blockchain is, core concepts of Blockchain technology lies here, all of the other blockchains out there are the derivative of Bitcoin. With the word derivative, I meant, they are either forks of Bitcoin-core software or are inspired by it. For example, Qutm is a fork of Bitcoin-core, and on top of it they’ve implemented EVM - Ethereum Virtual Machine in order to use smart contracts. Ethereum itself has taken inspirations from Bitcoin, added the feature like smart contracts, changed the transaction model to account (I will come to it in a bit) but the real motivation came from Bitcoin.

So, I strongly believe that a core blockchain developer should learn Bitcoin blockchain thoroughly, before getting their hands dirty on any other blockchain tech. Now to learn the Bitcoin blockchain, or any other blockchain for that matter, we can divide the whole concept into many smaller modules/topics, like, transaction module, consensus, distributed database etc. I am going to focus only on the transaction module in this blog. But before we talk about that, let’s have a quick recap of what blockchain is?

WhatBCIs

  • A wants to send some money to B.
  • He forms a transaction on his laptop and sends it to the connected node.
  • The node verifies the transaction and put it into the transaction pool, also called mempool.
  • Miners get notified about this transaction, pick it up, validate it and form a block along with other validated-transactions from the pool and broadcast that block to the network.
  • A node in the network gets the notification about the newly formed block verifies it (consensus happens) and store it into its ledger.
  • B, also connected to one of the nodes of the network, is now able to see her new balance.

What we are interested in understanding how does the transaction forms? What is the transaction object? What all parameters required to form the transaction? How does it look like and what is the data structure of this transaction object?

Transaction Models

Typically, there are two types of Transaction Models:

  1. Account Transaction Model
  2. UTXO Transaction Model

The analogy to Account transaction model would be your bank account. Like, for example, you have an account number and associated balance with it in the bank’s database. Essentially every account has its own balance, storage and code-space for calling other accounts or addresses. A transaction is valid if a sending account has enough balance to pay for it.

Alice wants to send Bob 5 tokens and Alice has 10 tokens in her account while Bob has 0. Alice sends Bob 5 tokens which are subtracted from her account balance and added to Bob’s account balance. Alice now has 5 tokens and Bob has 5. Blockchains like, Stellar, Ethereum etc. are using Account based transaction models.

The analogy to UTXO transaction model would be paper bills which you have in your wallet. In order to make a transaction of say, 1000 bucks, your wallet should have at least 1000 bucks in total (after summing up all the bills). You can imagine each of these bills as UTXO. We will discuss the UTXOs in details in the next section.

UTXO Transaction Model

As I said, UTXOs are nothing but paper bills residing in your wallet. The full form of UTXO is Unspent transaction output and the reason they are called unspent, because you have not spent them yet, they are sitting in your wallet. They are there for future transactions which you will make.

Now imagine you had 1000 rupees in your wallet in the form of 2, 500 rupee notes and you want to buy a kg of fish for rate 600 rupees a kg. Then for buying 1 kg of fish, you have to give both the notes to the vendor and get 400 rupees (say in the form of 4, 100 rupee notes) back as a change for that transaction. Let’s call it fish transaction. Now, these 4, 100 rupee notes are new UTXOs present in your wallet. So what we have observed in this transaction is, we used some UTXOs (let me call it output(s) for simplicity) as input and some output(s) as the output of the transaction.

In a transaction, set of UTXOs are consumed in order to produce new set of UTXOs.

Let’s try to think about designing the object structure for our fishTransaction. The simplest object structure we can think of this :

FishTransaction: {
  txId : "1234"
  inputs : "set of outputs from previous transaction"
  outputs :  "set of outputs for future transactions"
}

If we try to expand this object structure a bit more, we can think of inputs and outputs properties as an array or list since it may contain more than one objects (like you used 2 bills for fishTransaction as input), then it would look something like this:

FishTransaction: {
  txId : "1234",
  inputs : [
    {
      "Output of value 500 from some previous transaction to you"
    },
    {
      "Output of value 500 from some previous transaction to you"
    }
  ]
  outputs :  [
    {
      "Output of value 600 for the vendor"
    },
    {
      "Ouput of value 400 for you as change"
    }
  ]
}

Input

Let us try to focus on inputs, what else we can do with it:

Output of value 500 from some previous transaction to you.

  • When we say, previous transaction, which means we need to specify the trasnaction Id of that transaction.
  • Now we know that outputs of a transaction can contain multiple objects (it is an array right?), so we also need to specify, which index of that array we are referring to?
  • Since you are spending this money, for that you need to provide the proof that this 500 actually belongs to you. Which means give signature.

In short, in the input, we basically give a reference to the UTXO which you want to spend using previous transaction id and output index. So the input would now look something like this:

FishTransaction: {
  txId : "1234",
  inputs : [
    {
      txId : "which previous transaction are you referring to"
      index : "which output index you are referring to"
      scriptSig : "proof that this output belong to you"
    },
    {
      txId : "which previous transaction are you referring to"
      index : "which output index you are referring to"
      scriptSig : "proof that this output belong to you"
    },
  ]
...
..
.

OutPut

Let us now focus on outputs.

Output of value 600 for the vendor

  • The first parameter we can think of is amount.
  • And the other parameter we can think of is a way which should say this amount should only be spent by the owner and no one else. This is important since in a public blockchain, everyone can see all the transactions but they should not be able to spend those transactions which do not belong to them. We can call this property scriptPubKey.
FishTransaction: {
  .
  ..
  ...
  outputs : [
    {
      amount : 600
      scriptPubKey : "which says, the only vendor should be able to spend it"
    },
    {
      amount : 400
      scriptPubKey : "which says, only you should be able to spend it"
    },
  ]

Final Fish Transaction

This how our fishTransaction object has evolved.

FishTransaction: {
  txId : "1234",
  inputs : [
    {
      txId : "247593cdc481add115bdccc7bf6f3503ed8cc286297158c44889b237a647c6de"
      index : 0
      sigScript :"5d5014730440220680c174b610025f0c758a6d09073e302baab319af66f6123c16ad6"
    },
    {
      txId : "5235478110aa4e2c73403a0b1dfa87ca7329a9be158bc0c43b510a52d6c7a556"
      index : 1
      sigScript :"5d5014730440220680c174b610025f0c758a6d09073e302baab319af66f6123c16ad6"
    },
  ],
  outputs : [
    {
      amount : 600
      scriptPubKey : "OP_DUP OP_HASH160 <PubKey_Hash_Of_Vendor> OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      amount : 400
      scriptPubKey : "OP_DUP OP_HASH160 <PubKey_Hash_Of_You> OP_EQUALVERIFY OP_CHECKSIG"
    },
  ]
}
  • Only mystery we have here, is in outputs scriptPubKey which we will talk in a bit.

Comparision with Bitcoin Transaction

Now let us compare our fishTransaction object structure with any Bitcoin transaction in order to understand how accurately we have designed our object.

BitcoinTransaction {
  "txid": "5235478110aa4e2c73403a0b1dfa87ca7329a9be158bc0c43b510a52d6c7a556",
  "hash": "5235478110aa4e2c73403a0b1dfa87ca7329a9be158bc0c43b510a52d6c7a556",
  "version": 1,
  "size": 401,
  "vsize": 401,
  "weight": 1604,
  "locktime": 0,
  "vin": [
    {
      "txid": "a0a9e72e621db7370f622749a94f0840565b4015921f152c4fabfc854c8f1bfb",
      "vout": 0,
      "scriptSig": "30450221009029369405d8dc7d0f8aaa35d357b3bc179a599c891f82dd1452d010becae7990220325e66141919405fda1b47a1b2171b506370f9a6eff123f12d9148cc5e4e25d5[ALL] 30440220680c174b610025f0c758a6d09073e302baab319af66f6123c16ad6ee2be710f402204e9f5420e5bd6aa45a721aa65ff7993336ffbe4b7c1829eac04cc79d2deb39dd[ALL] 52210362b77a4907f1097078f0fd2e25b408ba4936f0a39d33965d5ac3ac9b4c67982521020e73213a16b76fe6a3bfbca5f0b5b71346a3638fef9d5ded7e4bf0d59bfe6b9d2103547cc515f91ff84c0895fcc92ce965dda6efd74cf2d754451cf6f3d643a1f22553ae",
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.01128872,
      "n": 0,
      "scriptPubKey": "OP_DUP OP_HASH160  f3bc71dfc9f6ec8eed3179d2b78d9d945b47e32e OP_EQUALVERIFY OP_CHECKSIG"
    },
    {
      "value": 0.00000600,
      "n": 1,
      "scriptPubKey": "OP_DUP OP_HASH160  167957fd009cce8c7a2a1d7ef084173bf537f54a OP_EQUALVERIFY OP_CHECKSIG"
    }
  ]
}

  • It’s almost the same right? with a few differences like, inputs are called vin, outputs are called vout, few new properties are added like, version, size, hash etc. But the overall structure is same.

Bitcoin Transaction Scripts

Remember we were talking about scriptSig in the input of the transaction and scriptPubKey in the output? scriptSig and scriptPubKey are called unlocking script and locking script respectively and together they are called Bitcoin Transaction Script. These scripts are needed for miners to validate the transaction.

And they have the following properties :

  • List of instructions recorded with each transaction
  • Describes how a next person wanting to spend the money
  • Stack-based
  • Processed from left to right
  • Non Turing complete
  • No Loops

Let us understand them in details.

scriptSig or unlocking script

scriptSig, also called unlocking script, consists of [spender’s digital signature and publick key] provided in input. The reason it is called unlocking, it is kind of key to the UTXO, referenced by transactionId and output index in vin.

scriptSig = <sig> <publicKey>

scriptPubkey or locking script

scriptPubkey, also called locking script, is a set of instructions which actuall locks the amount specified in the output so that only the owner of the UTXO can spend it.

scriptPubkey = DUP HASH160 <PubKHash> EQUALVERIFY CHECKSIG

Do not worry about these instructions as of now. We will talk about them later.

Bitcoin script

When a transaction is validated, the unlocking script in each input is executed alongside the corresponding locking script (referenced by transactionId and output index) to see if it satisfies the spending condition.

Bitcoin’s scripting language is called a stack-based language because it uses a data structure called a stack. A stack is a very simple data structure, which can be visualized as a stack of cards. A stack allows two operations: push and pop. Push adds an item on top of the stack. Pop removes the top item from the stack. The scripting language executes the script by processing each item from left to right.

Numbers (data constants) are pushed onto the stack. Operators push or pop one or more parameters from the stack, act on them, and may push a result onto the stack. For example, OP_ADD will pop two items from the stack, add them and push the resulting sum onto the stack.

Conditional operators evaluate a condition producing a boolean result of TRUE or FALSE. For example, OP_EQUAL pops two items from the stack and pushes TRUE if they are equal or FALSE if they are not equal.

Let us try to understand this simple script below, how it gets executed on the stack:

2 3 OP_ADD 5 OP_EQUAL

2 3 OP_ADD 5 OP_EQUAL

The execution pointer starts from 2, finds a constant, pushes into stack. Then goes to 3, pushes into the stack.

|     |
|     |
|  3  |
|__2__|

It finds operation, OP_ADD, so pops out 2 items from the top, 3 and 2, performs the add operation and pushes the result 5 on the stack.

2 3 OP_ADD 5 OP_EQUAL

|     |
|     |
|     |
|__5__|

2 3 OP_ADD 5 OP_EQUAL

Next encountered 5, so pushes it again.

|     |
|     |
|  5  |
|__5__|

2 3 OP_ADD 5 OP_EQUAL

Finally, it encountered, OP_EQUAL opeartion, evaluates the conditions, top 2 elements are equal, so pushes the result 1 (1 for TRUE and 0 for FALSE) into the stack.

|     |
|     |
|     |
|__1__|

Bitcoin Script Execution

Nodes validate the transaction by combining unlocking and corresponding locking scripts and executing the combined script on to stack machine. See the figure below:

bitcointxscript.png

Let’s us watch the video to understand bitcoin transaction script execution on a stack, in action. Though the video is self-explanatory, I would like to recall you the meaning of the instructions used in scriptPubKey before you watch it.

  • OP_DUP : Duplicates the top stack item.
  • OP_HASH160 : The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
  • OP_EQUALVERIFY : Same as OP_EQUAL, but runs OP_VERIFY afterward.
  • OP_EQUAL : Returns 1 if the inputs are exactly equal, 0 otherwise.
  • OP_VERIFY : Marks transaction as invalid if top stack value is not true.
  • OP_CHECKSIG : The signature used by OP_CHECKSIG must be a valid signature for this hash and public key. If it is, 1 is returned, 0 otherwise.

Here is the full list of other scripting instructions.

scriptexecpage

I hope this blog has given you enough insights of UTXO transactoin models and Bitcoin scripts. Cheers!

References

  • Bitcoin Script
  • Mastering Bitcoin by Andreas M.Antonopoulos
  • Watch full talk on “The UTXO Transaction Model of Blockchain” on my channel here
Vishwas Bhushan avatar
About Vishwas Bhushan, "Vishwas"
Member of blockchain team at Imaginea