Lecture 3: Bitcoin¶

A public peer to peer ledger that keeps track of how much each party owns of the native unit of account bitcoin. It was invented in 2009 by an anonymous party (or parties) holding the name Satoshi Nakamato. The first transaction on the bitcoin blockchain encodes the following message: "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks". This message was in reference to an article posted in a british newspaper on 03/Jan/2009.

In [ ]:
# This code connects to a bitcoin node (hosted on https://mempool.space) 
# holding the full bitcoin blockchain and 
# extracts the first message encoded in the genesis block of 
# the coinbase transaction. 
import requests
from binascii import unhexlify


# getting the block id for the genesis block (block 0).
genesis_block_id = requests.get('https://mempool.space/api/block-height/0').text

# getting all the block transactions of block 0 and parsing it
response = requests.get('https://mempool.space/api/block/' + genesis_block_id + '/txs')

# decode the special message in the genesis block
print(unhexlify(response.json()[0]['vin'][0]['scriptsig_asm'].split(" ")[5]).decode('utf-8'))
The Times 03/Jan/2009 Chancellor on brink of second bailout for banks
Informal description of a digital signature scheme

Image source: https://en.bitcoin.it/

From Previous Lectures:¶

Building Blocks:¶

  1. Digital Signature
  2. Hash function

Digital Signature Reminder:¶

Definition. A digital signature contains three algorithms:

  • KeyGen: takes no input, produces a secret key $sk$ and a public key $pk$
  • Sign(sk, M): produces a signature $\sigma$
  • Verify(pk, M, $\sigma$): returns true or false

All three algorithms should be efficiently computable. Moreover, the digital signature must satisfy two guarantees: correctness and unforgeability.

Hash Function Reminder:¶

