Create your own blockchain using Python (pt. 4)

Double-entry bookkeeping and UTXO’s

Guillaume Belanger
7 min readJul 13, 2021

You might have started this tutorial thinking you would do some cool crypto hacker stuff. No. Here we are doing accounting. In this section, we will deep-dive into how transactions are actually stored inside of blockchains (which is via double-entry bookkeeping). And by doing so, we will also cover the subject of Unspent Transaction Outputs (UTXO’s).

Double-entry bookkeeping

As always, let’s start with a Wikipedia definition:

Double-entry bookkeeping, in accounting, is a system of book keeping where every entry to an account requires a corresponding and opposite entry to a different account. The double-entry system has two equal and corresponding sides known as debit and credit. The left-hand side is debit and the right-hand side is credit.

This makes sense since there really are two different effects for every transaction. For example, if you are buying a car, you give money and you receive car. Those are two distinct things that will affect your business differently. Now if we look at the only equation in accounting (shown below), buying a $1000 car here will increase both your assets and your liabilities (accounts payable) by $1000 and both sides of the accounting equation remain the same.

  • Assets = Liabilities + Equity

Most businesses use double-entry bookkeeping for their accounting needs mainly because it makes it more simple to keep track of asset and liability.

Double-entry bookkeeping in blockchains

Up to now in our tutorial we were using single-entry bookkeeping inside of our blockchain (i.e. one entry per transaction). Our entries looked something like “sender, receiver, amount”. But like we’ve seen above, double-entry bookkeeping is useful, so how does it relate to bitcoin and cryptocurrency transactions in general? Here’s how Andrea Antonopoulos phrases it in his book Mastering Bitcoin: Programming the Open Blockchain:

In simple terms, each transaction contains one or more “inputs,” which are debits against a bitcoin account. On the other side of the transaction, there are one or more “outputs,” which are credits added to a bitcoin account.

This means that our transactions now contain two sections: inputs and outputs. If we re-use the example from pt.3 where Camille had 15 and she wanted to send 5 to Albert, we would have 1 input and 2 outputs:

Transaction Forms

This new format allows transactions to have any number of inputs and outputs, here are some common forms:

  • Common transaction (1 input, 2 outputs): This is the usecase we’ve just seen where person 1 sends money to person 2 and there’s change.
  • Aggregation transaction (N inputs, 1 output): If person 1 has received money through multiple transactions and he want to simplify his assets, he might want to aggregate them (just like you would exchange your pocket change against a 20$ bill).
  • Distributing transactions (1 input, N outputs): If person 1 wants to send money to all of his friends for example, he would use a distributing transaction.

Unspent transaction outputs (UTXO)

Let’s complicate things up a little bit. Let’s consider inputs for transactions to be a collection of previously unspent transaction outputs (UTXO). It is easy to compare this with real cash: person 1 gives you 10$, person 2 gives you 20$ and you want to buy a t-shirt for 25$, you have to use your two unspent transaction outputs (your two bills) to produce a new transaction to the t-shirt shop. At this point you will give your 20$ and your 10$ to the shop and you will get 5$ in change. Now, let’s look back at our example with Albert, Bertrand and Camille.

Applying this logic, here is how the chain of transactions look like:

This means that transaction inputs contain a past transaction’s hash and the output index corresponding to the UTXO. Let’s apply this to our Python code. We create two new classes that will help us form our transactions. This first one is for transaction inputs:

# transaction/transaction_input.pyimport json


class TransactionInput:
def __init__(self, transaction_hash: str, output_index: int, public_key: str = "", signature: str = ""):
self.transaction_hash = transaction_hash
self.output_index = output_index
self.public_key = public_key
self.signature = signature

def to_json(self, with_signature_and_public_key: bool = True) -> str:
if with_signature_and_public_key:
return json.dumps({
"transaction_hash": self.transaction_hash,
"output_index": self.output_index,
"public_key": self.public_key,
"signature": self.signature
})
else:
return json.dumps({
"transaction_hash": self.transaction_hash,
"output_index": self.output_index
})

And this second one is for transaction outputs:

# transaction/transaction_output.pyimport json


class TransactionOutput:
def __init__(self, public_key_hash: str, amount: int):
self.amount = amount
self.public_key_hash = public_key_hash

def to_json(self) -> str:
return json.dumps({
"amount": self.amount,
"public_key_hash": self.public_key_hash
})

We will update the various methods inside of our wallet’s Transaction class to reflect this change:

# wallet/wallet.pyimport json

from Crypto.Hash import SHA256
from Crypto.Signature import pkcs1_15

from transaction.transaction_input import TransactionInput
from transaction.transaction_output import TransactionOutput
class Transaction:
def __init__(self, owner: Owner, inputs: [TransactionInput], outputs: [TransactionOutput]):
self.owner = owner
self.inputs = inputs
self.outputs = outputs

