Bitcoin in a nutshell — Blockchain

Blockchain — це технологія, на базі якої побудований Bitcoin. І якщо кілька років тому вся слава доставлась криптовалюте, то сьогодні все частіше можна чути сміливі фразы зразок: "Forget Bitcoin, Long Live Blockchain". Активно розвиваються платформи на зразок Ethereum, IPFS або Overstock, які розглядають блокчейн не як інструмент для створення ще однієї платіжної системи, а як цілком відокремлену технологію, порівнянну по своїй інноваційності хіба що з Інтернетом.
У цій главі я розповім вам, що з себе представляє блокчейн Bitcoin. Навіть порівняно з Ethereum, це страшний анахронізм, але розуміння принципів його роботи вам дуже допоможе, якщо ви вирішите розібратися з більш складними проектами.
мем
Book
Table of content
  1. Blockchain for dummies
  2. Structure
  3. Merkle tree
  4. Timestamp
  5. Raw block
  6. Links
Blockchain for dummies
Сам по собі блокчейн — це дуже проста штука. Його найпростіше проілюструвати на прикладі книги бухгалтерського обліку, в яку записуються всі транзакції в мережі Bitcoin. Більше того, така книга є у кожного учасника мережі, а значить будь-який, при бажанні, може перевірити, чи була та чи інша транзакція в реальності чи ні.
Public ledger
І якщо блокчейн цілком — це книга, то окремі блоки можна представляти як сторінки, на яких "записуються" транзакції. Кожний блок "посилається" на попередній і так до самого першого блоку (genesis block). Саме це і створює таку цікаву особливість блокчейна, як незмінюваність. Не можна взяти і змінити блок #123 так, щоб цього ніхто не помітив. Тому що блокчейн влаштований таким чином, що це спричинить зміну блоку #124, потім #125 і так далі, до самого верху.
Structure
Звичним рухом руки відкриваємо специфікацію протоколу і дивимося на структуру блоку.

  • versionверсія блоку
  • prev_block — хеш попереднього блоку (parent block)
  • merkle_root — якщо спрощено, то це хеш всіх транзакцій в блоці
  • timestamp — дата і час створення блоку
  • bits, nonce — про ці параметри я докладно розповім у розділі Bitcoin in a nutshell — Mining
  • txn_count, txns — число транзакцій в блоці і їх список
Перші шість параметрів (все крім txn_count і txns) утворюють заголовок блоку (header). Саме хеш заголовка називають хешем блоку, тобто самі транзакції безпосередньої участі в хэшировании не приймають.
Замість цього вони заносяться в особливу структуру — дерево Тьмяніла, про яку я розповім нижче.
Merkle tree
Technical side
merkle
Дерево Тьмяніла — це структура даних, також відома як бінарне дерево хешей. У разі Bitcoin воно будується наступним чином:
  1. Спочатку вважаються хеші всіх транзакцій в блоці
    hash_A = SHA256(SHA256(A))

  2. Потім вважаються хеші від суми хешей транзакцій
    hash_AB = SHA256(SHA256(hash_A + hash_B))

  3. Точно також вважаємо хеші від суми отриманих хешей
    hash_ABCD = SHA256(SHA256(hash_AB + hash_CD))
    і далі по рекурсії. Ліричний відступ — так як бінарне дерево, то на кажом кроці повинно бути парне число елементів. Тому якщо, наприклад, у нас лише три транзакції, то остання транзакція просто дублюється:
    Double txn
  4. Процес продовжується до тих пір, поки не вийде один єдиний хеш — він і називається merkle_root (третє поле header блоку)
Нижче наведена реалізація деревини Тьмяніла, можете перевірити її на якомусь блоці.
import hashlib

# Hash pairs of items recursively until a single value is obtained
def merkle(hashList):
if len(hashList) == 1:
return hashList[0]
newHashList = []
# Process pairs. For odd length, the last is skipped
for i in range(0, len(hashList)-1, 2):
newHashList.append(hash2(hashList[i], hashList[i+1]))
if len(hashList) % 2 == 1: # odd, hash last item twice
newHashList.append(hash2(hashList[-1], hashList[-1]))
return merkle(newHashList)

def hash2(a, b):
# Reverse inputs before and after hashing
# due to big-endian / little-endian nonsense
a1 = a.decode('hex')[::-1]
b1 = b.decode('hex')[::-1]
h = hashlib.sha256(hashlib.sha256(a1 + b1).digest()).digest()
return h[::-1].encode('hex')

Immutability