Definition. A hash function $H: \{0, 1\}^* \to \{0, 1\}^n$ is an efficiently-computable function that accepts unbounded input and provides output strings of a fixed number of bits $n$.

  • One wayness: given $y = H(x)$, it is practically infeasible for anyone to find any preimage $x'$ such that $H(x') = y$.

  • Collision resistance: it is practically infeasible for anyone to find two different inputs $x_1$ and $x_2$ such that $H(x) = H(x')$.

  • Random oracle: $H$ acts like a randomly-generated truth table would.

If an adversary [Mallory] has not explicitly queried the oracles on some point $x$, then the value of $H(x)$ is completely random... at least as far as [Mallory] is concerned.

– Jon Katz and Yehuda Lindell, Introduction to Modern Cryptography

(Note: any modification to x will lead to a different hash)


Ledger Reminder:¶

19th century ledger

Source: Wikipedia

A ledger is a book or collection of accounts in which account transactions are recorded. Each account has an opening or carry-forward balance, and would record each transaction as either a debit or credit in separate columns, and the ending or closing balance.

For every debit recorded in a ledger, there must be a corresponding credit, so that the debits equal the credits in the grand totals. Source Wikipedia

Bitcoin a Public Ledger¶

The bitcoin ledger is maintained by everyone running the bitcoin software. Anyone can run the bitcoin software on their computer. All parties are in an agreement over this ledger and the transactions within this ledger. We call these parties nodes.

The ledger is seperated into blocks. Each block can contain a number of transactions and always references the hash of the previous block. A block gets added to the blockchain approximately every 10 minutes.

Here is a screenshot of some blocks that were mined yesterday (and one that is not the process of being so). I took these pictures using a blockchain explorer. Note that anyone can run a node and install a blockchain explorer.

bitcoin blocks

In this screenshot there is a bitcoin address with approx 3.22BTC sending approx 0.049BTC to one address and 3.17BTC to another.

bitcoin transaction
In [ ]:
# Every block references the previous block
# getting block 773610 that is in the screenshot
import requests
from binascii import unhexlify


# getting the block hash of 773610
block_hash = requests.get('https://mempool.space/api/block-height/773610').text

# get block metadata
response = requests.get('https://mempool.space/api/block/' + block_hash)

print(response.json()['previousblockhash'])
00000000000000000004574d28ac29cd49708820c68dd09d8bd88ed8bf1a341b

The Bitcoin Blockchain is Immutable and Append Only.¶

If one were to edit one transaction of a specific block they will also have to edit every block that comes after that block. This is because every future block is pointing to the hash of the previous block.

Bitcoin UTXO¶

For a transaction to be valid, the sender of the transaction must specifiy where they got their coins from. Also, the sender shouldn't be sending more than what they have. What goes out must be less than or equal to what goes in. If $in \ge out$, the amount $in-out$ is considered fees and a miner can claim the money for themself. Else the transaction is considered invalid.

19th century ledger

Bitcoin Coinbase Transaction¶

How can the first person spend if no one has paid them?¶

Every output can be traced back to a coinbase transaction. A coinbase transaction is when a miner pays for themselves bitcoins by creating new bitcoins out of thin air.

coinbase transaction

A bitcoin address is computed from a public key. In the below snippet of code, we are creating an actual bitcoin address right now!

In [ ]:
!pip install "cryptos @ git+https://github.com/nicolas3355/cryptos"

from cryptos.keys import gen_secret_key, PublicKey
from cryptos.bitcoin import BITCOIN

# generate a secret/public key pair
secret_key = gen_secret_key(BITCOIN.gen.n)
public_key = PublicKey.from_sk(secret_key)
print('generated secret key:')
print(hex(secret_key))
print('corresponding public key:')
print(public_key)

# get the associated bitcoin address
addr = public_key.address(net='main', compressed=True)
print('compressed bitcoin address (b58check format):')
print(addr)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting cryptos@ git+https://github.com/nicolas3355/cryptos
  Cloning https://github.com/nicolas3355/cryptos to /tmp/pip-install-8whna9ik/cryptos_e66739370806414c951bac11f8da58d0
  Running command git clone --filter=blob:none --quiet https://github.com/nicolas3355/cryptos /tmp/pip-install-8whna9ik/cryptos_e66739370806414c951bac11f8da58d0
  Resolved https://github.com/nicolas3355/cryptos to commit 4e01811d449e0292ffb2ac08f7a49a43ffa9cceb
  Preparing metadata (setup.py) ... done
Building wheels for collected packages: cryptos
  Building wheel for cryptos (setup.py) ... done
  Created wheel for cryptos: filename=cryptos-1.0-py3-none-any.whl size=22965 sha256=fc5522154aa5b486ca4ac2804e06e593b2764ea493a5694b83442fbff0562aeb
  Stored in directory: /tmp/pip-ephem-wheel-cache-wvjt2zea/wheels/7d/3f/14/44c1d38124ec0668722a79bc0718762cb538bf24f6c95aabc4
Successfully built cryptos
Installing collected packages: cryptos
Successfully installed cryptos-1.0
generated secret key:
0x7a3e8bfa09e6ceb36cebe84b2ad2181f8996ccb969326a0dc9677df2468d37eb
corresponding public key:
PublicKey(curve=Curve(p=115792089237316195423570985008687907853269984665640564039457584007908834671663, a=0, b=7), x=103016115360421655342945202929628046207358169868085253813782939278876286995237, y=94982696061610089227674092825845224192632594785648011908165719440875631939648)
compressed bitcoin address (b58check format):
12rgNEcxjTxJRAifSi7yDyGshhKAUT15v9

Spending from a UTXO (bitcoin's scripting language)¶

A bitcoin address spending their (unspent) output by having it as an input to another transaction. The spender is providing a public key and a valid digital signature. The hash of the public key must be $8ec...b1$. All nodes verify that the signature is valid and that the sender has enough money to spend.

Proof of Work.¶

Assume you have a job opening and there are millions of applicants. You can't handle this huge number. What do you do?

In this part we are going to talk about two problems that the bitcoin network has solved using proof of work:

  1. How to maintain consenus over the ledger?
  2. Why is the bitcoin ledger censorship resistant and who picks the next block?

In order to understand these two parts, we are going to revist the random oracle property of a hash function. For any new input, the hash function should behave like a random Oracle and it should return a uniformly random output. Given a new input x, each bit in h(x) (whether 0 or 1) should be equally likely to appear.

In [ ]:
# what is the probability that when I hash a random string, I get a hash that starts with 4 bits that are all zeroes?
from hashlib import sha256
import os
 
# count = 0
# hash = "1"
# #note that when working with hexadecimals each digit is 4 bits.
# while(hash[0] != '0'):
#   count += 1
#   hash = sha256(os.urandom(32)).hexdigest()
#   print(hash)

# print(count)

# running the trial a 100 times
arr = []
nb_of_trials = 100
for _ in range(0, 100):
  count = 0
  hash = "1"
  while(hash[0] != '0'):
    count += 1
    hash = sha256(os.urandom(32)).hexdigest()
  arr.append(count)
print(sum(arr)/100)
16.6
In [ ]:
# let's increase the number of zeroes and see what happens
from hashlib import sha256
import os
 
arr = []
nb_of_trials = 100
for _ in range(0, 100):
  count = 0
  hash = "1"
  while(hash[0:4] != '0'*4):
    count += 1
    hash = sha256(os.urandom(32)).hexdigest()
  arr.append(count)
print(sum(arr)/100)
66033.0

Whenever we add a '0' in bits (not in hex), we increase the difficulty of finding such a hash by exactly 2. As we will see later bitcoin uses a variation of this to do mining! However, bitcoin does so while increasing the difficulty in a smoother way. You can read more about it here.

Bitcoin Mining (leader election/ lottery)¶

In [ ]:
#Looking at block 773610 again
import requests
from binascii import unhexlify


# getting the block hash of block number 773610
block_hash = requests.get('https://mempool.space/api/block-height/773610').text

# geting block metadata
response = requests.get('https://mempool.space/api/block/' + block_hash)

print(response.json())
{'id': '0000000000000000000456ef87595d6674db29d43010345de5f372445cf9b6b1', 'height': 773610, 'version': 536895488, 'timestamp': 1674686421, 'tx_count': 2581, 'size': 1496003, 'weight': 3993173, 'merkle_root': '644550e8b7946910572e18b9872342a56c3ad9e4691cf7b9be7f7c8e9e76acbb', 'previousblockhash': '00000000000000000004574d28ac29cd49708820c68dd09d8bd88ed8bf1a341b', 'mediantime': 1674683784, 'nonce': 885853051, 'bits': 386366690, 'difficulty': 37590453655497}

You can see that the id of the block starts with a lot of zeroes! Bitcoin uses the property of hashes to do what we call leader election. Every party in the bitcoin network can compete over the right to add a block to the blockchain. However, the block hash has to meet some difficulty level. The first party to find such a block, earns the right to become a leader and put the next block in the blockchain.

Difficulty Adjustment¶

  • If miners start finding blocks too fast (on average). The difficulty of finding a block increases.

  • If miners start finding blocks too slowly (on average). The difficulty of finding a block decreases.