def sign_transaction_data(self):
transaction_dict = {
"inputs": [tx_input.to_json(with_signature_and_public_key=False) for tx_input in self.inputs],
"outputs": [tx_output.to_json() for tx_output in self.outputs]
}
transaction_bytes = json.dumps(transaction_dict, indent=2).encode('utf-8')
hash_object = SHA256.new(transaction_bytes)
signature = pkcs1_15.new(self.owner.private_key).sign(hash_object)
return signature

def sign(self):
signature_hex = binascii.hexlify(self.sign_transaction_data()).decode("utf-8")
for transaction_input in self.inputs:
transaction_input.signature = signature_hex
transaction_input.public_key = self.owner.public_key_hex

def send_to_nodes(self):
return {
"inputs": [i.to_json() for i in self.inputs],
"outputs": [i.to_json() for i in self.outputs]
}

Let’s say Camille wants to send 5 to Bertrand, here is her wallet would do it:

utxo_0 = TransactionInput(transaction_hash=blockchain.transaction_hash, output_index=0)
output_0 = TransactionOutput(public_key_hash=bertrand_wallet.public_key_hash, amount=5)
transaction = Transaction(camille_wallet, inputs=[utxo_0], outputs=[output_0])
transaction.sign()

On the node’s side, we also need to modify our various NodeTransaction methods. We create a validate method that will take care of validating three things:

  • Validate the sender’s signature
  • Validate that the funds referenced in the transaction UTXO are truly owned by the sender
  • Validate that the total amount of inputs is equal to the total amount of outputs
# node/node.pyclass NodeTransaction:

def validate(self):
self.validate_signature()
self.validate_funds_are_owned_by_sender()
self.validate_funds()

First, let’s tackle validate_signature that will now validate each of the transaction input’s signatures.

# node/node.pyimport binascii
import json

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15

from node.block import Block
from transaction.transaction_input import TransactionInput
from transaction.transaction_output import TransactionOutput
class NodeTransaction:

def validate_signature(self):
transaction_data = copy.deepcopy(self.transaction_data)
for count, tx_input in enumerate(transaction_data["inputs"]):
tx_input_dict = json.loads(tx_input)
public_key = tx_input_dict.pop("public_key")
signature = tx_input_dict.pop("signature")
transaction_data["inputs"][count] = json.dumps(tx_input_dict)
signature_decoded = binascii.unhexlify(signature.encode("utf-8"))
public_key_bytes = public_key.encode("utf-8")
public_key_object = RSA.import_key(binascii.unhexlify(public_key_bytes))
transaction_bytes = json.dumps(transaction_data, indent=2).encode('utf-8')
transaction_hash = SHA256.new(transaction_bytes)
pkcs1_15.new(public_key_object).verify(transaction_hash, signature_decoded)

And validate_funds_are_owned_by_sender will validate that each UTXO’s receiver address match the sender’s public key.

# node/node.pyclass NodeTransaction:

def get_transaction_from_utxo(self, utxo_hash: str) -> dict:
current_block = self.blockchain
while current_block:
if utxo_hash == current_block.transaction_hash:
return current_block.transaction_data
current_block = current_block.previous_block

def validate_funds_are_owned_by_sender(self):
for tx_input in self.inputs:
input_dict = json.loads(tx_input)
public_key = input_dict["public_key"]
sender_public_key_hash = calculate_hash(calculate_hash(public_key, hash_function="sha256"), hash_function="ripemd160")
transaction_data = self.get_transaction_from_utxo(input_dict["transaction_hash"])
public_key_hash = json.loads(transaction_data["outputs"][input_dict["output_index"]])["public_key_hash"]
assert public_key_hash == sender_public_key_hash

Also, validate_funds adds up all the totals from the referenced UTXO’s and validates that they match the amounts in the transaction output.

class NodeTransaction:

def validate_funds(self):
assert self.get_total_amount_in_inputs() == self.get_total_amount_in_outputs()

def get_total_amount_in_inputs(self) -> int:
total_in = 0
for tx_input in self.inputs:
input_dict = json.loads(tx_input)
transaction_data = self.get_transaction_from_utxo(input_dict["transaction_hash"])
utxo_amount = json.loads(transaction_data["outputs"][input_dict["output_index"]])["amount"]
total_in = total_in + utxo_amount
return total_in

def get_total_amount_in_outputs(self) -> int:
total_out = 0
for tx_output in self.outputs:
output_dict = json.loads(tx_output)
amount = output_dict["amount"]
total_out = total_out + amount
return total_out

Where is my balance stored?

Contrary to intuition, blockchains hold no concept of balance, there are only scattered UTXO’s locked to owners. UTXO’s are tracked by bitcoin nodes in a memory variable called UTXO pool. If your wallet shows you a balance, it achieved this by scrolling through the blockchain and collecting all of the UTXO’s associated with your address.

Transaction scripts

Here we implemented a secure way for transactions to be completed and validated by nodes. In the next section, we will deep-dive into transaction scripts and how they allow for more complex transactions to occur.

Code repository

Create your own blockchain using Python

References

--

--

Guillaume Belanger

Guillaume is a software developer from Montreal who writes about bip bop stuff.