Тепер про те, навіщо це потрібно в Bitcoin. Я думаю, ви розумієте, що якщо змінити хоча б одну транзакцію, то merkle_root також зміниться. Тому така структура даних дозволяє забезпечити "неподделываемость" транзакцій в блоці. Тобто не може відбутися наступної ситуації:
Хтось із майнер знайшов новий блок і почав розкидати його по мережі. В цей час зловмисник перехоплює блок і, наприклад, видаляє з блоку яку-небудь операцію, після чого розповсюджує вже змінений блок.
Для перевірки достатньо порахувати merkle_root самостійно і порівняти його з тим, що записано в header блоку.
SPV
Але тут можна резонно заперечити, що, по-перше, такі складності абсолютно ні до чого. Досить просто порахувати хеш від суми всіх транзакцій в блоці
txns_hash = SHA256(SHA256(sum(txns)))
— він точно також зміниться після будь-яких маніпуляцій з транзакціями. А, по-друге, що заважає зловмиснику підмінити merkle_root у блоці? На друге питання відповім одразу: насправді в блоці взагалі не можна нічого змінити, тому що блок тут же стане невалидным (це ви зрозумієте після прочитання наступної глави Bitcoin in a nutshell — Mining).
А дерево Тьмяніла потрібно насправді для того, щоб мати можливість створювати SPV nodes (Simplified Payment Verification). Такі ноди синхронізують тільки заголовки блоків, без самих транзакцій. В результаті блокчейн займає на порядок менше місця (для краси візьмемо висоту в 500.000 блоків, розмір header фіксований — 80 байт):
500.000 * 80 / 1024 / 1024 ≈ 40 Мб
Такий блокчейн вже можна без проблем розмістити на телефоні, планшеті або якому-небудь IoT. Що в перспективі має дати більшу децентралізацію, безпека мережі і так далі.
Суть спрощеної верифікації платежів в наступному: нехай у вас є SPV нода. У мене ж є весь блокчейн цілком і мені потрібно вас переконати, що яка-небудь транзакція дійсно була (на картинці це транзакція K). В цьому випадку, мені достатньо лише надати вам кілька хешей:
H_L, H_IJ, H_MNOP, H_ABCDEFGH
, вони ще називаються authentication path.
Auth path
Після чого ви спочатку вважаєте
H_K = SHA256(SHA256(K))
, потім
H_KL = SHA256(SHA256(H_K + H_L))
і так до самого верху. Якщо в результаті ви знаходите у себе блок з таким же merkle_root, то факт існування транзакції вважається підтвердженим.
BTW Ральф Меркл навіть запатентував свою структуру даних, про що свідчить патент US4309569 A.
Timestamp
Ще одне цікаве питання. Уявімо, що десь у мережі з'явився з'явився новий блок і ноди починають передавати його один одному. Кожна нода має переконатися в тому, що блок коректний. Для цього вона:
  • перевіряє синтаксис і структуру блоку
  • перевіряє на валідність кожну транзакцію в блоці
  • хэширует транзакції і порівнює merkle root
  • перевіряє кілька критеріїв, пов'язаних з майнингом, і так далі
Але як можна перевірити timestamp? Зрозуміло, що час на різних комп'ютерах може розрізнятися, так що навіть якщо у нового блоку timestamp відрізняється від вашого поточного часу на годину вперед, це ще не означає, що блок "неправильний", може у майнера просто годинник поспішає.
Тому для перевірки timestamp на валідність було придумано два критерії. По-перше, він повинен бути більше, ніж середнє арифметичне timestamp-ів попередніх 11 блоків. Це робиться для того, щоб не вийшло так, що блок #123 вийшов 12 березня 2011 року, а #124 — 13 лютого 1984. Але в теж час допускається деяка похибка.
По-друге, timestamp повинен бути менше ніж network adjusted time. Тобто нода, при отриманні нового блоку, цікавиться поточним часом у своїх "сусідів" по мережі, вважає середнє арифметичне і якщо block timestamp менше отриманого значення + 2 години, то все в порядку.
BTW як ви бачите, timestamp нового блоку може виявитися навіть менше, ніж timestamp більш раннього блоку. Це не така вже й рідкість, наприклад, #145045, #145046 і #145047.
145044: 2011-09-12 15:46:39 
145045: 2011-09-12 16:05:07 
145046: 2011-09-12 16:00:05 // ~5 minutes before prior block
145047: 2011-09-12 15:53:36 // ~7 & ~12 minutes before 2 prior blocks
145048: 2011-09-12 16:04:06 // after 2 prior blocks but still before 145045

Raw block
Якщо у вас до сих залишилися якісь питання по структурі блоку, то пропоную вам подивитися на них в "сиром" вигляді. Самий очевидний спосіб це зробити — запустити на пару годин
bitcoind --daemon
, а потім досліджувати вже викачані блоки. Але, по-перше, не у всіх є час / бажання синхронізувати блокчейн. По-друге, в Bitcoin блоки зберігаються у вкрай специфічною базі даних LevelDB, ще й доволі дивним чином. А так як книга розрахована не тільки на досвідчених розробників, то я піду вже перевіреним шляхом і знову використовую протокол у його первозданному вигляді.
Для отримання блоку надішлемо повідомлення getdata, в якому вкажемо
type : MSG_BLOCK
та
hash : 000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506
— це хеш блоку #100.000. Весь код цілком можете подивитися тут.
def getdataMessage():
block_hash = '000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506'

count = struct.pack("<B", 1)
inventory = struct.pack("<L", 2) # type : MSG_BLOCK
inventory += block_hash.decode('hex')[::-1]

return count + inventory

Wireshark block
Links
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.