Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save yangadam/3bec9dac46695990737b9334dc45db62 to your computer and use it in GitHub Desktop.
Save yangadam/3bec9dac46695990737b9334dc45db62 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Programming Blockchains Step-by-Step\n",
"\n",
"Contents\n",
"- (Crypto) Hash\n",
"- (Crypto) Block\n",
"- (Crypto) Block with Proof-of-Work\n",
"- Blockchain\n",
"- Blockchain Broken?\n",
"- Timestamping\n",
"- Mining, Mining, Mining\n",
"- Bitcoin, Bitcoin, Bitcoin\n",
"- (Crypto) Block with Transactions (Tx)\n",
"- References / Links\n",
"\n",
"Let’s build blockchains from scratch (zero) step by step."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## (Crypto) Hash\n",
"\n",
"Let’s start with crypto hashes\n",
"\n",
"Classic Bitcoin uses the SHA256 hash algorithm. Let’s try"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"33eedea60b0662c66c289ceba71863a864cf84b00e10002ca1069bf58f9362d5\""
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest'\n",
"\n",
"Digest::SHA256::hexdigest('Hello, Cryptos!')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Try some more"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"c4b5e2b9685062ecca5d0f6f6ba605b3f99eafed3a3729d2ae1ccaa2b440b1cc\""
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( 'Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' )"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"39459289c09c33a7b516bef926c1873c6ecd2e6db09218b065d7465b6736f801\""
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( 'Your Name Here' )"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"a7bbfc531b2ecf641b9abcd7ad8e50267e1c873e5a396d1919f504973090565a\""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( 'Data Data Data Data' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: The resulting hash is always 256-bit in size or 64 hex(adecimal) chars (0-9,a-f) in length even if the input is less than 256-bit or much bigger than 256-bit:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"c51023e2c874b6cf46cb0acef183ee1c05f14746636352d1b2cb9fc6aa5c3cee\""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( <<TXT )\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
"TXT"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"64"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"## use String#length\n",
"\n",
"Digest::SHA256.hexdigest( 'Hello, Cryptos!' ).length"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"64"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( 'Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' ).length"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: 1 hex char is 4-bits, 2 hex chars are 4x2=8 bits and 64 hex chars are 4x64=256 bits.\n",
"\n",
"Hexa(decimal) chart:\n",
"\n",
"| binary | hex (2^4=16) | decimal | binary | hex (2^4=16) | decimal |\n",
"|:------:|:----------------:|:-------:|:------:|:----------------:|:-------:|\n",
"| 0000 | **0** | 0 | 1000 | **8** | 8 |\n",
"| 0001 | **1** | 1 | 1001 | **9** | 9 |\n",
"| 0010 | **2** | 2 | 1010 | **a** | 10 |\n",
"| 0011 | **3** | 3 | 1011 | **b** | 11 |\n",
"| 0100 | **4** | 4 | 1100 | **c** | 12 |\n",
"| 0101 | **5** | 5 | 1101 | **d** | 13 |\n",
"| 0110 | **6** | 6 | 1110 | **e** | 14 |\n",
"| 0111 | **7** | 7 | 1111 | **f** | 15 |\n",
"\n",
"Let’s convert from hex (base 16) to decimal (integer) number (base 10)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"33eedea60b0662c66c289ceba71863a864cf84b00e10002ca1069bf58f9362d5\""
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hex = Digest::SHA256.hexdigest( 'Hello, Cryptos!' )"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"23490001543365037720284007500157053051505610714786813679598750288695740555989"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hex.to_i( 16 )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and convert to 256-bits (32-bytes) binary number (base 2) as a string:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"11001111101110110111101010011000001011000001100110001011000110011011000010100010011100111010111010011100011000011000111010100001100100110011111000010010110000000011100001000000000000001011001010000100000110100110111111010110001111100100110110001011010101\""
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hex.to_i( 16 ).to_s( 2 )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Trivia Quiz: What’s SHA256?\n",
"\n",
"- (A) Still Hacking Anyway\n",
"- (B) Secure Hash Algorithm\n",
"- (C) Sweet Home Austria\n",
"- (D) Super High Aperture\n",
"\n",
"A: SHA256 == Secure Hash Algorithms 256 Bits\n",
"\n",
"SHA256 is a (secure) hashing algorithm designed by the National Security Agency (NSA) of the United States of America (USA).\n",
"\n",
"Find out more @ [Secure Hash Algorithms (SHA) @ Wikipedia](https://en.wikipedia.org/wiki/Secure_Hash_Algorithms).\n",
"\n",
"A (secure) hash is also known as:\n",
"\n",
"- Digital (Crypto) Fingerprint == (Secure) Hash\n",
"- Digital (Crypto) Digest == (Secure) Hash\n",
"- Digital (Crypto) Checksum == (Secure) Hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## (Crypto) Block\n",
"\n",
"Let’s build blocks (secured) with crypto hashes. First let’s define a block class:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":initialize"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest' \n",
"require 'pp' ## pp = pretty print\n",
"\n",
"class Block\n",
" attr_reader :data\n",
" attr_reader :hash\n",
"\n",
" def initialize(data)\n",
" @data = data\n",
" @hash = Digest::SHA256.hexdigest( data )\n",
" end\n",
"end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And let’s mine (build) some blocks with crypto hashes:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"#<Block:0x00007fee1cb872b8\n",
" @data=\"Hello, Cryptos!\",\n",
" @hash=\"33eedea60b0662c66c289ceba71863a864cf84b00e10002ca1069bf58f9362d5\">\n",
"#<Block:0x00007fee1cb5e278\n",
" @data=\"Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!\",\n",
" @hash=\"c4b5e2b9685062ecca5d0f6f6ba605b3f99eafed3a3729d2ae1ccaa2b440b1cc\">\n",
"#<Block:0x00007fee1d082b78\n",
" @data=\"Your Name Here\",\n",
" @hash=\"39459289c09c33a7b516bef926c1873c6ecd2e6db09218b065d7465b6736f801\">\n",
"#<Block:0x00007fee2c9873b8\n",
" @data=\"Data Data Data Data\",\n",
" @hash=\"a7bbfc531b2ecf641b9abcd7ad8e50267e1c873e5a396d1919f504973090565a\">\n"
]
},
{
"data": {
"text/plain": [
"#<Block:0x00007fee2c9873b8 @data=\"Data Data Data Data\", @hash=\"a7bbfc531b2ecf641b9abcd7ad8e50267e1c873e5a396d1919f504973090565a\">"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pp Block.new( 'Hello, Cryptos!' )\n",
"pp Block.new( 'Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' )\n",
"pp Block.new( 'Your Name Here' )\n",
"pp Block.new( 'Data Data Data Data' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: All the hashes (checksums/digests/fingerprints) are the same as above! Same input e.g. `'Hello, Cryptos!'`, same hash e.g. `33eedea60b0662c66c289ceba71863a864cf84b00e10002ca1069bf58f9362d5`, same length e.g. 64 hex chars!\n",
"\n",
"And the biggie:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"#<Block:0x00007fee0d03ee60\n",
" @data=\n",
" \" Data Data Data Data Data Data\\n\" +\n",
" \" Data Data Data Data Data Data\\n\" +\n",
" \" Data Data Data Data Data Data\\n\" +\n",
" \" Data Data Data Data Data Data\\n\" +\n",
" \" Data Data Data Data Data Data\\n\",\n",
" @hash=\"c51023e2c874b6cf46cb0acef183ee1c05f14746636352d1b2cb9fc6aa5c3cee\">\n"
]
},
{
"data": {
"text/plain": [
"#<Block:0x00007fee0d03ee60 @data=\" Data Data Data Data Data Data\\n Data Data Data Data Data Data\\n Data Data Data Data Data Data\\n Data Data Data Data Data Data\\n Data Data Data Data Data Data\\n\", @hash=\"c51023e2c874b6cf46cb0acef183ee1c05f14746636352d1b2cb9fc6aa5c3cee\">"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pp Block.new( <<TXT )\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
" Data Data Data Data Data Data\n",
"TXT"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## (Crypto) Block with Proof-of-Work\n",
"\n",
"Let’s add a proof-of-work to the block and hash. and let’s start mining to find the nonce (=Number used ONCE) and let’s start with the “hard-coded” difficulty of two leading zeros ‘00’.\n",
"\n",
"In classic bitcoin you have to compute a hash that starts with leading zeros (`00`). The more leading zeros the harder (more difficult) to compute. Let’s keep it easy to compute and let’s start with two leading zeros (`00`), that is, 16^2 = 256 possibilities (^1,2). Three leading zeros (`000`) would be 16^3 = 4 096 possibilities and four zeros (`0000`) would be 16^4 = 65 536 and so on.\n",
"\n",
"(1): 16 possibilities because it’s a hex or hexadecimal or base 16 number, that is, `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` `a` (10) `b` (11) `c` (12) `d` (13) `e` (14) `f` (15).\n",
"\n",
"(2): A random secure hash algorithm needs on average 256 tries (might be lets say 305 tries, for example, because it’s NOT a perfect statistic distribution of possibilities)."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":compute_hash_with_proof_of_work"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest' \n",
"require 'pp' ## pp = pretty print\n",
"\n",
"class Block\n",
" attr_reader :data\n",
" attr_reader :hash\n",
" attr_reader :nonce # number used once - lucky (mining) lottery number\n",
"\n",
" def initialize(data)\n",
" @data = data\n",
" @nonce, @hash = compute_hash_with_proof_of_work\n",
" end\n",
"\n",
" def compute_hash_with_proof_of_work( difficulty='00' )\n",
" nonce = 0\n",
" loop do\n",
" hash = Digest::SHA256.hexdigest( \"#{nonce}#{data}\" )\n",
" if hash.start_with?( difficulty )\n",
" return [nonce,hash] ## bingo! proof of work if hash starts with leading zeros (00)\n",
" else\n",
" nonce += 1 ## keep trying (and trying and trying)\n",
" end\n",
" end # loop\n",
" end # method compute_hash_with_proof_of_work\n",
"\n",
"end # class Block"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And let’s mine (build) some blocks with crypto hashes with a “hard-coded” difficulty of two leading zeros ‘00’:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"#<Block:0x00007fee1cc86150\n",
" @data=\"Hello, Cryptos!\",\n",
" @hash=\"00ecb8b247998f9ddd15d2a5693777ee0041d138fa3bc5c1f6ccc12ec1cfece4\",\n",
" @nonce=143>\n",
"#<Block:0x00007fee1cc3ecd8\n",
" @data=\"Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!\",\n",
" @hash=\"0014406a868d202e2c6c3896af997e189daafc9df1878f9824cba2050fda199f\",\n",
" @nonce=59>\n",
"#<Block:0x00007fee1cc04420\n",
" @data=\"Your Name Here\",\n",
" @hash=\"0012c3a90e58c9569ef0c036e6220c86c7c253ac94c0eb0064bf98df59acdfad\",\n",
" @nonce=57>\n",
"#<Block:0x00007fee1cbc4e88\n",
" @data=\"Data Data Data Data\",\n",
" @hash=\"00e2da510b97434713d63234f3ba2d816c8d52f29f9ffd267423c39d9ced7a70\",\n",
" @nonce=73>\n"
]
},
{
"data": {
"text/plain": [
"#<Block:0x00007fee1cbc4e88 @data=\"Data Data Data Data\", @hash=\"00e2da510b97434713d63234f3ba2d816c8d52f29f9ffd267423c39d9ced7a70\", @nonce=73>"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pp Block.new( 'Hello, Cryptos!' )\n",
"pp Block.new( 'Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' )\n",
"pp Block.new( 'Your Name Here' )\n",
"pp Block.new( 'Data Data Data Data' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"See the difference? Now all hashes start with ‘00’ e.g.\n",
"\n",
"| **Block** | **Hash with Proof-of-Work** |\n",
"|-----------|--------------------------------------------------------------------|\n",
"| #1 | `00ecb8b247998f9ddd15d2a5693777ee0041d138fa3bc5c1f6ccc12ec1cfece4` |\n",
"| #2 | `0014406a868d202e2c6c3896af997e189daafc9df1878f9824cba2050fda199f` |\n",
"| #3 | `0012c3a90e58c9569ef0c036e6220c86c7c253ac94c0eb0064bf98df59acdfad` |\n",
"| #4 | `00e2da510b97434713d63234f3ba2d816c8d52f29f9ffd267423c39d9ced7a70` |\n",
"\n",
"That’s the magic of the proof-of-work. You have done the work, that is, found the lucky lottery number used once (nonce) and proof is the hash with the matching difficulty, that is, the two leading zeros `00`.\n",
"\n",
"In the first block the `compute_hash_with_proof_of_work` tried 143 nonces until finding the matching lucky number. The stat(istic)s for all blocks are:\n",
"\n",
"| **Block** | **Loops / Number of Hash calculations** |\n",
"|-----------|-----------------------------------------|\n",
"| #1 | 143 |\n",
"| #2 | 59 |\n",
"| #3 | 57 |\n",
"| #4 | 73 |\n",
"\n",
"The lucky nonce for block #1 is 143:\n",
"\n",
"Try:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"8954dec596f0baa0cb6b8cc9f5837037d4380e28338ccccdf5f00658010caf07\""
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '0Hello, Cryptos!' ) # keep trying..."
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"831c988d0745d1f02cf790c3b3d9c9f610ddb7d36d5b96c7b3413ccd1b6f46e1\""
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '1Hello, Cryptos!' ) # keep trying..."
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"ac6ccb11092f867dc5f10daaebcd7938f90d1627a7e277b940cdd2e4881ea712\""
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '2Hello, Cryptos!' ) # keep trying..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now try:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"00ecb8b247998f9ddd15d2a5693777ee0041d138fa3bc5c1f6ccc12ec1cfece4\""
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '143Hello, Cryptos!' ) # bingo!!!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s try a difficulty of four leading zeros ‘0000’.\n",
"\n",
"Note: One hex char is 4-bits, thus, ‘0’ in hex (base16) is ‘0000’ in binary (base2) and, thus, ‘00’ in hex (base16) is 2x4=8 zeros in binary (base2) e.g. ‘0000 0000’ and, thus, ‘0000’ in hex (base16) is 4x4=16 zeros in binary (base) e.g. ‘0000 0000 0000 0000’\n",
"\n",
"Change the “hard-coded” difficulty from `00` to `0000` e.g.\n",
"\n",
"```Ruby\n",
"def compute_hash_with_proof_of_work( difficulty='0000' )\n",
"...\n",
"end\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":compute_hash_with_proof_of_work"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"class Block\n",
" attr_reader :data\n",
" attr_reader :hash\n",
" attr_reader :nonce # number used once - lucky (mining) lottery number\n",
"\n",
" def initialize(data)\n",
" @data = data\n",
" @nonce, @hash = compute_hash_with_proof_of_work\n",
" end\n",
"\n",
" def compute_hash_with_proof_of_work( difficulty='0000' )\n",
" nonce = 0\n",
" loop do\n",
" hash = Digest::SHA256.hexdigest( \"#{nonce}#{data}\" )\n",
" if hash.start_with?( difficulty )\n",
" return [nonce,hash] ## bingo! proof of work if hash starts with leading zeros (00)\n",
" else\n",
" nonce += 1 ## keep trying (and trying and trying)\n",
" end\n",
" end # loop\n",
" end # method compute_hash_with_proof_of_work\n",
"\n",
"end # class Block"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and rerun or let’s mine blocks again:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"#<Block:0x00007fee1ca31d50\n",
" @data=\"Hello, Cryptos!\",\n",
" @hash=\"0000a1ee5cb18c8d9fff5262b6dcb1bc95d54a331713e247f699f158f2022143\",\n",
" @nonce=26762>\n",
"#<Block:0x00007fee1c8e7af8\n",
" @data=\"Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!\",\n",
" @hash=\"0000b27eeebe56b2daafeb935454d0e3f423fc7f5ac7a99e952f9b80475ef6c3\",\n",
" @nonce=68419>\n",
"#<Block:0x00007fee1cab33c8\n",
" @data=\"Your Name Here\",\n",
" @hash=\"00000e59a4a7fb35d0d03def6ce31f503c208e6c291dcc9a217a7278ad1b95ce\",\n",
" @nonce=23416>\n",
"#<Block:0x00007fee1d099620\n",
" @data=\"Data Data Data Data\",\n",
" @hash=\"00000e3cff496c5afc18645dba31ae9ba5c6077e5a5d980d8512e4581e7d61ec\",\n",
" @nonce=15353>\n"
]
},
{
"data": {
"text/plain": [
"#<Block:0x00007fee1d099620 @data=\"Data Data Data Data\", @hash=\"00000e3cff496c5afc18645dba31ae9ba5c6077e5a5d980d8512e4581e7d61ec\", @nonce=15353>"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pp Block.new( 'Hello, Cryptos!' )\n",
"pp Block.new( 'Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' )\n",
"pp Block.new( 'Your Name Here' )\n",
"pp Block.new( 'Data Data Data Data' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"See the difference? Now all hashes start with ‘0000’ e.g.\n",
"\n",
"| **Block** | **Hash with Proof-of-Work** |\n",
"|-----------|--------------------------------------------------------------------|\n",
"| #1 | `0000a1ee5cb18c8d9fff5262b6dcb1bc95d54a331713e247f699f158f2022143` |\n",
"| #2 | `0000b27eeebe56b2daafeb935454d0e3f423fc7f5ac7a99e952f9b80475ef6c3` |\n",
"| #3 | `00000e59a4a7fb35d0d03def6ce31f503c208e6c291dcc9a217a7278ad1b95ce` |\n",
"| #4 | `00000e3cff496c5afc18645dba31ae9ba5c6077e5a5d980d8512e4581e7d61ec` |\n",
"\n",
"The nonce hash calculation stat(istic)s for all blocks are:\n",
"\n",
"| **Block** | **Loops / Number of Hash calculations** |\n",
"|-----------|-----------------------------------------|\n",
"| #1 | 26 762 |\n",
"| #2 | 68 419 |\n",
"| #3 | 23 416 |\n",
"| #4 | 15 353 |\n",
"\n",
"In the first block the `compute_hash_with_proof_of_work` now tried 26 762 nonces (compare 143 nonces with difficulty ‘00’) until finding the matching lucky number.\n",
"\n",
"Now try it with the latest difficulty in bitcoin, that is, with 24 leading zeros - just kidding. You will need trillions of mega zillions of hash calculations and all minining computers in the world will need all together about ten (10) minutes to find the lucky number used once (nonce) and mine the next block.\n",
"\n",
"Let’s retry the ‘0000’ difficulty hash calculations “by hand”:"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"0000a1ee5cb18c8d9fff5262b6dcb1bc95d54a331713e247f699f158f2022143\""
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '26762Hello, Cryptos!' )"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"0000b27eeebe56b2daafeb935454d0e3f423fc7f5ac7a99e952f9b80475ef6c3\""
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '68419Hello, Cryptos! - Hello, Cryptos! - Hello, Cryptos!' )"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"00000e59a4a7fb35d0d03def6ce31f503c208e6c291dcc9a217a7278ad1b95ce\""
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '23416Your Name Here' )"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"00000e3cff496c5afc18645dba31ae9ba5c6077e5a5d980d8512e4581e7d61ec\""
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Digest::SHA256.hexdigest( '15353Data Data Data Data' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Blockchain\n",
"\n",
"Blockchain! Blockchain! Blockchain!\n",
"\n",
"![](http://yukimotopress.github.io/i/fake-dilbert-blockchain.png)\n",
"\n",
"Let’s link the (crypto) blocks together into a chain of blocks, that is, blockchain, to revolutionize the world one block at a time.\n",
"\n",
"Trivia Quiz: What’s the unique id(entifier) of a block?\n",
"\n",
"- (A) (Secure) Hash\n",
"- (B) Block Hash\n",
"- (C) Digital (Crypto) Digest\n",
"\n",
"A: All of the above :-). (Secure) hash == block hash == digital (crypto) digest.\n",
"\n",
"Thus, add the (secure) hash of the prev(ious) block to the new block and the hash calculation e.g.:\n",
"\n",
"```Ruby\n",
"Digest::SHA256.hexdigest( \"#{nonce}#{prev}#{data}\" )\n",
"```\n",
"\n",
"Bingo! Blockchain! Blockchain! Blockchain! All together now:"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":compute_hash_with_proof_of_work"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest'\n",
"require 'pp' ## pp = pretty print\n",
"\n",
"\n",
"class Block\n",
" attr_reader :data\n",
" attr_reader :prev # prev(ious) (block) hash\n",
" attr_reader :hash\n",
" attr_reader :nonce # number used once - lucky (mining) lottery number\n",
"\n",
" def initialize(data, prev)\n",
" @data = data\n",
" @prev = prev\n",
" @nonce, @hash = compute_hash_with_proof_of_work\n",
" end\n",
"\n",
" def compute_hash_with_proof_of_work( difficulty='0000' )\n",
" nonce = 0\n",
" loop do\n",
" hash = Digest::SHA256.hexdigest( \"#{nonce}#{prev}#{data}\" )\n",
" if hash.start_with?( difficulty )\n",
" return [nonce,hash] ## bingo! proof of work if hash starts with leading zeros (00)\n",
" else\n",
" nonce += 1 ## keep trying (and trying and trying)\n",
" end\n",
" end # loop\n",
" end # method compute_hash_with_proof_of_work\n",
"\n",
"end # class Block"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: For the first block, that is, the genesis block, there’s no prev(ious) block. What (block) hash to use? Let’s follow the classic bitcoin convention and lets use all zeros eg. `0000000000000000000000000000000000000000000000000000000000000000`.\n",
"\n",
"Genesis. A new blockchain is born!"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1cc0fe38 @data=\"Hello, Cryptos!\", @hash=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\", @nonce=24287, @prev=\"0000000000000000000000000000000000000000000000000000000000000000\">"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0 = Block.new( 'Hello, Cryptos!', '0000000000000000000000000000000000000000000000000000000000000000' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s mine (build) some more blocks linked (chained) together with crypto hashes:"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1d01b720 @data=\"Hello, Cryptos! - Hello, Cryptos!\", @hash=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\", @nonce=191453, @prev=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\">"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1 = Block.new( 'Hello, Cryptos! - Hello, Cryptos!',\n",
" '000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af' )\n",
"# -or-\n",
"# b1 = Block.new( 'Hello, Cryptos! - Hello, Cryptos!', b0.hash )"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee0c84e7f0 @data=\"Your Name Here\", @hash=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\", @nonce=109213, @prev=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\">"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2 = Block.new( 'Your Name Here', b1.hash )"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1d082c90 @data=\"Data Data Data Data\", @hash=\"000000c652265dcf44f0b18911435100f4677bdc468f8f1dd85910d581b3542d\", @nonce=129257, @prev=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\">"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3 = Block.new( 'Data Data Data Data', b2.hash )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s store all blocks together (in an array):"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[#<Block:0x00007fee1cc0fe38\n",
" @data=\"Hello, Cryptos!\",\n",
" @hash=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\",\n",
" @nonce=24287,\n",
" @prev=\"0000000000000000000000000000000000000000000000000000000000000000\">,\n",
" #<Block:0x00007fee1d01b720\n",
" @data=\"Hello, Cryptos! - Hello, Cryptos!\",\n",
" @hash=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\",\n",
" @nonce=191453,\n",
" @prev=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\">,\n",
" #<Block:0x00007fee0c84e7f0\n",
" @data=\"Your Name Here\",\n",
" @hash=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\",\n",
" @nonce=109213,\n",
" @prev=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\">,\n",
" #<Block:0x00007fee1d082c90\n",
" @data=\"Data Data Data Data\",\n",
" @hash=\"000000c652265dcf44f0b18911435100f4677bdc468f8f1dd85910d581b3542d\",\n",
" @nonce=129257,\n",
" @prev=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\">]\n"
]
},
{
"data": {
"text/plain": [
"[#<Block:0x00007fee1cc0fe38 @data=\"Hello, Cryptos!\", @hash=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\", @nonce=24287, @prev=\"0000000000000000000000000000000000000000000000000000000000000000\">, #<Block:0x00007fee1d01b720 @data=\"Hello, Cryptos! - Hello, Cryptos!\", @hash=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\", @nonce=191453, @prev=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\">, #<Block:0x00007fee0c84e7f0 @data=\"Your Name Here\", @hash=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\", @nonce=109213, @prev=\"00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f\">, #<Block:0x00007fee1d082c90 @data=\"Data Data Data Data\", @hash=\"000000c652265dcf44f0b18911435100f4677bdc468f8f1dd85910d581b3542d\", @nonce=129257, @prev=\"0000d85423bc8d3ccda0e83ddd6e7e9d6a30f393b73705409b481be57eeaad37\">]"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"blockchain = [b0, b1, b2, b3]\n",
"\n",
"pp blockchain ## pretty print (pp) blockchain"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: If you want to change the data in block b1, for examples, you have to change all the blocks on top (that is, b2 and b3) too and update their hashes too! With every block added breaking the chain gets harder and harder and harder (not to say practically impossible!). That’s the magic of the blockchain - it’s (almost) unbreakable if you have many shared / cloned copies. The data gets more secure with every block added (on top), …"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Blockchain Broken?\n",
"\n",
"How do you know if anyone changed (broke) the (almost) unbreakable blockchain and changed some data in blocks? Let’s run tests checking up on the chained / linked (crypto) hashes:"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0.prev == '0000000000000000000000000000000000000000000000000000000000000000'"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.prev == b0.hash"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.prev == b1.hash"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.prev == b2.hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All true, true, true, true. All in order? What if someone changes the data but keeps the original (now fake non-matching) hash? Let’s run more tests checking up on the (crypto) hashes by recalculating (using nonce+prev+data) right on the spot plus checking up on the proof-of-work difficulty (hash must start with 0000):"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":sha256"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"## shortcut convenience helper\n",
"def sha256( data )\n",
" Digest::SHA256.hexdigest( data )\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0.hash == sha256( \"#{b0.nonce}#{b0.prev}#{b0.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.hash == sha256( \"#{b1.nonce}#{b1.prev}#{b1.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.hash == sha256( \"#{b2.nonce}#{b2.prev}#{b2.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.hash == sha256( \"#{b3.nonce}#{b3.prev}#{b3.data}\" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 49,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0.hash.start_with?( '0000' )"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.hash.start_with?( '0000' )"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.hash.start_with?( '0000' )"
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 52,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.hash.start_with?( '0000' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All true, true, true, true, true, true, true, true. All in order? Yes. The blockchain is (almost) unbreakable.\n",
"\n",
"Let’s try to break the unbreakable. Let’s change the block b1 from `'Hello, Cryptos!'` to `'Hello, Koruptos!'`:"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1cc76fc0 @data=\"Hello, Koruptos! - Hello, Koruptos!\", @hash=\"00000c915e240a2b386fc86ef6170261a19292b9fdebebce049c621da1ab7e8f\", @nonce=27889, @prev=\"000047954e7d5877b6dea6915c48e84579b5c64fb58d5b6488863c241f1ce2af\">"
]
},
"execution_count": 53,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1 = Block.new( 'Hello, Koruptos! - Hello, Koruptos!', b0.hash )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now if you check:"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0.prev == '0000000000000000000000000000000000000000000000000000000000000000'"
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 55,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.prev == b0.hash"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"false"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.prev == b1.hash"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.prev == b2.hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Fail! False! No longer all true. The chain is now broken. The chained / linked (crypto) hashes\n",
"\n",
"- `b1.hash` => `00002acb41e00fb252b8fedeed7d4a629dafb28517bcf6235b90367ee6f63a7f`\n",
"- `b2.prev` => `00000c915e240a2b386fc86ef6170261a19292b9fdebebce049c621da1ab7e8f`\n",
"\n",
"do no longer match. The only way to get the chained / linked (crypto) hashes back in order to true, true, true, true is to rebuild (remine) all blocks on top."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Timestamping\n",
"\n",
"How can you make the blockchain even more secure? Link it to the real world! Let’s add a timestamp:"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2020-10-29 09:18:19.87816 +0800"
]
},
"execution_count": 59,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.now"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"or in Epoch time (that is, seconds since January 1st, 1970)"
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1603934322"
]
},
"execution_count": 60,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.now.to_i"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note: You can use `Time.at` to convert Epoch time back to the standard “classic” format:"
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2020-10-29 09:18:42 +0800"
]
},
"execution_count": 61,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.at( 1603934322 )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now the blockchain must always move forward, that is, you can only add a new block if the timestamp is bigger / younger than the previous block’s timestamp.\n",
"\n",
"Unbreakable. Unbreakable. Unbreakable. What else?\n",
"\n",
"Let’s add the proof-of-work difficulty (e.g. ‘00’, ‘000’, ‘0000’ etc.) to the hash to make the difficulty unbreakable / unchangeable too!\n",
"\n",
"Last but not least let’s drop the “pre-calculated” hash attribute and let’s always calculate the hash on demand e.g.:\n",
"\n",
"```Ruby\n",
"def hash\n",
" Digest::SHA256.hexdigest( \"#{nonce}#{time}#{difficulty}#{prev}#{data}\" )\n",
"end\n",
"```\n",
"\n",
"Remember: Calculating the block’s (crypto) hash is fast, fast, fast. What take’s time depending on the proof-of-work difficulty is finding the nonce, that is, the lucky number used once.\n",
"\n",
"All together now. Resulting in:"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":compute_hash_with_proof_of_work"
]
},
"execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest'\n",
"require 'pp' ## pp = pretty print\n",
"\n",
"\n",
"class Block\n",
" attr_reader :data\n",
" attr_reader :prev\n",
" attr_reader :difficulty\n",
" attr_reader :time\n",
" attr_reader :nonce # number used once - lucky (mining) lottery number\n",
"\n",
" def hash\n",
" Digest::SHA256.hexdigest( \"#{nonce}#{time}#{difficulty}#{prev}#{data}\" )\n",
" end\n",
"\n",
" def initialize(data, prev, difficulty: '0000' )\n",
" @data = data\n",
" @prev = prev\n",
" @difficulty = difficulty\n",
" @nonce, @time = compute_hash_with_proof_of_work( difficulty )\n",
" end\n",
"\n",
" def compute_hash_with_proof_of_work( difficulty='00' )\n",
" nonce = 0\n",
" time = Time.now.to_i\n",
" loop do\n",
" hash = Digest::SHA256.hexdigest( \"#{nonce}#{time}#{difficulty}#{prev}#{data}\" )\n",
" if hash.start_with?( difficulty )\n",
" return [nonce,time] ## bingo! proof of work if hash starts with leading zeros (00)\n",
" else\n",
" nonce += 1 ## keep trying (and trying and trying)\n",
" end\n",
" end # loop\n",
" end # method compute_hash_with_proof_of_work\n",
"\n",
"end # class Block"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Proof of the pudding. Let’s build a new (more secure) blockchain from scratch (zero). Genesis!"
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee2c8ffdc8 @data=\"Hello, Cryptos!\", @nonce=97123, @prev=\"0000000000000000000000000000000000000000000000000000000000000000\", @difficulty=\"0000\", @time=1603934544>"
]
},
"execution_count": 63,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0 = Block.new( 'Hello, Cryptos!', '0000000000000000000000000000000000000000000000000000000000000000' )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s mine (build) some more blocks linked (chained) together with crypto hashes:"
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1d083b18 @data=\"Hello, Cryptos! - Hello, Cryptos!\", @nonce=9727, @prev=\"000010d821134236f3dbd44b4ab9e36b05018246cd34c1091a17d5b3bbf962cd\", @difficulty=\"0000\", @time=1603934570>"
]
},
"execution_count": 64,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1 = Block.new( 'Hello, Cryptos! - Hello, Cryptos!', b0.hash )"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1cbae520 @data=\"Your Name Here\", @nonce=5951, @prev=\"00007e59e10db76f9edc1628854065ca3e2b9b21d3c313a7aed9054ae314b9d7\", @difficulty=\"0000\", @time=1603934579>"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2 = Block.new( 'Your Name Here', b1.hash )"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"#<Block:0x00007fee1cb4f160 @data=\"Data Data Data Data\", @nonce=92769, @prev=\"00005562ac6ae9da189e19a9894314269732909f48390a67a51e48a3a1fca31a\", @difficulty=\"0000\", @time=1603934586>"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3 = Block.new( 'Data Data Data Data', b2.hash )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Blockchain broken? Let’s run all the tests checking up on the chained / linked (crypto) hashes, timestamps, proof-of-work difficulty and more:"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":sha256"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"## shortcut convenience helper\n",
"def sha256( data )\n",
" Digest::SHA256.hexdigest( data )\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0.hash == sha256( \"#{b0.nonce}#{b0.time}#{b0.difficulty}#{b0.prev}#{b0.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 69,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.hash == sha256( \"#{b1.nonce}#{b1.time}#{b1.difficulty}#{b1.prev}#{b1.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.hash == sha256( \"#{b2.nonce}#{b2.time}#{b2.difficulty}#{b2.prev}#{b2.data}\" )"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.hash == sha256( \"#{b3.nonce}#{b3.time}#{b3.difficulty}#{b3.prev}#{b3.data}\" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# check proof-of-work difficulty (e.g. '0000')\n",
"b0.hash.start_with?( b0.difficulty )"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.hash.start_with?( b1.difficulty )"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.hash.start_with?( b2.difficulty )"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 76,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.hash.start_with?( b3.difficulty )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"## check chained / linked hashes\n",
"b0.prev == '0000000000000000000000000000000000000000000000000000000000000000'"
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b1.prev == b0.hash"
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.prev == b1.hash"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.prev == b2.hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
" "
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# check time moving forward; timestamp always greater/bigger/younger\n",
"b1.time > b0.time"
]
},
{
"cell_type": "code",
"execution_count": 82,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 82,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b2.time > b1.time"
]
},
{
"cell_type": "code",
"execution_count": 83,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 83,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b3.time > b2.time"
]
},
{
"cell_type": "code",
"execution_count": 84,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"true"
]
},
"execution_count": 84,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.now.to_i > b3.time ## back to the future (not yet) possible :-)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All true, true, true, true, true, true, true, true. All in order? Yes. The blockchain is (almost) unbreakable."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mining, Mining, Mining\n",
"\n",
"What’s your hash rate? Let’s find out. Let’s use a “stand-alone” version of the by now “classic” `compute_hash_with_proof_of_work` function:"
]
},
{
"cell_type": "code",
"execution_count": 85,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":compute_hash_with_proof_of_work"
]
},
"execution_count": 85,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest'\n",
"\n",
"def compute_hash_with_proof_of_work( data, difficulty='00' )\n",
" nonce = 0\n",
" loop do\n",
" hash = Digest::SHA256.hexdigest( \"#{nonce}#{data}\" )\n",
" if hash.start_with?( difficulty )\n",
" return [nonce,hash] ## bingo! proof of work if hash starts with leading zeros (00)\n",
" else\n",
" nonce += 1 ## keep trying (and trying and trying)\n",
" end\n",
" end # loop\n",
"end # method compute_hash_with_proof_of_work"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s try (run) benchmarks for the difficulty from `0` (4 bits) to `0000000` (28 bits). Remember: `0` in hex (base16, 2^4 bits) equals `0000` in binary (base2), thus, `0000000` in hex (base16) equals `0` x 4 x 7 = 28 zero bits in binary (base2). Example:"
]
},
{
"cell_type": "code",
"execution_count": 86,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"difficulty: 0 (4 bits)\n",
"difficulty: 00 (8 bits)\n",
"difficulty: 000 (12 bits)\n",
"difficulty: 0000 (16 bits)\n",
"difficulty: 00000 (20 bits)\n",
"difficulty: 000000 (24 bits)\n",
"difficulty: 0000000 (28 bits)\n"
]
},
{
"data": {
"text/plain": [
"1..7"
]
},
"execution_count": 86,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(1..7).each do |factor|\n",
" difficulty = '0' * factor\n",
" puts \"difficulty: #{difficulty} (#{difficulty.length*4} bits)\"\n",
"end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let’s add the hash proof-of-work hash computing machinery and re(run):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Difficulty: 0 (4 bits)\n",
"Starting search...\n",
"Elapsed Time: 0.0001 seconds, Hashes Calculated: 56\n",
"Hash Rate: 400000 hashes per second\n",
"\n",
"Difficulty: 00 (8 bits)\n",
"Starting search...\n",
"Elapsed Time: 0.0003 seconds, Hashes Calculated: 143\n",
"Hash Rate: 430722 hashes per second\n",
"\n",
"Difficulty: 000 (12 bits)\n",
"Starting search...\n",
"Elapsed Time: 0.0079 seconds, Hashes Calculated: 3834\n",
"Hash Rate: 485562 hashes per second\n",
"\n",
"Difficulty: 0000 (16 bits)\n",
"Starting search...\n",
"Elapsed Time: 0.0602 seconds, Hashes Calculated: 26762\n",
"Hash Rate: 444721 hashes per second\n",
"\n",
"Difficulty: 00000 (20 bits)\n",
"Starting search...\n",
"Elapsed Time: 0.2527 seconds, Hashes Calculated: 118592\n",
"Hash Rate: 469301 hashes per second\n",
"\n",
"Difficulty: 000000 (24 bits)\n",
"Starting search...\n",
"Elapsed Time: 40.2943 seconds, Hashes Calculated: 21554046\n",
"Hash Rate: 534916 hashes per second\n",
"\n",
"Difficulty: 0000000 (28 bits)\n",
"Starting search...\n"
]
},
{
"ename": "Interrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[31mInterrupt\u001b[0m: ",
"(pry):262:in `digest'",
"(pry):262:in `hexdigest'",
"(pry):262:in `block in compute_hash_with_proof_of_work'",
"(pry):261:in `loop'",
"(pry):261:in `compute_hash_with_proof_of_work'",
"(pry):298:in `block in <main>'",
"(pry):292:in `each'",
"(pry):292:in `<main>'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:290:in `evaluate_ruby'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:659:in `handle_line'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:261:in `block (2 levels) in eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `catch'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:260:in `block in eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `catch'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/pry-0.13.1/lib/pry/pry_instance.rb:259:in `eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/backend.rb:66:in `eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/backend.rb:12:in `eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/kernel.rb:90:in `execute_request'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/kernel.rb:49:in `dispatch'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/kernel.rb:38:in `run'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/command.rb:110:in `run_kernel'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/lib/iruby/command.rb:40:in `run'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/gems/iruby-0.4.0/bin/iruby:5:in `<top (required)>'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/bin/iruby:23:in `load'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/bin/iruby:23:in `<main>'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/bin/ruby_executable_hooks:24:in `eval'",
"/Users/adam.yang/.rvm/gems/ruby-2.7.0/bin/ruby_executable_hooks:24:in `<main>'"
]
}
],
"source": [
"(1..7).each do |factor|\n",
" difficulty = '0' * factor\n",
" puts \"Difficulty: #{difficulty} (#{difficulty.length*4} bits)\"\n",
"\n",
" puts \"Starting search...\"\n",
" t1 = Time.now\n",
" nonce, hash = compute_hash_with_proof_of_work( 'Hello, Cryptos!', difficulty )\n",
" t2 = Time.now\n",
"\n",
" delta = t2 - t1\n",
" puts \"Elapsed Time: %.4f seconds, Hashes Calculated: %d\" % [delta,nonce]\n",
"\n",
" if delta > 0\n",
" hashrate = Float( nonce / delta )\n",
" puts \"Hash Rate: %d hashes per second\" % hashrate\n",
" end\n",
" puts\n",
"end\n",
"# Resulting on a “low-end” home computer:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To sum up the hash rate is about 100 000 hashes per second on a “low-end” home computer. What’s your hash rate? Run the benchmark on your machinery!\n",
"\n",
"The search for the 28 bits difficulty proof-of-work hash is still running… expected to find the lucky number in the next hours…\n",
"\n",
"Trivia Quiz: What’s the Hash Rate of the Bitcoin Classic Network?\n",
"\n",
"A: About 25 million trillions of hashes per second (in March 2018)\n",
"\n",
"Estimated number of tera hashes per second (trillions of hashes per second) the Bitcoin network is performing.\n",
"\n",
"![](http://yukimotopress.github.io/i/bitcoin-hashrate.png)\n",
"\n",
"(Source: [blockchain.info](blockchain.info))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Bitcoin, Bitcoin, Bitcoin\n",
"\n",
"Let’s calculate the classic bitcoin (crypto) block hash from scratch (zero). Let’s start with the genesis block, that is block #0 with the unique block hash id 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f.\n",
"\n",
"Note: You can search and browse bitcoin blocks using (online) block explorers. Example:\n",
"\n",
"- [blockchain.info/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f](blockchain.info/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f)\n",
"- [blockexplorer.com/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f](blockexplorer.com/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f)\n",
"- and others.\n",
"\n",
"The classic bitcoin (crypto) block hash gets calculated from the 80-byte block header:\n",
"\n",
"| Field | Size (Bytes) | Comments |\n",
"|------------|--------------|-------------------------------------------------------------|\n",
"| version | 4 byte | Block version number |\n",
"| prev | 32 byte | 256-bit hash of the previous block header |\n",
"| merkleroot | 32 byte | 256-bit hash of all transactions in the block |\n",
"| time | 4 bytes | Current timestamp as seconds since 1970-01-01 00:00 |\n",
"| bits | 4 bytes | Current difficulty target in compact binary format |\n",
"| nonce | 4 bytes | 32-bit number of the (mined) lucky lottery number used once |\n",
"\n",
"Note: 32 byte x 8 bit = 256 bit\n",
"\n",
"Using the data for the genesis block the setup is:"
]
},
{
"cell_type": "code",
"execution_count": 91,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2083236893"
]
},
"execution_count": 91,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"version = 1\n",
"prev = '0000000000000000000000000000000000000000000000000000000000000000'\n",
"merkleroot = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'\n",
"time = 1231006505\n",
"bits = '1d00ffff'\n",
"nonce = 2083236893"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Remember: To convert from Epoch time (seconds since January 1st, 1970) to classic time use:"
]
},
{
"cell_type": "code",
"execution_count": 90,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2009-01-03 18:15:05 UTC"
]
},
"execution_count": 90,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.at( 1231006505 ).utc"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Yes, the bitcoin classic started on January 3rd, 2009 at 18h 15m 5s (2009-01-03 18:15:05). Or in the other direction use:"
]
},
{
"cell_type": "code",
"execution_count": 92,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1231006505"
]
},
"execution_count": 92,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Time.utc( 2009, 1, 3, 18, 15, 5 ).to_i"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What’s UTC? Coordinated Universal Time is the “standard” world time. Note: UTC does NOT observe daylight saving time.\n",
"\n",
"### Binary Bytes - Little End(ian) vs Big End(ian)\n",
"\n",
"In theory calculating the block hash is as easy as:\n",
"\n",
"```Ruby\n",
"## pseudo-code\n",
"header = \"...\" # 80 bytes (binary)\n",
"d1 = sha256( header )\n",
"d2 = sha256( d1 )\n",
"d2.to_s # convert 32-byte (256-bit) binary to hex string\n",
"#=> \"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\"\n",
"```\n",
"Note: Classic bitcoin uses a double hash, that is, for even higher security the hash gets hashed twice with the SHA256 algorithm e.g. `sha256(sha256(header))`.\n",
"\n",
"In practice let’s deal with the different byte order conversions from big endian (most significant bit first) to little endian (least significant bit first) and back again.\n",
"\n",
"Tip: Read more about [Endianness @ Wikipedia](https://en.wikipedia.org/wiki/Endianness).\n",
"\n",
"Let’s put together the (binary) 80-byte header using the int4 and hex32 big-endian to little-endian byte order (to binary bytes) conversion helpers:\n",
"\n",
"```ruby\n",
"header =\n",
" int4( version ) +\n",
" hex32( prev ) +\n",
" hex32( merkleroot ) +\n",
" int4( time ) +\n",
" int4( bits.to_i(16) ) +\n",
" int4( nonce )\n",
"\n",
"header.size\n",
"#=> 80\n",
"\n",
"bin_to_hex( header )\n",
"# => \"01000000\" +\n",
"# \"0000000000000000000000000000000000000000000000000000000000000000\" +\n",
"# \"3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a\" +\n",
"# \"29ab5f49\" +\n",
"# \"ffff001d\" +\n",
"# \"1dac2b7c\"\n",
"```\n",
"And run the hash calculations:\n",
"```ruby\n",
"d1 = Digest::SHA256.digest( header )\n",
"d2 = Digest::SHA256.digest( d1 )\n",
"bin_to_hex32( d2 )\n",
"#=> '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'\n",
"```\n",
"\n",
"Bingo! The resulting block hash is 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f.\n",
"\n",
"Let’s backtrack and add the missing binary conversion helpers, that is, int4, hex32, bin_to_hex32 and bin_to_hex."
]
},
{
"cell_type": "code",
"execution_count": 93,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":bin_to_hex"
]
},
"execution_count": 93,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def int4( num ) ## integer 4 byte(32bit) to binary (little endian)\n",
" [num].pack( 'V' )\n",
"end\n",
"\n",
"def hex32( hex ) ## hex string 32 byte(256bit) / 64 hex chars to binary\n",
" [hex].pack( 'H*' ).reverse ## change byte order (w/ reverse)\n",
"end\n",
"\n",
"def bin_to_hex32( bytes )\n",
" bytes.reverse.unpack( 'H*' )[0] ## note: change byte order (w/ reverse)\n",
"end\n",
"\n",
"def bin_to_hex( bytes )\n",
" bytes.unpack( 'H*' )[0]\n",
"end"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To convert integers (4 bytes / 32 bit) to binary bytes (in little endian) use:"
]
},
{
"cell_type": "code",
"execution_count": 94,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"\\x01\\x00\\x00\\x00\""
]
},
"execution_count": 94,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"int4( version )"
]
},
{
"cell_type": "code",
"execution_count": 95,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"01000000\""
]
},
"execution_count": 95,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bin_to_hex( int4( version ))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"compare to “classic” hex string (in big endian):"
]
},
{
"cell_type": "code",
"execution_count": 96,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": []
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\"00000001\"\n"
]
},
{
"data": {
"text/plain": [
"\"00000001\""
]
},
"execution_count": 96,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pp \"%08x\" % version "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What’s better? Big-endian `00000001` or little-endian `01000000`? What’s better? Ruby or Python? Red or Blue? Bitshilling or Bitcoin?\n",
"\n",
"Let’s celebrate that there’s more than one way to do it :-). Onwards.\n",
"\n",
"To convert a hex string (32 byte / 256 bit / 64 hex chars) to binary bytes (in little endian) use:"
]
},
{
"cell_type": "code",
"execution_count": 97,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\";\\xA3\\xED\\xFDz{\\x12\\xB2z\\xC7,>gv\\x8Fa\\x7F\\xC8\\e\\xC3\\x88\\x8AQ2:\\x9F\\xB8\\xAAK\\x1E^J\""
]
},
"execution_count": 97,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"hex32( merkleroot )"
]
},
{
"cell_type": "code",
"execution_count": 98,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a\""
]
},
"execution_count": 98,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bin_to_hex( hex32( merkleroot ))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and to convert back from binary bytes (in little endian) to a hex string use:"
]
},
{
"cell_type": "code",
"execution_count": 99,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b\""
]
},
"execution_count": 99,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bin_to_hex32( hex32( merkleroot ))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What’s better? Big-endian `4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b` or little-endian `3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a`?\n",
"\n",
"What’s that `pack`/`unpack` magic? See the ruby documentation for `Array#pack` and `String#unpack` for the binary data packing and unpacking machinery\n",
"\n",
"To sum up all together now. Let’s use the Block #125552 used as a sample in the [Bitcoin Block hashing algorithm](https://en.bitcoin.it/wiki/Block_hashing_algorithm) wiki page:"
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d\""
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"version = 1\n",
"prev = '00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81'\n",
"merkleroot = '2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3'\n",
"time = 1305998791 ## 2011-05-21 17:26:31\n",
"bits = '1a44b9f2'\n",
"nonce = 2504433986\n",
"\n",
"header = int4( version ) +\n",
" hex32( prev ) +\n",
" hex32( merkleroot ) +\n",
" int4( time ) +\n",
" int4( bits.to_i(16) ) +\n",
" int4( nonce )\n",
"\n",
"d1 = Digest::SHA256.digest( header )\n",
"d2 = Digest::SHA256.digest( d1 )\n",
"bin_to_hex32( d2 )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bonus. For easy (re)use let’s package up the bitcoin block header code into a class:"
]
},
{
"cell_type": "code",
"execution_count": 101,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
":bin_to_h32"
]
},
"execution_count": 101,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"require 'digest'\n",
"\n",
"module Bitcoin\n",
" class Header\n",
" attr_reader :version\n",
" attr_reader :prev\n",
" attr_reader :merkleroot\n",
" attr_reader :time\n",
" attr_reader :bits\n",
" attr_reader :nonce\n",
"\n",
" def initialize( version, prev, merkleroot, time, bits, nonce )\n",
" @version = version\n",
" @prev = prev\n",
" @merkleroot = merkleroot\n",
" @time = time\n",
" @bits = bits\n",
" @nonce = nonce\n",
" end\n",
"\n",
" ## lets add a convenience c'tor helper\n",
" def self.from_hash( h )\n",
" new( h[:version],\n",
" h[:prev],\n",
" h[:merkleroot],\n",
" h[:time],\n",
" h[:bits],\n",
" h[:nonce] )\n",
" end\n",
"\n",
" def to_bin\n",
" i4( version ) +\n",
" h32( prev ) +\n",
" h32( merkleroot ) +\n",
" i4( time ) +\n",
" i4( bits.to_i(16) ) +\n",
" i4( nonce )\n",
" end\n",
"\n",
" def hash\n",
" bytes = sha256(sha256( to_bin ))\n",
" bin_to_h32( bytes )\n",
" end\n",
"\n",
" def sha256( bytes )\n",
" Digest::SHA256.digest( bytes )\n",
" end\n",
"\n",
" ## binary pack/unpack conversion helpers\n",
" def i4( num ) ## integer (4 byte / 32bit) to binary (in little endian)\n",
" [num].pack( 'V' )\n",
" end\n",
"\n",
" def h32( hex ) ## hex string (32 byte / 256 bit / 64 hex chars) to binary\n",
" [hex].pack( 'H*' ).reverse ## change byte order (w/ reverse)\n",
" end\n",
"\n",
" def bin_to_h32( bytes )\n",
" bytes.reverse.unpack( 'H*' )[0] ## note: change byte order (w/ reverse)\n",
" end\n",
" end # class Header\n",
"end # module Bitcoin"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"and let’s test drive it with the genesis block #0 and block #125552:"
]
},
{
"cell_type": "code",
"execution_count": 102,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f\""
]
},
"execution_count": 102,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b0 =\n",
"Bitcoin::Header.from_hash(\n",
" version: 1,\n",
" prev: '0000000000000000000000000000000000000000000000000000000000000000',\n",
" merkleroot: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',\n",
" time: 1231006505,\n",
" bits: '1d00ffff',\n",
" nonce: 2083236893 )\n",
"\n",
"b0.hash"
]
},
{
"cell_type": "code",
"execution_count": 103,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"\"00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d\""
]
},
"execution_count": 103,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b125552 =\n",
"Bitcoin::Header.from_hash(\n",
" version: 1,\n",
" prev: '00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81',\n",
" merkleroot: '2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3',\n",
" time: 1305998791,\n",
" bits: '1a44b9f2',\n",
" nonce: 2504433986 )\n",
"\n",
"b125552.hash"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## (Crypto) Block with Transactions (Tx)\n",
"\n",
"To be continued.\n",
"\n",
"## References / Links\n",
"\n",
"[Programming Cryptocurrencies and Blockchains in Ruby](http://yukimotopress.github.io/blockchains) @ [Yuki & Moto Press Bookshelf](http://yukimotopress.github.io/), Free (Online) Booklet\n",
"[Awesome Blockchains](https://github.com/openblockchains/awesome-blockchains) @ [Open Bockchains](https://github.com/openblockchains)\n",
"[Awesome Crypto](https://github.com/planetruby/awesome-crypto) @ [Planet Ruby](https://github.com/planetruby)\n",
"\n",
"## Questions? Comments?\n",
"\n",
"Send them to the [ruby-talk mailing list](https://www.ruby-lang.org/en/community/mailing-lists/) or the [(online) ruby-talk discourse mirror](https://rubytalk.org/) or post them on the [ruby reddit](https://www.reddit.com/r/ruby). Thanks."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Ruby 2.7.0",
"language": "ruby",
"name": "ruby"
},
"language_info": {
"file_extension": ".rb",
"mimetype": "application/x-ruby",
"name": "ruby",
"version": "2.7.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment