Осваиваем Ethereum. Создание смарт-контрактов и децентрализованных приложений. 9781491971949, 9785041067816


121 17 36MB

Russian Pages [514] Year 2021

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
0000001
0000003
0000004
0000005
0000006
0000007
0000008
0000009
0000010
0000011
0000012
0000013
0000014
0000015
0000016
0000017
0000018
0000019
0000020
0000021
0000022
0000023
0000024
0000025
0000026
0000027
0000028
0000029
0000030
0000031
0000032
0000033
0000034
0000035
0000036
0000037
0000038
0000039
0000040
0000041
0000042
0000043
0000044
0000045
0000046
0000047
0000048
0000049
0000050
0000051
0000052
0000053
0000054
0000055
0000056
0000057
0000058
0000059
0000060
0000061
0000062
0000063
0000064
0000065
0000066
0000067
0000068
0000069
0000070
0000071
0000072
0000073
0000074
0000075
0000076
0000077
0000078
0000079
0000080
0000081
0000082
0000083
0000084
0000085
0000086
0000087
0000088
0000089
0000090
0000091
0000092
0000093
0000094
0000095
0000096
0000097
0000098
0000099
0000100
0000101
0000102
0000103
0000104
0000105
0000106
0000107
0000108
0000109
0000110
0000111
0000112
0000113
0000114
0000115
0000116
0000117
0000118
0000119
0000120
0000121
0000122
0000123
0000124
0000125
0000126
0000127
0000128
0000129
0000130
0000131
0000132
0000133
0000134
0000135
0000136
0000137
0000138
0000139
0000140
0000141
0000142
0000143
0000144
0000145
0000146
0000147
0000148
0000149
0000150
0000151
0000152
0000153
0000154
0000155
0000156
0000157
0000158
0000159
0000160
0000161
0000162
0000163
0000164
0000165
0000166
0000167
0000168
0000169
0000170
0000171
0000172
0000173
0000174
0000175
0000176
0000177
0000178
0000179
0000180
0000181
0000182
0000183
0000184
0000185
0000186
0000187
0000188
0000189
0000190
0000191
0000192
0000193
0000194
0000195
0000196
0000197
0000198
0000199
0000200
0000201
0000202
0000203
0000204
0000205
0000206
0000207
0000208
0000209
0000210
0000211
0000212
0000213
0000214
0000215
0000216
0000217
0000218
0000219
0000220
0000221
0000222
0000223
0000224
0000225
0000226
0000227
0000228
0000229
0000230
0000231
0000232
0000233
0000234
0000235
0000236
0000237
0000238
0000239
0000240
0000241
0000242
0000243
0000244
0000245
0000246
0000247
0000248
0000249
0000250
0000251
0000252
0000253
0000254
0000255
0000256
0000257
0000258
0000259
0000260
0000261
0000262
0000263
0000264
0000265
0000266
0000267
0000268
0000269
0000270
0000271
0000272
0000273
0000274
0000275
0000276
0000277
0000278
0000279
0000280
0000281
0000282
0000283
0000284
0000285
0000286
0000287
0000288
0000289
0000290
0000291
0000292
0000293
0000294
0000295
0000296
0000297
0000298
0000299
0000300
0000301
0000302
0000303
0000304
0000305
0000306
0000307
0000308
0000309
0000310
0000311
0000312
0000313
0000314
0000315
0000316
0000317
0000318
0000319
0000320
0000321
0000322
0000323
0000324
0000325
0000326
0000327
0000328
0000329
0000330
0000331
0000332
0000333
0000334
0000335
0000336
0000337
0000338
0000339
0000340
0000341
0000342
0000343
0000344
0000345
0000346
0000347
0000348
0000349
0000350
0000351
0000352
0000353
0000354
0000355
0000356
0000357
0000358
0000359
0000360
0000361
0000362
0000363
0000364
0000365
0000366
0000367
0000368
0000369
0000370
0000371
0000372
0000373
0000374
0000375
0000376
0000377
0000378
0000379
0000380
0000381
0000382
0000383
0000384
0000385
0000386
0000387
0000388
0000389
0000390
0000391
0000392
0000393
0000394
0000395
0000396
0000397
0000398
0000399
0000400
0000401
0000402
0000403
0000404
0000405
0000406
0000407
0000408
0000409
0000410
0000411
0000412
0000413
0000414
0000415
0000416
0000417
0000418
0000419
0000420
0000421
0000422
0000423
0000424
0000425
0000426
0000427
0000428
0000429
0000430
0000431
0000432
0000433
0000434
0000435
0000436
0000437
0000438
0000439
0000440
0000441
0000442
0000443
0000444
0000445
0000446
0000447
0000448
0000449
0000450
0000451
0000452
0000453
0000454
0000455
0000456
0000457
0000458
0000459
0000460
0000461
0000462
0000463
0000464
0000465
0000466
0000467
0000468
0000469
0000470
0000471
0000472
0000473
0000474
0000475
0000476
0000477
0000478
0000479
0000480
0000481
0000482
0000483
0000484
0000485
0000486
0000487
0000488
0000489
0000490
0000491
0000492
0000493
0000494
0000495
0000496
0000497
0000498
0000499
0000500
0000501
0000502
0000503
0000504
0000505
0000506
0000507
0000508
0000509
0000510
0000511
0000512
0000513
0000514
9999999999999999999999
Recommend Papers

Осваиваем Ethereum. Создание смарт-контрактов и децентрализованных приложений.
 9781491971949, 9785041067816

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Andreas М. Antonopoulos Dr. Gavin Wood

Jf Ethereum MASTERING

BUILDING SMART CONTRACTS AND DAPPS

O"REILLY"

Андреас Антонопулос Гэвин Вуд

ОСВАИВАЕМ

Jf

Ethereum СОЗДАНИЕ СМАРТ-КОНТРАКТОВ И ДЕЦЕНТРАЛИЗОВАННЫХ ПРИЛОЖЕНИЙ



БОМБОРА Москва 2021

УДК 336.74:004.9 ББК 65.26+32.973.202 А72

Andreas Antonopoulos & Gavin Wood Mastering Ethereum © 2020 Eksmo PuЬlishing Company Authorized Russian translation of the English edition ofMastering Ethereum ISBN 9781491971949 © 2019 The Ethereum Book LLC, Gavin Wood This translation is puЬlished and sold Ьу permission of O'Reilly Media, !пс., which owns or controls all rights to puЬlish and sell the same.

Издательство благодарит за помощь и активное содействие в подготовке и переводе этой книги

Андрея Власова.

А72

Антонопулос, Андреас.

Осваиваем Ethereum : создание смарт-контрактов и децентрали­ зованных приложений / Андреас Антонопулос, Гэвин Вуд ; [перевод с английского М. А. Райтман, А. В. Власова]. - Москва: Эксмо, 2021. 512 с. - (Мировой компьютерный бестселлер). ISBN 978-5-04-106781-6 Ethereum - окно в мир всемирных децентрализованных вычислений. Эта плат­ форма позволяет реализовывать децентрализованные приложения (DApps) и смарт­ контракты без центральных точек доступа или контроля, интегрироваться с платеж­ ной сетью и работать с открытым блокчейном. В этом практическом пособии Андреас Антонопулос и Гэвин Вуд дают всю информацию, необходимую для построения смарт-контрактов и децентрализованных приложений в Ethereum и других блокчейн­ системах. УДК 336.74:004.9 ББК 65.26+32.973.202

ISBN 978-5-04-106781-6

© Райтман М.А., перевод на русский язык, 2019 © Власов А.В., перевод на русский язык, 2020 ©Оформление.ООО «Издательство «Эксмо», 2021

Оглавление Предисловие научного редактора 15 Предисловие 16 Как читать эту книгу . 17 Целевая аудитория.... 17 .......... ....... 17 Условные обозначения Примеры кода ................... 18 Использование примеров кода 19 Ссылки на компании и продукты... . 19 ... 20 Адреса и транзакции на Ethereum, указанные в книге Как связаться с Андреасом ............. 20 Как связаться с Гэвином ...... ....... 20 . 21 Благодарности от Андреаса . Благодарности от Гэвина ...... 21 Люди, внесшие свой вклад. ... 21 Источники 25 Краткий глоссарий ..................... 26 Глава 1. Что такое Ethereum?. ..... 38 Сравнение с Вitcoin .. 38 Компоненты блокчейна. .................. .................... ... 39 Рождение Ethereum .. 41 Четыре стадии разработки Ethereum ... "43 Ethereum: блокчейн общего пользования .. 44 Компоненты Ethereum... ............... 45 Дополнительный материал . . 46 Ethereum и полнота по Тьюрингу ..... 46 Полнота по Тьюрингу как отдельная «возможность)) ....... 47 Последствия полноты по Тьюрингу................ ............. 48 От блокчейна общего пользования до децентрализованных ..... 49 приложений (DApps) Интернет третьего поколения ... 50 . 50 Культура разработки в Ethereum Зачем изучать Ethereum?. . .. . 52 Чему эта книга вас научит .......... 52 Оглавление

5

Глава 2. Основы Ethereum. .............................. 54 Выбор кошелька Ethereum ...... 56 Контроль и ответственность . 58 Начало работы с MetaMask . . . 58 Создание кошелька ...................... 61 Переключение между сетями ................... 62 Получение эфира для тестирования ...................... 64 Отправка эфира из MetaMask ... 66 Исследование истории транзакций для определенного адреса . . ... ............ 67 Знакомство с глобальным компьютером..... ..... .. . ..... ..... .. У четные записи и контракты с внешними владельцами. ............................ 68 Простой контракт: тестовый контракт faucet с эфиром. 69 ............ 72 Компиляция контракта Faucet . ............. 74 Создание контрактов на блокчейне ................ ........ 76 Взаимодействие с контрактом Просмотр адреса контракта в обозревателе (explorer) ........................... 76 ..... 77 Пополнение контракта . ..... 78 Списание средств с контракта 82 Выводы ... .. .. 83 Глава 3. Клиенты Ethereum ............ 84 Сети Ethereum . . .... .. 84 Стоит ли запускать полноценную поду? Преимущества и недостатки полноценной поды .................................... 86 ............ 86 Преимущества и недостатки публичной тестовой сети Преимущества и недостатки локальной симуляции блокчейна. .. .. 87 ... ................. 88 Запуск клиента Ethereum . .. ..... 88 Аппаратные требования для запуска полноценной ноды . Требования к программному обеспечению для сборки .... .... ....... 90 и запуска клиента (поды) . .... 91 Parity.. ..... ................ 93 Go-Ethereum (Geth) .. ........... 96 Первая синхронизация с блокчейнами на основе Ethereum . . ..... 97 Запуск Geth или Parity .... . 97 Интерфейс JSON-RPC.... 100 Удаленные клиенты Ethereum . .. 100 Мобильные кошельки (для смартфонов). 101 Кошельки в браузере......... 103 Выводы. 6

Оглавление

................... ............... ...................... ..................... 104 Глава 4. Криптография .............................................. 105 Ключи и адреса.... .............. Криптография с публичным ключом и криптовалюта ............................... 106 ................................. 108 Приватные (закрытые) ключи... Генерация приватного ключа из случайного числа ............................. 109 111 Публичные ключи..................................................................................... .................... 112 Подробнее об эллиптической криптографии Арифметические операции с эллиптической кривой ......... ................. 115 116 Генерация публичного ключа ......................................................... 118 Библиотеки эллиптической криптографии ... .... ........... ................ 118 Криптографические функции хеширования .. .......................... Криптографическая функция хеширования в Ethereum: Keccak-256 ...... 120 .... 121 Какую функцию хеширования вы используете? . ... 122 Адреса Ethereum............. ................. ........................... .... 123 Форматы адресов Ethereum... ............. 123 Протокол обмена клиентскими адресами. ................ 125 Шестнадцатеричная кодировка (EIP-55) . 128 Выводы 129 Глава 5. Кошельки......... 129 Обзор технологий кошельков .... 131 Недетерминистические (случайные) кошельки ...... .. ... ...... ...... ... . 133 Детерминистические кошельки......... ...... . Иерархические детерминистические кошельки (BIP-32/BIP-44) ......... 133 Значения seed и мнемонические коды (BIP-39) ...................................... 134 135 Лучшие практики работы с кошельками 136 Мнемоническая кодовая фраза (BIP-39) . 144 Создание НD-кошелька с помощью seed ........................................... ..... 145 НD-кошельки (BIP-32) и пути (BIP-43/44) 150 Выводы ........... 151 Глава 6. Транзакции ················· 151 Структура транзакции .... 153 Одноразовый код транзакции 154 Отслеживание одноразовых кодов ................................ ............ . Разрывы между одноразовыми кодами, дубликация 157 и подтверждение Параллелизм, происхождение транзакции и одноразовые коды ..... 158 159 Газ для транзакций .......... ................. .................... 161 Получатель транзакции ...... lbl Значение и данные транзакции ........................ .................... Оглавление

1

7

Передача значения учетным записям ЕОА и контрактам Передача полезных данных учетным записям ЕОА и контрактам. ... Специальные транзакции: создание контрактов .... Цифровые подписи....... . ...... Алгоритм цифровых подписей на основе эллиптической кривой . Принцип работы цифровых подписей. Проверка подписи .. ... ........... ....... . Математические вычисления алгоритма ECDSA. . Подписание транзакции на практике. ............. ...... Создание «сырых транзакций» и подписание...... .. ........... Создание «сырой транзакции» с помощью EIP-155 Префиксное значение подписи (v) и восстановление публичного ключа.... .. .... .. ....... Разделение операции подписания и передачи (офлайн-подписание) . Распространение транзакций ........ Запись в блокчейн................ Транзакции с несколькими подписями Выводы ..... Глава 7. Смарт-контракты и язык Solidity ..... Что такое смарт-контракт?. .... Жизненный цикл смарт-контракта........... .. Введение в языки высокого уровня, доступные в Ethereum Написание смарт-контракта на языке Solidity .............. Выбор версии Solidity Загрузка и установка.......................... Среда разработки Написание простой программы на языке Solidity .. Компиляция с помощью компилятора Solidity (solc) АВI-интерфейс контрактов в Ethereum . Выбор версии компилятора и языка Solidity... ............ Программирование на языке Solidity..................... .... . .... ...... Типы данных . .. Встроенные глобальные переменные и функции .... ........... Определение контракта. ............................ .... Функции .. ....................... ........................ . ... Конструктор контракта и его самоуничтожение ......... Добавление конструктора и деструктора в пример контракта faucet. Модификаторы функций . ....... Наследование контрактов ................. 8

1

Оглавление

164 165 167 170 170 171 172 172 174 175 176 177 178 180 181 182 183 184 184 185 187 189 190 190 191 191 192 192 194 195 195 197 199 200 202 203 205 206

Обработка ошибок (assert, require, revert) .. ................. 208 ···············... 210 События....... .............. 214 Вызов других контрактов (send, call, callcode, delegatecall) Соображения относительно газа..................... .............................. .. 221 ................ 222 Избегайте динамических массивов . ............................ . ....................... 222 Избегайте вызовов других контрактов Оценка расходования газа ················· 222 ............. .... 224 Выводы .... ............. ........................... Глава 8. Смарт-контракты и Vyper ... ..................................... ............. n5 Уязвимости и Vyper ...... 225 Сравнение с Solidity.......... ························· 226 .......................... 226 Модификаторы Наследование классов ······························ 228 ···············............. 228 Ассемблерные вставки Перегрузка функций . .. 229 Приведение типов переменных...... ·············· ................... 229 Предусловия и постусловия .... ................................................................ 231 Декораторы . ············································ ··�2 .. 232 Порядок добавления функций и переменных ....... .................... Компиляция.. ............................................................. 234 Защита от ошибок переполнения буфера на этапе компиляции ...... 235 Чтение и запись данных .. 236 Выводы ... ............... .................. 237 Глава 9. Безопасность смарт-контрактов . ............................. 238 Лучшие практики безопасности ........... ····················· 238 Риски обеспечения безопасности и антишаблоны .................. 240 Реентерабельность..... ............ ................................. .................. 240 Уязвимость ····················· 240 ................ .............. ····························· 244 Превентивные меры...... Реальный пример: DAO... ............. 245 Арифметическое переполнение и антипереполнение ......................... 246 Уязвимость .. . ............. .... ........... ..... ................... .............................. 246 Превентивные меры... ........................... .......... 249 Реальные примеры: PoWHC и переполнение пакетной передачи .............. .............. ......................... 251 (CVE-2018-10299) «Неожиданный» эфир ........ . ............. 252 Уязвимость ..... ................ ............... 252 Превентивные меры.......... ............. .............. ...... 256 Другие примеры . .... ....................... .............................. ............. 257 Оглавление

1

9

....... 257 DELEGATECALL. 258 Уязвимость ... ...... 263 Превентивные меры............ Реальный пример: кошелек Parity с «мультисиг» подписями . ................. ... . 263 (второй взлом) .. . 266 Видимость по умолчанию ... 266 Уязвимость ........ ... 267 Превентивные меры... Реальный пример: кошелек Parity с «мультисиг» подписями . ..... ....... 267 (первый взлом) .. .. ... ...... 269 Иллюзия энтропии ........... 269 Уязвимость ...... ......... .... .. 270 Превентивные меры.... Реальный пример: контракты, использующие генератор ............ 270 псевдослучайных чисел .... ........................... 270 Ссылки на внешние контракты .. ........... 271 Уязвимость ... 275 Превентивные меры 276 Реальный пример: ловушка реентерабельности .. ..................... 278 Атака короткого адреса/параметра .... ················· 279 Уязвимость ................. .................. . 280 Превентивные меры.. .......... 280 Непроверяемые значение, возвращенные из вызова CALL . .................... 281 .............. Уязвимость .... ... ... ... ... .. . ... 282 Превентивные меры............................... .......... 282 Реальный пример: Etherpot и Кing of the Ether. ... 284 Состояние гонки и фронт-раннинг....... ..... 284 Уязвимость .................. .. 286 Превентивные меры...... ......... 287 Реальные примеры: ERC20 и Bancor ........ 288 Атака на отказ в обслуживании (DoS) ..... ........ 288 Уязвимость . ..... 291 Превентивные меры.............. ........... 291 Реальный пример: GovernMental.. .. 292 Манипуляции с временной меткой блока . 292 Уязвимость ....... .. 293 Превентивные меры........................ ........ . ..... 294 Реальный пример: GovernMental ... 294 Неточности в названиях конструкторов 10

1

Оглавление

...... 295 Уязвимость ....... 296 Превентивные меры ............. 296 Реальный пример: Rubixi ·············· 296 Хранение неинициализированных указателей... ··············· 297 Уязвимость .............................. . ....................... 299 Превентивные меры... ................ Реальные примеры: ловушки OpenAddressLottery и CryptoRoulette ... 299 ...................................... .. 300 Плавающая запятая и точность . ... . 300 Уязвимость .... ............................. 301 Превентивные меры. ·········· 302 Реальный пример: Ethstick ............... 303 Аутентификация с помощью Тх. Origin . .. 303 Уязвимость .. 305 Превентивные меры..... . .. 305 Библиотеки для контрактов ... 306 Выводы ....... . ·····················.. 307 Глава 10. Токены... 308 Способы применения токенов ............................................ .. 310 Токены и взаимозаменяемость ......................................... .. 310 Риск контрагента ........ ............. .. 311 Токены и их свойства (назначение) . Назначение токенов: утилитарные токены и токены-акции. .................... 312 ................................ .......................... .................. 313 Это утка! ... .................... 313 У тилитарные токены: кому они нужны?.. ... 315 Токены в Ethereum ....... .. 316 Стандарт токенов ERC20 .. 321 Запуск собственного токена ERC20 ... 335 Проблемы с токенами ERC20 ERC223: предложенный стандарт интерфейса для контракта токена . .. .............. ................... .............. ....................... .. 337 ERC777: предложенный стандарт интерфейса .. 338 для контракта токена ... ERC721: стандарт (актов) невзаимозаменяемых токенов..................... 341 .. 344 Использование стандартов токенов .... 344 Что такое стандарты токенов? Каково их назначение? .. ........ 344 Стоит ли использовать эти стандарты?............... 345 ....... ...... Безопасность, основанная на зрелости .... 346 Расширение стандартов интерфейса токенов .............. .... 347 Токены и процедура выпуска токенов (ICO) Оглавление

11

Выводы ........ . .. Глава 11. Оракулы ............. Зачем нужны оракулы? ...... Примеры оракулов и их использование .... Шаблоны проектирования оракулов .. .................... Подлинность данных... ... Вычислительные оракулы .. ... ......... ... .. ....... ............................ Децентрализованные оракулы Клиентские интерфейсы оракулов в Solidity.............. ................................. Выводы.... ............... ...... ........................ Глава 12. Децентрализованные приложения (DApps) .................................... Что такое DАрр-приложение? Серверная часть (смарт-контракт) ............ .. ... . Клиентская часть (пользовательский веб-интерфейс) Хранилище данных ....... Децентрализованные протоколы взаимодействия на основе сообщений....... . ................................................. ..................... Пример простого децентрализованного приложения: аукцион . ............. Децентрализованный аукцион: смарт-контракты на стороне сервера.... Децентрализованный аукцион: клиентский пользовательский интерфейс ..... ..... ... ... ............. .......... Дальнейшая децентрализация приложения-аукциона. Хранение децентрализованного аукциона в Swarm ... Подготовка Swarm ......... ........................... .. Загрузка файлов в Swarm ......... . ............................ Сервис имен Ethereum (ENS) ......................................... .................... История появления сервиса имен Ethereum Спецификация ENS..... ..... ...... Нижний слой: имена владельцев и сопоставителей Средний слой: поды (узла) .eth .... . ................. ....... Верхний слой: dееd-контракты (акты) ... ................................... Регистрация имени ...... ... Управление ENS именем................ .............. ЕNS-сопоставители .................... Сопоставление имени с Swarm хешем (его содержимым. - Ред.) .... От «обычного» приложения к DApp ...... .. Выводы ... Глава 13. Виртуальная машина Ethereum ....................... Что такое EVM? .............................. . .................... 12

1

Оглавление

348 349 349 350 352 356 358 360 362 367 368 369 370 371 372 373 373 375 379 381 382 382 384 386 386 387 387 390 392 393 396 398 399 401 402 403 403

Сравнение с существующими технологиями ...... ...... ....... . ... ..... . .. . . .... Набор инструкций EVM (операции на основе байт-кода) ..... ............................... Состояние Ethereum Компиляция кода Solidity в байт-код EVM .... Код развертывания контракта. .. Дизассемблирование байт-кода .... . .. Полнота по Тьюрингу и газ .. Газ. .... . У чет газа во время выполнения .. Как ведется учет газа .. . Расход и цена газа .. Лимит газа для блока .......... Выводы .. Глава 14. Консенсус .. .. ... Консенсус на основе доказательства работы ...... Консенсус на основе доказательства доли владения. .. .. .......... Ethash: алгоритм РоW в Ethereum........... ....... ................ . ..... Casper: алгоритм доказательства доли владения в Ethereum .... Принципы консенсуса. .. Разногласия и конкуренция .......... . .... Выводы .... Дополнение А. История ответвлений Ethereum Ethereum Classic (ЕТ С) ........... Децентрализованная автономная организация Ошибка реентерабельности . ......... . Технические подробности............... .. ... ..... .. .... Порядок выполнения атаки . .... ... . .. ................. .. Жесткое ответвление DAO Хронология жесткого ответвления DAO . .. .......... .. ........... ........ .. . ....... ................. Ethereum и Ethereum Classic ...... ..... .. ..................... ........ ..... EVM ....... Разработка основного кода сети .. ...... ......... . Другие хардфорки Ethereum, заслуживающие внимания . ... . Дополнение Б. Стандарты Ethereum .. ........ ....... Предложения по улучшению Ethereum Таблица наиболее важных документов EIP и ERC Дополнение В. Опкоды EVM и потребление газа в Ethereum Дополнение Г. Инструменты разработки, фреймворки и библиотеки ........ . . Фреймворки................. ... Оглавление

405 406 410 412 416 417 423 424 425 425 426 427 428 429 430 431 432 433 434 435 435 437 437 438 438 439 439 439 441 443 443 444 444 447 447 448 457 467 467 13

......... Truffle Embark .. OpenZeppelin ZeppelinOS. .................................................. ......... ........ У тилиты ........... EthereumJS helpeth: утилита командной строки dapp.tools ... ........ SputnikVM .. ................. Библиотеки ... ........... web3.js .. web3.py ................ EthereumJS web3j.. ....... EtherJar ... Nethereum .. ethers.js ... ............ Платформа Emerald .. Тестирование смарт-контрактов .... Тестирование в рамках блокчейна . ........ Ganache: локальный блокчейн для тестирования . Дополнение Д. Руководство по работе с web3.js .. ............................................ ................... .... ... .. . ..... . .... .. Описание Простое взаимодействие с контрактами в асинхронном стиле ............................... с помощью web3.js ............ Выполнение скрипта Node.js .................. Обзор демонстрационного скрипта . ...... Взаимодействие с контрактом .. Асинхронные операции с помощью await . Дополнение Е. Список сокращенных ссылок . Безопасность смарт-контрактов ........ Токены .... Об авторах. В завершение. Алфавитный указатель .......

467 477 478 483 484 484 485 485 486 486 486 486 486 487 487 487 487 488 489 490 492 492 492 493 493 494 497 498 498 500

501 503 .. 504

П редисл овие нау ч но го редактора Если вы хотите поймать криптогазелей в саванне «нового мира» технологиче­ ских инноваций, целеустремленно пересекая океан децентрализованных финан­ сов 1, то эта книга для вас! Как исследователю перспективных технологий, технологий распределенного реестра и блокчейна, позвольте мне на миг возвратить вас к периоду появления криптовалют Bitcoin и Ethereum, первых цифровых валют, с которых началось становление уникального альтернативного финансового рынка и с помощью которых по сей день продолжается развитие финтех-приложений и экосистем. К периоду, когда создатели криптоактивов начинали внедрять технологии крип­ тографического хеширования и цифровой подписи; когда первые пользователи на их основе начинали тестировать протоколы в социально-экономических от­ ношениях, адаптировать их к новым формам доверительного сетевого взаимо­ действия. Вам, читателю этой книги, авторы - Андреас Антонопулос и д-р Гэвин Вуд предлагают окунуться в океан -DeFi, где каждый может стать банкиром личных финансов или выступить в роли программиста токенизации любых видов акти­ вов, или примерить роль продуктового менеджера (создателя!) публичных благ (в форме коммодити 2 ) для локального сообщества и даже всего человечества. Блокчейн-сеть и платформа смарт-контрактов Ethereum, вкупе с данной кни­ гой как «инструкцией по применению», позволят вам самостоятельно и откры­ то сделать это - сыграть свою «роль», с каждой прочитанной строчкой книги и написанной строчкой кода. Я уверен, что молодые пользователи и опытные разработчики почерпнут пользу и в прочтении этой книги, и в программировании на платформе Ethereum. Андрей Власов, научный редактор и переводчик книги «Осваиваем Ethereum. Создание смарт-контрактов и децентрализованных приложений» (издательство «Бамбара»), эксперт «ДЕМОНТРОЯЛ» (DeMontroyal)

1 2

От англ. DeFi ( сокр. от англ. decentralized jinance) осеап. - Прим. ред. От англ. commodity. - Прим. ред.

Предисловие научного редактора

15

П редисловие Данное издание является результатом совместной работы Андреаса М. Антоно­ пулоса и д-ра Гэвина Вуда. Этих двух писателей свела вместе цепочка счастливых случайностей, и в лучшем духе проектов с открытым исходным кодом и культу­ ры Creative Commons вместе с сотнями друrих участников они создали эту книгу. Г эвин Вуд уже давно хотел написать расширенную версию своего докумен­ та Yellow Paper (технического описания протокола Ethereum) - в основном для того, чтобы открыть его для более широкой аудитории, так как оригинал, напол­ ненный математическими формулами, доступен далеко не для всех. Уже найдя издателя и начав воплощать свою идею в жизнь, Вуд встретился с Андреасом Антонопулосом, которого он знал с первых дней своей работы над проектом Ethereum, был известной личностью в этой области 1 • Чуть ранее Андреас опубликовал первое издание своей книги «Осваиваем биткойн» 2 (издательство O'Reilly), которая быстро стала авторитетным техни­ ческим пособием по биткойну и друrим криптовалютам. Почти одновременно с выходом книги читатели начали спрашивать его: «Когда вы напишете то же са­ мое про Ethereum!» Антонопулос уже подумывал о следующем проекте, и плат­ форма Ethereum казалась ему заманчивой технической темой. Наконец в мае 2016 года Гэвин Вуд и Андреас Антонопулос по случайности одновременно оказались в одном и том же городе. Они встретились, чтобы об­ судить за чашкой кофе совместную работу над новой книгой. Поскольку оба они являются приверженцами концепции открытого исходного кода, итоговый ре­ зультат было решено выпустить под лицензией Creative Commons 3 • К счастью, издатель, O'Reilly Media, с радостью пошел им навстречу. Таким образом, было положено начало проекта «Осваиваем Ethereum».

1

Имеется в виду область блокчейн-технолоrий. - Прим. ред.

2

В России вышла под назв. «Осваиваем биткойн. Программирование блокчейна» (изд-во ДМК Пресс, 20 1 9 r.). - Прим. ред.

3

Creative Commons - лицензии и правовые документы, защищающие авторское право и по­ зволяющие запретить, ограничить или разрешить использование своего произведения всем желающим на тех или иных условиях. - Прим. ред.

16

Предисловие

Как читать эту книгу Эта книга может служить как справочным руководством, так и полноценным ис­ следованием Ethereum. Первые две главы содержат введение, нацеленное на но­ вичков, а также упражнения, для решения которых достаточно лишь небольших технических навыков. Это позволит вам получить представление об основах Ethereum и использовать фундаментальные инструменты данной платформы. Все главы, начиная с третьей, предназначены в основном для программистов и содержат много технического материала и примеров программного кода. Так как данная книга является одновременно руководством по Ethereum и последовательным повествованием, в ней не обошлось без определенного дуб­ лирования. Некоторые темы, такие как газ (англ. gas), пришлось рассматривать на ранних этапах, чтобы последующий материал был понятен; позже они обсу­ ждаются более глубоко в отдельных разделах. Наконец, предметный указатель позволит вам, читателям, легко находить узкоспециализированные темы и соответствующие разделы, используя поиск по ключевым словам.

Целева я ауд итория Эта книга предназначена в основном для программистов. Если у вас есть опыт использования языков программирования, вы сможете узнать принцип работы смарт-контрактов, научитесь их разработке и применению в децентрализован­ ных приложениях. Первые несколько глав могут также послужить углубленным введением в Ethereum для читателей, далеких от программирования.

Усло вные обозначени я В этой книге используются следующие типографические обозначения. Курсив Обозначае_т новые термины, URL, адреса электронной почты и расшире­ ния файлов. Моно ширинный шрифт Используется в листингах кода, а также в тексте, обозначая такие про­ граммные элементы, как имена переменных и функций, базы данных, типы данных, переменные окружения, утверждения и ключевые слова. Предисловие

17

ЖИрный моноширинный шрифт Обозначает команды или другой текст, который должен быть введен поль­ зователем. Курсивный моноширинный шрифт Обозначает текст, вместо которого следует подставить пользовательские значения или данные, зависящие от контекста. Этот значок обозначает совет или предложение.

Этот значок обозначает примечание общего характера.

Этот значок обозначает предупреждение или предостере­ жение.

г

j

Примеры кода Примеры проиллюстрированы на языках Solidity, Vyper и JavaScript, а также с ис­ пользованием командной строки в операционной системе семейства Unix. Все фрагменты кода доступны в репозитории GitHub внутри поддиректории code. Вы можете создавать собственные форки 1 кода книги, пробовать использо­ вать примеры кода и отправлять исправления с помощью GitHub: github. com/ ethereumbook!ethereumbook. Все фрагменты кода можно воспроизвести в большинстве операционных систем с минимальной установкой компиляторов, интерпретаторов и библиотек для соответствующих языков. При необходимости мы предоставим простые ин­ струкции по установке и пошаговые примеры их вывода. Некоторые фрагменты кода и вывода были переформатированы для печа­ ти. Во всех таких случаях разрывы строк помечены обратной косой чертой ( \ ), за которой следует переход на новую строку. При наборе таких примеров ' Определение данного понятия дано в разделе «Краткий глоссарий». - Прим. ред.

18

Предисловие

удаляйте эти символы и соединяйте строки; это позволит вам получить резуль­ таты, идентичные тем, которые представлены в книге. Во всех примерах по мере возможности используются реальные значения и вычисления, поэтому вы можете плавно переходить от примера к примеру и получать те же результаты при вычислении одинаковых значений. Например, все приватные и соответствующие им публичные ключи (см. более подробно в соответствующем разделе), а также адреса являются настоящими. Все приме­ ры транзакций, контрактов, блоков и ссылок на блокчейн были добавлены в ак­ туальную блокчейн-сеть Ethereum; они являются частью публичного реестра и доступны для просмотра.

Использование п р имеров кода Эта книга предназначена для того, чтобы помочь вам решать ваши задачи. В це­ лом вы можете использовать в своих программах и документации любые приме­ ры кода, которые здесь встречаются. Если вы не воспроизводите существенную их часть, вам не нужно с нами связываться. Это, например, касается ситуаций, когда вы включаете в свою программу несколько фрагментов кода, которые при­ водятся в этой книге. Однако продажа или распространение CD-ROM с приме­ рами требует отдельного разрешения. Если вы цитируете эту книгу с примера­ ми кода при ответе на вопрос, разрешение не требуется. Вам нужно связаться с нами, если вы хотите включить существенную часть приводимого здесь кода в документацию своего продукта. Мы приветствуем, но не требуем отсылки к оригиналу. Отсылка обычно со­ стоит из названия, имени автора, издательства, ISBN и копирайта. Если вы считаете, что использование вами примеров кода выходит за рамки добросовестного или не соответствует условиям, перечисленным выше, можете обратиться к издателю, используя email [email protected].

Ссылки на ко мпани и и п р оду кты Все ссылки на компании и продукты по тексту книги сделаны в образователь­ ных, демонстрационных и справочных целях. Авторы не выражают свою под­ держку упоминаемым компаниям или продуктам. Они не проверяли опера­ ционную работу или безопасность продуктов, проектов или фрагментов кода, представленных в данной книге. Если вы собираетесь их использовать, то ис­ пользуйте их на свое усмотрение! Предисловие

19

Адр еса и транзакции на Ethereum , указанны е в книге Адреса, транзакции, ключи, QR-коды и данные блокчейн-сети Ethereum, исполь­ зуемые в этой книге, по большей части являются настоящими. Это означает, что вы можете просматривать их на блокчейне, знакомиться с транзакциями, пред­ ложенными в примерах, извлекать их с помощью собственных скриптов или программ и т. д. Тем не менее следует понимать, что приватные ключи, использованные в формировании адресов, которые здесь напечатаны, уже скомпрометированы. Это означает, что деньги, отправленные по этим адресам, будут либо безвозврат­ но утеряны, либо (что более вероятно) кем-то присвоены, так как любой чита­ тель этой книги может забрать их с помощью указанных здесь (по тексту) при­ ватных ключей.

&

НЕ ОТ ПРАВЛЯ Й Т Е ДЕНЬГ И ПО АДРЕСАМ, СОДЕРЖА­ ЩИМСЯ В ЭТ О Й КНИГ Е, иначе ваши средства будут при­ своены другим читателем или безвозвратно утеряны.

Как связаться с А ндреасом Вы можете связаться с Андреасом М. Антонопулосом на его личном сайте: antonopoulos. com Подпишитесь на УоuТuЬе-канал Андреаса: www.youtube. com/aantonop Поставьте лайк на странице Андреаса в Facebook: wwwjacebook.com/ AndreasMAntonopoulos Следите за Андреасом в Twitter: twitter. com!aantonop Поддерживайте связь с Андреасом в Linkedln: linkedin.com/company/aantonop Андреас также хотел бы поблагодарить всех, кто поддерживает его работу сво­ ими ежемесячными пожертвованиями на сайте Patreon. Вы можете присоеди­ ниться к их числу на странице patreon.com!aantonop.

Как связаться с Гэвином Вы можете связаться с д-ром Гэвином Вудом на его личном сайте: gavwood.com Следите за Гэвином в Twitter: twitter.comlgavofyork Гэвина обычно можно найти на канале Polkadot Watercooler в Riot.im: Ьit.ly! 2xciG68 20

1

Предисловие

Бла годарности от А ндреаса Своей любовью к словам и книгам я обязан своей матери, Терезе. Она вырасти­ ла меня в доме, все стены которого были заставлены книгами. Она также купи­ ла мне мой первый компьютер в 1982 году, несмотря на свою самопровозrлашен­ ную технофобию. Мой отец, Менелаос, инженер-строитель, опубликовал свою первую книгу в возрасте 80 лет и был одним из тех, кто научил меня логическому и аналитическому мышлению, а также привил мне любовь к науке и инженерии. Спасибо всем тем, кто поддерживал меня на этом пути.

Бла годарности от Гэ вина Свой первый компьютер я получил от своей матери, когда мне было 9 лет. Он был куплен у соседа и, несомненно, ускорил мое техническое развитие. Мате­ ри я также обязан детской боязнью электричества, в связи с чем хочу поблаго­ дарить Тревора и моих бабушку и дедушку, которые взяли на себя тяжкий долг по «присмотру за тем, как я втыкаю штепсель в розетку>> - без этого вышеупо­ мянутый компьютер был бы бесполезным. Я также должен выразить свою при знательность различным педагогам, у которых мне посчастливилось учиться: от преподавателя в начальной школе, выделившего мне больше уроков програм­ мирования вместо истории, до таких учителей средних классов, как Ричард Фур­ лонr-Браун, благодаря которому я проводил больше времени за компьютером и меньше - играя в регби. Я обязан поблагодарить мать моих детей, Джутту, за ее непрерывную поддерж­ ку и многих других людей в моей жизни, новых и старых друзей, которые помога­ ют мне, грубо говоря, сохранять здравомыслие. Наконец, солидная порция благо­ дарности уходит Аэрону Бьюкенену, без которого последние пять лет моей жизни никогда не сложились бы таким образом. Без его потраченного времени, поддерж­ ки и наставлений эта книга не была бы в таком хорошем состоянии, как сейчас.

Л юди , внесш ие свой в кл ад Многие люди предложили свои комментарии, исправления и дополнения к ран­ нему черновику, доступному на GitHub. Рассмотрением всех этих предложений занялись два редактора-добровольца, которые взяли на себя управление проек­ том, проверку, редактирование, слияние и одобрение заявок и запросов на вклю­ чение изменений: Предисловие

21

- ведущий редактор на GitHub: Франциско Хавьер Рохас Гарсия (*ojasgarcia); - ассистент редактора на GitHub: Уильям Биннс (wbnns). Большой вклад был сделан в такие темы, как DApps, ENS, EVM, история фор­ ков, газ, оракулы, безопасность смарт-контрактов и Vyper. Дополнительный ма­ териал, который ввиду ограничений по времени и объему не вошел в это первое издание, можно найти в папке contrib GitНuЬ-репозитория. Тысячи исправле­ ний и дополнений сделали эту книгу более качественной, понятной и точной. Искреннее спасибо всем, кто помогал! Ниже в алфавитном порядке перечислены все участники проекта на GitHub с указанием их идентификаторов (GitHub ID) в скобках. - Abhishek Shandilya (abhishandy) - Adam Zaremba (zaremba) - Adrian Li (adrianmcli) - Adrian Manning (agemanning) - Alejandro Santander (ajsantander) - Alejo Salles (fiiiu) - Alex Manuskin (amanusk) - Alex Van de Sande (alexvandesande) - Anthony Lusardi (pyskell) - Assaf Yossifoff (assafy) - Ben Kaufman (ben-kaufman) - Bok Кhоо (bokkypoobah) - Brandon Arvanaghi (arvanaghi) - Brian Ethier (dbe) - Bryant Eisenbach (fubuloubu) - Chanan Sack (chanan-sack) - Chris Remus (chris-remus) - Christopher Gondek (christophergondek) - Cornell Blockchain (CornellВlockchain) о Alex Frolov (sashafrolov) о Brian Guo (BrianGuo) о Brian Leffew (Ьleffew99) о Giancarlo Pacenza (GPacenza) о Lucas Switzer (LucasSwitz) о Ohad Koronyo (ohadhl23) о Richard Sun (richardsfc) - Cory Solovewicz (CorySolovewicz) 22

1

Предисловие

- Dan Shields (NukeManDan) - Daniel Jiang (WizardOfAus) - Daniel McClure (danielmcclure) - Daniel Peterson (danrpts) - Denis Milicevic (D-Nice) - Dennis Zasnicoff (zasnicoff) - Diego Н. Gurpegui (diegogurpegui) - Dimitris Tsapakidis (dimitris-t) - Enrico Camblaso ( auino) - Ersin Bayraktar (ersinbyrktr) - Flash Sheridan (FlashSheridan) - Franco Daniel Berdun (fМercury) - Harry Moreno (morenoh l 49) - Hon Lau (masterlook) - Hudson Jameson (Souptacular) - Iuri Matias (iurimatias) - lvan Molto (ivanmolto) - Jacques Daffion (jacquesd) - Jason Hill (denifednu) - Javier Rojas (�rojasgarcia) - Jaycen Horton (jaycenhorton) - Joel Gugger (guggerjoel) - Jon Ramvi (ramvi) - Jonathan Velando (rigzba2 1) - Jules Laine (fakje) - Karolin Siebert (karolinkas) - Kevin Carter (kcar l) - Krzysztof Nowak (krzysztof) - Lane Rettig (lrettig) - Leo Arias ( elopio) - Liang Ма (liangma) - Luke Schoen (ltfschoen) - Marcelo Creimer (mcreimer) - Martin Berger (drmartinberger) - Masi Dawoud (mazewoods) - Matthew Sedaghatfar ( sedaghatfar) - Michael Freeman (stefek99) - Miguel Baizan (mbaiigl) - Mike Pumphrey (bmmpxf)

Предисловие

1

23

- Mobln Hosseini (iNDicatOr) - Nagesh Subrahmanyam (chainhead) - Nichanan Kesonpat (nichanank) - Nick Johnson (arachnid) - Omar Boukli-Hacene (oboukli) - Paulo Trezentos (paulotrezentos) - PetЗrpan (petЗr-pan) - Pierre-Jean Subervie (pjsub) - Pong Cheecharern (Pongch) - Qiao Wang (qiaowang26) - Raul Andres Garcia (manilabay) -. Roger Hausermann (haurog) - Solomon Victorino (Ьitsol) - Steve Кlise (sklise) - Sylvain Tissier (SylТi) - Taylor Masterson (tjmasterson) - Tim Nugent (timnugent) - Timothy McCallum (tpmccallum) - Tomoya Ishizaki (zaq ltomo) - Vignesh Karthikeyan (meshugah) - Will Вinns ( wbnns) - Xavier Lavayssiere (xalava) - Yash Bhutwala (yashbhutwala) - Yeramin Santana (ysfdev) - Zhen Wang (zmxv) - ztz (zt2) Без помощи всех, кто перечислен выше, эта книга не увидела бы свет. Ваш вклад демонстрирует силу Open Source 1 сообщества и культуры открытости. Мы навечно благодарны за вашу помощь. Спасибо.

1

24

Открытый код, или ПО с открытым кодом. - Прим. ред.

1

Предисловие

И сточники В этой книге даются отсылки к разным публичным источникам, в том числе с от­ крытыми лицензиями: github.com/ethereum!vyper!ЫoЬ!master!README. md Тhе МIТ License (МIТ) vyper. readthedocs. io!en!latest/ Тhе МIТ License (МIТ) solidity. readthedocs. io/en!v0. 4.21 /common-patterns. html Тhе МIТ License (МIТ) arxiv.org!pdf/ 1 802. 06038.pdf Arxiv Non-Exclusive-Distribution github. сот!ethereum!solidity!ЫoЬ!release!docs!contracts. rst#inheritance Тhе МIТ License (МIТ) github. com!trailofЬi ts!evm-opcodes Apache 2.0 github.com!ethereum/EIPs/ Creative Commons ССО Ыog.sigmaprime. io!solidity-security. html Creative Commons СС ВУ 4.0

Кр атк ий гл о сса рий Этот краткий глоссарий содержит многие термины, которые используются в кон­ тексте работы с Ethereum. Эти термины можно встретить на страницах данной книги, поэтому сделайте себе закладку, чтобы они всегда были у вас под рукой. - Assert 1 • В языке Solidity инструкция а s s е r t ( f а 1 s е ) компилируется в недействительный опкод O x fe, который расходует весь оставшийся газ (gas) и откатывает все изменения (т. е. возвращает ошибку). Когда выпол­ нение инструкции a s s e r t ( ) завершается неудачей, это говорит о наличии существенной ошибки, это означает, что необходимо исправить код. assert() помогает убедиться, что условия, которые никогда не должны произойти, не произойдут. - ВIР. Предложение по улучшению Bitcoin (англ. Bitcoin lmprovement Proposal). Такие предложения подаются участниками сообщества и направлены на улуч­ шение прокотола Bitcoin. Например, BIP-21 предлагает улучшить унифициро­ ванные идентификаторы ресурсов (URI). - DAO. Децентрализованная автономная организация (англ. Decentralized Autonomous Organization, сокр. DAO). Компания или другая организация, которая функционирует без иерархической модели управления. Может так­ же обозначать одноименный смарт-контракт, запущенный 30 апреля 2016 и взломанный в июне того же года; это событие в итоге послужило мотива­ цией для хардфорка (англ. hardfork, см. далее по тексту определение), т. е. вет­ вление блокчейн-сети (с кодовым именем DAO) от блока № 1192000, которое откатило взломанный контракт и привело к разделению на две независимые системы: Ethereum и Ethereum Classic. - DApp (англ. decentralized application). Децентрализованное приложение. В сущности, это смарт-контракт с пользовательским веб-интерфейсом. В бо­ лее широком смысле DApp - это веб-приложение, построенное поверх от­ крытых децентрализованных пиринговых инфраструктурных сервисов. Кро­ ме того, многие DАрр-приложения содержат децентрализованное хранилище и/или протокол с платформой для обмена сообщениями.

1

26

Assert или assertion (утверждение). - Прим. ред.

Краткий глоссарий

- Deed. Стандарт уникальных токенов (англ. non-fungiЬle token, или NFT), пред­ ложенный в документе ERC72 l. В отличие от ERC20 токены deed содержат до­ казательство владения и не являются взаимозаменяемыми, хотя они не име­ ют никакой юридической силы ни в одной из стран мира, по крайней мере, на сегодня (см. также NFT). - ECDSA. Алгоритм создания цифровой подписи на основе эллиптической кри­ вой (англ. Elliptic Curve Digital Signature Algorithm, сокр. ECDSA). Криптогра­ фический алгоритм, благодаря которому средства в Ethereum могут быть по­ трачены только их владельцем. - EIP. Предложение по улучшению Ethereum (англ. Ethereum lmprovement Proposal, сокр. EIP). Проектный документ, предоставляющий информацию сообществу Ethereum и описывающий предлагаемую реализацию нового функционала, или процесса, или среды. Подробности ищите на странице github.com/ethereum/EIPs (см. также ERC). - ENS. Сервис имен Ethereum (англ. Ethereum Name Service, сокр. ENS). Допол­ нительную информацию можно получить по адресу github.com/ethereum/ens/. - БОА. Учетная запись с внешним владельцем (англ. Externally Owned Account, или ЕОА). У четная запись, созданная пользователем-человеком в блокчейн­ сети Ethereum (или для него). - ERC. Запрос на комментарии в Ethereum (англ. Ethereum Request for Comments, сокр. ERC). Метка, которой маркируются некоторые документы EIP в попыт­ ке определить конкретный способ использования стандарта Ethereum. - Ethash. Алгоритм доказательства выполнения работы для Ethereum 1.0. Боль­ ше информации по адресу github.com!ethereum/wiki/wiki/Ethash. - EVM. Виртуальная машина Ethereum (англ. Ethereum Virtual Machine, сокр. EVM). Виртуальная машина, основанная на стековой архитектуре и выпол­ няющая байт-код. В Ethereum модель выполнения определяет способ изме­ нения состояния системы в зависимости от последовательности инструкций в байт-коде и небольшого массива данных источника в виртуальной среде. Это происходит в рамках формальной модели виртуального конечного ав­ томата 1 . - Faucet. Сервис (контракт faucet), распределяющий средства в виде бесплатно­ го пробного эфира (ether), который может использоваться в тестнете. - Finney. Деноминация эфира. 10 1 5 finney = 1 эфир. 1

Виртуальный конечный автомат предоставляет метод спецификации программного обеспе­ чения для описания поведения системы управления с использованием присвоенных имен свойств элемента управления вводом и действий вывода. - Прим. ред.

Краткий глоссарий

27

- Frontier. Начальная тестовая стадия разработки Ethereum, которая началась в июле 2015 года и продолжалась до марта 2016 года. - Ganache. Персональный блокчейн Ethereum, который можно использовать для проведения тестов, выполнения команд и исследования состояния с со­ хранением контроля над формированием цепочки блоков. - Geth. Go Ethereum. Одна из самых известных реализаций протокола Ethereum, написанная на языке Go. - НD-кошелек. Кошелек, в котором используются иерархически детерминиро­ ванное (англ. hierarchical deterministic, сокр. HD) создание ключа и протокол передачи данных (BIP-32). - Homestead. Второй этап разработки Ethereum, запущенный в марте 2016 года на блоке № 1150000. - ICAP. Протокол совместного обмена клиентскими адресами (англ. Inter­ exchange Client Address Protocol, или ICAP). Кодировка адресов в Ethereum, частично совместимая с IВAN (International Bank Account Number - между­ народный номер банковского счета). Обладает гибкостью, переносимостью и поддержкой контрольных сумм. Адреса ICAP используют новый нацио­ нальный псевдокод IВAN - ХЕ, который расшифровывается как eXtended Ethereum (расширенный Ethereum) и применяется в цифровых валютах, не являющихся юридически значимыми 1 на уровне финансовых систем стран (таких как ХВТ, XRP и ХСР). - Ice Age. Хардфорк (или ветвление) сети Ethereum от блока № 200000 с целью введения экспоненциального повышения сложности (так называемой бом­ бы сложности, англ. difficulty bomb), которое послужило мотивацией для пе­ рехода на алгоритм Ро S. - IDE. Интегрированная среда разработки (англ. Integrated Development Environment, сокр. IDE). Пользовательский интерфейс, который обычно со­ четает в себе редактор кода, компилятор, среду выполнения и отладчик. - IPFS. Межпланетная файловая система (англ. InterPlanetary File System или сокр. IPFS). Протокол, сеть и проект с открытым исходным кодом, нацелен­ ный на создание контентно-адресуемого, пирингового 2 метода хранения и обмена гипермедийными данными в распределенной файловой системе. 1 Для цифровых валют (криптовалют) в различных странах существует определенный ре­ жим, они могут быть как запрещены, так и (условно) не запрещены, или может быть разре­ шено их использование. - Прим. ред. 2

28

От анrл. peer-to-peer. - Прим. ред.

Краткий глосса рий

- KDF. Функция формирования ключа (англ. Кеу Derivation Function, сокр. KDF), известная также как «алгоритм растяжения для паролю). Использует­ ся в форматах хранения ключей для защиты от взлома паролей, основанно­ го на переборе (простом и по словарю) и радужных таблицах, и заключается в многократном хешировании кодовой фразы. - Keccak-256. Криптографическая хеш-функция, используемая в Ethereum. Стандартизирована как алгоритм хеширования SНА-3 1 • - LevelDB. Хранилище с открытым исходным кодом вида «ключ-значение)>, раз­ мещаемое на диске. Реализуется в виде легковесной, узкоспециализирован­ ной библиотеки с возможными интеграциями с разными платформами. - METoken. Расшифровывается как Mastering Ethereum Token. Токен ERC20 ис­ пользуется в этой книге в демонстрационных целях. - Metropolis. Третья стадия разработки Ethereum, запущенная в октябре 2017 года. - Mist. Браузер с поддержкой Ethereum, впервые созданный организацией Ethereum Foundation. Содержит кошелек в браузере, который являлся пер­ вой реализацией стандарта токенов ERC20 (Фабиан Фоrельштеллер - автор ERC20, главный разработчик Mist). Кошелек Mist первым реализовал под­ держку контрольных сумм в формате camelCase (EIP-55; подробнее см. раз­ дел «Шестнадцатеричная кодировка EIP-55» на с. 125). Mist запускает полно­ ценную ноду (узел сети) и является настоящим децентрализованным (DApp) браузером с поддержкой хранилища на основе Swarm и ЕNS-адресов. - NFT. Невзаимозаменяемый (англ. non-fungiЬle) токен (известный также как deed). Этот стандарт предложен в документе ERC721. Токены NFT можно от­ слеживать и обменивать, но каждый из них является уникальным, в отличие от взаимозаменяемых токенов ERC20. Токены NFT могут быть представле­ нием владения цифровыми или физическими активами. - Parity. Одна из самых известных интероперабельных реализаций программ­ ного обеспечения клиентской части Ethereum. - RLP. Рекурсивный префикс· длины (англ. Recursive Length Prefix, сокр. RLP). Стандарт, созданный разработчиками Ethereum для кодирования и сериали­ зации объектов (структур данных) произвольной сложности и длины. - Seed НD-кошелька. Значение, с помощью которого генерируются главный приватный (закрытый) ключ и код цепочки для НD-кошелька. Значение seed кошелька может быть представлено мнемоническими словами, что упрощает 1

Подробнее см. стандарт «SНА-3 Standard: Permutation-Based Hash and ExtendaЬ!e-Output Functions», FIPS 202 (2- 1 5}; http://dx.doi.org/ 10.6028/NIST.FIPS.202. - Прим. ред.

Краткий глоссарий

29

ручную процедуру клонирования, резервного копирования и восстановле­ ния закрытых ключей. - Serenity. Четвертая, заключительная, стадия разработки Ethereum 1 • У стадии Serenity пока нет запланированной даты выпуска. - Serpent. Процедурный (императивный) язык программирования смарт­ контрактов с синтаксисом, похожим на Python. - SHA. Алгоритм криптографического хеширования (англ. Secure Hash Algorithm, сокр. SНА). Семейство криптографических хеш-функций, опуб­ ликованных Национальным институтом стандартов и технологий (англ. National Institute of Standards and Technology). - Solidity. Процедурный (императивный) язык программирования с синтакси­ сом, похожим на JavaScript, С++ или Java. Является самым популярным и вос­ требованным языком для написания смарт-контрактов в Ethereum. Создан доктором Гэвином Вудом (соавтором данной книги). - Spurious Dragon. Хардфорк в блокчейне Ethereum, который произошел на бло­ ке № 2675000 и был направлен на защиту от дополнительных векторов DоS­ атак и очистку состояния (см. также Tangerine Whistle). Также механизм за­ щиты от атаки повторного воспроизведения. - Swarm. Децентрализованная (пиринговая) сеть для хранения данных, кото­ рая вместе с WеЬЗ и Whisper используется для построения приложений DApp. - Szabo. Деноминация эфира (ether). 10 1 2 szabo = 1 эфир. - Tangerine Whistle. Хардфорк в блокчейне Ethereum, который произошел на блоке № 2463000. Был призван изменить способ подсчета газа для опреде­ ленных операций, чувствительных к вводу/выводу, и очистить состояние, на­ копившееся в результате DоS-атаки, которая использовала низкую газовую СТОИМОСТЬ этих операций. - Testnet. Тестнет, или тестовая сеть, сокр. от англ. test network. Сеть, в которой запускаются симуляции основной сети Ethereum. - Truffle. Один из самых часто используемых фреймворков для разработки в Ethereum. - Vyper. Язык программирования высокого уровня, похожий на Serpent, син­ таксис которого напоминает Python. Его основная цель - стать как мож­ но более чистым функциональным языком. Создан Виталиком Бутериным.

1 Serenity, или Ethereum 2.0 (ЕТН2) . На дату публикации книrи уже произошел переход на Ethereum 2.0 1 декабря 2020 rода. - Прим. ред.

30

Краткий глоссарий

- WеЬЗ. Третья версия Интернета, изначально предложенная Гэвином Вудом и на­ целенная на веб-приложения - как с централизованным механизмом владения и управления, так и построенных на основе децентрализованных протоколов. - Wei. Самая мелкая деноминация эфира (ether). 10 1 8 wei = 1 эфир. - Whisper. Децентрализованный (пиринговый) сервис обмена сообщениями. Используется в сочетании с WеЬЗ и Swarm для построения децентрализован­ ных приложений (DApps). - Адрес. Представляет собой ЕОА (см. определение выше. - Авт.) или кон­ тракт, который может принимать (конечный адрес) или отправлять (исход­ ный адрес) транзакции в блокчейн-сети. Если быть более точным, это послед­ ние 160 бит хеша Keccak в публичном ключе ECDSA. - Ассемблер EVM. Язык Ассемблер EVM. Байт-код EVM в человеко-читаемой форме. - Ассемблерная вставка в Solidity. Ассемблер EVM внутри программы на Solidity. Поддержка ассемблерных вставок в Solidity облегчает написание кода для выполнения определенных операций. - Атака повторного воспроизведения (англ. re-entrancy attack). Атака, при ко­ торой контракт злоумышленника вызывает контрактную функцию жертвы таким образом, чтобы во время выполнения та вызывала его рекурсивно. Это, например, может привести к хищению денежных средств путем пропу­ ска участков контракта жертвы, которые обновляют баланс или вычисляют объем суммы списания средств. - Байт-код (англ. bytecode). Набор абстрактных инструкций, предназначенных для эффективного выполнения программным интерпретатором или вирту­ альной машиной. В отличие от исходного человеко-читаемого кода, байт-код выражен в числовом формате. - Библиотека. Контракт особого типа, у которого нет платежных функций и нет хранилища данных. Таким образом, он не может принимать (хранить) эфир или содержать данные. Библиотека играет роль заранее развернутого кода, который другие контракты могут использовать в своих вычислениях без права на запись. - Блок (англ. Ыосk). Набор необходимой информации о транзакциях (заголо­ вок блока), входящих в блок, и набор заголовков других блоков, известных как оммеры (англ. ommers). Блоки добавляются в сеть Ethereum майнерами. - Блокчейн (англ. Ыockchain). В Ethereum это последовательность блоков, про­ веренных системой алгоритмов консенсуса РоW (Proof of work), каждый из ко­ торых указывает на цепочку предыдущих блоков вплоть до генезиз-блока Краткий глоссарий

31

(genesis Ыосk). В отличие от протокола Bitcoin данная сеть не ограничивает максимальный размер блоков; вместо этого используются изменяющиеся лимиты на газ (gas). - Византийский форк (или «Византийское ветвление»). Первое из двух хард­ форков на этапе разработки Metropolis. Включало в себя EIP-649: задерж­ ку бомбы сложности Metropolis и уменьшение награды за блок, когда форк Ice Age (см. ниже) было отложено на год, а награда за блок была уменьшена с 5 до 3 эфиров. - Виталик Бутерин. Русско-канадский программист и писатель, известный в основном как соучредитель Ethereum и журнала Вitcoin Magazine. - Вн_утренняя транзакция (или «сообщение»). Транзакция, отправленная с учетной записи контракта на другую учетную запись или ЕОА. - Вызов сообщения. Передача сообщения от одной учетной записи к другой. Если конечная учетная запись связана с кодом EVM, виртуальная машина за­ пустится в состоянии переданного объекта и с сообщением, с которым нуж­ но произвести какие-то действия. - Газ (англ. gas). Виртуальное «топливо» 1 , которое используется в протоколе Ethereum для выполнения смарт-контрактов. EVM поддерживает учетный механизм, который измеряет расход газа и ограничивает потребление вычис­ лительных ресурсов (см. «Полнота по Тьюрингу»). - Генезис-блок (англ. genesis Ыосk). Первый блок в блокчейн-сети, который ис­ пользуется для инициализации конкретной блокчейн-сети и ее внутренней криптовалюты. - Гэвин Вуд. Британский программист, соучредитель и бывший технический директор Ethereum. В августе 2014 года он предложил язык Solidity - кон­ трактно-ориентированный язык программирования для написания смарт­ контрактов. - Дерево Меркла, или дерево Патриции-Меркла (англ. Merkle Patricia tree). Структура данных, которая используется в Ethereum для эффективного хра­ нения пар «ключ-значение». - Доказательство доли владения, или алгоритм доказательства (доли) владе­ ния (англ. proof of stake или со кр . PoS). Метод, с помощью которого криптова­ лютный протокол блокчейна пытается достичь распределенного консенсуса. PoS просит пользователей доказать, что они владеют определенным объемом

1 Используется как внутренняя единица системы смарт-контрактов. - Прим. ред.

32

Краткий глоссарий

криптовалюты (их «долей» в блокчейн-сети), чтобы они могли участвовать в проверке транзакций. - Доказательство работы, или алгоритм доказательства работы (англ. proof of work или сокр. PoW ). Фрагмент данных (доказательство), нахождение кото­ рого требует существенных вычислительных ресурсов. В Ethereum майнеры (англ. miners) ищут цифровое решение алгоритма Ethash, которое соответ­ ствует общесетевому уровню сложности. - Квитанция (англ. receipt). Данные, возвращаемые клиентом Ethereum для представления результата отдельной транзакции, включая ее хеш, номер ее блока, объем расходованного газа (gas) и адрес контракта, если речь идет о его развертывании. - Компиляция. Преобразование кода, написанного на языке программирова­ ния высокого уровня (таком как Solidity}, в низкоуровневый язык (напри­ мер, байт-код EVM). - Консенсус (англ. consensus). Когда многочисленные узлы (обычно большин­ ство узлов в сети) содержат одни и те же блоки в своих локально проверен­ ных эталонных версиях блокчейна. Не стоит путать с правилами консенсуса. - Константинопольский форк, или константинопольское ветвление (англ. Constantinople fork). Вторая часть этапа Metropolis, которая изначально пла­ нировалась на середину 2018 года. Среди прочего предусматривала переход на гибридный алгоритм консенсуса РоW / Ро S. - Кошелек (англ. wallet). Программное обеспечение, хранящее секретные клю­ чи. Используется для доступа к учетным записям Ethereum и их администрирования, а также для взаимодействия со смарт-контрактами. Чтобы улучшить безопасность, ключи могут находиться не в самом кошельке, а в автономном хранилище (например, на карте памяти или на записке на бумаге). Несмотря на свое название, кошельки никогда не хранят цифровые монеты или токены. - Легкий клиент (aнrл. lightweight client). Клиент для Ethereum, который не хра­ нит локальную копию блокчейна и не проверяет блоки и транзакции. Он пре­ доставляет ПО с функциями кошелька, способен создавать и распространять транзакции. - Лимит на газ. Максимальное количество газа, которое может быть использо­ вано при выполнении транзакции или формировании блока. - Майнер (англ. miner). Сетевой узел, который путем многократного хеширо­ вания находит доказательство работы (РоW ) для новых блоков.

Кратки й глоссарий

33

- Вознаграждение, или награда (англ. reward). Объем эфира, который включа­ ется в каждый блок как вознаграждение от блокчейн-сети для майнера за по­ иск решения PoW 1 • - Нода (узел) (англ. node). Клиент 2 программного обеспечения (программный клиент), который участвует в работе сети. - Нулевой адрес. Специальный адрес в Ethereum, состоящий из одних нулей. Указывается в качестве адреса назначения для транзакций, создающих кон­ тракты. - Одноразовый код (или по англ. nonce). В контексте криптографии - значе­ ние, которое можно использовать только один раз. В Ethereum такие значе­ ния применяются в двух местах: в учетных записях и в счетчиках транзак­ ций для каждой учетной записи, с помощью которых предотвращаются атаки на основе воспроизведения; одноразовый код в алгоритме РоW -это случай­ ное значение в блоке, который был использован для доказательства работы. - Оммер, или оммер-блок (англ. ommer). Дочерний блок предка, который сам не является предком. При нахождении корректного блока другой майнер уже мог опубликовать альтернативный блок, добавленный в конец блокчей­ на. В отличие от Bitcoin, в Ethereum «осиротевшие блоки» могут становиться оммер-блоками в процессе майнинrа и получать частично награду за форми­ рование блока. Термин «оммер» является rендерно-нейтральным и обозна­ чает узел, который находится на одном уровне с родительским узлом; иногда его называют «дядей» (англ. uncle). - Публичный (открытый) ключ (англ. public key). Число, выведенное из приватно­ го (закрытого) ключа с помощью однонаправленной функции. Доступен для пуб­ личного обмена и позволяет кому угодно удостовериться в том, что цифро­ вая подпись сделана с использованием соответствующего приватного ключа. - Полнота по Тьюринrу. Концепция, названная в честь английского математика и специалиста в области информатики Алана Тьюринrа: система правил для манипуляции данными (такая как набор компьютерных инструкций, язык программирования или клеточный автомат) считаетс;:я «тьюринr-полной» или «вычислительно универсальной», если с ее помощью можно симулиро­ вать любую машину Тьюринrа. 1 Майнер получает вознаrраждения за выполнение вычислений соrласно алrоритму РоW, ero получает тот майнер, который сделал быстрее друrих вычисления (т. е. нашел доказатель­ ство работы - PoW), необходимые для формирования новоrо блока в сети. - Прим. ред.

2

«Клиент» - это аппаратный или проrраммный компонент вычислительной системы. -

Прим. ред.

34

Краткий глоссарий

- Порядок от старшего к младшему. Позиционное число, указывающее на то, что старшая цифра должна находиться в начале (известное также как Ьig­ endian). Противоположным ему является порядок от младшего к старшему, при котором число начинается с младшей цифры. - Правила консенсуса (англ. consensus rules). Правила проверки блока, кото­ рым следуют полные узлы, чтобы оставаться в консенсусе с другими узлами. Не стоит путать с определением «консенсуса». - Приватный (закрытый) ключ (англ . private key). См. определение «секретный ключ» (англ. secret key). - Проблема неизменяемости развернутого кода (англ. immutaЫe deployed code proЫem). После развертывания код контракта (или библиотеки) становится неиз­ меняемым. Стандартные методики разработки ПО предусматривают возмож­ ность исправления потенциальных ошибок (bugs) и добавления новых функ­ ций, что представляет определенный челлендж для развития смарт-контрактов. - Публичный (открытый) ключ (англ. puЬlic key). Число, выведенное из при­ ватного (закрытого) ключа с помощью однонаправленной функции. Досту­ пен для публичного обмена и позволяет кому угодно удостовериться в том, что цифровая подпись сделана с использованием соответствующего закры­ того ключа. - Резервная функция, или функция fallback. Функция по умолчанию, которая вызывается в случае отсутствия данных или функции с задекларированным именем. - Сатоши Накамото (англ. Satoshi Nakamoto). Имя (никнейм), использьванное лицом или группой лиц, которые спроектировали Bitcoin, создали его пер­ вую эталонную реализацию и предложили первое решение проблемы двой­ ной траты 1 для цифровой валюты. Его (их) личность остается неизвестной. - Секретный (или закрытый) ключ (англ. secret key). Секретное число, с помо­ щью которого пользователи Ethereum могут доказать, что они владеют учет­ ной записью или контрактами. Для этого генерируется цифровая подпись (см. определения «публичный (открытый) ключ», «адрес», ECDSA). - Сеть (англ. network). Отсылка к сети Ethereum. Пиринговая сеть, которая распространяет транзакции и блоки между всеми своими нодами (узлами) участниками блокчейн-сети. - Синглтон (англ. singleton). Термин из области программирования, описываю­ щий объект, который может существовать лишь в единственном экземпляре. 1

В оригинале: douЫe spending proЫem (англ.). - Прим. ред.

Краткий глоссарий

35

- Сложность. Общесетевой параметр, который определяет, какой объем вы­ числений требуется для доказательства работы (PoW ) 1 • - Смарт-контракт, или контракт (англ. smart contract). Программа, которая вы­ полняется в рамках вычислительной инфраструктуры Ethereum. - Событие (англ. event). Позволяет использовать средства ведения журнала EVM. Децентрализованные приложения (DApps) могут отслеживать события и инициировать с их помощью функции обратного вызова в пользователь­ ском интерфейсе, написанные на JavaScript. Подробнее об этом см. solidity. readthedocs. io/еп/develop!contracts. html#events. - Сообщение. Внутренняя транзакция, которая никогда не сериализуется и пе­ редается лйшь внутри EVM. - Транзакция по созданию контракта. Специальная транзакция с «нулевым ад­ ресом» в качестве получателя. Используется для регистрации контракта и за­ писи его в блокчейн Ethereum (см. «нулевой адрес»). - Транзакция. Данные, зафиксированные в блокчейне Ethereum, инициирован­ ные и подписанные с помощью учетной записи, направленные на определен­ ный адрес. Транзакция содержит метаданные, такие как максимальный объ­ ем газа, который может пойти на ее выполнение. - У четная запись (англ. account). Объект, содержащий адрес, баланс, однора­ зовое число и, при необходимости, хранилище и код. У четная запись мо­ жет принадлежать контракту или иметь внешнего владельца (англ. externally owned account, или ЕОА). - У четная запись контракта (англ. contract account). У четная запись с кодом, который выполняется при получении транзакции от другой учетной записи (ЕОА или контракта). - Файл keystore. Файл в формате JSON с одним (сгенерированным произволь­ ным образом) закрытым ключом. Для дополнительной защиты шифруется с ПОМОЩЬЮ кодовой фразы. - Форк (англ. fork)(uлu ветвление в блокчейн-сети. - Ред.). Изменение в про­ токоле, которое приводит к созданию альтернативной цепочки (блоков), или временное расхождение в двух потенциальных маршрутах блока во время майнинга. - Хардфорк (англ. hardfork) (дословно «жесткое ветвление» блокчейн-сети. Постоянное расхождение в блокчейне. - Ред.). Обычно происходит, ко­ гда необновленные ноды (узлы сети) не могут проверить блоки, созданные 1

36

То есть для работы алгоритма PoW и формирования блока в сети. - Прим. ред.

Краткий глоссарий

обновленными подами, которые следуют более новым правилам консенсуса. Хардфорк не стоит путать со «софтфорком» (англ. softfork, или «мягкое вет­ вление»), ветвлением ПО или ветвлением в Git. - Хеш (англ. hash). Отпечаток ввода произвольного размера, который имеет фиксированную длину и генерируется хеш-функцией. - Цифровая подпись (англ. digital signature). Короткая строка данных, которую пользователь генерирует на основе документа с помощью приватного (за­ крытого) ключа. Любой, у кого есть соответствующий публичный (откры­ тый) ключ, подпись и документ, может удостовериться в том, что (а) доку­ мент «подписан» владельцем конкретного приватного ключа; и (6) он не был изменен после создания подписи. - Энтропия. В контексте криптографии - нехватка предсказуемости или сте­ пень хаотичности. При генерации случайных данных, таких как приватные ключи, обычно алгоритмы используют источник высокой энтропии, чтобы сделать результат (вычислений) непредсказуемым. - Эфир (англ. ether). Стандартизированная криптовалюта, которая использу­ ется в экосистеме Ethereum и покрывает стоимость газа (gas) при исполне­ нии смарт-контрактов. Обозначается символом 8 - прописной греческой буквой кси.

ГЛ АВА 1

Ч то тако е Ethereum? Ethereum часто называют !9е713!1631-.. ■ Ох9е71396За...

Ох81Ы >). Пример 4. 1 . Подтверждение нахождения точки на эллиптической кр ивой с помощью Python Python 3 . 4 . 0 ( de f au l t , Mar 3 0 2 0 1 4 , 1 9 : 2 3 : 1 3 )

[ GCC 4 . 2 . 1 Comp a t i Ы e App le LLVM 5 . 1 ( c lang- 5 0 3 . 0 . 3 8 ) ] on darwin

Туре " he lp " , " copyr i ght " , " c redi t s " o r " l i ce nse " f o r more i n forma t i o n .

> > > р = 1 1 5 7 92 0 8 9 2 3 7 3 1 6 1 9542357 0 98 5 0 0 8 6 8 7 9078532 6 9 9 8 4 66564056403945758

4007 908834 \ 6 7 1 6 6 3

> > > х = 4 9 7 903908252 4 93 8 4 4 8 6033144355 9 1 6 8 6 4 6 0 7 61 608352 0 1 0 1 63 8 6 8 1 4 0 3 9 7 3

7 4 9255924539515

> > > у = 5 9 5 7 4 1 32 1 6 1 8 9 9 9 0 0 0 4 5 8 62 0 8 6 4 9 3 92 1 0 1 5 7 8 0 0 3 2 1 7 5 2 9 1 7 5 5 8 0 7 3 9 9 2 8 4 0 0 7

72105034 1297360 > > > (х

01

**

3 + 7 - - у* *2 ) % р

А рифмети ч еские операции с эллипти ч еской криво й Математика эллиптических кривых во многом выглядит и работает так, как це­ лочисленная арифметика, которую мы выучили в школе. В частности, мы можем определить оператор сложения, который вместо перемещения по численному ряду выполняет переход в другую точку на кривой. Научившись складывать, мы можем также выполнить умножение точки и целого числа (эквивалентное мно­ гократному сложению). Определение эллиптического сложения звучит так: если на эллиптической кривой есть две точки, Р1 и Р2, существует третья точка Р3 = Р1 + Р2 , которая тоже принадлежит этой кривой. В геометрии для вычисления третьей точки нужно провести прямую линию между точками Р1 и Р2• Эта линия пересечет кривую ровно в одном дополнитель­ ном месте (удивительно). Назовем эту точку Р/ = (х, у). Затем отразим ее на оси Х, чтобы получить Р3 = (х, -у) . Если точки Р1 и Р2 совпадают, прямая «между» Р1 и Р2 может быть касатель­ ной по отношению к кривой в точке Р 1 • Она коснется кривой ровно в одной но­ вой точке. Вы можете воспользоваться школьными знаниями математики, чтобы определить наклон касательной линии. Интересно, что эти вычисления работа­ ют даже несмотря на то, что мы ограничиваем наш интерес к точкам кривой дву­ мя целочисленными координатами! Глава 4. Криптография

115

В эллиптической математике есть такое понятие, как «бесконечно удаленная точка», которое по большому счету играет роль ноля в сложении. В информа­ тике оно иногда представлено выражением х = у = О (которое не удовлетворяет уравнение эллиптической кривой, но при этом является отдельным случаем, ко­ торый можно легко проверить). Есть также несколько особых случаев, которые объясняют необходимость в бесконечно удаленной точке. В некоторых случаях (например, когда Р 1 и Р2 имеют одинаковые значения х, но разные у) прямая оказывается строго вертикальной, в результате чего Р3 ста­ новится бесконечно удаленной точкой. Если бесконечно удаленной точкой является Р1 , тогда Р1 + Р2 = Р2 • Аналогично, если Р2 - бесконечно удаленная точка, тогда Р1 + Р2 = Р,. Это показывае·т, как беско­ нечно удаленная точка играет ту же роль, что и ноль в «нормальной» арифметике. Оказывается, операция + является ассоциативной, поэтому (А + В) + С = А + (В + С). Это означает, что мы можем явно записать А + В + С (без скобок). Определив сложение, мы можем определить на его основе умножение, как это обычно делается. Если Р - точка на эллиптической кривой, а k - целое число, тогда k * Р = Р + Р + Р + . . . + Р (k раз). Обратите внимание на то, что в этом слу­ чая k иногда (возможно, ошибочно) называют «экспонентой».

Генераци я публи ч ного клю ч а Вначале у нас есть приватный ключ в виде случайно сгенерированного числа k. Далее мы умножаем его на заранее определенную точку на кривой, которая на­ зывается точкой генерации G. В итоге мы получим еще одну точку на той же кри­ вой, которая соответствует открытому ключу К: K=k *G Точка генерации определяется как часть стандарта s e cp 2 5 6 k l и является общей для всех его реализаций, а все ключи, полученные из этой кривой, ис­ пользуют одну и ту же точку G. Поскольку точка генерации является одинако­ вой для всех пользователей Ethereurn, операция умножение приватного ключа k на G всегда дает один и тот же публичный ключ К. Отношение между k и К яв­ ляется постоянным, но его можно вычислить только в одном направлении, от k до К. В связи с этим адресом Ethereurn (выведенным из К) можно делиться с кем угодно, не раскрывая при этом приватный ключ пользователя (k). Как уже упоминалось в предыдущем разделе, умножение k * G эквивалент­ но многократному сложению, то есть выражению G + G + G + . . . + G, повторен­ ному k раз. 116

Публичные кл ючи

Приватный ключ можно преобразовать в публичный, но не наоборот, поскольку математическая функция работает только в одном направлении.

Давайте применим эти вычисления, чтобы найти публичный ключ для при­ ватного ключа, показанного в разделе «Приватные ключи» на с. 108. К = f 8 f8a2 f 4 3 c 8 3 7 6ccb 0 8 7 1 3 0 5 0 6 0d7b2 7b0 5 5 4d2cc7 2bcc f 4 1 b2 7 0 5 6 0 8 4 5 2 f 3 1 5 * G

Криптографическая библиотека поможет нам вычислить К, используя опе­ рацию с эллиптической кривой. Полученный публичный ключ К определяет­ ся в виде точки: К

(х,

у)

где х = 6e l 4 5cce f l 0 3 3dea2 3 9 8 7 5dd0 0 d f b 4 fee 6e 3 3 4 8b 8 4 9 8 5 c 9 2 f l 0 3 4 4 4 6 8 3bae 0 7 b у

8 3b 5 c 3 8 e 5 e2 b 0 c 8 5 2 9d7 f a 3 f 6 4 d 4 6daa l e c e 2 d 9 a c l 4 cab 9 4 7 7 d0 4 2 c 8 4 c 3 2 c cd0

Публичные ключи в протоколе Ethereum могут быть представлены в виде по­ следовательности (сериализации) 130 шестнадцатеричных символов (65 байт). Данный метод позаимствован из стандартного формата сериализации, пред­ ложенного промышленным консорциумом Standards for E fficient Cryptography Group ' и описанного в документе Standards for E fficient Cryptography 2 • Стандарт определяет четыре префикса, которые можно использовать для идентификации точек на эллиптической кривой. Они перечислены в табл. 4.1. Табли ца 4. 1. Сериализованные префиксы для открытых ключей стандарта ЕС П реф икс ОхО О Ох0 4 Ох02 ОхОЗ

Значение

Бесконечно удаленная точка Несжатая точка

Сжатая точка с четным у Сжатая точка с нечетным у

Длина (в байта х, вкл ю чая преф икс) 1 65 33 33

1

Сокр. SECG. - Прим. ред. 2 Сокр. SEC l ; www.secg.org/secl -v2.pdf. - Прим. ред.

Глава 4. Криптография

117

В Ethereum используются только несжатые публичные ключи; поэтому нас интересуеттолько (шестнадцатеричный) префикс 0 4. Сериализация соединяет координаты х и у, принадлежащие публичному ключу: 0 4 + координата-х ( 3 2 байт / 6 4 шестн . ) + координата-у ( 3 2 байт / 6 4 шестн . )

Таким образом публичный ключ, который мы вычислили ранее, сериализу­ ется как 0 4 6e 1 4 5 c ce f 1 0 3 3dea2 3 9 8 7 5dd0 0 d fb 4 fe e бe 3 3 4 8 b 8 4 9 8 5 c 9 2 f 1 0 3 4 4 4 6 8 3bae 0 7 b8 3b5

с 3 8 е 5 е 2 Ь 0 \ c 8 5 2 9d7 f a 3 f 6 4 d 4 6daa l e c e 2 d 9 a c 1 4 cab 9 4 7 7 d0 4 2 c 8 4 c 3 2 ccd0

Б иблиотеки эллиптической криптографии У эллиптической кривой s e cp 2 5 6 k l есть несколько реализаций, которые ис­ пользуются в криптографических проектах. - OpenSSL. Библиотека OpenSSL предлагает комплексный набор криптогра­ фических концепций, включая полноценную реализацию s е ер 2 5 6 k 1 . Например, для получения публичного ключа следует использовать функ­ цию Е С P O I NT_mu l. - libsecp256kl. Реализация эллиптической кривой s e cp 2 5 6 k l и других криптографических концепций на языке С. Написана с нуля, чтобы заме­ нить OpenSSL в проекте Bitcoin Core, и считается более производитель­ ной и безопасной.

Криптографи ческие функции хе ш иро вани я Криптографические функции хеширования повсеместно используются как в Ethereum, так и в почти любой криптографической системе. Этот факт отме­ тил Брюс Шнайер, который однажды сказал: «Односторонние функции хеширо­ вания являются рабочими лошадками современной криптографии в куда боль­ шей степени, чем алгоритмы шифрования». В этом разделе мы обсудим функции хеширования, исследуем их основные свойства и посмотрим, за счет чего они стали такими полезными во многих обла­ стях современной криптографии. Функции хеширования рассматриваются здесь по причине того, что с их участием в Ethereum выполняется преобразование 118

Криптографические функции хеширования

публичных ключей в адреса. Их также можно использовать для создания цифро­ вых отпечатков, которые помогают проверять подлинность данных. Говоря простым языком, функцией хеширования является «любая функция, которая может привязать данные произвольного размера к данным фиксиро­ ванного размера». Ее ввод (input) называют прообразом, или сообщением, или просто входящими данными. Ее вывод (output) называется хешем. Отдельную подкатегорию составляют криптографические функции хеширования; они об­ ладают определенными свойствами, которые являются полезными для обеспе­ чения защиты платформ, таких как Ethereum. Криптографической называют одностороннюю функцию хеширования, ко­ торая привязывает данные произвольного размера к строке битов фиксирован­ ной длины. Благодаря «односторонности)), воссоздание входящих данных на ос­ нове итогового хеша является неосуществимым с вычислительной точки зрения. Единственный способ определить возможный ввод заключается в поиске мето­ дом простого перебора с проверкой каждого потенциального варианта; посколь­ ку пространство поиска является практически бесконечным, невыполнимость этой задачи становится очевидной. Даже если вы найдете какие-то входящие данные, которые выдают подходящий хеш, они могут не совпасть с оригиналом: функции хеширования работают по принципу «многие-к-одному)). Нахождение двух наборов данных, которые хешируются с одним и тем же результатом, на­ зывается коллизией хеширования. Грубо говоря, чем лучше функция хеширова­ ния, тем реже возникают коллизии хеширования 1 . В случае с Ethereum они прак­ тически исключены. Давайте поближе рассмотрим основные свойства криптографической функ­ ции хеширования, включая следующие. - Детерминизм. Заданное входящее сообщение всегда дает один и тот же хеш. - Проверяемость. Вычисление хеша сообщения является эффективным (линейная сложность). - Некорреляционность. Небольшое изменение в сообщении (например, мо­ дификация одного бита) должно поменять итоговый хеш до такой степе­ ни, чтобы он не коррелировал с хешем оригинального сообщения. - Необратимость. Вычисление сообщения из хеша является невыполнимым и эквивалентным поиску путем простого перебора всех возможных сооб­ щений. - Защита от коллизий. Вычисление двух разных сообщений, которые дают один и тот же хеш, должно быть невозможным. 1

Коллизии хеш-функции. - Прим. ред.

Глава 4. Криптография

11 9

Устойчивость к коллизиям хеширования особенно важна для обеспечения защиты от подделки цифровых подписей в Ethereum. Сочетание этих свойств делает криптографические функции хеширования подходящими для широкого диапазона задач, связанных с безопасностью, включая: - цифровые отпечатки; - целостность сообщений (обнаружение ошибок). - доказательство работы (PoW }; - аутентификацию (хеширование паролей и растяжение ключа}; - генераторы псевдослучайных чисел; - фиксацию обмена сообщениями 1 (работу механизмов исполнения обязательств}; - уникальные идентификаторы. Со многими из этих свойств мы встретимся при постепенном разборе раз­ личных системных уровней Ethereum.

Криптографич еская функц ия хеширован ия в Ethereum : Keccak-256 Во многих частях Ethereum используется криптографическая функция хеши­ рования Keccak-256. Она разрабатывалась в качестве одного из кандидатов в конкурсе на стандартизацию SНА-3, который проводился Национальным ин­ ститутом науки и технологий (NIST) 2 в 2007 году. Алгоритм Keccak оказался победителем и стал частью федерального стандарта по обработке информации (FIPS} 3 202 в 2015 году. Однако на ранних этапах развития Ethereum стандартизация института NIST еще не была завершена. После завершения этого процесса некоторые параме­ тры алгоритма Keccak были немного отрегулированы - как утверждается, для повышения их эффективности. Одновременно с этим героический разоблачи тель Эдвард Сноуден разгласил документы, согласно которым институт NIST под влиянием ЦРУ намеренно ослабил стандарт генерации случайных чисел Dual_ EC_DRВG, чем, по сути, создал в нем лазейку в виде «бэкдора» 4. В результате это­ го скандала у предложенных изменений к стандарту появилось много против­ ников, а стандартизация SНА-3 значительно затянулась. В то время организация 1

Анrл.: message commitment. - Прим. ред.

2

А!fГЛ.: National Institute of Science and Technology. - Прим. ред.

3

Анrл.: Federal Information Processing Standard. - Прим. ред. «Потайная дверь», англ. backdoor. - Прим. ред.

4

120

Кри птогра ф ические фун кц ии хеширования

Ethereum Foundation решила реализовать оригинальный алгоритм Keccak, пред­ ложенный его создателями, отказавшись тем самым от стандарта SНА-3, кото­ рый был модифицирован институтом NIST.

,- - - - - i 1

Вы можете заметить, что SНА-3 упоминается ,в документации и коде Ethereum, но на самом деле во многих из этих случаев (если не во всех) имеется в виду Keccak-256, а не окончательный стандарт FIPS-202 SНА-3. Разница в реализации совсем небольшая и относится к параметрам отступов, но из-за нее Keccak-256 и FIPS-202 SНА-3 выдают разные хеши для одного и того же ввода (input).

Какую функци ю хе ш ировани я вы испол ьзуете ? Если и FIPS-202 SНА-3, и Keccak-256 могут называться SНА-3, как узнать, какой именно алгоритм реализует ваша программная библиотека? Чтобы ответить на этот вопрос, можно использовать тестовый вектор, ожи­ даемый вывод (output) для заданного ввода (input). Для функций хеширования чаще всего выполняют проверку с пустым вводом (empty input). Если передать функции хеширования пустую строку, результаты будут следующими: Keccak2 5 6 ( " " ) =

c 5 d2 4 6 0 1 8 6 f 7 2 3 3 c 9 2 7 e 7 db2dc c 7 0 3 c 0 e 5 0 0b 6 5 3 c a 8 2 2 7 3b 7 b f ad8 0 4 5 d 8 5 a 4 7 0

S НА З ( " " )

=

a7ffc б f 8b f l ed7 6 6 5 l c l 4 7 5 6 a 0 6 l d 6 6 2 f 5 8 0ff4de 4 3b 4 9 fa 8 2 d 8 0 a 4 b 8 0 f 8 4 3 4 a

Эта простая проверка позволяет определить, что собой представляет ваша функция, независимо от ее названия: оригинальный алгоритм Keccak-256 или окончательный стандарт FIPS-202 SHA-3, одобренный NIST. Помните, что Ethereum использует Keccak-256, хотя в коде он часто называется SНА-3. В связи с путаницей, возникшей из-за отличий между функ­ цией хеширования, которая используется в Ethereum (Keccak-256), и окончательным стандартом (FIP-202 SНА-3), предпринимаются попытки заменить все упоминания s h a З в исходном коде, опкодах и библиотеках на ke c c a k 2 5 6 . По­ дробности см. в документе ERC59 (github.com/ethereum/EIPs/ issues/59). Глава 4. Криптография

121

Теперь давайте рассмотрим сценарий, в котором алгоритм Keccak-256 был впервые задействован в Ethereum: генерация адресов из публичных ключей.

Адреса Ethereum Адреса Ethereum представляют собой уникальные идентификаторы, которые ге­ нерируются на основе публичных ключей или контрактов с использованием од­ нонаправленной функции хеширования Keccak-256. В предыдущих примерах мы начали с приватного ключа и затем с помощью умножения эллиптической кривой сформировали публичный (открытый) ключ. Приватный ключ k: k = f 8 f 8 a 2 f 4 3 c 8 3 7 6 c c b 0 8 7 1 3 0 5 0 6 0 d 7 b 2 7 b 0 5 5 4 d 2 c c 7 2 b c c f 4 1 b2 7 0 5 6 0 8 4 5 2 f 3 1 5

Публичный ключ К (координаты х и у объединяются и выводятся в шестна­ дцатеричном виде): К = 6 e 1 4 5 cce f 1 0 3 3 d e a 2 3 9 8 7 5dd0 0 d fb 4 fee бe 3 3 4 8b 8 4 9 8 5 c 9 2 f l 0 3 4 4 4 6 8 3bae 0 7b 8 ЗЬ 5 с 3 8 е 5 е ...

Стоит отметить, что после вычисления адреса из публичного ключа убирается (шестнадцатеричный) префикс 0 4. �

Воспользуемся Keccak-256, чтобы вычислить хеш данного публичного ключа: Ke c c a k2 5 6 ( K ) = 2 a 5bc 3 4 2 e d 6 1 6b5ba 5 7 3 2 2 6 9 0 0 l d 3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7b d 3 e c f l f 0 8 6ba 0 f 9

Сохраняя только последние (младшие) 20 байт (хеша), получим наш Ethereum адрес: 0 0 1 d 3 f l e f 8 2 7 5 5 2 ae 1 1 1 4 0 2 7bd3 e c f l f 0 8 6b a 0 f 9

Чаще всего Ethereum адреса записываются с префиксом О х, который ука­ зывает на то, что они закодированы в шестнадцатеричной системе. Например: O x 0 0 1 d3 f l e f 8 2 7 5 5 2 a e l l l 4 0 2 7bd3 e c f l f 0 8 6ba 0 f 9

1 22

Адреса Ethereum

Форматы адресов Ethereum Адреса Ethereum являются шестнадцатеричными числами - идентификатора­ ми, которые получены из последних 20 байт хеша Keccak-256 публичного ключа. В отличие от адресов Bitcoin, которые во всех клиентах с пользовательским интерфейсом включают в себя встроенную контрольную сумму, предотвращаю­ щую опечатки в адресах, Ethereum-aдpeca представлены в виде обычных шест­ надцатеричных чисел без каких-либо контрольных сумм. Это решение обусловлено тем, что адреса Ethereum рано или поздно бу­ дут скрыты за абстракциями ( такими как сервисы наименований, англ. name services) на более высоких уровнях системы, где при необходимости уже можно будет добавлять контрольные суммы. В реальности эти более высокие уровни разрабатывались слишком медленно, в результате чего на ранних стадиях развития экосистемы такое архитектурное решение привело к ряду проблем, включая потерю денежных средств из-за опе­ чаток в адресах и ошибок валидизации ввода. Более того, поскольку темпы раз­ работки сервисов наименований Ethereum оказались медленнее, чем ожидалось вначале, авторы кошельков сильно задерживали внедрение альтернативных ко­ дировок. Далее мы рассмотрим несколько доступных вариантов кодирования.

Протокол обмена клиентскими адресами Протокол обмена клиентскими адресами (ICAP) 1 - это кодировка адресов Ethereum, частично совместимая с форматом международных банковских сче­ тов (IBAN) 2. Она обеспечивает гибкое кодирование адресов Ethereum с под­ держкой контрольных сумм и переносимости. ICAP может кодировать адре­ са Ethereum или обычные имена, зарегистрированные в реестре имен Ethereum. Больше об этом протоколе можно узнать на странице Ethereum Wiki (github.com/ ethereumlwikilwiki!Inter-exchange-Client-Address-Protocol-(ICAP)). IВAN - это международный стандарт идентификации банковских счетов, который в основном используется для денежных переводов. Он широко распро­ странен в единой зоне платежей в евро (SEPA} 3 и за ее пределами. Это централи­ зованный и строго регулируемый сервис. Проток ICAP является совместимой, но при этом децентрализованной реализацией для адресов Ethereum.

1 2

3

Англ.: Inter exchange Client Address Protocol. - Прим. ред. Англ.: International Bank Account Number. - Прим. ред. Англ.: Single Euro Payments Area. - Прим. ред.

Глава 4. Криптография

123

IВAN представляет собой алфавитно-цифровую строку длиной не более 34 символов (с учетом регистра), состоящую из кода страны, контрольной сум­ мы и идентификатора банковского счета (формат которого зависит от страны). ICAP использует ту же структуру, но с нестандартным кодом страны ХЕ, ко­ торый обозначает Ethereum. Далее идет двухсимвольная контрольная сумма и идентификатор учетной записи в одном из трех возможных форматов. - Прямой. Целое число в формате base-36 с порядком следования байт от старшего к младшему. Может достигать 30 алфавитно-цифровых сим­ волов и представляет 155 младших бит адреса Ethereum. Поскольку эта ко­ дировка не занимает все 160 бит обычного адреса Ethereum, она подходит только для тех адресов, которые начинаются с одного или нескольких ну­ левых байт. Ее преимущество заключается в том, что по длине своих по­ лей и контрольной сумме она совместима с IBAN. Пример: XE б 0 HAMI C D XSV5 QXVJA 7 T JW 4 7 Q 9 CHWKJD (33 символа). - Простой. Тот же самый формат, что и прямая кодировка, только длиной в 31 символ. Это позволяет закодировать любой адрес Ethereum, но в ре­ зультате теряется совместимость с проверкой полей в IВAN. Пример: ХЕ 1 8 CHDJB PLTBCJ0 3 FE 9O2NS 0 B POJVQCU2 P (35 символов). - Непрямой. Кодирует идентификатор, связанный с адресом Ethereum че­ рез провайдера регистра имен 1 • Использует 16 алфавитно-цифровых сим­ волов, содержащих идентификатор активов (например, ЕТ Н), сервис на­ именований (например, XREG) и 9-символьное имя, понятное человеку (например, KIТ TYCATS). Пример: XE##E THXRE GK I T TYCAT S (20 сим­ волов), где вместо ## следует подставить два символа вычисленной кон­ трольной суммы. Для создания адресов ICAP можно использовать утилиту командной строки helpe th. Давайте для эксперимента возьмем наш пример приватного ключа. Добавим к нему префикс О х и передадим его в качестве параметра для he lpeth: $ helpeth keyDetail s \

- р Oxf8f8a2f43c837 6ccЬ0 8 7 1 305060d7b2 7b0554d2cc72bccf4 1b2705608452f315 Addre s s : O x 0 0 l d3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7 bd3e c f l f 0 8 6ba 0 f 9 I CAP : ХЕ б О НАМI CDXS V S QX VJA7 T JW 4 7 Q 9 C HWKJ D

PuЬ l i c key : O x бe l 4 5 c ce f l 0 3 3dea2 3 9 8 7 5dd0 0 d fb 4 fee бe 3 3 4 8 b 8 4 9 8 5 c 9 2 f l 0 3 4

4 4 6 8 3Ьае 0 7 Ь ... 1

Англ.: name registry provider. - Прим. ред.

1 24

Адреса Ethereum

Команда helpeth сформирует для нас два адреса: шестнадцатеричный (для Ethereum) и в формате ICAP. Последний в нашем случае выглядит так: XE 6 0 HAМ I C DXSV5QXVJA 7 T JW4 7 Q 9CHWKJD

Так получилось, что наш демонстрационный адрес Ethereum начинается с ну­ левого байта, поэтому его можно закодировать с помощью прямой кодировки ICAP, совместимой с форматом IВAN. Все благодаря тому, что длина адреса рав­ на 33 символам. Если бы наш адрес не начинался с ноля, то он был бы представлен в простой кодировке длиной в 35 символов, которая несовместима с IВAN. Вероятность того, что адрес Ethereum начинается с ноля, состав­ ляет 1 к 256. На генерацию такого адреса ICAP, который совме­ стим с IВAN, в среднем уходит 256 попыток с 256 разными при­ ватными ключами, сгенерированными случайным образом. К сожалению, на сегодня ICAP поддерживается лишь несколькими кошель­ ками.

Шестнадцатеричная кодировка (EIP-55) 1 Ввиду медленного распространения ICAP и сервисов наименований был пред­ ложен стандарт EIP-55 (github.com/Ethereum!EIPs!ЬloЬ/master/EIPS/eip-55. md). Он предусматривает обратно совместимую контрольную сумму для адресов Ethereum за счет изменения подхода к верхнему регистру шестнадцатерич­ ных адресов. Идея в том, что адреса Ethereum не являются чувствительными к регистру, и все кошельки должны игнорировать разницу между прописными и строчными буквами. Изменив подход к интерпретации регистров алфавитных символов, мы мо­ жем передать контрольную сумму, которая способна оградить адрес от опечаток и ошибок чтения. Кошельки, не поддерживающие контрольные суммы EIP-55, просто игнорируют тот факт, что адреса содержат символы в разных регистрах; а все остальные получают в свое распоряжение метод обнаружения ошибок в ад­ ресах с точностью в 99,986%.

1

Стандарт EIP-55 - шестнадцатеричная кодировка с контрольной суммой в верхнем реги­ стре. - Прим. ред.

Глава 4. Криптография

125

Смешение регистров в кодировке сначала можно даже не заметить. Наш де­ монстрационный адрес выглядит так: O x 0 0 l d3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7bd3 e c f l f 0 8 6ba 0 f 9

После добавления контрольной суммы EIP-55 со смешением регистров он превращается в O x O O l d 3 F l e f 8 2 7 5 5 2 Ae l l l 4 0 2 7BDЗEC F l f 0 8 6bA O F 9

Видите разницу? Некоторые алфавитные символы (A-F) из шестнадцатерич­ ной кодировки стали прописными, а другие остались строчными. Стандарт EIP-55 довольно прост в реализации. Мы получаем хеш Keccak-256 из шестнадцатеричного адреса в нижнем регистре. Он служит цифровым от­ печатком адреса с удобной контрольной суммой. Малейшее изменение ввода (input адреса) должно полностью изменить итоговый хеш (контрольную сум­ му), что делает возможным эффективное обнаружение ошибок. Затем хеш на­ шего адреса кодируется в его прописных буквах. Давайте разберем этот процесс шаг за шагом. 1. Хешируем адрес в нижнем регистре, без префикса О х: Ke c c a k2 5 6 ( " 0 0 l d 3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7 bd3 e c f l f 0 8 6 b a 0 f 9 " ) = 2 3 а б 9 c l 6 5 3 e 4 ebbb 6 1 9 b 0 b 2 cb 8 a 9bad4 9 8 9 2 a 8 b 9 6 9 5 d 9 a l 9 d 8 f 6 7 3 ca 9 9 l d e a e l

2. Переводим каждый алфавитный символ адреса в верхний регистр, если соответствующая шестнадцатеричная цифра хеша больше или равна Ох 8. Для наглядности запишем адрес и хеш в столбик: Адре с : 0 0 l d3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7 bd3ec f l f 0 8 6ba 0 f 9 Хеш : 2 3 a 6 9 c l 6 5 3 e 4 ebbb 6 1 9b0b2 cb 8 a 9bad4 9 8 9 2 a 8b 9_

Четвертый символ нашего адреса равен d. Четвертый символ хеша равен 6, что меньше 8. Таким образом, d остается в нижнем регистре. Следующий алфа­ витный символ в нашем адресе, f, идет шестым. Шестой символ шестнадцате­ ричного хеша равен с, что больше 8. В связи с этим мы переводим F в верхний регистр и т. д. Как видите, для контрольной суммы используются лишь пер­ вые 20 байт (40 шестнадцатеричных символов) хеша, поскольку только 20 байт (40 шестнадцатеричных символов) адреса можно сделать прописными. 1 26

Адреса Ethereum

Сами взгляните на итоговый адрес со смешением регистров и попытайтесь определить, какие символы стали прописными и с какими символами хеша они соотносятся. Адре с : O O l d 3 F l e f 8 2 7 5 5 2Ae l l l 4 0 2 7BDЗEC F l f 0 8 6bA O F 9 Хеш : 2 3 а 6 9 с 1 6 5 3 е 4 еЬЬЬ б 1 9Ь0Ь2сЬ8 а 9Ьаd4 9 8 9 2 а 8 Ь 9 ...

Обнаружение о ш и бки в ад ресе, закоди рованном с п ом о щью EIP-SS Теперь давайте посмотрим, как адреса формата EIP-55 помогают с поиском оши­ бок. Предположим, что мы напечатали Ethereum адрес в кодировке EIP-55: O x 0 0 l d 3 F l e f 8 2 7 5 5 2 Ae l l l 4 0 2 7BDЗEC F l f 0 8 6bA O F 9

Теперь давайте допустим простую ошибку при его чтении. Представьте, что мы прочитали предпоследний символ, прописную F, как прописную Е. В итоге мы (по ошибке) введем в наш кошелек следующий адрес: O x 0 0 l d 3 F l e f 8 2 7 5 5 2 Ae l l l 4 0 2 7 B D 3 E C F l f 0 8 6bAO E 9

К счастью, наш кошелек следует стандарту EIP-55! Он заметит смешение регистров и попытается проверить корректность адреса. Он преобразует его в нижний регистр и вычислит хеш контрольной суммы: Ke ccak2 5 6 ( " 0 0 l d 3 f l e f 8 2 7 5 5 2 ae l l l 4 0 2 7bd3e c f l f 0 8 6b a 0 e 9 " ) fb4Ы l a f 9cb8 8b7bb 7 6d8 9 2 8 8 6 2 e 0 a 5 7 d 4 6dd l 8 dd8 e 0 8 a 6 9 2 7

5 4 2 9b 5 d 9 4 6 0 1 2 2

Как видите, несмотря на изменение лишь одного символа (на самом деле одного бита, так как е и f разделяет лишь один бит), хеш адреса поменялся до неузнаваемости. Это свойство функций хеширования делает их очень полез­ ными для создания контрольных сумм! Теперь давайте запишем в столбик два значения и проверим регистры: O O l d3F l e f 8 2 7 5 5 2 Ae l l l 4 0 2 7 B D 3 E C F l f 0 8 6bA O E 9 5 4 2 9b 5 d 9 4 6 0 1 2 2 fb4 Ы l a f 9 cb 8 8 b 7

Ь Ь7 бd8 92 88 6 ...

Видим полное несоответствие! Несколько алфавитных символов ошибочно переведены в верхний регистр. Как вы помните, вверху приводится кодировка корректной контрольной суммы. Глава 4. Криптография

127

Введенный нами адрес с переводом в верхний регистр не совпадает с кон­ трольной суммой, которую мы только что вычислили. Это означает, что адрес был изменен, то есть допущена ошибка.

Выводы В этой главе мы провели краткий обзор ассиметричной криптографии, сосредо­ точившись на применении публичных и приватных ключей в Ethereum, а также на использовании таких криптографических инструментов, как функции хеши­ рования для создания и проверки Ethereum-aдpecoв. Помимо этого, мы рассмо­ трели цифровые подписи и узнали, как с их помощью подтвердить факт вла­ дения приватным ключом без его раскрытия. В главе 5 мы совместим все идеи на практике и посмотрим, как управлять наборами ключей с использованием кошельков.

ГЛАВА 5

Ко шел ь ки В Ethereum слово «кошелек» (wallet) используется для описания разных вещей. На высоком уровне кошелек представляет собой приложение, которое слу­ жит основным пользовательским интерфейсом к Ethereum. Он обеспечива­ ет контроль доступа к деньгам пользователя, занимаясь управлением ключа­ ми и адресами, отслеживанием баланса, созданием и подписанием транзакций. Кроме того, некоторые кошельки Ethereum способны взаимодействовать с кон­ трактами, такими как токены ERC20. В более узком смысле, с точки зрения программиста, слово «кошелек» обозна­ чает систему для хранения и администрирования ключей. У каждого кошелька есть компонент для управления ключами. Иногда они этим и ограничиваются. Но есть кошельки, которые попадают в куда более широкую категорию под на­ званием браузеры; это интерфейсы для децентрализованных приложений на ос­ нове Ethereum (DApp), которые более подробно будут рассмотрены в главе 12. Вместе с тем нельзя провести четкие границы между разными категориями про­ грамм, объединенных в термин «кошелек». В этой главе кошельки рассматриваются в качестве контейнеров для приват­ ных ключей и систем для управления этими ключами.

О бзор тех нологий кошельков В этом разделе мы пройдемся по различным технологиям, которые используют­ ся для построения удобных, безопасных и гибких кошельков Ethereum. Одним из ключевых моментов при проектировании кошельков является баланс между удобством и конфиденциальностью. Самый удобный кошелек Ethereum содержит лишь один приватный ключ, который используется для лю­ бых задач. К сожалению, с точки зрения безопасности такое решение - сплош­ ной кошмар, поскольку кто угодно может с легкостью отследить и сопоставить все ваши транзакции. Наиболее конфиденциальный способ состоит в использо­ вании нового ключа для каждой транзакции, но в результате управление клю­ чами становится очень сложным. Достичь правильного баланса может быть Глава 5 . Ко шельки

12 9

непросто, именно поэтому на первый план выходит хорошая архитектура ко­ шелька. Популярным является заблуждение о том, что кошельки в Ethereum содер­ жат эфир и токены. На самом же деле кошельки, строго говоря, хранят только ключи. Эфир и другие токены записываются в блокчейн Ethereum. Для управле­ ния своими токенами в сети пользователи подписывают транзакции с помощью ключей, которые находятся в их кошельках. В этом смысле кошелек Ethereum яв­ ляется связкой ключей. Тем не менее на практике эта разница довольно незна­ чительная, поскольку все, что нужно для передачи эфира или токенов другим участникам, это ключи, которые хранятся в кошельке. Где отличия начинают иг­ рать существенную роль, так это в изменении образа мышления при переходе от общепринятых централизованных банковских систем (когда только вы и ваш банк можете видеть деньги на вашем счете и для выполнения транзакции вам нужно лишь убедить свой банк в том, что вы хотите переместить свои средства) к децентрализованным платформам на основе блокчейна (когда любой может увидеть баланс на вашем счете, хотя никто, скорее всего, не знает, кому этот счет принадлежит, и для выполнения транзакции все должны быть уверены в том, что владелец действительно хочет переместить свои средства). На практике это означает, что у вас есть независимый способ проверки баланса на счете без ис­ пользования кошелька, который к нему привязан. Кроме того, если вам переста­ ло нравиться определенное приложение, вы можете передать управление своей учетной записью от одного кошелька к другому.



Кошельки Ethereum содержат только ключи, а не эфир или то­ кены. Их можно представить в виде связки приватных и пуб­ личных ключей. Подписывая транзакцию с помощью приват­ ного ключа, пользователь доказывает факт владения эфиром. Сам эфир хранится в блокчейне.

Кошельки делятся на две основные категории в зависимости от того, связа­ ны ли между собой хранящиеся в них ключи. В первую категорию входят недетерминистические кошельки, в которых каж­ дый ключ генерируется из отдельного случайного числа. У таких ключей нет ни­ чего общего. Для подобных кошельков иногда используют аббревиатуру JBOK (от англ. Just а Bunch of Keys - просто набор ключей). Вторую категорию составляют детерминистические кошельки, в которых все ключи выводятся из одного мастер-ключа (master key) и связаны между собой. Все они могут быть сгенерированы заново, если у вас есть мастер-ключ. В детерми­ нистических кошельках применяются разные методы выведения ключей. Самый 130

Обзор технологий кошельков

распространенный из них основан на древовидной структуре, описанной в разде­ ле «Иерархические детерминистические кошельки (BIP-32/BIP-44)>► на с. 133. Чтобы сделать детерминистические кошельки более защищенными от слу­ чайных потерь данных (например, ваш телефон может быть похищен или вы­ пасть из кармана в туалете), мастер-ключи часто кодируются в виде списка слов (на английском или другом языке), которые вы должны записать и использовать для их восстановления. Это так называемые мнемонические кодовые слова ко­ шелька. Конечно, если эти слова окажутся у кого-то другого, этот человек тоже сможет воссоздать ваш кошелек и тем самым получить доступ к вашему эфиру и смарт-контрактам. Поэто�у будьте очень осторожны! Никогда не храните эти слова в электронном виде - внутри файла на своем компьютере или телефоне. Запишите их на бумаге и поместите в безопасное место. В следующих нескольких разделах мы рассмотрим каждую из этих техноло­ гий в общих чертах.

Н едетерминисти ч еские (случайные) кошельки В первом кошельке Ethereum (созданном для предварительной версии платфор­ мы) в каждом файле хранился отдельный приватный ключ, сгенерированный случайным образом. Такие «старомодные» реализации имеют много изъянов, поэтому им на смену приходят детерминистические кошельки. Например, реко­ мендуется избегать многократного использования Ethereum-aдpeca, чтобы мак­ симально повысить уровень конфиденциальности при работе с этой платфор­ мой, то есть каждый раз при получении средств используется новый адрес (для которого нужен новый приватный ключ). Вы можете пойти дальше и использо­ вать новый адрес для каждой транзакции, хотя это может оказаться накладно в случае интенсивной работы с токенами. Для соблюдения этой рекомендации недетерминистическому кошельку пришлось бы регулярно пополнять список своих ключей, что потребовало бы от вас регулярного создания резервных ко­ пий. Если вы не успеете скопировать свой кошелек и потеряете данные (в ре­ зультате поломки диска, кражи телефона или чрезмерного употребления горя­ чительных напитков), вы больше не сможете получить доступ к своим средствам и смарт-контрактам. Хуже всего дело обстоит с недетерминистическими кошель­ ками «нулевого типа>►, поскольку они создают отдельный файл для каждого но­ вого адреса «по мере необходимости>►• Тем не менее многие клиенты Ethereum (включая geth) используют файл keystore в формате JSON, содержащий один (случайно сгенерированный) приват­ ный ключ, который для дополнительной защиты зашифрован с помощью кодо­ вой фразы. Внутри он выглядит так: Глава 5. Кошельки

131

" addres s " : " 0 0 l d3 f l e f 8 2 7 5 5 2 a e l l l 4 0 2 7 bd3e c f l f 0 8 6b a 0 f 9 " , " crypto " : {

" cipher " : " ae s - 1 2 8 - ct r " , " ciphertext " :

" 2 3 3 a 9 f 4 d2 3 6ed0 c l 3 3 9 4 b 5 0 4 b 6da 5 d f 0 2 5 8 7 c 8b f l ad8 9 4 6 f 6 f 2 b 5 8 f 0 5 5 5 0

7 е се " ,

" cipherparams " :

" iv" : " d l 0 c 6 ec5bae 8 l b 6 cb 9 1 4 4 de 8 1 0 3 7 fa l 5 "

},

" kdf " : " s c r ypt " , " kdfparams " : {

" dklen" : 3 2 ,

"n" : 2 62 1 4 4 , "р " : 1 ,

"r" : 8 ,

" sal t" :

" 9 9d 3 7 a 4 7 c 7 c 9 4 2 9 c 6 6 9 7 6 f 6 4 3 f 3 8 6 a 6 l b 7 8 b 9 7 f 3 2 4 6adc a 8 9abe

4 2 4 5 d2 7 8 8 4 0 7 " },

"mac " : " 5 9 4 c 8 d f l c 8 ee 0 de d 8 2 5 5 a 5 0 ca f 0 7 e 8 c l 2 0 6 1 fd 8 5 9 f 4 Ь 7 с 7 6аЬ7 0 4 Ы7с957е842"

},

" id" : " 4 fcb2ba 4 - c cdb- 4 2 4 f - 8 9d5 - 2 6 c ce 3 0 4 b f 9 c " ,

" version " : 3

Формат keystore предусматривает функцию формирования ключа (англ. key derivation function, сокр. KDF), известную также как алгоритм растяжения ключа; она предназначена для защиты от взлома методом простого перебора (brute-force attack), перебора по словарю (dictionary attack) и применения ра­ дужных таблиц (rainbow tаЫе attack). Говоря простым языком, кодовая фраза не используется напрямую для шифрования приватного ключа. Вместо этого она растягивается путем многократного хеширования. Хеширующая функция выполняется 262 144 раза; в файле keystore это значения указано в параметре c r ypt o . kdfparams . n. При каждой попытке подобрать кодовую фразу зло­ умышленнику придется выполнить 262 144 итерации хеширования. Если ко­ довая фраза достаточно сложная и длинная, то это сделает атаку практически невыполнимой ввиду ее медленной скорости. 132

Обзор технологий кошельков

Существует ряд программных библиотек, которые способны выполнять чтение и запись в формате keystore - например, k e y t h e r e um (github.coml ethereumjs!keythereum). Недетерминистические кошельки не рекомендуется использовать ни для чего другого, кроме как для простых тестов. Они слишком громоздкие для создания резервных копий и решения каких-то не совсем тривиальных задач. Вместо этого используйте НD­ кошельки, основанные на индустриальных стандартах и поддерживающие мнемонические фразы для резервного копирования.

Детерминисти ч еские кошельки Детерминистическими называют кошельки, у которых все приватные ключи выведены из единого мастер-ключа, или seed. Seed - это случайно сгенериро­ ванное число, которое объединяется с другими данными, такими как числовой индекс или «код цепочки» (см. раздел «Расширенные публичные и приватные ключи» на с. 145), чтобы сформировать произвольное количество приватных ключей. В детерминистических кошельках этого значения достаточно, чтобы восстановить все производные ключи. Таким образом, чтобы обезопасить все свои средства и смарт-контракты, вам нужно сделать лищь одну резервную ко­ пию в момент создания кошелька. Значения seed также достаточно для экспорта и импорта кошелька, благодаря чему вы можете легко переносить все свои клю­ чи между разными кошельками. В такой архитектуре безопасность значения seed выходит на первый план, по­ скольку его одного достаточно для получения доступа ко всему кошельку. С дру­ гой стороны, возможность сосредоточиться на защите одного фрагмента данных можно рассматривать как преимущество.

Иерархи ч еские детерминистические кошельки (BIP-32/BIP-44) Детерминистические кошельки созданы для того, чтобы упростить получение мно­ жества ключей из одного значения seed. В настоящее время самой совершенной разновидностью этой концепции являются иерархические детерминистические (англ. hierarchical deterministic или HD) кошельки, описанные в стандарте Bitcoin ВIР-32 (github.com!Ьitcoin!Ьips!ЫoЬ!master!Ьip-0032.mediawiki). В НD-кошельках ключи хранятся в виде древовидной структуры. Например, родительский ключ формирует последовательность дочерних ключей, у каждого из которых есть свои ключи-потомки, и т. д. Эта структура проиллюстрирована на рис. 5.1. Глава 5 . Кошельки

133

о

Рис. 5. 1. НD-кошелек: дерево ключей, сгенерированных из единого значения seed

Н О-кошельки имеют несколько существенных преимуществ по сравнению с более простыми детерминистическими кошельками. Прежде всего, древовид­ ная структура может иметь дополнительное организационное значение - на­ пример, когда одна ветвь подключей используется для получения входящих пла­ тежей, а на другую поступает остаток от исходящих от переводов средств. Этот принцип можно применить и в корпоративной среде, выделяя ветви под разные отделы, филиалы, специальные функции или бухгалтерские категории. Еще одно преимущество Н О -кошельков состоит в том, что пользователи мо­ гут создавать последовательности из публичных ключей, не имея доступа к со­ ответствующим приватным ключам. Это позволяет использовать Н О-кошель­ ки на небезопасных серверах или только для наблюдения или приема платежей; в этом сценарии у кошелька нет приватного ключа, который позволяет расходо­ вать средства (с него).

Зна чения seed и мнемони ч еские коды (BIP-39) Чтобы обезопасить резервное копирование и извлечение закрытых ключей, их можно шифровать множеством различных способов. На сегодня предпоч­ тительным методом является использование последовательности слов, ко­ торые, если их расставить в правильном порядке, позволяют полностью 134

Обзор технологий кошельков

воссоздать закрытый ключ. Этот подход, входящий в стандарт BIP-39, ино­ гда называют мнемоническим. Он применяется во многих современных ко­ шельках для Ethereum (и кошельках для других криптовалют), позволяет им­ портировать-экспортировать значения seed для резервного копирования (backup) и восстановления (recovery) с помощью интероперабельных мне­ монических фраз ( «переносимых мнемоник)), англ. interoperaЫe mnemonics). Чтобы понять, почему этот подход пользуется популярностью, рассмотрим пример: FCCF1AB 3 3 2 9 F D S DA 3 DA9 5 7 7 5 1 1 F 8 F 1 3 7 wo l f j u i c e proud gown woo l unfa i r wa l l c l iff i n s e c t more de ta i l hub

С практической точки зрения шанс возникновения ошибки в момент запи­ си шестнадцатеричной последовательности является недопустимо высоким. Для сравнения, список слов довольно прост в обращении, в основном благодаря вы­ сокому уровню избыточности речи, переданной на письме (особенно в англий­ ском языке). Если по ошибке было записано слово inzect, вы сможете быстро это обнаружить в процессе восстановления кошелька, так как в английском языке та­ кого слова нет, а вместо него следует использовать insect. Запись значения seed в мнемоническом виде - это один из рекомендуемых подходов для управления НD-кошельками: значение seed необходимо для восстановления кошелька в слу­ чае потери данных (случайной или в результате кражи), поэтому хранение резерв­ ной копии будет чрезвычайно благоразумно. Однакоэто значение следует держать в максимально надежном месте, поэтому цифровых копий необходимо тщательно избегать; отсюда следует совет, данный ранее: используйте бумагу и ручку. Подведя итоги, применение списка слов восстановления для шифрования значения seed в НD-кошельке - это самый простой способ безопасно экспор­ тировать, переписать, записать на бумаге, прочитать без ошибок и импортиро­ вать приватный ключ в другой кошелек.

Л уч ш ие практики работы с ко ш ельками По мере развития технологии криптокошельков возникли определенные обще­ отраслевые стандарты, которые делают кошельки широко совместимыми, про­ стыми в использовании, безопасными и гибкими. Они также позволяют кошель­ кам генерировать ключи для нескольких разных криптовалют, применяя единую мнемоническую фразу. Эти стандарты перечислены ниже: Глава 5. Кошельки

135

- мнемоническая кодовая фраза на основе BIP-39; - НD-кошельки на основе BIP-32; - многоцелевая структура НD-кошельков на основе BIP-43; - кошельки с поддержкой нескольких валют и учетных записей (BIP-44). В будущем эти спецификации могут измениться или устареть, но пока они формируют набор взаимосвязанных технологий, которые стали стандартом де-факто для большинства блокчейн-платформ и криптовалют. Эти стандарты реализованы в целом ряде программных и аппаратных ко­ шельков, делая их совместимыми между собой. Пользователь может экспор­ тировать мнемоническую фразу, сгенерированную в одном из этих кошельков, и импортировать ее в другом, чтобы восстановить все свои ключи и адреса. В качестве примера программных кошельков, поддерживающих эти стандарты, можно привести (в алфавитном порядке) Jaxx, MetaMask, MyCrypto и MyEtherWallet (MEW ). Примерами аналогичных аппаратных кошельков мо­ гут служить Keepkey, Ledger и Trezor. Каждая из этих технологий подробно рассматривается в следующих разделах. Если вы создаете кошелек для Ethereum, он должен быть иерархически-детерминистическим, а его значение seed дол­ жно быть зашифровано в виде мнемонического кода для ре­ зервного копирования с соблюдением спецификаций BIP-32, BIP-39, BIP-43 и ВIР-44 (см. следующие разделы).

М немони ческая кодовая ф раза ( BIP-39) Мнемоническая кодовая фраза -это последовательность слов для шифрования случайного числа, которая используется в качестве значения seed для формиро­ вания детерминистического кошелька. Этой последовательности достаточно для воссоздания seed и, впоследствии, самого кошелька вместе со всеми производ­ ными ключами. Приложение, реализующее детерминистический кошелек с мне­ монической фразой, показывает пользователю на этапе инициализации набор из 12-24 слов. Эти слова служат резервной копией кошелька и могут быть ис­ пользованы для восстановления и воссоздания всех ключей в том же кошель­ ке или совместимом с ним приложении. Как уже объяснялось ранее, мнемони­ ческие_слова облегчают резервное копирование, поскольку их проще прочитать и правильно записать.

136

1

Лучшие практики работы с кошельками

Мнемонические слова часто путают с так называемыми Ьrаin­ кошельками 1 • Это разные понятия. Основное отличие между ними в том, что Ьrаin-кошелек состоит из слов, выбранных пользователем, тогда как мнемонические слова создаются случайным образом. Это делает мнемонические слова куда более безопасными, поскольку человеческий выбор - это плохой источник энтропии. Возможно, еще более важным является то, что термин «Ьrаin-кошелек» подразумевает запоминание слов; это очень плохая идея, так как в нужный момент у вас мо­ жет просто не оказаться резервной копии. Мнемонические коды описываются в спецификации BIP-39. Стоит отметить, что это лишь одна из реализаций. Существует другой, более старый стандарт с другим набором слов, который используется в кошельке Electrum Bitcoin. Спе­ цификацию BIP-39 предложила компания, стоящая за аппаратным кошельком Trezor. Несмотря на свою несовместимость с реализацией Electrum, она получила широкое распространение и породила десятки совместимых между собой реали­ заций, в связи с чем ее следует считать отраслевым стандартом де-факто. Более того, в отличие от значений seed реализации Electrum спецификация BIP-39 по­ зволяет создавать многовалютные кошельки с поддержкой Ethereum. BIP-39 описывает процесс создания мнемонического кода и значения seed, который состоит из девяти шагов и изложен ниже. Для ясности мы разбили его на две стадии: шаги 1-6 показаны в разделе «Генерация мнемонических слов» ниже, а шаги 7-9 рассмотрены в разделе «От мнемоники к seed» на с. 139.

Генерация мнемонических слов Мнемонические слова генерируются автоматически самим кошельком с приме­ нением стандартизованного процесса, описанного в спецификации BIP-39. Фор­ мирование кошелька начинается с источника энтропии, затем добавляется кон трольная сумма и привязывается случайное значение к списку слов. 1. Создание криптографически случайной последовательности S размером от 128 до 256 бит. 2. Создание контрольной суммы для S. Для этого из хеша S формата SНА-256 берется определенное количество бит, равное длине S, деленной на 32. 1

Англ.: brainwallet. Также существует название «мозговой кошелек», который использует длинную и сложную фразу случайным образом «из головы» (поэтому такое название) в ка­ честве аналога «мнемонической фразы». - Прим. ред.

Глава 5 . Кошельки

137

3. Добавление контрольной суммы в конец случайной последовательно­ сти s. 4. Разбиение последовательности и контрольной суммы на участки длиной 11 бит. 5. Привязывание каждого 11-битного значения к слову из заранее опреде­ ленного словаря длиной 2048 слов. 6. Создание мнемонического кода из последовательности слов с сохранени­ ем порядка их следования. На рис. 5.2 показано применение энтропии при генерации мнемонических слов. В табл. 5.1 продемонстрирована взаимосвязь между размером случайных данных и длиной мнемонического кода. Таблица 5. 1 . Мнемонический код: энтропия и количество слов Энтропия (биты)

1 28 160 192 224 256

138

1

Контроnьная сумма (биты) 4 5 6 7 8

Энтропия + контроnьная сумма (биты) 1 32 165 198 231 264

Лучшие практики работы с кошельками

Дnина м немоники (сnова)

12 15 18 21 24

Мнемонические слова Пример с 1 28-битной энтропией / 1 2 словами

ф Генерируем случайное значение ( 1 28 бит)

SНА256 Первые 4 бита

Случайное значение ( 1 28 бит)

@ Разбиваем 1 32 бита на .1 2 сегментов по 1 1 бит в каждом Список из 2048 английских слов (BIP39) 0 0 0 0 0 0 0 0 0 0 0 abandon 0 0 0 0 0 0 0 0 0 0 1 aЫ l i t y 0000� 100000 a:r:my 1 1 1 1 1 11 1 1 1 1

zoo

Мнемонический код из двенадцати слов: anay van defense carry j ealous true ga rbage claim echo media make crunch

Рис. 5.2. Генерация случайного значения и его шифрование в виде мнемонической фразы

От мнемоники к seed Мнемонические слова представляют случайное значение длиной от 128 до 256 бит. Затем это значение делается более длинным (512 бит) с помощью функции рас­ тяжения ключа PBKDF2. Полученное значение seed используется для создания детерминистического кошелька и формирования его ключей. Функция растяжения ключа принимает два параметра: мнемоническую фра­ зу и соль (salt). Последняя нужна для того, чтобы осложнить построение табли­ цы соответствия, с помощью которой выполняется атака методом простого пе­ ребора. В стандарт BIP-39 соль имеет другое назначение: она позволяет ввести Глава 5. Кошельки

13 9

кодовую фразу, которая служит дополнительным элементом безопасности для защиты значения seed. Это продемонстрировано в разделе «Дополнительная ко­ довая фраза в BIP-39» на с. 142. Пункты с 7 по 9 продолжают процесс, описанный в предыдущем разделе. 1. Первым параметром функции растяжения ключа PBKDF2 является мне­ моническая фраза, полученная на шаге 6. 2. Вторым параметром функции растяжения ключа PBKDF2 служит соль. Соль состоит из строковой константы " mnemon i c " , к которой пользо­ ватель при желании может добавить кодовую фразу. 3. PBKDF2 растягивает параметры-с мнемоническими словами и солью пу­ тем хеширования алгоритмом НМАС-SНА512 с 2048 итерациями. В ре­ зультате получается 512-битное значение seed. На рис. 5.3 показано, как мнемоническая фраза используется для генерации seed.

ф

От мнемоники к seed

Мнемонические кодовые слова " army van de fense carry j ealous true garbage claim echo media make crunch"

Соль " mnemo n i c " + (опциональная) кодовая фраза

Функция растяжения ключа PBKDF2 с п рименением HMAC-SHAS 1 2

2048 итераций

S 1 2-битное значение seed 5Ь56с4173OЗ fаа З fсЬа7е57 4 OO�12OаОса8Зес5а 4 fс 9 ! fЬа757fЬе 6 З fЬd7 7а89 а l аЗЬе4с67 1 9б f 5 7 с З 9а 0 8Ь7б37 3 7 3 3 8 91ЬfаЬа l беd2 7 а 8 1 Зсееd4 9 8 8 O 4 с O 5 7 O

Рис. 5.3. От мнемоники к seed Функция растяжения ключа с помощью 2048 итераций хеши­ рования является относительно эффективной защитой от подбора мнемонической или кодовой фразы. Она делает за­ тратными вычисления, которые требуются для перебора 140

Лучшие пра кти ки работы с кошель ками

возможных комбинаций. В результате злоумышленнику нет никакого смысла делать более нескольких тысяч попыток, что значительно меньше количества значений seed, которые мож­ но сгенерировать (2 5 1 2 или примерно 1 0 1 54 ; напомним, что ко­ личество атомов в обозримой Вселенной приблизительно рав­ но 1080 ) . В табл. 5.2, 5.3 и 5.4 показаны некоторые примеры мнемонических кодов и значений seed, которые они генерируют. Таблица 5.2. 128-битный случайный мнемонический код без кодовой фразы и с итоговым значением seed Случайный ввод ( 12 8 бИ'I.' )

O c l e 2 4 e 5 9 1 7 7 7 9d2 9 7 e l 4 d4 5 f l 4 e l a l a

Мнемоииха ( 12 спов )

a rmy van de f e n s e c a r r y j ea l o u s t ru e g a rbage c l a im echo med i a make c runch

Кодовая фраза ( не т )

Seed (512 бИ'I.' )

5b5 6 c 4 1 7 3 0 3 fa a 3 fcba 7 e 5 7 4 0 0 e l 2 0 a 0 c a 8 3 e c 5 a 4 f c 9ffЬ a 7 5 7 fbe 6 3 fbd7 7 a 8 9 a l a Зbe 4 c 6 7 1 9 6 f 5 7 c 3 9 a 8 8 b 7 6 3 7 3 7 3 3 8 9 l b f aba l б e d2 7 a 8 1 3 ce e d 4 9 8 8 0 4 c 0 5 7 0

Таблица 5.3. 128-битный случайный мнемонический код с кодовой фразой и итоговым значением seed Случайный ввод ( 128 бИ'I.' )

O c l e 2 4 e 5 9 1 7 7 7 9d2 9 7 e l 4 d4 5 f l 4 e l a l a

Мнемоииха ( 12 спов )

a rmy van de fen s e c a r r y j e a l ou s t rue g a rbage c l a im echo med i a ma ke c runch

Кодовая фраза

Supe r Dupe r S e c r e t

Seed (512

бит )

Зb5df l бd f 2 1 5 7 1 0 4 c fdd2 2 8 3 0 1 6 2 a 5 e l 7 0 c 0 1 6 1 6 5 3 e 3 a f e б c 8 8 de fe e fb 0 8 1 8 c 7 9 3

dbb2 8 ab 3 ab 0 9 1 8 9 7 d0 7 1 5 8 6 l dc 8 a l 8 3 5 8 f 8 0 b 7 9 d 4 9a c f 6 4 1 4 2 a e 5 7 0 3 7 d l d5 4

Глава 5 . Кошельки

141

Табли ца 5.4. 256-битный случайный мнемонический код без кодовой фразы и с итоговым значением seed Спучайный ввод ( 2 5 6 бИ'l' )

2 0 4 1 5 4 6 8 6 4 4 4 9 c aff9 3 9d3 2 d5 7 4 7 5 3 fe 6 8 4 d 3 c 9 4 7 c 3 3 4 6 7 1 3 dd8 4 2 3 e 7 4 abc f 8 c

Мнемоника ( 2 4 слова )

c a ke app l e b o r r o w s i l k e ndo r s e fit ne s s t op den i a l c o i l r i o t s t ay

w o l f l uggage oxygen f a i n t ma j o r e d i t mea s u re i n v i t e l ove t rap fie l d di l emma oЬ l i ge

Кодовая фраза ( не т )

Seed ( 5 1 2 бит )

3 2 6 9b c e 2 6 7 4 a cbdl 8 8 d 4 f l 2 0 0 7 2 Ы 3b 0 8 8 a 0 e c f 8 7 c б e 4 c ae 4 1 6 5 7 a 0bb 7 8 f 5 3 1 5b 3 З b 3 a 0 4 3 5 6 e 5 3 d0 6 2 e 5 5 f l e 0 de aa 0 8 2 d f 8 d 4 8 7 3 8 1 3 7 9 d f 8 4 8 a б a d 7 e 9 8 7 9 8 4 0 4

Дополнительная кодовая фраза в BIP-39 Стандарт BIP-39 позволяет использовать в процессе формирования seed допол­ нительную кодовую фразу. Если эту фразу не указать, мнемонические слова рас­ тягиваются с применением строковой константы " mnemon i c " (соли), в резуль­ тате чего из любой мнемонической последовательности всегда получается одно и то же значение seed. При наличии кодовой фразы функция растягивания воз­ вращает разные значения seed для одной и той же мнемонической последова­ тельности. На самом деле каждая возможная кодовая фраза дает другое значение seed. В сущности, кодовая фраза не может быть «неправильной)). Все варианты являются корректными, и каждый из них приводит к уникальному результату, формируя огромный набор потенциальных неинициализированных 1 кошель­ ков. Его размер настолько большой (25 1 2 ) , что нахождение задействованной ко­ довой фразы путем простого перебора или по счастливой случайности являет­ ся практически невозможным (при условии, что эта фраза достаточно сложная и длинная). Согласно BIP-39, «неправильных)) кодовых фраз не существу­ ет. Каждый вариант приводит к созданию какого-то кошелька (пустого, если он перед этим не использовался).

1

Незапущенных. - Прим. ред.

142

Лучшие практики работы с кошельками

Опциональная кодовая фраза обладает двумя важными свойствами. - Это второй фактор (нечто запоминаемое), благодаря которому мнемони­ ческая последовательность сама по себе является бесполезной. Это защи та на случай похищения резервной копии мнемонической последователь­ ности. - Форма правдоподобного отрицания, когда заданная фраза ведет к кошель­ ку с небольшой суммой денег, который должен отвлечь злоумышленника от «настоящего» кошелька с большей частью денежных средств. Однако необходимо отметить, что применение кодовой фразы чревато сле­ дующими рисками: - если владелец кошелька становится недееспособным или умирает и никто больше не знает кодовую фразу, значение seed является бесполезным, а все средства, хранящиеся в кошельке, безвозвратно теряются; - и наоборот, если владелец сохранит резервную копию кодовой фразы вме­ сте со значением seed, теряется весь смысл второго фактора. Кодовые фразы очень удобны, однако их следует применять только в соче­ тании с тщательно спланированным процессом резервного копирования и вос­ становления, чтобы в случае смерти владельца его наследники могли восстано­ вить криптовалюту.

Работа с мнемоническими кодами Спецификация BIP-39 реализована в виде библиотек для множества разных язы­ ков программирования. Например: - python-mnemonic (github. com/trezor!python-mnemonic). Эталонная реализа­ ция на языке Python от команды SatoshiLabs, которая предложила ВIР-39. - ConsenSys/eth-lightwallet (github. com!ConsenSys!eth-lightwallet). Легковес­ ный кошелек Ethereum на JavaScript для NodeJS и браузеров (с поддерж­ кой BIP-39). - прт!Ьiр39 (www. npmjs. com/package!Ьip39). Реализация Bitcoin BIP-39 на JavaScript: мнемонический код для генерации детерминистических ключей. Есть также генератор BIP-39, реализованный в виде отдельной веб-страни­ цы (см. рис. 5.4). Данный Mnemonic Code Converter чрезвычайно полезен для Глава 5. Кошельки

143

тестирования и проведения экспериментов. Он генерирует мнемонические фра­ зы, значения seed и расширенные закрытые ключи. Он доступен как в Интерне­ те, так и локально.

Mnemonic

You can enter an exlsling BIP39 mnвmonlc, or generate а new random one. 1yplng your own twelve words wlll рrоЬаьtу not work how you expect, since the woros requlre а partlcular structure (the last woro 1s а ChecksUm) For more lnfo see the ВIР39 spec

ВIРЗ9 Мnemonic

ВIРЗ9 Panphraи Coptional)

BIP39 Seed Coln

BIP32 Root Кеу



1

i

а random ,2 � : word mnemonlc, or enter your own ЬeJow.

army van defense сапу jealous true � Cl8Jm echO media make cruncl'C

]' '

✓,,,

5Ь56с4, 7303faa3fcьa7e57400e1 2080са83ес5841с911Ьа7571Ье631Ьd77е89а1 е3Ь84с671 9 1 6157с39а88Ь76373733891 ЫвЬа1 6еd27а81 Зсееd498804с0570

I Вitcoln

I xprv!ls21 ZrQH1 43КЗt�UZINgeAЗw;1 fwjYL.aGwmPtQyPMmzs�V2o.,.;;,,p!В$d2Q�sHZ9j6 j l6ddYjЬ5PLtudМZn8LhvuCVhGcQntq5m7JVМqnle

),,

i

)

Рис. 5.4. Генератор ВIР-39 в виде отдельной веб-страницы

Создание Н D- ко ш ел ь ка с помо щь ю seed НD-кошельки создаются из корневого значения seed 1 , которое представляет со­ бой случайное число размером 128, 256 или 512 бит. Чаще всего оно генериру­ ется из мнемонической фразы, как это было показано в предыдущем разделе. Каждый ключ в НD-кошельке выводится из корневого значения seed детер­ министическим способом; благодаря этому из seed можно воссоздать все клю­ чи, используя любой совместимый кошелек. Это позволяет легко выполнять экс­ порт, резервное копирование и восстановление НD-кошельков с тысячами или даже миллионами ключей, передавая лишь мнемоническую фразу, из которой было получено корневое значение seed.

1

Анrл . : root seed . - Прим. ред.

144

Лучшие практики работы с кошел ька ми

Н D- кошельки ( BIP-32 ) и пути (BIP-43/44) Большинство НD-кошельков следуют спецификации ВIР-32, которая стала фак­ тическим отраслевым стандартом для генерации детерминистических ключей. Мы не станем углубляться во все подробности BIP-32. Нас интересуют толь­ ко те компоненты, которые необходимы для понимания того, как этот стан дарт применяется в кошельках. Основным его аспектом являются древовидные иерархические связи, которые, как видно на рис. 5.1, могут быть у сгенерирован­ ных ключей. Также важно понимать концепции расширенных и усиленных клю­ чей, которые рассматриваются в следующих разделах. Стандарт BIP-32 имеет десятки совместимых между собой реализаций во многих программных библиотеках. Большинство из них предназначены для кошельков Bitcoin, которые используют другие адреса, но имеют тот же меха­ низм генерации ключей, что и кошельки Ethereum с поддержкой BIP-32. Вы мо­ жете использовать проект, созданный специально для Ethereum (github. com! ConsenSys!eth-lightwallet), или позаимствовать реализацию из Bitcoin, добавив в нее библиотеку кодирования адресов Ethereum. Есть также генератор BIP-32, реализованный в виде отдельной веб-страни­ цы (Ьip32. orgl). Он очень полезен для тестирования и проведения эксперимен­ тов с этим стандартом.

i

I

Генератор BIP-32 на отдельной странице не доступен по HT TPS, что делает использование этого инструмента небезопасным. Он предназначен только для тестировани�. Вы не должны применять ключи, сгенерированные этим саитом, вместе с настоящими средствами.

Расш иренные публичные и приватные ключи В терминологии ВIР-32 ключи могут быть «расширенными» (extended). С помо­ щью подходящих математических операций из этих расширенных «родитель­ ских» ключей можно извлечь «дочерние», создавая тем самым иерархию ключей и адресов, описанную ранее. Родительский ключ не обязательно должен нахо­ диться на вершине дерева. Его можно выбрать в любом месте иерархии. Чтобы расширить ключ, к нему нужно добавить код цепочки - 256-битную двоичную строку, которая при добавлении генерирует дочерние ключи. Приватные расширенные ключи имеют префикс xprv: xprv 9 s 2 1 Z rQH 1 4 3 K 2 J F 8 Ra fpqtKiTbsbaxEeUaMnNH sm5o бwCW3 z 8 ySyH4 UxFVS f Z 8 n 7 E Su7 fgi r 8 i ...

Глава 5. Кошельки

145

Для публичных расширенных ключей используется префикс xpub: xpub 6 6 1MyMwAqRbcEnKbXcCqD2 GT l d i 5 z QxVqoH PAgHNe 8 dv5 J P 8 gWmDproS 6 kFHJnLZd 2 3 tWevhdn ...

Одной из очень полезных характеристик НD-кошельков является возмож­ ность извлечь дочерние публичные ключи из родительских, не имея приватно­ го ключа. Благодаря этому дочерний публичный ключ можно получить двумя путями: либо напрямую из дочернего приватного ключа, либо из родительско­ го публичного ключа. Таким образом, публичный расширенный ключ можно использовать, к примеру, для выведения всех публичных (и только таковых) ключей в заданной вет­ ке иерархии HD-кошелька. С помощью этой упрощенной процедуры можно создавать очень безопасные развертывания, в которых сервер или приложение содержат лишь копию пуб­ личного расширенного ключа, но не имеют никакого доступа к приватным клю­ чам. Это позволяет сгенерировать бесконечное количество публичных ключей и адресов Ethereum, исключая возможность расходования средств, отправлен­ ных по этим адресам. В то же время приватный расширенный ключ, из которо­ го извлекаются все соответствующие приватные ключи для создания подписей и передачи средств, находится на другом, более защищенном сервере. Одной из распространенных сфер применения данного метода являет­ ся установка публичного расширенного ключа на веб-сервер, который игра­ ет роль приложения для электронной торговли. Этот веб-сервер может ис­ пользовать функцию выведения публичных ключей, чтобы создавать новый адрес Ethereum для каждой транзакции (например, для корзины покупок), не храня при этом никаких закрытых ключей, которые могут быть похищены. Без НD-кошельков это можно сделать только одним способом - сгенериро­ вав тысячи адресов Ethereum на отдельном защищенном сервере и затем пред­ варительно загрузив их на сервер интернет-магазина. Такой подход является громоздким и требует постоянного обслуживания, чтобы у сервера не закон чились ключи. Поэтому желательно использовать открытые расширенные клю­ чи из НD-кошельков. Это решение также часто применяют для «холодных» или аппаратных ко­ шельков. В этом сценарии закрытый расширенный ключ может храниться в ап­ паратном кошельке, тогда как открытый расширенный ключ может быть до­ ступен по сети. Пользователь может при желании создавать адреса, тогда как закрытые ключи будут храниться в отдельном месте. Чтобы распоряжаться денежными средствами, пользователь может создать цифровую подпись для 146

Лучшие практики работы с кошельками

клиента Ethereum с помощью закрытого расширенного ключа или подписывать транзакции на устройстве с аппаратным кошельком.

Извлече ни е усиленн ых дочер них ключей 1 Возможность сгенерировать ветку публичных ключей из публичного расши репного ключа (хриЬ), является очень полезной, но несет в себе потенциальный риск. Доступ к xpub не дает доступа к дочерним приватным ключам. Но, по­ скольку xpub содержит код цепочки (с помощью которого дочерние публичные ключи извлекаются из родительского), раскрытие приватного ключа позволит извлечь все остальные дочерние приватные ключи. У течка одного единственно­ го дочернего приватного ключа в сочетании с кодом цепочки приводит к рас­ крытию всех приватных ключей, принадлежащих всем потомкам. Что еще хуже, дочерний приватный ключ вместе с кодом цепочки можно использовать для из­ влечения родительского приватного ключа. Чтобы противостоятьэтой угрозе, НD-кошельки используют альтернативную, усиленную функцию извлечения ключей, которая «разрывает» связь между пуб­ личным родительским ключом и дочерним кодом цепочки. В усиленной функции для извлечения дочернего кода цепочки используется родительский приватный ключ вместо родительского публичного. Это создает «барьер» в последовательно­ сти родительских/дочерних ключей, благодаря которому код цепочки нельзя при­ менять для раскрытия родительского или соседнего приватного ключа. Говоря более простым языком, если вы хотите использовать xpub для удоб­ ного извлечения иерархий публичных ключей, не подвергаясь при этом риску утечки кода цепочки, вы должны брать для этого не обычный, а усиленный ро­ дительский ключ. Согласно рекомендациям, потомки первого уровня главного ключа должны всегда извлекаться с использованием усиленной функции. Это позволяет предотвратить раскрытие мастер-ключей.

Инде ксы для обыч н ого и усиле нного и звлечения Очевидно, что мы хотим сохранить возможность извлечения нескольких клю­ чей из заданного родителя. Для этого используются индексы (или номера ин­ дексов). Каждый индекс, если его объединить с родительским ключом с помо­ щью специальной функции выведения потомков, дает другой дочерний ключ. В качестве индекса в функции «родитель-потомок» из BIP-32 используется це­ лое 32-битное число. Чтобы мы могли легко различать ключи, извлеченные че­ рез обычную и усиленную функции, индексы разбиты на два диапазона. Но­ мера с О по 2 3 1 -1 (от О х О до 0 x 7 FFFFFFF) используются исключительно для 1 Анrл.: hardened child key. - Прим. ред.

Глава 5 . Кошельки

147

обычного извлечения. Номера с 23 1 по 232 -1 (от О х 8 0 0 0 0 0 0 0 до 0 x FFFFFFFF) предназначены только для усиленного извлечения. Таким образом, если индекс меньше 2 3 1 , это обычный потомок; если же индекс равен или больше 231 , пото­ мок является усиленным. Чтобы индекс было проще читать и отображать, в случае с «усиленными» по­ томками они начинаются с ноля со штрихом. Таким образом, первый обычный дочерний ключ выглядит как О, тогда как первый усиленный потомок (с индек­ сом О х 8 0 0 0 0 0 0 0) выводится как О ' . Затем второй усиленный ключ с индек­ сом О х 8 О О О О О О 1 отображается как 1 ' и т. д. Если вы встретите индекс НD-ко­ шелька i ' , это означает 2 3 1 + i.

Идентификатор ключа (путь) в НD-кошельке Для идентификации ключей в НD-кошельках используются «пути», в которых каждый уровень разделяется косой чертой (см. табл. 5.5). Закрытые ключи, вы­ веденные из приватного мастер-ключа, начинаются с m. Публичные ключи, из­ влеченные из публичного мастер-ключа, начинаются с М. Следовательно, первый дочерний приватный ключ, полученный из главного приватного ключа, выгля­ дит как m / 0. Первый дочерний публичный ключ имеет путь М/ 0. Второй внук первого потомка будет m / О / 1 и т. д. «Происхождение» ключа следует читать справа налево, вплоть до достиже­ ния главного предка, из которого тот получен. Например, идентификатор m / х / y / z описывает ключ, который является прямым z-м потомком ключа m / x / y, который является у-м прямым потомком ключа m / х, который является х-м пря­ мым потомком m. Таблица 5.5. Примеры путей в НD-кошельке

Н D- путь m/ О m/ О / О m/ О ' / О m/ 1 / О М/ 2 3 / 1 7 / О / О

Описание кл юча Пе р вый (0-й ) потомо к масте р кл ю ча (m) Пе р вый приватны й в нук п е рвого потом ка (m/0) Пе рвый обыч ны й в нук п е рвого усил енн ого потом ка (m/0') Пе р вый приватны й в ну к второго потом ка (m/1 ) Пе р вый публич ны й праправ нук п е р вого прав ну ка 1 8-го в ну ка 24-го прямого потом ка

Перемещение по древовидной структуре НD-кошельков Древовидная структура НD-кошельков обладает необычайной гибкостью. Но, с другой стороны, это приводит к тому, что уровень сложности ничем 148

Лучши е пра кти ки работы с кош ель ками

не ограничен. У каждого родительского расширенного ключа может быть 4 мил­ лиарда прямых потомков: 2 миллиарда обычных и еще 2 миллиарда усиленных. У каждого из этих потомков могут быть свои дочерние ключи и т. д. Разветвлен­ ность иерархии зависит лишь от вашего желания и может иметь бесконечное количество уровней. У читывая все это, перемещение по слишком разросшему­ ся дереву может оказаться крайне затруднительным. Чтобы совладать с этой потенциальной сложностью, было предложено два стандарта BIP, которые описывают структуру иерархий в Н О-кошельках. BIP-43 предлагает использовать индекс первого усиленного потомка в качестве спе­ циального идентификатора, который подчеркивает «назначение» древовидной структуры. Согласно этой спецификации Н О -кошелек должен использовать только ответвления первого уровня; при этом индексные номера определяют назначение кошелька, идентифицируя структуру и пространство имен осталь­ ной части иерархии. В частности, НО-кошелек, ограниченный веткой m / i ' / ..., имеет определенное назначение, идентификатором которого является индекс­ ный номер i. BIP-44 расширяет эту спецификацию и предлагает структуру с множествен­ ными учетными записями, которая обозначается «целевым» номером 4 4 ' . Все Н О-кошельки, имеющие структуру BIP-44, отличаются тем, что они используют только одну ветку дерева: m / 4 4 ' / *. Согласно BIP-44, дерево состоит из пяти заранее определенных уровней: m / назначение ' / тип_монеты ' / учетная запись ' / остаток / индекс_адреса

Первый уровень, назначение, всегда равен 4 4 ' . Второй уровень, тип_моне­ ' ты , определяет тип токена и позволяет создавать мультивалютные Н О -кошель­ ки, в которых для каждой валюты выделяется отдельное поддерево, начиная со второго уровня. В спецификации SLIP0044 (github. com!satoshilabs!slips!ЫoЫ master!slip-0044. md) определено несколько криптовалют, таких как Ethereum (m/ 4 4 ' / 6 О ' ), Ethereum Classic (m/ 4 4 ' / 6 1 ' ) и Bitcoin (m/ 4 4 ' / О ' ); все токе­ ны в тестнете обозначаются как m / 4 4 ' / 1 ' . Третий уровень дерева, учетная_запись>, дает возможность пользователям разделять свои кошельки на отдельные логические учетные подзаписи в бух­ галтерских или организационных целях. Например, Н О-кошелек может содер­ жать две «учетные записи» Ethereum, m / 4 4 ' / 6 О ' / О ' и m / 4 4 ' / 6 О ' / 1 ' , каж­ дая из которых является корнем собственного поддерева. Поскольку стандарт BIP-44 изначально предназначался для Bitcoin, он содер­ жит один «нюанс», который не совсем уместен во вселенной Ethereum. На четвер­ том уровне пути, остаток, у НО-кошельков есть два поддерева: одно для создания Глава 5. Кошель ки

149

адресов получателей, а другое для создания адресов с остатком. В Ethereum ис­ пользуется только первое, поскольку необходимости в адресе с остатком, ко­ торый существует в Bitcoin, нет. Заметьте, что этот уровень, в отличие от пре­ дыдущих, не использует усиленное извлечение. Это позволяет экспортировать на уровне учетных записей публичные ключи, предназначенные для исполь­ зования в небезопасных окружениях. Удобные в применении адреса выводят­ ся НD-кошельком в виде потомков четвертого уровня, что делает пятый уро­ вень дерева индексом_адреса. Например, третий адрес получателя для платежей в главной учетной записи 1 Ethereum выглядит как М/ 4 4 ' / 6 О ' / О ' / О / 2. Еще несколько примеров показано в табл. 5.6. Таблица 5.6. Примеры структуры НD-кошельков стандарта ВIР-44 НD-путь М/ 4 4 ' / 6 0 ' / 0 ' / 0 / 2

Описание ключа

М/ 4 4 ' / 0 ' / 3 ' / 1 / 1 4

1 5-й публичный ключ с адресом остатка для 4-й учетной записи Bit­ coin

m/ 4 4 ' / 2 ' / 0 ' / 0 / 1

Второй приватный ключ в главной учетной записи 2 Litecoin для под­ писания транзакций

Третий принимающий публичный ключ для главной учетной запи­ си Ethereum

Выводы Кошельки являются фундаментом любого блокчейн-приложения, которое взаи модействует с пользователями. Они позволяют управлять наборами ключей и адресов. С помощью кошельков пользователи также могут продемонстриро­ вать факт владения эфиром и авторизовать транзакции, применяя к ним цифро­ вые подписи. Подробнее об этом в главе 6.

1

Англ.: primary account. - Прим. ред.

2

Англ.: main account. - Прим. ред.

ГЛАВА 6

Транзакции

Транзакции - это подписанные сообщения, которые создаются учетными за­ писями с внешним владельцем, передаются по сети Ethereum и записывают­ ся в блокчейн Ethereum. В этом простом определении скрывается множество неожиданных и удивительных деталей. Если подойти к этому с другой стороны, транзакции - это единственный механизм, способный инициировать измене­ ние состояния или запустить выполнение контракта в EVM. Ethereum является глобальным конечным автоматом, представленным в единственном экземпляре, и транзакции приводят этот автомат «в движение», меняя его состояние. Кон­ тракты не выполняются сами по себе. Блокчейн-сеть Ethereum не работает ав­ тономно. Все начинается с транзакции. В этой главе мы подробно поговорим о транзакциях, покажем, как они ра­ ботают, и рассмотрим тонкости. Стоит отметить, что большая часть этой главы предназначена для читателей, которые заинтересованы в управлении собствен­ ными транзакциями на низком уровне - например, для авторов кошельков; если вас удовлетворяют существующие кошельки, этот материал вам вряд ли пригодится, хотя некоторые детали могут оказаться довольно интересными!

Структура транзакции Давайте сначала рассмотрим общую структуру транзакции в ходе ее сериали­ зации и передачи по сети Ethereum. Все клиенты и приложения, принимающие транзакции, хранят их в памяти в виде собственной внутренней структуры дан­ ных - возможно, с внедрением дополнительной метаинформации. Сетевая се­ риализация является единственным стандартным форматом транзакции. Транзакция представляет собой сериализованное двоичное сообщение, со­ держащее следующие данные. - Одноразовый код. Порядковый номер, выдаваемый исходной учетной за­ писью ЕОА. Предотвращает повторное воспроизведение сообщений. - Цена газа. Цена на газ (в wei), которую готов заплатить инициатор. Глава 6. Транзакции

151

- Лимит на газ. Максимальный объем газа, который инициатор готов купить для этой транзакции. - Получатель. Конечный Ethereum-aдpec. - Значение. Объем эфира, отправляемый получателю. - Данные. Двоичные данные переменной длины. - v, r, s. Три компонента цифровой подписи ECDSA, принадлежащей исходной учетной записи ЕОА. Структура транзакционного сообщения сериализуется с помощью систе­ мы кодирования с рекурсивным префиксом длины (RLP 1 ), которая была созда­ на специально для выполнения простой сериализации в Ethereum с точностью до байта. Все числа в Ethereum являются целыми и имеют порядок следования байт от старшего к младшему, а их длина кратна 8 битам. Обратите внимание на то, что метки поля (to, ga s l imi t и т. д.) указыва­ ются здесь лишь для ясности; они не являются частью сериализованных данных транзакции, значения полей в которых кодируются в формате RLP. В целом RLP не содержит никаких разделителей или меток полей. Префикс длины использу­ ется для определения длины каждого поля. Все, что выходит за пределы задан­ ной длины, принадлежит следующему полю структуры. Транзакции передаются именно в таком виде, но в большинстве случаев их внутренние представления и визуализации в пользовательских интерфейсах содержат дополнительную информацию, полученную из транзакции или блок­ чейна. Например, вы могли заметить, что в адресе исходной учетной записи ЕОА нет поля «от». Это связано с тем, что публичный ключ ЕОА можно вывести из компонентов v , r , s, принадлежащих подписи ECDSA. А адрес, в свою очередь, может быть получен из публичного ключа. Если вы видите в тран­ закции поле «от», знайте, что оно было добавлено программным обеспечени­ ем для визуализации. Среди других метаданных, которые часто добавляются к транзакции клиентским ПО, можно выделить номер блока (если он уже был сгенерирован и включен в блокчейн) и 1D транзакции (вычисляемый хеш). Опять же, эта информация выводится из транзакции и не является частью са­ мого сообщения.

1

Англ.: Recursive Length Prefix. - Прим. ред.

152

Структура транзакции

Одноразовый код транзакции Одноразовый код (англ. nonce) - это самый важный и загадочный компонент транзакции. Его определение находится в техническом документе Yellow paper (см. «Дополнительный материал)) на с. 46) звучит так. Одноразовый код: скалярное значение, равное числу транзак­ ций, отправленных с этого адреса или, если это учетная за­ пись со связанным кодом, количеству процедур создания кон тракта, выполненных этой учетной записью. Строго говоря, одноразовый код является атрибутом исходного адреса, то есть он имеет смысл только в контексте адреса отправителя. Однако он не хра­ нится явно в блокчейне как часть состояния учетной записи. Вместоэтого он вы числяется динамически путем подсчета подтвержденных транзакций, иниции­ рованных данным адресом. Существует два сценария, в которых одноразовый код ·на основе подсчета транзакций играет важную роль: включение транзакций в порядке их создания (для удобства использования) и защита от дублирования транзакций (жизненно необходимая функция). Давайте рассмотрим примеры каждого из этих случаев. 1. Представьте, что вы хотите выполнить две транзакции: платежи в раз­ мере 6 и 8 ЕТ Н, первый из которых является приоритетным. Первым де­ лом вы подписываете и передаете транзакцию с 6 ЕТ Н, поскольку она для вас более важна, и затем вы делаете то же самое с 8 ЕТ Н. К сожалению, вы не заметили, что на вашем счете находится лишь 1 О ЕТ Н, поэтому сеть не сможет принять обе транзакции: одна из них завершится неудач но. Но, поскольку более важная транзакция была отправлена первой, вы, по понятным причинам, ожидаете, что с ней все будет в порядке и что от­ клоненным окажется перевод в размере 8 ЕТ Н. Но в децентрализованных системах, таких как Ethereum, узлы могут получать транзакции в любом порядке; нет никакой гарантии того, что одна транзакция дойдет к узлу раньше другой. Таким образом, вы почти наверняка окажетесь в ситуа­ ции, когда одни узлы получат первой транзакцию с 6 ЕТ Н, а другие с 8 ЕТ Н. Без одноразового кода невозможно предугадать, какая из них будет принята, а какая отклонена. В реальности же у первой транзакции одноразовый код будет равен, скажем, 3, а у второй - следующему значе­ нию (то есть 4). Поэтому вторая транзакция будет игнорироваться до тех Глава 6. Транзакции

153

пор, пока не закончится обработка транзакций с одноразовыми кодами от О до 3, даже если она дошла первой. Ну и ну! 2. Теперь представьте, что на вашем счете лежит 100 ЕТ Н. Чудесно! Вы на­ шли в Интернете кого-то, кто готов продать за эфир эту безделушку, ко­ торую вы так хотите купить. Вы отправляете ему 2 ЕТ Н, а он вам пере­ дает товар. Прекрасно. Чтобы выполнить этот платеж, вы подписываете транзакцию по переводу 2 ЕТ Н со своей учетной записи на учетную за­ пись продавца и транслируете ее в сеть Ethereum, чтобы ее проверили и добавили в блокчейн. Если бы вы не использовали однора:эовый код, вторая транзакция с отправкой 2 ЕТ Н по тому же адресу выглядела бы в точности как первая. Это означает, что любой, кто увидел вашу тран­ закцию в сети Ethereum (то есть буквально любой человек, включая по­ лучателя и ваших врагов), мог бы ее «воспроизвести» снова и снова, пока у вас не закончится весь эфир; для этого достаточно было бы скопировать и вставить вашу оригинальную транзакцию, и затем повторно послать ее в сеть. Однако ввиду наличия одноразового кода каждая транзакция яв­ ляется уникальной, даже если вы многократно пошлете одну и ту же сум­ му по одному и тому же адресу. Благодаря тому, что инкрементация одно­ разового кода является частью транзакции, «продублировать» сделанный вами платеж попросту невозможно. Подводя итог, необходимо отметить, что использование одноразовых ко­ дов является жизненно важным для любого протокола, основанного на учет­ ных записях. Это контрастирует с механизмом UTXO (Unspent Transaction Output, то есть выход неизрасходованных транзакций), который применяется в протоколе Bitcoin.

Отслеживание одноразовых кодов С практической точки зрения одноразовый код - это актуальный счетчик под­ твержденных (то есть записанных в цепочку) транзакций, инициированных учетной записью. Чтобы узнать его значение, вы можете опросить блокчейн например, через интерфейс wеЬЗ. Откройте консоль JavaScript в браузере с уста­ новленным расширением MetaMask или используйте команду t ruff!e cons о le, чтобы получить доступ к JаvаSсriрt-библиотеке wеЬЗ. Затем введите: > weЬЗ . eth . 9etTransactionCount ( " Ox9e7 1 3 963a92c02317a68lb9bb306Sa82 4 9d

e l 2 4f" ) 40

154

Одноразовый код транзакции

Одноразовый код - это счетчик, начинающийся с нуля. То есть код первой транзакции равен О. В этом примере мы по­ лучили значение 40; это означает, что имели место одноразо­ вые коды от О до 39. Код следующей транзакции должен быть равен 40. Ваш кошелек отслеживает одноразовые коды для всех адресов, которыми он управляет. Это довольно просто делать, если вы инициируете все транзакции из одной точки. Представьте, что вы пишете собственный кошелек или какое-то другое приложение, которое инициирует транзакции. Как бы вы отслеживали одноразовые коды? При создании новой транзакции вы должны назначить ей следующий по по­ рядку одноразовый код. Но пока ее не подтвердят, она не будет учитываться при подсчете общего числа транзакций g e t T ran s a c t i onCount.

& г- --·· 1

'

Будьте осторожны, подсчитывая отлож енные транзакции с помощью функции g e t T r a n s a c t i o n. C o u n t , поскольку при отправке нескольких транзакций подряд у вас могут воз­ никнуть некоторые проблемы.

Давайте рассмотрим пример: > weЬЗ . eth . getTransactionCount ( " Ox9e7 1 3 9 63a92c02 317aб8lb9bb30 65a8 24 9de 124f" , \ "pending" ) 40

> weЬЗ . eth . sendTransaction ( { from : weЬЗ . eth . accounts [ O ] , to : \

" 0xВ092 0c523d582040f2BCBlbD7FВlc7ClECEЬc:IВ34 " , value : weЬЗ . toWei ( 0 . 0 1 , "ether " ) } ) ;

> weЬ3 . eth . getTransactionCount ( " Ox9e7 1 3 9 63a92c02317aб8lb9bb30 65a824 \ 9de12 4 f " , "pending" ) 41

> weЬЗ . eth . sendTransaction ( { from : weЬЗ . eth . accounts [ O ] , to : \

" 0xВ092 0c523d582040f2BCBlbD7FВlc7ClECEЬdВ34 " , value : weЬЗ . toWei ( 0 . 0 1 , "ether " ) } ) ;

> weЬ3 . eth . getTransactionCount ( " Ox9e7 1 3 9 63a92c02317aб81b9bb30 65a8 24 \ 9de12 4 f" , "pending" ) 41

> weЬЗ . eth . sendTransaction ( { from : weЬЗ . eth . accounts [ O ] , to : \

Глава 6. Транзакции

155

" 0xВ0 920c523d5 8 204 0 f2BCBlbD7FВlc7ClECEЬdВ34 " , value : webЗ . toWei ( 0 . 0 1 , \ "ether " ) } ) ;

> weЬ3 . eth . getTransactionCount ( " Ox9e7 1 3 9 63a92c02317a68lb9bb30 65a824 \ 9del2 4 f " , "pending " ) 41

Как видите, первая отправленная нами транзакция увеличила счетчик до 41, оставаясь при этом неподтвержденной. Однако вызов get Т r ans act i onCoun t не учел следующие три транзакции, отправленные подряд. Он посчитал только первую, хотя, как можно было ожидать, в пуле памяти должно находиться еще три. Если подождать несколько секунд, пока не завершится сетевое взаимодей­ ствие, вызов getTran s a c t i onCount вернет ожидаемое значение. Но до это­ го, когда у нас есть хотя бы одна отложенная транзакция, эта функция нам мало чем помогла. Если вы пишете приложение, которое формирует транзакции, при подсчете отложенных сообщений нельзя полагаться на ge t Т r an s а с t i on С oun t. Вывод этого вызова становится достоверным только в случае, когда количество отло­ женных и подтвержденных транзакций совпадает (то есть все они уже выпол­ нены); только после этого вы можете запускать свой счетчик одноразовых ко­ дов. Далее отслеживайте одноразовый код в своем приложении до тех пор, пока каждая транзакция не будет подтверждена. Интерфейс JSON RPC клиента Parity предлагает функцию p a r i t y _ nextNon ce, которая возвращает следующий одноразовый код для вашей тран­ закции. Эта функция ведет корректный подсчет одноразовых кодов, даже если вы сформировали сразу несколько неподтвержденных транзакций подряд: $ curl --data ' { "method" : "parity_nextNonce " , \

"params " : [ " 0x9e 7 1 3 9 63a92c02 317a68lb9bb30 65a8 2 4 9del24 f " ] , \

" id" : l , " j sonrpc " : " 2 . 0 " } ' -Н •: content-Тype : application/j son " -Х POST \

localhos t : 8545

{ " j s o n rp c " : " 2 . 0 " , " re s u l t " : " O x 3 2 " , " i d " : l }

У Parity есть веб-консоль для доступа к интерфейсу JSON RPC, но здесь вместо нее используется консольный НТ Т Р-клиент.

156

Одноразовы й код транзакции

Разрывы между одноразовыми кодами , дубликация и подтверждение Если транзакции создаются программным способом, одноразовые коды необ­ ходимо отслеживать, особенно если этим одновременно занимается несколько независимых процессов. Блокчейн-сеть Ethereum обрабатывает транзакции последовательно, в зави­ симости от одноразовых кодов. То есть, если вы передадите две транзакции с ко­ дами О и 2, вторая не будет включена ни в какой блок. И пока сеть Ethereum ожи­ дает пропущенного одноразового кода, она будет храниться в пуле памяти. Все узлы будут считать, что пропущенный код просто задерживается и что вторая транзакция пришла вне очереди. Если затем передать транзакцию с недостающим кодом 1, обе они (1 и 2) бу­ дут обработаны и включены (при условии прохождения проверки, конечно). По­ сле заполнения этого разрыва сеть сможет сгенерировать «внеочередную» тран­ закцию, которая хранилась в ее пуле памяти. Это означает, что если при создании нескольких транзакций подряд одна из них не будет официально включена ни в какой блок, все последующие тран­ закции «застопорятся» в ожидании пропущенного одноразового кода. Если транзакция оказалась недействительной или ей не хватает газа, она может слу­ чайно создать «разрыв» в последовательности одноразовых кодов. Чтобы возоб­ новить процесс, вы должны передать корректную транзакцию с пропущенным кодом. Вы также должны помнить, что, как только сеть подтвердит транзак­ цию с «недостающим» кодом, все остальные транзакции с последующими ко­ дами начнут становиться действительными одна за другой; транзакцию нель­ зя «отозвать» ! 1 С другой стороны, если вы случайно продублируете одноразовый код (напри­ мер, передав две транзакции с одинаковым кодом, но с разными получателями или значениями), одна из транзакций будет подтверждена, а другая отклонена. Это будет зависеть от того, в каком порядке они дойдут до первого проверяюще­ го узла, который их примет, то есть исход довольно непредсказуемый. Как видите, вам необходимо отслеживать одноразовые коды, и, если ваше приложение не делает это правильно, у вас возникнут проблемы. К сожалению, все становится еще сложнее, это делается параллельно. В этом вы убедитесь в следующем разделе.

1

Англ.: recall. - Прим. ред.

Глава 6. Транзакции

157

П араллелизм, происхождение тран закции и одноразовые коды Параллелизм является сложным аспектом информатики, который иноrда созда­ ет неожиданные проблемы, особенно в децентрализованных и распределенных системах реального времени, таких как Ethereum. Говоря простым языком, параллелизм - это коrда вы одновременно прово­ дите вычисления в нескольких независимых системах. Это может происходить в рамках одной программы (мноrопоточность), на одном процессоре (мноrо­ процессорность) или на разных компьютерах (как в распределенных системах). Ethereum по определению является системой с поддержкой параллельного вы­ полнения (на нодах, клиентах, в DApp), которая, благодаря консенсусу, имеет единое состояние. Теперь представьте, что у вас есть несколько независимых кошельков, ко­ торые генерируют транзакции из одноrо адреса (или набора адресов). Приме­ ром такой ситуации может служить то, как биржа обрабатывает вывод средств со своеrо rорячеrо кошелька (который, в отличие от «холодных кошельков» (cold wallets) 1 , хранит свои ключи в Интернете). В идеале вам бы хотелось обрабаты­ вать вывод сразу на нескольких компьютерах, чтобы этот процесс не стал уз­ ким местом или единой точкой отказа в системе. Но это очень быстро начинает создавать проблемы с параллелизмом, и не в последнюю очередь из-за выбо­ ра одноразовых кодов. Как скоординировать работу нескольких компьютеров, которые генерируют, подписывают и распространяют транзакции из одноrо и тоrо же rорячеrо кошелька? Для назначения одноразовых кодов можно было бы использовать один ком­ пьютер, обслуживая запросы других компьютеров, подписывающих транзакции, в порядке их поступления. Но в этом случае данный компьютер превратился бы в единую точку отказа. Что еще хуже, если один из нескольких назначенных ко­ дов не будет задействован (из-за сбоя в компьютере, обрабатывавшем транзак­ цию с этим кодом), все последующие транзакции застопорятся. К этой задаче можно подойти иначе и не назначать одноразовые коды сгене­ рированным транзакциям (тем самым оставляя их неподписанными - помни те, одноразовый код является неотъемлемой частью транзакционных данных и поэтому должен быть включен в цифровую подпись, которая аутентифициру­ ет транзакцию). Затем вы моrли бы направлять их на одну ноду, которая будет их подписывать и отслеживать одноразовые коды. Но опять же, это стало бы уз­ ким местом в вашем процессе: подписание и отслеживание одноразовых кодов, 1

Х о л о д н ы й к о ш е л е к - это приложение или пакет программ (например, на внешней кар­ те памяти, USВ-носителе), с помощью которого можно хранить информацию об учетных записях и которая не требует постоянного интернет-соединения. - Прим. ред.

1 S8

Одноразовы й код транзакции

скорее всего, начало бы отставать под высокой нагрузкой; в то же время генера­ ция неподписанных транзакций на самом деле не нуждается в распараллелива­ нии. Вы бы получили некий параллелизм, но на ключевых этапах вашего про­ цесса его было бы недостаточно. В итоге подобные проблемы с параллелизмом вдобавок к отслеживанию ба­ лансов учетных записей и подтверждений транзакций в независимых процес­ сах заставляют большинство разработчиков кошельков избегать параллельной обработки. Это помогает избежать возникновения узких мест, таких как еди­ ный процесс для обработки всех транзакций по выводу средств с биржи или со­ здание нескольких «горячих кошельков» (hot wallets), которые могут выводить деньги совершенно независимо друг от друга и лишь требуют периодической балансировки.

Газ для транзакци й 1 Мы затрагивали газ в предыдущих главах, и мы еще обсудим его подробно в раз­ деле «Газ» на с. 424. Но пока давайте рассмотрим основы и то, какую роль игра­ ют компоненты транзакции ga s P r i ce и ga s L imi t. Газ (gas) - это топливо для Ethereum. Это отдельная виртуальная валюта со своим обменным курсом относительно эфира (ether). Ethereum использует газ для ограничения ресурсов, доступных транзакции, так как та обрабатыва­ ется на тысячах компьютеров по всему миру. Модель (тьюринr-полных) вычис­ лений с ограниченными ресурсами нуждается в какой-то системе измерения, чтобы избежать DоS-атак или непреднамеренно расточительных транзакций. Газ отделен от эфира, чтобы оградить систему от резких скачков в его цене и управлять важной и деликатной разницей в цене между различными ресурса­ ми (вычислениями. - Ред.), которые оплачиваются газом (такими как процес­ сор, память и хранилище). Поле ga s P r i ce позволяет инициатору транзакции установить цену, кото­ рую он готов заплатить за газ. Эта цена измеряется в вэй за единицу газа. На­ пример, в транзакции, продемонстрированной в главе 2, ваш кошелек присвоил полю ga s P r i ce 3 Gwei (3 gigawei или 3 миллиарда wei). Существует популярный сайт, ethgasstation. info, который пре­ доставляет сведения о текущих ценах на газ в главной сети Ethereum и другие связанные с этим показатели. 1 Англ.: transaction gas. - Прим. ред.

Глава 6. Транзакции

15 9

Кошельки, которые инициируют транзакции, могут ускорить их подтвержде­ ние, регулируя поле g а s Р r i се. Чем выше цена, тем быстрее транзакция может быть подтверждена. И наоборот, низкоприоритетные транзакции могут иметь пониженную цену, что приводит к их замедленному подтверждению. Минималь­ ным значением ga s P r i c e является ноль - это означает отсутствие комиссии за транзакцию. В периоды низкого спроса на место в блоке такие транзакции вполне могут быть сгенерированы. Минимально допустимым значением g а s Р r i с е является ноль. Это означает, что кошельки могут генерировать совер­ шенно бесплатные транзакции. В зависимости от ресурсов сети они могут так и не дождаться подтверждения, но их существование никак не противоречит протоколу. Вы можете найти несколько примеров бесплатных транзакций, которые были успешно добавлены в блокчейн Ethereum. Интерфейс wеЬЗ умеет подсказывать значение ga s P r i ce, вычисляя меди­ анную цену для нескольких блоков (для этого можно воспользоваться консолью truffle или любой реализации wеЬЗ на JavaScript): > weЬЗ . eth . qetGasPrice ( console . loq) > nu l l B i gNumb e r { s : 1 , е : 1 0 , с :

( 10000000000] }

Вторым важным полем, относящимся к газу, является ga s L imi t. Говоря простым языком, ga s L imi t задает максимальное количество единиц газа, ко­ торое инициатор транзакции готов купить для ее завершения. В простых пла­ тежах, когда транзакция передает эфир от одной учетной записи ЕОА к другой, объем необходимого газа зафиксирован на уровне 21 ООО единиц. Чтобы узнать, сколько это стоит в эфире, нужно умножить 21 ООО на цену газа, которую вы го­ товы заплатить. Например: > weЬЗ . eth . qetGasPrice ( function (err , res ) { console . loq ( re s * 2 1 0 0 0 ) } ) > 210000000000000

Если адрес назначения вашей транзакции принадлежит контракту, объем необходимого газа можно лишь оценить, но нельзя точно определить. Это свя­ зано с тем, что контракт может проверять разные условия, которые ведут к вы полнению разных веток кода, в результате чего общий расход газа может раз­ ниться. Контракт может выполнить простые или более сложные вычисления 160

Газ для транзакций

в зависимости от условий, которые вы не в силах контролировать или предска­ зать. Продемонстрируем это на примере: напишем смарт-контракт, который при каждом своем вызове увеличивает счетчик и выполняет определенное количество итераций цикла, равное значению счетчика. Возможно, на сотом вызове он выдаст нам какой-то приз, как в лотерее, но для этого ему нужно будет произвести допол­ нительные вычисления. Первые 99 вызовов дадут один результат, а на сотый раз вы получите что-то совсем другое. Объем газа, который нужно заплатить, зависит от того, сколько раз другие транзакции вызвали эту функцию перед включением ее в блок. Представьте, что в своей оценке вы исходите из того, что ваша транзак­ ция будет 99-й, но прямо перед подтверждением кто-то другой выполняет 99-й вы­ зов. Теперь ваша транзакция является 100-й, в результате чего значительно возра­ стает сложность вычислений (и объем расходуемого газа). Если воспользоваться популярной аналогией, которая часто встречается в мире Ethereum, ga s L imi t можно воспринимать как объем топливного бака вашей машины ( � сама машина - это транзакция). Вы заливаете в бак столько топлива, сколько, по вашему мнению, должно хватить для поездки (вычисления, которые нужно выполнить для проверки вашей транзакции). Вы можете в неко­ торой степени оценить этот объем, но в вашем маршруте вероятны непредвиденные изменения, такие как объезд (более сложная ветвь выполнения), кото­ рые увеличат расход горючего. Следует признать, что аналогия с топливным баком отчасти обманчива. Это больше похоже на кредитную карту для сети заправок с оплатой по завершению поездки и с учетом того, сколько топлива вы на самом деле израсходовали. Когда вы передаете свою транзакцию, на одном из первых этапов проверяется, содер­ жит ли учетная запись, которая ее инициировала, достаточно эфира для уплаты комиссии ga s P r i ce * gas. Однако эта сумма не списывается с вашего счета, пока транзакция не завершит работу. Вы должны заплатить только за тот газ, ко­ торый расходовала ваша транзакция, но прежде чем ее отправлять, у вас на счете должно быть достаточное количество эфира, чтобы покрыть максимально воз­ можный объем (газа), за который вы готовы заплатить.

Получатель транзакции Получатель транзакции указывается в поле «кому» (to), которое содержит адрес Ethereum длиной 20 байт. Этот адрес может принадлежать как ЕОА, так и контракту. Ethereum не выполняет дальнейших проверок этого поля. Корректным счита­ ется любое 20-байтовое значение. Если оно соответствует адресу, у которого нет приватного ключа или контракта, транзакция все равно будет действительной. Глава 6 . Транзакции

161

У Ethereum нет механизма, который проверял бы, был ли адрес корректно сге­ нерирован из существующего публичного (и, следовательно, приватного) ключа. �·

Протокол Ethereum не валидирует 1 адрес получателя транзак­ ции. Вы можете отправить транзакцию по адресу, с которым не связаны приватный ключ или контракт, в результате чего ваш эфир «сгорит» и его больше нельзя будет потратить. Проверка должна выполняться на уровне пользовательского интерфейса.

Отправка транзакции по неправильному адресу, скорее всего, приведет к сrо­ ранию 2 посланного эфира (доступ к нему потеряется, и его больше нельзя бу­ дет потратить), так как у большинства адресов нет общеизвестных приватных ключей и, следовательно, для них нельзя сгенерировать подходящую подпись. Подразумевается, что проверка происходит на уровне пользовательского интер­ фейса (см. раздел «Шестнадцатеричная кодировка (EIP-55)» на с. 125). На самом деле у операции сжигания эфира может быть целый ряд уважительных при чин - например, это преграда, предохраняющая от мошенничества в каналах оплаты и смарт-контрактах. Кроме того, ввиду ограниченного количества эфи­ ра, его сжигание, по сути, распределяет утраченные средства между всеми дер­ жателями эфира (пропорционально тому объему эфира, которым они владеют).

З начение и данные транзакции Основная «полезная нагрузка» транзакции содержится в двух полях: v а 1 u е и da t a. Транзакция может нести в себе как значение, так и данные, или что-то одно, или ничего. Допустимы все четыре комбинации. Транзакция, содержащая только значение, называется платежом. Транзак­ ция, содержащая только данные, называется вызовом. Транзакция со значением и данными является и тем и другим. Если у транзакции нет ни значения, ни дан­ ных, это, наверное, пустая трата газа. Но она все равно возможна. Давайте попробуем все эти комбинации. Вначале мы укажем исходный и ко­ нечный адреса из нашего кошелька, просто чтобы сделать наш пример нагляднее: src

dst 1 2

webЗ . e th . accounts [ O ] ; webЗ . e th . accounts [ l ] ;

Не проверяет. - Прим. ред. Англ.: burn. - Прим. ред.

162

Значение и данные транзакции

Наша первая транзакция содержит только значение (платежа), без каких-ли­ бо данных : web З . e th . s e ndTran s a c t i o n ( { from : s r c , to : d s t , \ value : web З . toWe i ( 0 . 0 1 , " ethe r " ) , data : " " } ) ;

Как видно на рис. 6. 1 , наш кошелек показывает панель подтверждения со зна­ чением, которое будет отправлено.

Рис. 6. 1. Кошелек Parity выводит транзакцию со значением, но без данных В следующем примере укажем сразу значение и данные : web З . e th . sendTran s a c t i o n ( { from : s r c , to : d s t , \

value : webЗ . toWe i ( 0 . 0 1 , " ethe r " ) , data : " 0 х 1 2 3 4 " } ) ;

Как показано на рис. 6.2, на панели подтверждения нашего кошелька выво­ дится отправляемое значение и полезные данные.

Рис. 6.2. Кошелек Parity выводит транзакцию со значением и данными Следующая транзакция включает в себя данные, но ее значение равно нолю: webЗ . eth . sendTransaction ( { from : s r c , to : dst , value : О , data : " Ох 1 2 3 4 " } ) ;

Глава 6. Транзакции

163

Как видно на рис. 6.3, панель подтверждения нашего кошелька показывает нулевое значение и полезные данные.

Рис. 6.3. Кошелек Parity выводит транзакцию без значения, только с данными

Наконец, последняя транзакция не содержит ни отправляемого значения, ни полезных данных: webЗ . e th . sendT ran s a c t i on ( { f rom : s r c , to : ds t , value : О , data : " " } ) ) ;

На рис. 6.4 видно, что наш кошелек выводит панель подтверждения с нуле­ вым значением.

Рис. 6.4. Кошелек Parity выводит транзакцию без значения и данных

П ередача з начения учетным записям ЕОА и контрактам В Ethereum транзакция со значением (value) является эквивалентом платежа. По­ ведение таких транзакций может отличаться в зависимости от того, принадле­ жит ли конечный адрес контракту. Для адресов ЕОА (или, скорее, для любых адресов, не помеченных в блок­ чейне как контракты) Ethereum записывает изменение состояния, добавляя по­ сланное значение на соответствующий счет. Если этот адрес ранее не встречался, он добавляется к внутреннему представлению состояния клиента, а его баланс инициализируется с помощью значения вашего платежа. 164

1

Значение и данные транзакции

Если конечный адрес ( t о) принадлежит контракту, EVM выполнит этот кон тракт и попытается вызвать функцию, указанную в полезных данных вашей транзакции. Если данных нет, EVM вызовет резервную функцию и, если та ока­ жется оплачиваемой, выполнит ее, чтобы определить последующие шаги. Если резервная функция отсутствует, результатом транзакции будет увеличение ба­ ланса на счете контракта - в точности как в случае с платежом на кошелек. Контракт может отклонить входящий платеж, сгенерировав исключение в момент вызова функции или согласно условиям, описанным в коде функции. Если функция завершится успешно (без исключения), то состояние контракта обновится, чтобы отразить увеличение баланса эфира на его счете.

Переда ч а полезных данны х уч етным записям ЕОА и контрактам Если ваша транзакция содержит данные, она, скорее всего, отправляется по ад­ ресу контракта. Это не означает, что вы не можете послать полезные данные учетной записи ЕОА - такое действие полностью соответствует протоколу Ethereum. Но в таком случае интерпретация данных ложится на кошелек, кото­ рый вы используете для доступа к ЕОА. Протокол Ethereum их игнорирует. Боль­ шинство кошельков тоже игнорируют любые данные, поступающие в учетную запись ЕОА, которую они контролируют. Возможно, в будущем появятся стан­ дарты, которые позволят кошелькам интерпретировать данные так, как это де­ лают контракты, то есть транзакции смогут вызывать функции, выполняемые внутри пользовательских кошельков. Однако важно отметить, что, в отличие от выполнения контрактов, любая интерпретация полезных данных учетными записями ЕОА будет проводиться без соблюдения правил консенсуса Ethereum. Пока давайте исходить из того, что ваша транзакция доставляет данные по адресу контракта. В этом случае EVM интерпретирует данные как вызов контракта. В большинстве контрактов эти данные служат для вызова функции с определенным именем и передачи ей закодированных аргументов. Полезные данные, отправленные контракту, совместимому с ABI (можете считать, что это справедливо для всех контрактов), сериализуются 1 в шестна­ дцатеричном виде и состоят из: - селектора функции. Первые 4 байта хеша Keccak-256, полученного из про­ тотипа функции. Это позволяет контракту однозначно определить, какую функцию вы хотите вызвать; 1

С е р и а л и з а ц и я - это процесс перевода какой-либо структуры данных в последователь­ ность байтов. - Прим. ред.

Глава 6. Транзакции

165

- аргументов функции. Аргументы функции кодируются в соответствии с правилами для различных простых типов данных, определенными в спе­ цификации ABI. В примере 2.1 мы определили функцию для вывода средств: function wi thdraw ( uint w i thdraw_amount ) puЫic {

Прототип функции состоит из ее имени и типов данных для всех ее аргумен­ тов, заключенных в скобки и разделенных запятыми. Наша функция называ­ ется w i t hdr aw; она принимает один аргумент типа u i n t (сокращение для u i n t 2 5 6). Поэтому ее прототип будет выглядеть так: wi thdraw (uint2 5 6 )

Давайте вычислим хеш Keccak-256 для этой строки: > weЬЗ . sha3 ( "withdraw (uint2 5 6 ) " ) ;

' O x 2 e l a 7 d4 d l 3 3 2 2 e 7b 9 6 f 9 a 5 7 4 1 3 e 1 5 2 5 c 2 5 0 fb 7 a 9 0 2 1 c f 9 1 d l 5 4 0d5b 6 9 f l б a4 9 f '

Первые 4 байта хеша, 0 x2 e l a 7 d 4 d - это наш «селектор», благодаря кото­ рому контракт будет знать, какую функцию мы хотим вызвать. Теперь давайте вычислим значение, которое следует передать в качестве ар­ гумента w i thdraw_amount. Мы хотим вывести 0,01 эфира. Давайте переведем это значение в шестнадцатеричное беззнаковое целое 256-битное число с поряд­ ком следования битов от старшего к младшему, предварительно записав его в wei: > withdraw_amount = weЬЗ . toWei ( 0 . 0 1 , " e ther " ) ; ' 10000000000000000 '

> withdraw amount hex = weЬЗ . toHex (withdraw_amount) ; ' O x2 3 8 6 f 2 6 f c 1 0 0 0 0 '

Теперь добавим к сумме селектор функции (с отступом в 32 байта): 2 e l a 7 d 4 d0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 8 6 f 2 6 fcl O O O O

Это полезные данные нашей транзакции, которая вызывает функцию wi thdraw и запрашивает 0,01 эфира в качестве w i thdraw_amount. 166

Значение и данные транзакции

С пециальные транзакции: создание контрактов Особым случаем, который следует упомянуть, являются транзакции, создаю­ щие в блокчейне новый контракт и развертывающие его для дальнейшего ис­ пользования. Такие транзакции отправляются по специальному адресу, кото­ рьiй называют нулевым; в их поле to указан адрес О х О, не связанный ни с ЕОА (у него нет соответствующей пары приватного-публичного ключей), ни с кон­ трактом. Он не может потратить эфир или инициировать транзакцию. Он ис­ пользуется лишь в качестве конечного адресата со специальной командой «со­ здать этот контракт». Нулевой адрес предназначен исключительно для создания контрактов, но иногда он принимает платежи с других адресов. Этому может быть два объ­ яснения: чистая случайность, приводящая к потере эфира, или намеренное сжи­ гание эфира (осознанное разрушение эфира путем отправки его на адрес, кото­ рый никогда не сможет его потратить). Но, если вы хотите сжечь свой эфир, вам следует явно задекларировать свои намерения; используйте адрес, который спе­ циально для этого предназначен: O x O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O dEa D

- -� : ,�

Любой эфир, отправленный на адрес для его сжигания, теря­ ется навсе да, и е о больше нельзя будет потратить. с

,

Транзакция для создания контракта должна содержать только полезные дан­ ные со скомпилированным байт-кодом, который создает контракт. Единственный результат такой транзакции выражается в появлении нового контракта. Если вы хотите поместить на баланс нового контракта какую-то сумму, можете указать ко­ личество эфира в поле value; но делать это вовсе не обязательно. Если вы по­ шлете определенную сумму (эфира) на адрес для создания контракта и не укажите полезные данные 1 (не укажете сам контракт), результат будет аналогичен отправ­ ке средств на адрес для сжигания - т. к. у вас нет контракта, на баланс которого можно было бы поместить какую-то сумму, поэтому эфир утрачивается. В качестве примера мы можем создать контракт Faucet.sol из главы 2. Для это­ го пошлем на нулевой адрес транзакцию с контрактом. Контракт должен быть

1

Англ.: data payload. - Прим. ред.

Глава 6. Транзакции

167

скомпилирован в байт-код. Чтобы это сделать, можно воспользоваться компи­ лятором Solidity: $ solc - -bin Faucet . sol Binary :

6 0 6 0 6 0 4 0 5 2 3 4 1 5 6 1 0 0 0 f 5 7 6 0 0 0 8 0 fd5b 6 0 e 5 8 0 6 1 0 0 l d 6 0 0 0 3 9 6 0 0

O f 3 0 0 6 0 6 0 6 0 4 0 52 6 0 0 4 3 6 1 0 6Q_

Эту же информацию можно получить из онлайн-компилятора Remix.

591:ilii:IA

Transactlon lnformatlon TxНash:

Ox7Ьcc327ae5d369f751>98cOd59037eec41d44dfae75447fd753d912dl>9439124Ь

TxRecelpt Status:

SuC weЬЗ . eth . sendTransaction ( { from : src , to : О , data : faucet_code , \

168

Специальные транзакции: создание контрактов

qas : 1 1 3558 , qasPrice : 2 0 0 0 0 0 0 0 0 0 0 0 } ) ; " 0 x7bc c 3 2 7 ae 5 d3 6 9 f 7 5 b 9 8 c 0 d 5 9 0 3 7 e e c 4 l d 4 4 df a e 7 5 4 4 7 fd7 5 3 d 9 f 2 db 9 4 3 9 1 2 4b " Параметр

t o рекомендуе,ся указывать всегда, даже в случае создания кон­

тракта, поскольку цена ошибочного перевода эфира на адрес Ох О с его без­ возвратной потерей слишком велика . Вы также должны указать и

ga s L imi t.

ga s P r i c e

Когда контракт будет сгенерирован, мы сможем увидеть его в обозревателе

блоков Etherscan, как показано на рис. 6.5. Мы можем проверить квитанцию транзакции, чтобы получить информацию о контракте: > eth . qetTransactionReceipt ( \

" 0x7bcc327ae5d3 69f75b98c0d59037eec4 ld44dfae75447fd753d9f2dЬ9439124b" ) ;

Ы o c kHa s h : " 0 x б fa 7 d 8 b f 9 8 2 4 9 0 de 6 2 4 6 8 7 5 deb2 c 2 l e 5 f 3 6 6 5 b 4 4 2 2 0 8 9 c 0 6 0 1 3 8 f с 3 9 0 7 а 9 5ЬЬ2 " ,

Ы o c kNumbe r : 3 1 0 5 2 5 6 ,

contractAddre s s : " 0 xb2 2 62 7 0 9 6 5 b 4 3 3 7 3 e 9 8ffc бe 2 c 7 6 9 3 c l 7 e 2 c f 4 0b " , cumu l a t iveGa sUsed : 1 1 3 5 5 8 ,

from : " 0 x 2 a 9 6 6a 8 7 db 5 9 1 3 c l b2 2 a 5 9b 0 d 8 a l l c c 5 l c l 6 7 a 8 9 " ,

gasUsed : 1 1 3 5 5 8 , l ogs :

[] ,

logsBl oom : \

" О хО О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О ... 0 0 0 0 0 " ,

status : " O x l " , to : nul l ,

tr a nsac t i onHa s h : \

" 0x7bcc32 7 ae5d3 6 9 f 7 5b 9 8 c O d5 9 0 3 7eec4 ld4 4dfae 7 5 4 4 7 fd7 53d9f2dЬ 9 4 3 9 1 2 4b " ,

trans a c t i o n i ndex : О

Она включает в себя адрес контракта, который мы можем использовать для отправки или получения средств, как это было показано в предыдущем разделе: > contract address = " 0xЬ22 6 2 7 0 9 65b4 3373e98ffc6e2c7 693cl 7e2cf40b" > weЬЗ . eth . sendTransaction ( { from : src , to : contract_addres s , \

Глава 6. Транзакции

169

value : weЬЗ . toWei ( 0 . 1 , "ether " ) , data : " " } ) ; " 0 x бebf2 e l fe 9 5 c c 9 c l fe 2 e l a 0 dc 4 5 6 7 8 c cdl 2 7 d3 7 4 fdf l 4 5 c 5 c 8 e бcd4ea 2 e б ca 9 f "

> weЬЗ . eth . sendTransaction ( { from : src , to : contract_address , value : О ,

data : \

" 0x2ela7d4d00000000000000000000000000000000000000000000000000238 бf2 бfс1 0000 " } ) ;

" 0 x 5 9 8 3 6 0 2 9 e 7 ce 4 3 e 9 2 da f 8 4 3 1 3 8 1 6 c a 3 1 4 2 0 a 7 6 a 9 a 5 7 1 b 6 9e 3 1 e c 4 b f 4 b 3 7 cdl б e "

Через какое-то время обе транзакции появятся в Etherscan, как показано на рис. 6.6.

•,н,ш,

lntomal Transгctk>n•

Code

Events

1

lf Letast З txns

1

TxНnh

Block

Ago

From

Ох.59836029е7се43...

3105346

1 mtn ago

Ох2а956а87dЬ591З . . .

OX6ebl'2e tfe95cc9c. . .

3105319

6 mrns ago

Ох28966а87dЬ591З . . .

Ох-7Ьсс327ае5dЭ69f. . .

3105258

33 mfns ago

0Х2&906а67dЬ591З . . .

1

1

i 1

l -- -- - -

-

--

То

IМ>Ф226270965Ь4ЗЗ . . . li!) О>Ф226270965Ь4ЗЗ. . . COntrect Creatlon

VIIU•

,,.,..1

О Elher

0.00002941-'

0. 1 E!her

,..,,,..,.

О Elher

0,0227116

Рис. 6.6. Etherscan демонстрирует информацию о транзакциях отправки и получения средств

Ц и ф р о вые ПОДПИСИ До сих пор мы не углублялись ни в какие подробности о цифровых подписях. В этом разделе мы покажем, как они работают и как с их помощью можно дока­ зать факт владения закрытым ключом без раскрытия последнего.

Ал горитм цифровых подписей на основе эллипти ч еской криво й В Ethereum для цифровых подписей используется алгоритм ECDSA 1 на основе эллиптической кривой и пар публичных-приватных ключей, описанный в раз­ деле «Подробнее об эллиптической криптографии» на с. 112.

1

Elliptic Curve Digital Signature Algorithm. - Прим. ред.

170

Цифровые подписи

В Ethereum цифровая подпись имеет тройное назначение (см. следующий блок «Определение цифровой подписи согласно " Википедии"»). Во-первых, она доказывает, что владелец приватного ключа (по совместительству и учет­ ной записи Ethereum) разрешил расходованиеэфира или выполнение контракта. Во-вторых, она гарантирует неопровержимость: факт выдачи данного разреше­ ния является бесспорным. В-третьих, цифровая подпись доказывает, что данные транзакции не были и не могли быть изменены после ее подписания. Оп редел ение ци ф рово й п одпи си со гласно « В и кипедии» Цифровая подпись -это реквизит электронного документа, полученный в результате криптографического преобразования информации с исполь­ зованием закрытого ключа подписи и позволяющий проверить отсутствие искажения информации в электронном документе с момента формиро­ вания подписи (целостность), принадлежность подписи владельцу сер­ тификата ключа подписи (авторство), а в случае успешной проверки под­ твердить факт подписания электронного документа (неопровержимость). Источник: ru. wikipedia.org!wiki/Элeкmpoннaя_noдnucь

П ринцип работы цифров ы х подписей Цифровая подпись -это математическое выражение, состоящее из двух частей. Первая часть содержит алгоритм создания подписи, который использует приват­ ный (подписной} ключ, полученный из сообщения (в нашем случае из транзак­ ции). Вторая часть - это алгоритм, который позволяет кому утодно проверить подпись, используя только сообщение и публичный ключ. Создан ие ци ф рово й п одпи си В реализации ECDSA, которая используется в Ethereum, подписываемое «сообще­ ние» является транзакцией или, если быть более точным, хешем Keccak-256, по­ лученным из данных транзакции в кодировке RLP. Подписной ключ - это при­ ватный ключ учетной записи ЕОА. В результате получается следующая подпись: Sig = F,/Fk,ccak2s/m), k)

где - k - подписной приватный ключ; - т - транзакция в кодировке RLP; Глава 6. Транзакции

171

функция хеширования Keccak-256; - F.s,g - алгоритм подписания; - Sig - итоговая подпись. - Fk,ccak256 �

Функция F,;g генерирует подпись Sig, состоящую из двух значений, которые обычно помечают как r и s: Sig = (r, s)

П роверка подписи Чтобы проверить подпись, у вас должны быть обе ее части (r и s), сериализован­ ная транзакция и публичный ключ, соответствующий приватному ключу, с по­ мощью которого подпись создана. В сущности этот процесс сводится к тому, что «только владелец приватного ключа, сгенерировавшего данный публичный ключ, мог сформировать эту подпись для этой транзакции». Алгоритм проверки подписи принимает сообщение (то есть хеш использу­ емой нами транзакции), публичный ключ подписанта и саму подпись (значения r и s). Если подпись является подлинной для этих сообщений и публичного клю­ ча, он возвращает t rue.

Математические в ычисления алгоритма ECDSA Как уже упоминалось ранее, подписи создаются математической функцией F,;g' которая возвращает два значения, r и s. В данном разделе мы рассмотрим этот процесс более подробно. Вначале алгоритм создания подписи генерирует фиктивный (временный) приватный ключ, применяя криптоrрафически безопасный способ. Этот ключ используется в вычислении значений r и s, чтобы убедиться в том, что настоя­ щий приватный ключ отправителя не мог быть подобран злоумышленниками на основе подписанных транзакций в сети Ethereum. Как мы знаем из раздела «Публичные (открытые) ключи» на с. 111, фиктив­ ный приватный ключ используется для формирования соответствующего (фик­ тивного) публичного ключа, поэтому мы имеем: - криптоrрафически безопасное случайное число q, которое применяется в качестве фиктивного приватного ключа; - соответствующий фиктивный публичный ключ Q, сгенерированный из q и точки генератора эллиптической кривой G. 172

Ц и ф ровые подписи

Затем значение цифровой подписи берется в качестве координаты х фиктив­ ного публичного ключа Q. Далее алгоритм вычисляет значение подписи s: s = q- 1 (Keccak256(m) + r * k) (mod р)

где - q - фиктивный приватный ключ; - r - координата х фиктивного публичного ключа; - k - подписной приватный ключ (принадлежащий владельцу ЕОА); - т - данные транзакции; - р - степень эллиптической кривой (простое число). Процедура проверки является обратной по отношению к функции генера­ ции подписи. Она использует значения r и s, а также публичный ключ отправи теля, чтобы вычислить Q - точку на эллиптической кривой (фиктивный пуб­ личный ключ, использованный при создании подписи). Этот процесс состоит из таких этапов. 1. 2. 3. 4. 5.

Убеждаемся в том, что все входящие данные сформированы корректно; Вычисляем w = s- 1 mod р; Вычисляем и 1 = Keccak256(m) * w mod р; Вычисляем и2 = r * w mod р; Наконец, вычисляем точку на эллиптической кривой: Q = и 1 * _G + и2 * K (mod p),

где - r и s - значения подписи; - К - публичный ключ подписанта (владельца ЕОА); - т - данные транзакции, которая была подписана; - G - точка генератора эллиптической кривой; - р - степень эллиптической кривой (простое число). Если координата х вычисленной точки Q равна r, проверяющая сторона мо­ жет сделать вывод о том, что подпись является подлинной. Заметьте, что при проверке подписи приватный ключ неизвестен и не рас­ крывается. Глава 6. Транзакции

173

Алгоритм ECDSA специально сделан довольно сложным с ма­ тематической точки зрения; его полное объяснение выходит за рамки этой книги. В Интернете можно найти ряд прекрас­ ных руководств, которые разбирают его шаг за шагом. Ищите по ключевой фразе ECDSA explained или откройте статью по адресу Ьit. ly/2r0HhGB.

Подписание тран закции на практике Чтобы создать корректную транзакцию, инициатор должен подписать сообще­ ние, используя алгоритм ECDSA. Когда мы говорим «подписать транзакцию», мы на самом деле имеем в виду «подписать хеш Keccak-256, полученный из дан­ ных транзакции в кодировке RLP». Подпись применяется не к самой транзак­ ции, а к хешу ее данных. Чтобы подписать транзакцию в Ethereum, инициатор должен: 1. Создать структуру данных транзакции с девятью полями: n o n c e, ga s P r i ce, ga s L imi t, t o, val ue, data, cha i n I D, О, О. 2. Сгенерировать из структуры данных транзакции сериализованное сооб­ щение в кодировке RLP. 3. Вычислить хеш Keccak-256 из этого сериализованноrо сообщения. 4. Вычислить подпись ECDSA, подписав хеш с помощью приватного ключа, принадлежащего ЕОА инициатора. 5. Присоединить к транзакции значения v, r и s, вычисленные для подпи­ си ECDSA. Специальная переменная подписи v определяет два значения: ID цепочки и идентификатор восстановления, с помощью которых функция ECDSArecover проверяет подпись. Она может быть равна 27 или 28, или же сумме удвоенного ID цепочки и числа 35 или 36. Больше информации об ID цепочки можно найти в разделе «Создание "сырой транзакции" с помощью EIP-155» на с. 176. Иден­ тификатор восстановления (27/28 в «старомодных» подписях или 35/36 в пол­ ноценных транзакциях в стиле Spurious Dragon) используется для обозначения четности компонента у публичного ключа (см. «Префиксное значение подписи (v) и восстановление публичного ключа» на с. 177). На блоке № 2675000 в сети Ethereum реализован хардфорк Spurious Dragon, который, помимо прочего, позволил реали­ зовать новый механизм подписания, включающий в себя 174

Цифровые подписи

защиту от воспроизведения транзакций (не давая воспроиз­ водить транзакцию из одной сети в другой). Этот новый ме­ ханизм описан в стандарте EIP-155, влияет на форму транзак­ ций и их подписей. В связи с этим следует уделять внимание первой из трех переменных подписи (то есть v), которая мо­ жет быть указана в одном из двух форматов и определяет поля данных транзакционного сообщения, подлежащие хе­ шированию.

Создание «сырых транзакций» и подписание В этом разделе мы создадим и подпишем сырую транзакцию, используя библио­ теку ethe reumj s - tx. Это будет демонстрация функций, которые обычно при­ меняются внутри кошелька или приложения, которое подписывает транзакции от имени пользователя. Исходный код для этого примера находится в файле raw_ tx_demo.js внутри репозитория GitHub данной книги: // Сна чала за гружа ем зависимости : //

// прт i n i t

// прт i n s t a l l e there umj s - tx

//

// Команда запуска : $ node ra w_ tx_ demo . j s

const ethTx

const txData

=

requ i re ( ' ethe reumj s - t x ' ) ;

= {

nonce : ' О х О ' ,

gas P r i ce : ' О х 0 9 1 8 4 е 7 2 а 0 0 0 ' , gasLimi t : ' О х З О О О О ' ,

to : ' O xb0 9 2 0 c 5 2 3 d5 8 2 0 4 0 f2bcЫbd7 fЬ l c 7 c l e cebdb 3 4 ' ,

value : ' О х О О ' , data : ' ' ,

v : " O x l c " , // cha i n ID главной сети E t here um r: О, s: о

};

tx

=

new ethTx ( t xDat a ) ;

c o n s o l e . l og ( ' RL P - E n coded Т х : О х ' + t x . s e r i a l i z e ( ) . t o S t r i ng ( ' hex ' ) )

Глава 6. Транзакции

175

t x H a s h = t x . h a s h ( ) ; // Перевод в кодировку RLP и вычисление хеша con s o l e . l og ( ' Tx H a s h : О х ' + txHa s h . t o S t r i ng ( ' he x ' ) ) // Подписыва ем тра нза кцию

cons t p r i v K e y = Buffe r . f rom (

' 9 l c 8 3 6 O c 4 cb 4 b 5 f a c 4 5 5 1 3 a 7 2 1 3 f 3 1 d4 e 4 a 7 b f cb 4 6 3 O e 9 fb f O 7 4 f 4 2 a 2 O 3ac

ОЬ9 ' ,

' hex ' ) ;

t x . s i g n ( p r i vKe y ) ; s e r i a l i z edTx = tx . s e r i a l i z e ( ) ;

rawTx = ' S i gned Raw T r a n s a c t i o n : О х ' + s e r i a l i z edTx .

t o S t r i ng ( ' he x ' ) ;

con s o l e . l og ( rawTx )

Запуск этого демонстрационного кода выдаст такой результат: $ node raw_tx_demo . j s

RLP-En coded Тх : O xe 6 8 O 8 6 O 9 1 8 4 e 7 2 a O O O 8 3 O 3 O O O O 9 4 b O 9 2 O c 5 2 3 d5 8 2 O 4 O f2bcЫb d7 fЬ l c 7 c l ...

Тх Ha sh : O x a a 7 f O 3 f 9 f 4 e 5 2 f c f 6 9 f 8 3 6a бd2bbc7 7 O 6 5 8 Oadce O a O 6 8 ff6 5 2 5ba3 3 7 2 1 8 е 6 9 92

S i gned Raw Transact i on : O x f 8 6 6 8 O 8 6 O 9 1 8 4 e 7 2 a O O O 8 3 O 3 O O O O 9 4 b O 9 2 O c 5 2 3 d 5 8 2 O 4 O f2ЬсЫ ...

Создание «сырой транзакции» с помощью EIP-1 55 Стандарт EIP-155 описывает кодировку транзакций с защитой от атаки вос­ произведения, которая перед созданием подписи включает в данные транзак­ ции идентификатор цепочки. Благодаря этому транзакция создается только для определенного блокчейна (например, для главной сети Ethereum) и явля­ ется недействительной в других сетях (таких как Ethereum Classic или тестовой сети Ropsten). Следовательно, транзакции, транслируемые в одной сети, нель­ зя воспроизвести в другой. Отсюда и название стандарта: Simple Replay Attack Protection (простая защита от атаки воспроизведения). EIP-155 добавляет к основным шести полям структуры данных транзакции еще три: идентификатор цепочки, О и О. Добавление происходит перед кодиро­ ванием и хешированием транзакции. Это меняет хеш, к которому позже приме­ няется подпись. Благодаря включению идентификатора цепочки в число подпи­ сываемых данных любые изменения исключаются, так как подпись проверяет, 176

Цифровые подписи

не был ли изменен этот идентификатор. Таким образом, EIP-155 делает невоз­ можным воспроизведение транзакции в других блокчейнах, поскольку коррект­ ность подписи зависит от идентификатора цепочки. Как показано в табл. 6.1, поле с идентификатором цепочки принимает значе­ ние в соответствии с сетью, для которой предназначена транзакция. Таблица 6. 1 . Идентификаторы цепочек

Цепоч ка

ID цепо ч ки

Мейннет Ethereum Morden (устаревшая), Expanse

2

Ropsten

3

Rinkeby

4

Мейннет Rootstock

30

Тепнет Rootstock

31

Kovan

42

Майннет Ethereum Classic

61

Тепнеты Ethereum Classic

62

Приватные тепнеты Geth

1 337

Итоговая структура транзакции закодирована в формате RLP, захеширова­ на и подписана. Алгоритм подписи немного изменен с учетом идентификатора цепочки в префиксе v. Больше подробностей можно найти в спецификации EIP-155 по адресу github. сот/ethereum!EIPs!ЬloЬ!master/EIPS/eip-155. md.

Префиксное зна чение подписи (v) и в осстано вление публичного клю ча Как уже упоминалось в разделе «Структура транзакции» на с. 151, транзакци­ онное сообщение не включает в себя поле «от» ( f r om). Это связано с тем, что публичный ключ инициатора можно вычислить непосредственно из подписи ECDSA. Имея публичный ключ, вы можете легко получить адрес. Этот процесс называется восстановлением публичного ключа. Имея значения r и s, вычисленные в разделе «Математические вычисления алгоритма ECDSA» на с. 172, мы можем восстановить два вероятных публич­ ных ключа. Глава 6. Транзакции

1

1 77

Вначале из координаты х значения r, включенного в подпись, вычисляются две точки на эллиптической кривой, R и R'. Точек две, поскольку эллиптическая кривая является симметричной относительно оси Х, поэтому у каждой коорди­ наты х есть два возможных значения, которые подходят для кривой: по одному с каждой стороны оси Х. Из r также вычисляется r 1 - обратное число по отношению к r. Наконец, мы вычисляем значение z; это п младших бит хеша сообщения, где п - степень эллиптической кривой. Таким образом, мы получаем два возможных ключа: К1 = r 1 (sR - zG)

и К2 = r 1 (sR' - zG) где: - К 1 и К2 - два возможных варианта публичного ключа подписанта; - r 1 - число, обратное по отношению к значению r подписанта; - s - значение s подписи; - R и R' - два возможных варианта фиктивного публичного ключа Q; - z - п младших бит хеша сообщения; - G - точка генератора эллиптической кривой. Для повышенной эффективности подпись транзакции включает в себя пре­ фиксное значение v, благодаря которому можно понять, какое из двух возмож­ ных значений R является фиктивным публичным ключом. Если v четное, кор­ ректным будет значение R; если v нечетное, следует выбрать R'. Таким образом, нам нужно вычислить по одному варианту для R и К.

Разделение опера ц ии подписания и п ередачи (офла й н - подписание) После подписания транзакции она готова к передаче 1 в блокчейн-сеть Ethereum. Все три стадии (создание, подписание и транслирование транзакции) обычно ' Англ.: transmit. - Прим. ред.

178

Разделение операции подписания и передачи (офлайн-подписание)

выполняются в виде единой операции - например с помощью wеЬ З . e t h . sendTran s a c t i on. Но, как вы уже видели в разделе «Создание и подписание сырых транзакций» на с. 175, транзакции можно создавать и подписывать в два отдельных этапа. Подписав транзакцию, вы можете передать ее с помощью вы­ зова wеЬ З . eth . s endS i gn e dTran s a c t i on, который принимает ее шестна­ дцатеричное представление и передает по сети Ethereum. Но зачем нам разделять подписание транзакции и ее передачу? Наиболее ча­ стой причиной является безопасность. Компьютер, подписывающий транзак­ цию, должен держать в памяти разблокированные приватные ключи. Компью­ тер, выполняющий передачу, должен быть подключен к Интернету (и иметь запущенный клиент Ethereum). Если за эти две функции отвечает одно устрой­ ство, ваши приватные ключи находятся в онлайн-системе, что довольно опасно. Выполнение подписания на отдельном компьютере, который не имеет доступа к Интернету, называется офлайн-подписанием (offiine signing) и является рас­ пространенной методикой обеспечения безопасности. Этот процесс показан на рис. 6.7. 1. Создаем неподписанную транзакцию на онлайн-устройстве, способном получить текущее состояние учетной записи - в частности, текущий од­ норазовый код и доступные средства. 2. Передаем неподписанную транзакцию на физически изолированный компьютер, который ее подпишет. Для этого используется, к примеру, QR-код или флеш-накопитель. 3. Передаем подписанную транзакцию (обратно) на онлайн-устройство для ее дальнейшей трансляции в блокчейн Ethereum (опять с помощью QR-кода или флеш-накопителя).

Автономное подписание � :

Сеть Ethereum

Физическая и золя и я 1 QR- код (u�рониз ц и я � ► : � или нла й н ко м п ьютер О -

, Автоно ��н:ь1 й ь � JI., .. . _, Faucet . deployed ( ) . then ( i => { FaucetDeployed = i } )

truffie ( deve lop ) > FaucetDeployed . send (weЬЗ . toWei ( l , "ether " ) ) . then (res => \ { console . loq ( res . loqs [ O ] . event , res . loqs [ O ] . arqs ) } )

Depo s i t { from : ' 0 x 6 2 7 3 0 6 0 9 0 abab 3 a 6e 1 4 0 0 e 9 3 4 5b c 6 0 c 7 8 a 8be f 5 7 ' , amount : B i gNumЬe r { s : 1 , е : 1 8 , с : ( 1 0 0 0 0 ] ) )

truffie ( deve l op ) > FaucetDeployed . withdraw (weЬЗ . toWei ( 0 . 1 , "ether " ) ) . then ( res => \

{ console . loq ( res . loqs [ O ] . event , res . loqs [ O ] . arqs ) } )

Wi thdrawa l { to : ' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а 6е 1 4 0 0 е 9 3 4 5Ьс 6 0 с 7 8 a 8 be f 5 7 ' , amount : B igNumЬe r { s : 1 , е : 1 7 , с : ( 1 0 0 0 ] ) )

После развертывания контракта с помощью функции dep l o yed выполним две транзакции. Первая внесет средства на счет (с использованием s end), сге­ нерировав тем самым событие Depo s i t в журнале транзакций: Depo s i t { f rom : ' 0 x 6 2 7 3 0 6 0 9 0 abab 3 a 6 e 1 4 0 0 e 9 3 4 5b c 6 0 c 7 8 a 8be f 5 7 ' , amount : Bi gNumber { s : 1 , е : 1 8 , с : [ 1 0 0 0 0 ] ) )

Дальше воспользуемся функцией w i thdraw, чтобы вывести средства. Это сгенерирует событие Wi thdrawa l : W i thdrawal { to : ' О х 6 2 7 3 0 6 0 9 0 abab 3 a 6 e l 4 0 0 е 9 3 4 5Ь с 6 0 с 7 8 a 8be f 5 7 ' , amount : Bi gNumЬe r { s : 1 , е : 1 7 , с : ( 1 0 0 0 ] ) )

Глава 7. (март-контракты и язык Solidity

213

Чтобы получить эти события, пройдемся по массиву логов, возвращенному в качестве результата транзакции (re s). Первая запись ( l o g s [ О ] ) содержит имя события в поле l o g s [ О ] • event и его аргументы в поле l o g s [ О ] • args. Выведя эти значения в консоль, мы можем увидеть названия и аргументы сгене­ рированных событий. Это очень полезный механизм - не только для внутреннего взаимодействия (т. е. внутри контракта), но и для отладки в ходе разработки.

Вызов других контрактов (send, call, callcode, delegatecall) Вызов других контрактов из своего кода - это очень полезная, но потенциаль­ но опасная операция. Мы исследуем различные способы, как этого можно до­ стичь, и оценим риски в каждом из подходов. Если вкратце, то опасность связа­ на с тем фактом, что вы можете мало что знать о контрактах, которые вызываете или которые вызывают ваш контракт. При написании смарт-контрактов следу­ ет помнить, что помимо учетных записей ЕОА, с которыми вы преимуществен но будете иметь дело, ваш код вполне может взаимодействовать с потенциально вредоносными контрактами произвольной сложности.

Создание нового э кземпляра Безопаснее всего вызывать собственный контракт. Так вы можете быть уве­ рены в его интерфейсах и поведении. Для этого, как и в других объектно­ ориентированных языках, достаточно создать его экземпляр с помощью клю­ чевого слова new. В Solidity это ключевое слово создает контракт в блокчейне и возвращает объект, через который вы можете к нему обращаться. Представь­ те, что вы хотите создать и вызвать контракт Fau cet внутри другого контрак­ та под названием To ken: contract Token is mo r t a l

Faucet

fauce t ;

constructor ( )

faucet = new Faucet ( ) ;

Благодаря механизму формирования контракта вы будете в точности знать его тип и интерфейс. Контракт Faucet должен быть определен в области види­ мости To ken; если определение находится в другом файле, можно воспользо­ ваться выражением imp o r t: 214

Программирование на языке Solidity

import " Faucet . s o l " ; contract Token is mo r t a l Faucet

fauce t ;

constructor ( )

faucet = new Faucet ( ) ;

При желании можно указать объем эфира (va l u e), который переводится во время создания, и передать аргументы конструктору нового контракта: import " Faucet . s o l " ; contract Token is mo r t a l Faucet

fauce t ;

constructor ( ) faucet

( new Faucet ) . va l ue ( 0 . 5 ether) ( ) ;

После этого можно вызывать функции f а u се t. В данном примере мы вызы­ ваем функцию de s t roy из одноименной функции контракта To ken: import " Faucet . s o l " ; contract Token is mo r t a l Faucet

faucet ;

constructor ( ) faucet

( new Fauce t ) . va l ue ( 0 . 5 ether) ( ) ;

function de s t roy ( ) owne rOn l y fauce t . de s t r o y ( ) ;

Глава 7. (март-контракты и язык Solidity

215

Стоит отметить, что контракт To ken, которым вы владеете, сам является владельцем контракта Fau cet, поэтому только он может его уничтожить. Обращение к существующему экземпляру Если контракт уже существует, его экземпляр можно вызвать путем приведения его адреса. При этом вы применяете известный вам интерфейс к существующе­ му экземпляру. В связи с этим очень важно быть уверенным в том, что экзем­ пляр, к которому вы обращаетесь, на самом деле имеет предполагаемый тип. Да­ вайте рассмотрим пример: import " Faucet . s o l " ; contract Token is mo r t a l {

Faucet

fauce t ;

cons tructor ( address

f)

{

faucet = Faucet ( f ) ;

faucet . w i thdraw ( O . l ether )

Здесь мы берем адрес, предоставленный аргументом конструктора, _ f, и при­ водим его к типу Faucet. Этот механизм куда более рискованный по сравнению с предыдущим, поскольку мы не можем точно сказать, принадлежит ли этот ад­ рес объекту Fau c e t. Вызывая функцию wi thdraw, мы исходим из того, что принимаемые ею аргументы и выполняемый ею код соответствуют объявле­ нию нашему F a u c e t. Но абсолютной уверенности быть не может. Функция wi thdraw, находящаяся по этому адресу, может выполнить совсем не то, что мы ожидаем, даже если имя будет совпадать. Таким образом, использование пе­ реданного адреса и приведение его к определенному типу является намного бо­ лее опасным, чем самостоятельное создание контракта. «Сырой» вызов, delegatecall Solidity поддерживает еще более «низкоуровневые» функции для вызова других контрактов. Они напрямую соответствуют одноименным опкодам EVM и по­ зволяют вручную сформировать вызов одного контракта из другого. Таким об­ разом, они представляют собой самый гибкий и опасный механизм вызова дру­ гих контрактов. 216

Программирование на языке Solidity

Ниже показан пример с использованием метода c a l l: contract Token is mo r t a l { constructor ( address

fauce t ) {

faucet . c a l l ( " wi thdraw " , 0 . 1 ether ) ;

Как видите, это слепой вызов функции, очень похожий на формирование сы­ рой транзакции, только в контексте контракта. Он может подвергнуть ваш кон­ тракт целому ряду рисков безопасности, самым важным из которых является ре­ ентерабельность (подробнее об этом в разделе «Реентерабельность» на с. 240). В случае возникновения проблем функция c a l l возвращает f a l s e, поэтому вы можете проверить возвращаемое значение и обработать ошибки: contract To ken is mo r t a l { constructor ( address

faucet ) {

if ! ( faucet . ca l l ( " wi thdraw " , 0 . 1 ether ) ) {

reve r t ( " Wi thdrawal f rom faucet f a i l e d " ) ;

Другая разновидность с а 1 1, de l е g а t е с а 1 1, является менее опасной заме­ ной метода cal l code. Последний скоро станет устаревшим, поэтому его не сле­ дует использовать. Как уже упоминалось в разделе «Объект адрес» на с. 198, вызов de legateca l l отличается от ca l l тем, что его контекст m s g не меняется. На­ пример, ca l l присваивает msg . s ende r адрес вызывающего контракта, тогда как de l egateca l l оставляет то же знaчeниe msg. s ende r, которое было у вы­ зывающей стороны. В сущности, de l e g a t e c a l l, выполняет код другого кон­ тракта в контексте работы текущего. Этот метод чаще всего используется для вызова кода из библиотеки. Он позволяет применять библиотечные функции, хранимые в другом месте, но при этом делает так, чтобы их код работал с дан­ ными вашего контракта. Метод de legat e ca l l следует использовать с крайней осторожностью. Он может иметь некоторые неожиданные последствия, особенно если вызываемый вами контракт не был спроектирован в виде библиотеки.

Глава 7. (март-контракты и язык Solidity

217

Давайте возьмем для примера контракт, чтобы продемонстрировать различ­ ные форматы вызовов. Воспользуемся функциями c a l l и de legateca l l для вызова библиотек и контрактов. В примере 7.4 мы используем событие, чтобы записать в журнал подробности о каждом вызове и показать, как контекст вы­ полнения меняется в зависимости от типа вызова. Пример 7.4. CallExamples.sol: пример разных форматов вызова 1 pragma solidi ty л о . 4 . 2 2 ;

2

3 contract c a l l edCon t r a c t { 4

5

6

event c a l l Event ( address sende r , address o r i g i n , address from) ; function c a l l edFun c t i o n ( ) puЬlic {

emit c a l lEvent (msg . sende r , tx . o r i g i n , t h i s ) ;

7}

8) 9

1 0 library c a l l edL i b r a r y { 11

12

13

event c a l l Event ( addres s sende r , address o r i g i n , address from) ; function c a l l edFun c t i o n ( ) puЬlic {

emit c a l l Event (msg . sende r , tx . o r i gi n , thi s ) ;

14 )

15 } 16

1 7 contract c a l l e r { 18

19

20

function make c a l l s ( c a l l edCon t r a c t

21

c a l ledCon t rac t ) puЬlic {

// Вызов ca l l edCon t ra c t и ca l l edLibra ry напрямую

_ca l l edCont r a ct . ca l le dFun c t i on ( ) ;

22

c a l l edLi b r a r y . ca l l edFun c t i on ( ) ;

23 24

25

// Низкоуровневые вызовы ca l l edCon t ra c t с помощью объекта

26

requ i re ( address ( c a l l edCon t r a c t ) .

28

requ i r e ( address ( c a l l edCon t r a c t ) .

a ddress 27

29 30

218

c a l l ( bytes4 ( ke c ca k 2 5 6 ( " ca l l edFun c t i o n ( ) " ) ) ) ) ;

delegateca l l (bytes4 ( ke c c a k2 5 6 ( " ca l l edFun c t i o n ( ) " ) ) ) ) ;

Программирование на языке Solidity

31

32

33 1 34 1

В этом примере наш главный контракт, c a l l e r, вызывает библиотеку c a l l e dL i b r a r y и контракт c a l l e dCon t ra c t . Обе вызываемые стороны содержат одну и ту же функцию ca l l edFunc t i on, которая генерирует собы­ тие ca l l edEvent. Событие ca l l edEvent записывает в журнал три фрагмен­ та данных: m s g . s e n de r , t x . o r i g i n и t h i s . В каждом случае функция cal ledFunc t i on может иметь другой контекст выполнения (с другими зна­ чениями для некоторых или всех переменных контекста) в зависимости от того, как она была вызвана: напрямую или через de legateca l l . Вначале ca l l e r обращается к библиотеке и контракту напрямую, вызывая

c a l l edFunct i on. Затем мы вручную используем низкоуров­ ca l l и de legate c a l l , чтобы вызвать ca l l edC o n t r a c t . ca l l edFunct i on. Таким образом, мы видим, как ведут себя разные механиз­

из них функцию невые функции

мы вызова контрактов.

Давайте запустим данный код в среде разработки Truffle и выведем события, чтобы показать, как это выглядит: truffie ( deve l op ) > migrate

U s i ng netwo r k ' deve l op ' .

[ ... ]

Saving a r t i f a c t s _

truffie ( deve lop ) > weЬЗ . eth . accounts [ O ]

' 0 х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5Ьс 6 0 с 7 8 а 8Ье f 5 7 '

truffie ( deve l op ) > caller . address

' O x 8 f 0 4 8 3 1 2 5 fcb 9 a a a e f a 9 2 0 9 d 8 e 9d7 b 9 c 8b 9 fb 9 0 f '

truffie ( deve l op ) > calledContract . address

' 0 x 3 4 5 c a 3 e 0 1 4 aa f 5dca4 8 8 0 5 7 5 9 2 e e 4 7 3 0 5 d 9 b 3 e l 0 '

truffie ( deve l op ) > calledLibrary . address

' O x f 2 5 1 8 6b 5 0 8 lff5 ce 7 3 4 8 2 ad7 6 l db 0 eb 0 d2 5 a b fb f '

truffie ( deve l op ) > caller . deployed ( ) . then ( i => { callerDeployed = i } )

t ruffie ( deve l op ) > callerDeployed . m.ake_calls ( calledContract . address ) . \ then ( res => { res . lo9s . forEach ( lo9 => { console . lo9 ( lo9 . ar9s ) } ) } ) { sender : ' О х В f O 4 8 3 1 2 5 fcb9aaae f a 9 2 0 9 d 8 e 9 d7 b 9 c 8b 9 fb 9 0 f ' ,

o r i g i n : ' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а бе 1 4 0 0 е 9 3 4 5Ьс 6 0 с 7 8 а 8Ье f 5 7 ' , from : ' 0 x 3 4 5 c a 3 e 0 1 4 aa f 5 dca4 8 8 0 5 7 5 9 2 ee 4 7 3 0 5 d 9 b 3 e l 0 ' )

Глава 7. (март-контракты и язык Solidity

219

{ s e n de r :

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 а 8 Ь е f 5 7 ' ,

origin : f r om :

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 а 8 Ь е f 5 7 ' ,

' 0 x 8 f 0 4 8 3 1 2 5 f cb 9 a a a e f a 9 2 0 9 d 8 e 9 d 7 b 9 c 8 b 9 fb 9 0 f ' }

{ sender :

' 0 x 8 f 0 4 8 3 1 2 5 fcb9aaae fa 9 2 0 9d8 e 9d7b 9 c 8 b 9 f b 9 0 f ' ,

origin : f r om :

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 а 8 Ь е f 5 7 ' ,

' 0 x 3 4 5 ca 3 e 0 1 4 aa f 5dca 4 8 8 0 5 7 5 9 2 e e 4 7 3 0 5 d9b3 e 1 0 ' }

{ s e nde r : . ' О х 6 2 7 3 0 6 0 9 0 аЬ аЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 a 8 b e f 5 7 ' , origin : f r om :

' О х 6 2 7 3 0 6 0 9 0 аЬ аЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с б 0 с 7 8 а 8 Ье f 5 7 ' ,

' 0 x 8 f 0 4 8 3 1 2 5 f cb 9 a a a e f a 9 2 0 9 d 8 e 9 d 7 b 9 c 8 b 9 fb 9 0 f ' }

Давайте посмотрим, что здесь произошло. Мы вызвали функцию ma ke c a l l s и передали адрес контракта c a l l e dCo n t r a c t ; затем мы перехвати­ ли все четыре события, сгенерированные вызовами разных типов. Пройдемся по функции ma ke _ c a l l s , шаг за шагом: _ca l l e dC o n t r a c t . c a l l e dFun c t i o n ( ) ;

Здесь мы вызываем c a l l edC o n t r a c t . c a l l edFu n c t i o n напрямую, ис­ пользуя высокоуровневый АВI-интерфейс для ca l l edFu n c t i on. Было сгене­ рировано такое событие: s e nde r :

' 0 x 8 f 0 4 8 3 1 2 5 f cb 9 a a a e f a 9 2 0 9 d 8 e 9 d 7 b 9 c 8 b 9 fb 9 0 f ' ,

origin :

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 а 8 Ье f 5 7 ' ,

f r om :

' О х 3 4 5 c a 3 e 0 1 4 a a f 5 d c a 4 8 8 0 5 7 5 9 2 е е 4 7 3 0 5 d 9b 3 e l О '

Как видите, m s g . s ende r хранит адрес контракта c a l l e r, а в tx . o r i g i n находится адрес нашей учетной записи, wеЬЗ . eth . accounts [ О ] , которая от­ правила транзакцию этому контракту. Событие было сгенерировано контрактом c a l l edCon t ract; это видно по его последнему аргументу. Следующий вызов в ma ke _ с а 1 1 s направлен к библиотеке: c a l l e d L i b r a r y . c a l l e dFu n c t i o n ( ) ;

С виду это ничем не отличается от того, как мы вызывали контракт, но по­ ведение у этого кода совершенно другое. Давайте взглянем на второе сгенери­ рованное событие: s e n de r :

220

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5 Ь с 6 0 с 7 8 а 8 Ье f 5 7 ' ,

Программирование на языке Solidity

origin : f rom :

' О х 6 2 7 3 0 6 0 9 0 аЬаЬ 3 а б е 1 4 0 0 е 9 3 4 5Ь с 6 0 с 7 8 а 8 Ь е f 5 7 ' ,

' О х В f 0 4 В 3 1 2 5 f cb9aaae f a 9 2 0 9 d 8 e 9 d 7 b 9 c 8 b 9 f b 9 0 f '

На этот раз адрес, хранящийся в m s g . s e nde r, принадлежит не c a l l e r, а нашей учетной записи, и он совпадает с адресом инициатора транзакции. Это связано с тем, что обращение к библиотеке всегда делается с помощью de l e gat e c a l l и выполняется в контексте вызывающей стороны. Поэтому во время своей работы код c a l l e dL i b r a r y наследовал контекст выполне­ ния у контракта c a l l e r, как будто он был запущен внутри него. Перемен­ ная t h i s (показанная в сгенерированном сообщении в виде поля f r om) яв­ ляется адресом c a l l e r, хотя доступ к ней происходит внутри библиотеки c a l l edL i b r a r y. Следующие два вызова, сделанные с помощью низкоуровневой версии с а 1 1 и de l e ga t e ca l l , подтверждают наши ожидания: события, которые они сге­ нерировали, отражают то, что мы только что увидели.

Сообра жени я относительно газа Газ (подробно рассматривается в разделе « Газ» на с. 424) играет очень важную роль в программировании смарт-контрактов. Это ресурс, ограничивающий мак­ симальный объем вычислений, который может потребить транзакция во время ее выполнения в сети Ethereum. Если во время вычисления достигается лимит на газ, происходит следующая цепочка событий: - генерируется исключение «закончился газ»; - восстанавливается состояние, имевшее место до выполнения контракта; - все количество эфира, потраченное на оплату газа, изымается в качестве комиссии за транзакцию и не возвращается. Поскольку газ оплачивает пользователь, который инициирует транзакцию, вызов функций, потребляющих много газа, не поощряется. Таким образом, программисту выгодно минимизировать объем газа, расходуемый функциями контракта. В связи с этим было выработано несколько методик, которые реко­ мендуется применять при построении смарт-контрактов. Они помогут вам ми­ нимизировать газ, который потребляют ваши вызовы.

Глава 7 . (март- контракты и язык Solidity

221

И збе га йте динамических массивов 1 Любой цикл, перебирающий динамический массив (или массив динамической длины) с обработкой каждого элемента или поиском конкретного значения, со­ здает риск расходования слишком большого количества газа. Действительно, у контракта может закончиться газ еще до того, как он найдет нужный результат или переберет каждый элемент. Таким образом, эфир будет потрачен впустую.

И збе га йте в ы зовов других контрактов Вызов других контрактов, особенно если вам неизвестна стоимость функций (их выполнения), чреват перерасходом газа. Избегайте библиотек, которые не прошли тщательное тестирование и не используются широко. Чем меньше библиотека изучена другими программистами, тем более высок риск ее приме­ нения.

О ценка расходования газа Если вам нужно оценить объем газа, необходимый для выполнения определен­ ного метода контракта с учетом его аргументов, вы можете воспользоваться сле­ дующей процедурой: var contract = web З . e th . contract ( ab i ) . a t ( addre s s ) ;

var g a s E s t imate = contract . myAwe S omeMe thod . e s t imateGas ( a rg l , arg2 , { f rom : account } ) ;

g a s E s t imate покажет вам количество единиц газа, необходимых для вы­ полнения метода. Это оценочное значение, поскольку тьюринг-полнота EVM позволяет относительно легко создать функцию, ресурсоемкость которой бу­ дет существенно варьироваться от вызова к вызову. Даже рабочий код может менять маршруты выполнения неявным образом, что выливается в совершен­ но разный объем газа, потребляемый разными вызовами. Однако большинство функций ведут себя разумно, и e s t imateGas в большинстве случаев будет да­ вать реалистичную оценку. Цену на газ в сети можно узнать следующим образом: var gas P r i ce = webЗ . e th . getGa s Pr i ce ( ) ; 1

От англ.: dynamically sized arrays. - Прим. ред.

222

Соображения относительно газа

Отсюда можно оценить то, сколько придется заплатить за газ: var gasCo s t i nEther = webЗ . f romWe i ( ( ga s E s t imate * g a s P r i ce ) , ' ethe r ' ) ;

Давайте применим эти функции, чтобы оценить затраты на покупку газа для нашего примера Fau cet. Воспользуемся кодом из репозитория книги (github. сот/ethereumbook/ethereumbook!tree!develop!code!truffie!FaucetEvents). Запустите Truffie в режиме разработки и выполните файл JavaScript gas_ estimates.js, показанный в примере 7.5. Пример 7.5. gas_estimates.js: использование функции e s t imateGa s var Fauce tCont ract

a r t i f a c ts . requ i re ( " . / Faucet . s o l " ) ;

FaucetContract . webЗ . e th . getGa s Pr i ce ( function ( e r r o r , r e s u l t ) { var g a s P r i c e = NumЬ e r ( re s u l t ) ;

console . l og ( " Gas P r i c e i s " + gas P r i ce + " we i " ) ;

// " 1 0 0 0 0 0 0 0 0 0 0 0 0 0 "

// Получа ем экземпляр контра кта

Fauce tCont ract . deployed ( ) . then ( function ( FaucetCont rac t i n s tance ) // Добавим ключевое слово ' e s t ima t e Ga s ' после имени функции,

// чтобы получить оценочный ра сход га за этой конкретной функции // (aprove )

FaucetContract i n s tance . s end ( webЗ . t oWe i ( l , " ethe r " ) ) ;

return FaucetCont r ac t i n s tance . w i thdraw . e s t imateGas ( webЗ .

toWei ( О . 1 , " e the r " ) ) ;

} ) . then ( function ( re s u l t ) {

var gas = NumЬe r ( re s u l t ) ;

con s o l e . l og ( " g a s e s t ima t i o n

console . log ( " ga s c o s t e s timation

+ gas + " u n i t s " ) ;

" + ( gas * gasPrice ) + " wei " ) ;

con s o l e . l og ( " ga s c o s t e s t imat i o n = " +

Fau c e t C o n t r a c t . webЗ . f r omWe i ( ( ga s * ga s P r i ce ) , " e t he r " ) ;

' ether ' ) +

}) ; )) ;

Глава 7. (март-контракты и язык Solidity

223

Вот как это выглядит в консоли разработчика Truffie : $ trufВe develop t ruffie ( deve l op ) > ехес gas_estimates . j s U s i n g netwo r k ' deve l op ' . Gas P r i ce i s 2 0 0 0 0 0 0 0 0 0 0 wei gas e s t ima t i o n = 3 1 3 9 7 u n i t s

g a s c o s t e s t ima t i o n gas c o s t e s t ima t i o n

62 7 94 0 0 0 0 0 0 0 0 0 0 wei

0 . 0 0 0 6 2 7 9 4 ether

Оценивать расход газа рекомендуется в ходе процесса разработки, чтобы из­ бежать каких-либо сюрпризов при развертывании контрактов в мейннете.

Выводы В этой главе мы начали тесно работать со смарт-контрактами и исследовали язык программирования Solidity. Мы взяли простой демонстрационный кон тракт, Faucet.sol, и постепенно его улучшили. В результате изучения различных аспектов языка Solidity он стал более сложным. В главе 8 мы будем работать с Vyper - еще одним контрактноориентированным языком программирования. Мы сравним его с Solidity, выделив некоторые архитектурные отличия и углубив наше понимание того, как создаются смарт-контракты.

ГЛАВА 8

(ма рт- конт ракт ы и Vyper Vyper - это экспериментальный контрактноориентированный язык програм­ мирования для виртуальной машины Ethereum, который стремится обеспечить непревзойденную проверяемость, помогая разработчикам писать понятный код 1 • На самом деле один из принципов Vyper состоит в том, чтобы практически исключить возможность написания кода, вводящего в заблуждение. В этой главе мы рассмотрим распространенные проблемы, присущие смарт­ контрактам, познакомимся с языком написания контрактов Vyper и сравним его с Solidity, выделив основные отличия.

Уя з вимости и Vyper Недавно проведено исследование 2, в ходе которого обнаружилось, что многие развернутые в Ethereum смарт-контракты (всего проанализировали около мил­ лиона контрактов) содержат уязвимости. Во время анализа исследователи выде­ лили три основные категории выявленных рисков. - Суицидальные контракты 3. Смарт-контракты, которые могут быть унич­ тожены произвольными адресами. - Жадные контракты 4. Смарт-контракты, способные достигнуть состояния, в котором они не смогут отдать свой эфир. - Расточительные контракты 5 • Смарт-контракты, которые можно заставить перевести эфир на произвольный адрес. Уязвимости содержатся в коде смарт-контрактов. Можно с уверенностью утверждать, что эти и другие риски безопасности являются непреднамеренными, 1

Дословно из ориг.: интеллектуальный код (intelligiЬle code). - Прим. ред.

2

Подробнее см.: arxiv.org/pdf/1 802.06038.pdf - Прим. ред.

3

Англ.: suicidal contracts. - Прим. ред.

Англ.: greedy contracts. - Прим. ред. 5 Англ.: prodigal contracts. - Прим. ред.

4

Глава 8 . (март- контракты и Vyper

225

но, несмотря на это, нежелательный код контрактов вполне может привести к потере средств пользователей сети Ethereum. Эта ситуация далека от идеала. Язык Vyper создан, чтобы упростить написание безопасного кода и, что не ме­ нее важно, усложнить создание контрактов, которые вводят в заблуждение или подвержены уязвимостям.

С равнение с Solidity Один из способов, с помощью которого Vyper пытается затруднить написа­ ние небезопасного кода, состоит в том, чтобы намеренно избегать некоторых возможностей Solidity. Тем, кто подумывает о разработке смарт-контрактов на Vyper, важно понимать, что именно этот язык не умеет делать и чем это про­ диктовано. Таким образом, в данном разделе мы исследуем эти возможности и попытаемся обосновать их отсутствие.

М одификаторы В предыдущей главе мы рассмотрели, как Solidity позволяет создавать функ­ ции с использованием модификаторов. Например, следующая функция, changeOwn e r, выполнит в ходе своей работы код модификатора onl yBy: function changeOwne r ( addre s s _newOwne r ) puЬlic

on l yBy ( owne r ) owne r

newOwne r ;

Этот модификатор следит за соблюдением правила в отношении владения. Как видите, в этом конкретном случае он играет роль механизма для выполне­ ния предварительной проверки от имени функции changeOwner: modifie r o n l yBy ( addr e s s requi re (msg . s ende r

226

Сравнение с Solidity

a c count ) a c count ) ;

Но модификаторы не ограничиваются выполнением проверок, как это по­ казано здесь. На самом деле они существенно влияют на окружение смарт­ контрактов в контексте функции вызова. Проще говоря, модификаторы про­ никают в код. Давайте рассмотрим еще один пример в стиле Solidity: enum S tages { S a feSt age

Dange r S tage , Fina l S tage

u i nt puЬlic c r e a t i onT ime = now ;

Stages puЬlic s tage = Stage s . S a f e S t age ; function nextS tage ( ) inte r na l

s tage = Stages ( u i n t ( s t age ) + 1 ) ;

modifie r s t ageT imeConfirma t i o n ( ) {

if ( s tage == Stage s . S a f e S tage & & nextSt age ( ) ;

now >= creat ionT ime + 1 0 days )

function а ( ) puЬlic

s t ageT imeConfirma t i o n

/ / Здесь размеща ется дополнительный код

С одной стороны, разработчики всегда должны проверять код, который вызы­ вается из их собственного кода. Но в некоторых ситуациях (например, когда огра­ ничения по времени или усталость приводят к нехватке концентрации) програм­ мист может упустить из виду какую-то строчку. Вероятность этого повышается, если ему приходится перемещаться по огромному файлу, держа в голове иерар­ хию вызовов функций и фиксируя в уме состояние переменных смарт-контракта.

Глава 8. (март-контракты и Vyper ·

227

Давайте внимательнее рассмотрим наш предыдущий пример. Пред­ ставьте, что разработчик пишет публичную функцию с именем а. Он недав­ но начал работать с этим контрактом и использует модификатор, написан­ ным кем-то другим. На первый взгляд может показаться, что модификатор s tageT ime C onfirma t i on просто выполняет некоторые проверки касатель­ но возраста контракта, связанного с функцией вызова. Но при этом разработ­ чик может не осознавать, что этот модификатор вызывает еще и другую функ­ цию, next Stage. В этом простом примере вызов публичной функции приводит к тому, что переменная контракта s t age меняет свое значение с S a f e S tage на Dange r S t age. Язык Vyper полностью избавился от модификаторов. Разработчикам реко­ мендуется делать следующее: если модификатор вам нужен лишь для выпалнения утверждений, сделайте свои проверки частью функции; если вы меняете состояние смарт-контракта и т. д. - опять же, делайте это явно внутри функ­ ции. Это упростит аудит и чтение вашего контракта, поскольку читателю боль­ ше не нужно будет «оборачивать» код модификатора вокруг функции (в уме или вручную), чтобы понять, что она делает.

Н аследование классов Наследование позволяет программистам брать уже готовый библиотечный код и использовать его возможности, свойства и поведение. Это мощный механизм, способствующий многократному использованию кода. Solidity поддерживает та­ кие ключевые для объектно-ориентированных языков программирования воз­ можности, как множественное наследование наряду с полиморфизмом. Однако в Vyper они отсутствуют. Это аргументировано тем, что для понимания поведе­ ния программы, реализующей наследование, программистам и аудиторам необ­ ходимо перемещаться между несколькими файлами. Кроме того, создатели Vyper считают, что множественное наследование делает код слишком сложным для по­ нимания. С этой точкой зрения молчаливо соглашается и документация Solidity (github. сот/ethereum/solidity!ЫoЬ/release/docs!contracts. rst#inheritance), в которой приводятся примеры того, как множественное наследование может оказаться проблематичным.

Ассемблерные вставки Ассемблерные вставки дают разработчикам низкоуровневый доступ к вирту­ альной машине Ethereum, позволяя программам на языке Solidity напрямую 228

Сравнение с Solidity

использовать инструкции EVM. Например, следующая вставка ассемблерного кода добавляет 3 к ячейке памяти О х 8 О: 3 ОхВО mload add О х В О mstore

Создатели Vyper считают, что такой малопонятный код -это слишком высо­ кая цена за дополнительную мощь. Поэтому ассемблерные вставки в этом язы­ ке не поддерживаются.

Перегрузка функци й Перегрузка функций I позволяет разработчикам создать несколько функций с одинаковым именем. То, какая из них будет использована в том или ином слу­ чае, зависит от типов переданных аргументов. Например, взгляните на следую­ щие две функции: function f ( u i n t

i n ) �uЬlic pure returns ( u i n t out ) {

function f ( u i n t

i n , byte s 3 2

out = 1 ;

out = 2 ;

ke y ) puЬlic pure returns ( u i n t out ) {

Первая функция (с именем f) принимает аргумент типа u i nt; вторая (тоже с именем f) принимает два аргумента - один u i n t, а второй byte s 3 2. Нали­ чие нескольких определений функций с одним и тем же именем, но разными ар­ гументами может внести путаницу, поэтому перегрузка функций в Vyper не под­ держивается.

Приведение типов переменны х Приведение типов 2 бывает двух видов: неявное и явное. Неявное приведение часто выполняется на этапе компиляции. Например, если преобразование типов выглядит разумно с семантической точки зрения и, скорее всего, не приведет к потере информации, компилятор может выполнить ' Англ.: function overloading. - Прим. ред. 2 Англ.: typecasting, или variaЬle typecasting. - Прим. ред.

Глава 8. (март-контракты и Vyper

229

его автоматически - например, привести переменную типа u i n t 8 к u i n t 1 6. Самые ранние версии Vyper позволяли неявное приведение типов переменных, но в последних выпусках этой возможности нет. Solidity допускает явное приведение типов. К сожалению, это может привести к непредсказуемым последствиям. Например, как показано ниже, в результате при­ ведения u in t 3 2 к меньшему типу, и in t 1 6, попросту теряются старшие биты: uint32 а

u i nt l б Ь

=

Ох1234 5 67 8 ;

uint l б ( a ) ;

// Переменная Ь теперь равна Ох5 6 7 8

В Vyper для явного приведения используется функция conve rt. Ее код (ко­ торый можно найти в строчке 82 файла convert.py) выглядит так: def conve rt ( expr , context ) :

output_t ype = expr . a rgs [ l ] . s

if output t ype in conve r s i on_taЫ e :

return conve r s i o n_taЫ e [ output t ype ] ( expr , context )

else :

raise Exception ( " Convers ion to { ) i s inva l id . " . format ( output_type ) )

Обратите внимание на использование переменной conve r s i on_t aЬ l e (которую можно найти в строчке 90 того же файла), которая выглядит так: conve r s i o n t аЫ е = {

' i n t 1 2 8 ' : to_int 1 2 8 ,

' u i n t 2 5 6 ' : t o_un i n t 2 5 6 ,

' de c ima l ' : t o de ci ma l ,

' byte s 3 2 ' : t o_byt e s 3 2 ,

Когда разработчик вызывает с о n v e r t , он указывает переменную c o n ve r s i o n_t aЫ e, чтобы обеспечить подходящее преобразование. На­ пример, если функции conve rt будет передано значение i nt 1 2 8, запустится функция t o _ i nt 1 2 8 в строчке 26 того же файла (convert.py). t o _ i nt 1 2 8 име­ ет следующий вид: @ s i gnature ( ( ' i nt 1 2 8 ' , ' u i n t 2 5 6 ' , ' byte s 3 2 ' , ' bytes ' ) , ' s t r_l i t e r a l ' )

def to_int 1 2 8 ( expr , a r g s , kwa r g s , context ) : 230

Сравнение с Solidity

i n_node

typ , len

ar g s [ O ]

= =

get t ype ( i n_node )

if typ in ( ' i nt l 2 8 ' , ' u i n t 2 5 6 ' , ' byte s 3 2 ' ) : if i n_node . t yp . i s l i te r a l

and not S i zeLimits . MINNUМ < = in node . value < = S i zeLimits . МAXNUМ : raise I nva l i d L i t e r a l Except i o n (

" NumЬ e r out o f range : { } " . fo rmat ( i n_node . va l ue ) , expr

return LLLnode . f rom_l i s t ( [ ' c l amp ' ,

[ ' ml o ad ' , Memo ryPo s i t i o n s . M INNUM ] , i n_node ,

[ ' ml o ad ' , Memo ryPo s i t i o n s . МAXNUM ] ] , t yp = BaseType ( ' i nt l 2 8 ' ) , pos = getpo s ( expr )

else :

return byte_a r r a y_to_num ( i n_node , exp r ,

' i nt l 2 8 ' )

Как видите, процесс преобразования следит за тем, чтобы не была потеряна никакая информация; если появляется риск потери, генерируется исключение. Код, выполняющий преобразование, предотвращает усечение и другие анома­ лии, которые обычно допускаются при неявном приведении типов. Выбор явного приведения типов вместо неявного подразумевает, что раз­ работчик сам ответственен за выполнение всех преобразований (casts). Такой подход делает код более многословным, но при этом улучшается безопасность и аудируемость 1 смарт-контрактов.

Предусловия и постусловия Vyper поддерживает предусловия, постусловия и изменение состояния. Несмо­ тря на дополнительный код 2, это позволяет сделать ваш контракт максимально понятным и безопасным. При написании смарт-контракта на языке Vyper раз­ работчик должен учитывать следующие три аспекта. - Состояние. Какие текущие значения имеют переменные состояния Ethereum? - Последствия. Как код этого смарт-контракта повлияет на переменные со­ стояния во время выполнения? То есть что будет затронуто, а что нет? Со­ ответствуют ли эти последствия назначению контракта? 1

Проведение аудитов безопасности смарт-контрактов. - Прим. ред. 2 Дополнительный код, который должен быть добавлен в смарт-контракт. - Прим. ред.

Глава 8 . (март- контракты и Vyper

231

- Взаимодействие. Закрыв все вопросы относительно первых двух аспектов, мы можем запустить наш контракт. Перед развертыванием пройдитесь по коду шаг за шагом и рассмотрите все возможные результаты, последствия и сце­ нарии выполнения кода, включая взаимодействие с другими контрактами. В идеале каждый из этих аспектов следует тщательно рассмотреть и осно­ вательно задокументировать в коде, что улучшит структуру вашего контракта и в целом упростит его чтение и аудит.

Декораторы В начале каждой функции можно использовать следующие декораторы. - @р r i v а t е . Декоратор @ р r i v а t е делает функцию недоступной за пре­ делами смарт-контракта. - @ puЫ i c. Декоратор @ puЫ i c делает функцию видимой и исполняемой для всех. Например, даже кошелек Ethereum будет выводить такие функ­ ции при просмотре контракта. - @ co n s t ant. Функциям с декоратором @ co n s tant не позволено менять переменные состояния. Если функция попытается это сделать, компиля тор отклонит всю программу целиком (с соответствующей ошибкой). - @рауаЫ е. Только функциям с декоратором @рауаЫ е позволено пере­ водить средства. Логика декораторов в Vyper 1 реализована эплицитно. Например, если функ­ ция одновременно содержит декораторы @ р а у аЫ е и @ c o n s t a nt, про­ цесс компиляции будет прерван. Это логично, ведь функция, которая перево­ дит денежные средства, по определению обновляет состояние и не может быть @ cons tant. У каждой функции в Vyper должен быть один из двух декораторов: @ рuЫ i с или @ p r i va te (но не оба сразу!).

Поря док добавл ени я функций и пер е м е нных В языке Vyper каждый смарт-контракт состоит лишь из одного файла. Дру­ гими словами, весь код смарт-контракта, включая все функции, переменные 1

github.com/ethereum/vyper!ЫoЬ/master/vyper/signatureslfunction_signature.py#L93. - Прим. авт .

232

Де коратор ы

и т. д., находится в одном месте. Vyper требует, чтобы все функции и перемен­ ные смарт-контракта были описаны и физически прописаны (в коде) в опреде­ ленном порядке. У Solidity такого требования нет. Давайте взглянем на пример, написанный на Solidity: pragma s o l i d i t y л о . 4 . 0 ; contract o rde r i ng { function topFun c t i o n ( )

external

returns ( boo l )

i n i t i a t i zedBel owTopFu n c t i o n = thi s . l owe rFun c t i o n ( ) ;

return i n i t i a t i z edBe l owTopFun c t i o n ;

bool i n i t i a t i zedBe l owTopFun c t i o n ; bool lowe r Func t i onVa r ;

function l owerFun c t i o n ( )

external

returns ( bo o l )

l owe rFun c t i onVa r = true ;

return l owerFun c t i onVa r ;

В этом примере функция с именем t opFun c t i on вызывает другую функ­ цию, l owe r Fun c t i on. topFun c t i on также присваивает значение перемен­ ной i n i t i at i z edBe l owTopFunc t i on. Как видите, Solidity нe требует, чтобы эти функции и переменные были физически добавлены до того, как они будут использованы в исполняемом коде. Это работающий 1 контракт на Solidity, кото­ рый успешно скомпилируется. Vyper - не первый язык с подобными требованиями; на самом деле, требова­ ние использовать порядок функций и переменных всегда присутствовало в язы­ ке Python. Порядок, который нужно соблюдать в Vyper, является простым и ло­ гичным. Это проиллюстрировано на следующем примере: 1

Валидный (англ. valid). - Прим. ред.

Глава 8. (март-контракты и Vyper

233

# Объявляем переменную с именем theBool

theBoo l : puЫ i c ( b oo l )

# Объявляем функцию с именем t opFun c t i on

@puЫ i c

def topFunc t i o n ( ) - > bool :

# Присваива ем зна чение уже объявленной переменной theBool

s e l f . theBool

=

True

return s e l f . theBool

# Объявляем функцию с именем l o werFun c t i on

@puЫ i c

def lowerFun c t i o n ( ) :

# Вызыва ем уже объявленную функцию t opFunc t i on

assert s e l f . topFunc t i o n ( )

Здесь показан корректный порядок функций и переменных в смарт-контракте на языке Vyper. Обратите внимание на то, что переменная theBo o l и функция topFun c t i on указаны перед присвоением значения и, соответственно, вызо­ вом. Если бы переменная theBoo l была объявлена после topFu n c t i on (или topFun c t i o n после lowe r Fun c t i on), этот контракт не был скомпилирован.

Компиляция У Vyper есть собственные онлайн-редактор кода и компилятор кода 1 , которые позволяют писать код и компилировать смарт-контракты в байт-код, АВI и LLL используются прямо в веб-браузере. Для удобства этот сайт предлагает ряд гото­ вых смарт-контрактов, включая открытый аукцион, безопасные удаленные пла­ тежи, токены ERC20 и др. Vyper реализует ERC20 в виде предварительно скомпилиро­ ванного контракта, что позволяет легко использовать эти смарт-контракты без какой-либо настройки. Контракты в Vyper должны быть объявлены в качестве глобальных переменных. Пример объявления переменной ERC20 выглядит так: t o ken : addre s s ( ERC2 0 ) 1

vyper.online - Прим. авт.

234

Компиляция

Контракт можно скомпилировать и в командной строке. В Vyper он должен находиться в отдельном файле с расширением.vу. Установив необходимые ин­ струменты, вы можете скомпилировать свой контракт, выполнив следующую команду: vype r ~ / he l l o wo r l d . vy

Чтобы получить читаемое человеком описание АВI-интерфейса в формате JSON, можно воспользоваться такой командой: vyper - f j s on ~ / he l l o world . v . py

Защита от ошибок переполнения буф ера на этапе компиля ц ии Ошибки переполнения буфера могут иметь катастрофические последствия при работе с настоящими средствами. Например, одна транзакция в середине ап­ реля 2018 года 1 выполнила неправомерный перевод более чем 57 896 044 618 658 100 ООО ООО ООО ООО ООО ООО ООО ООО ООО ООО ООО ООО ООО ООО токенов ВЕС. Она стала результатом целочисленного переполнения в контракте ЕRС20-токе­ на BeautyChain 2 • Разработчики Solidity имеют доступ к таким библиотекам, как SafeMath 3 , как и к инструментам анализа безопасности смарт-контрактов Ethereum наподобие Mythril OSS 4 • Однако применение этих инструментов не является обязательным. Проще говоря, если безопасность не обеспечивает­ ся на уровне языка, разработчики могут написать уязвимый код, который будет скомпилирован и затем «успешно» выполнен. Vyper имеет встроенную защиту от переполнений буфера 5 , реализован ную в два этапа. Во-первых, Vyper предоставляет эквивалент SafeMath 6 , кото­ рый включает в себя необходимые примеры исключений для целочисленной ' etherscan. io/tx/Oxad89ffl бfdl ebe3a0a7cf4ed282302c06626c 1 а/33221 ebe0d3a470aba4a660f Прим. авт.

' BecToken.sol. - Прим. авт. 3

github. com/OpenZeppelin!openzeppelin-solidity/Ь/oЬ!v 1 . 1 2. 0/contracts/math/SafeMath.sol. Прим. авт.

4

github.com/ConsenSys!mythril. - Прим. авт.

5

Англ.: overf\ow protection. - Прим. ред.

6

github.com/ethereum/vyper/ЬloЬ!master/vyper!parser/expr.py#L275. - Прим. авт.

Глава 8 . (март-контракты и Vyper

235

арифметики. Во-вторых, Vyper использует так называемые зажимы ( clamps) при загрузке любых литеральных констант (переменных), передаче значения в функ­ цию или выполнении операции присваивания. Зажимы реализованы с помощью кастомизированых функций в компиляторе низкоуровневого языка в стиле List (LLL 1 ) и не могут быть отключены (вместо байт-кода EVM компилятор Vyper ге­ нерирует LLL; это упрощает разработку самого языка).

Чтение и запись данн ых Хранение, чтение и изменение данных являются затратными операциями, но без них не может обойтись большинство смарт-контрактов. Контракты могут запи­ сывать данные в два места. - Глобальное состояние. Переменные состояния заданного контракта хра­ нятся в глобальном префиксном дереве Ethereum; смарт-контракт может хранить, читать и модифицировать только те данные, которые относятся к его конкретному адресу (то есть один смарт-контракт не может читать или записывать информацию другого). - Логи. Смарт-контракт также может производить запись в данные цепоч­ ки Ethereum через логи событий. Изначально для объявления этих со­ бытий в Vyper использовался синтаксис _log_, но после очередного обновления эта операция стала более похожей на оригинальный синтак­ сис Solidity. Например, раньше объявление события MyLog выглядело как MyLog : 1 log2 ( { a r g l : indexed ( by t e s [ 3 ] ) } ) . Теперь синтаксис имеет вид MyLog : event ( { argl : i ndexed (bytes [ 3 ] ) } ) . Необхо­ димо отметить, что выполнение лога события в Vyper не поменялось и все еще выглядит так: l o g . MyLog ( " 1 2 3 " ) . Несмотря на то что смарт-контракты могут записывать данные цепочки Ethereum (через логи событий), у них нет возможности прочитать то, что они записали. Тем не менее одним из преимуществ такого рода записи данных це­ почки Ethereum является то, что логи событий могут быть найдены в публич­ ной цепочке (транзакций) и прочитаны легкими клиентами. Например, значе­ ние l o g s B l oom в сгенерированном блоке может указывать на присутствие или отсутствие лога события. Установив существование логов событий, вы можете получить их содержимое по получаемой квитанции транзакции. 1

От Low-level Lisp-like Language. - Прим. ред.

236

Чтение и запись данны х

Выводы Vyper - это мощный и интересный контрактно-ориентированный язык про­ граммирования, появившийся относительно недавно. Его архитектура делает ударение на «корректности», жертвуя некоторой гибкостью. Благодаря этому программисты могут лучше писать код смарт-контрактов, избегая неявных оши­ бок, которые приводят к серьезным уязвимостям. Далее мы более детально рас­ смотрим тему безопасности смарт-контрактов. Некоторые нюансы архитектуры Vyper могут стать более понятными после того, как вы познакомитесь со все­ ми возможными проблемами безопасности, которые могут возникнуть в смарт­ контрактах.

ГЛ АВА 9

Б ез о п асн о сть см а рт- кон т рактов Безопасность - это один из самых важных аспектов, которые следует учиты­ вать при написании смарт-контрактов. В этой области ошибки вызывают убыт­ ки и могут стать источником уязвимости. В этой главе рассматриваются лучшие практики и шаблоны проектирования, такие как «антишаблоны безопасностю>, которые смогут указать вам на потенциально возможные уязвимости в смарт­ контрактах. Как и другие программы, смарт-контракты выполняются именно так, как они написаны разработчиками, что не всегда совпадает с их намерениями. Бо­ лее того, все смарт-контракты являются публичными, и любой пользователь мо­ жет с ними взаимодействовать путем создания транзакций. Злоумышленники могут воспользоваться любой уязвимостью, а потери почти никогда не удается возместить. В связи с этим очень важно следовать лучшим практикам и исполь­ зовать протестированные шаблоны проектирования.

Л уч ш ие практики безопасности Безопасное программирование 1 - это стиль написания кода, который особенно хорошо подходит для смарт-контрактов. Он делает упор на следующих характе­ ристиках, каждая из которых входит в число рекомендуемых практик програм­ мирования. - Минимализм/простота. Сложность - враг безопасности. Чем проще код и чем меньше он делает, тем ниже вероятность возникновения ошибок или непредвиденных побочных эффектов. Во время своего знакомства с программированием смарт-контрактов разработчики часто испытыва­ ют соблазн написать объемный код. Вместо этого вам следует взглянуть на код своего контракта и попытаться сделать его более простым, урезать его «возможности» и уменьшить количество строчек кода. Если вам го­ ворят, что в каком-то проекте для смарт-контрактов написаны «тысячи 1

Англ.: defensive programming. - Прим. ред.

238

Лучшие практики безопасности

строк кода», у вас должны возникнуть сомнения относительно безопас­ ности такого проекта. Чем проще, тем безопаснее. - Повторное использование кода. Старайтесь не изобретать велосипед. Если то, что вам нужно, по большей части реализовано в готовых библиотеках или контрактах - используйте их. В собственном коде следуйте принципу DRY: Don't Repeat Yourself (не повторяйтесь). Если вы видите один и тот же фрагмент кода в нескольких местах, подумайте, можно ли его оформить в виде функции или библиотеки и затем повторно использовать. Широко используемый и оттестированный код, скорее всего, является более без­ опасным по сравнению с новым кодом, который вы пишете сами. Остере­ гайтесь синдрома «неприятия чужой разработки», когда у вас возникает соблазн «улучшить» функцию или компонент путем их полного перепи­ сывания. Риски обеспечения безопасности зачастую перевешивают по­ тенциальную выгоду такого улучшения. - Качество кода. Код смарт-контрактов неумолим. Каждая ошибка может при­ вести к потерям средств. Не следует применять к смарт-контрактам стан­ дарты программирования общего назначения. Написание DАрр-приложе­ ний на Solidity -это вовсе не то, что создание веб-виджета 1 на JavaScript. Вы должны тщательно следовать методологиям проектирования и программ­ ной разработки, как будто это аэрокосмическая инженерия или любая по­ добная область, не прощающая ошибок. После «запуска» вашего кода суще­ ствует мизерная возможность исправить любую из проблем. - Понятность/аудирумость 2 • Ваш код должен быть четким и простым для понимания. Чем проще его читать, тем легче будет проводить его аудит. Смарт-контракты являются публичными, так как их код может быть про­ читан и разобран кем угодно. Поэтому ваши проекты тоже лучше делать публичными, с применением совместных и открытых методологий и опи­ раясь на коллективную мудрость сообщества разработчиков. Это позво­ лит вам воспользоваться наилучшими результатами данного сообщества с открытым исходным кодом. Ваши контракты должны быть хорошо до­ кументированными и понятными, следуя декларируемыми стилем и на­ именованиям, которые практикуются в сообществе Ethereum. - Покрытие тестами. Тестируйте все, что можете. Смарт-контракты работа­ ют в публичной среде, где они могут быть запущены кем угодно и с каким угодно вводом (input). Никогда не исходите из того, что ввод (например, 1

Англ.: web widget. - Прим. ред. 2 Англ.: readability/auditability. - Прим. ред.

Глава 9. Безопасноаь смарт-контрактов

23 9

аргументы функции) является корректным, имеет верные ограничения и имеет благую цель. Прежде чем продолжать выполнение кода, проверь­ те все аргументы, чтобы убедиться в том, что они находятся в ожидаемом диапазоне и правильно отформатированы.

Р иски обеспечени я безопасности и анти ш аблоны Как программист смарт-контрактов вы должны быть знакомы с наиболее рас­ пространенными рисками обеспечения безопасности, чтобы иметь возможность из своевременно выявить и не допустить использования шаблонов (ошибочно­ го) программирования, из-за которых ваши контракты могут быть подвержены данным рискам. В нескольких следующих разделах мы рассмотрим разные риски безопасности, примеры возникновения уязвимостей, а также контрмеры и пре­ вентивные решения, которые помогут в борьбе с данными проблемами.

Реентерабельность Одной из характерных черт смарт-контрактов Ethereum является их способность вызывать и использовать код из других внешних контрактов. К тому же контракты обычно работают с эфиром, отправляя его по адресам разных внешних пользовате­ лей. Эти операции требуют выполнения внешних вызовов. Внешние вызовы могут быть перехвачены злоумышленниками, которые способны заставить контракт вы­ полнить дальнейший код (через функцию fallback), включая обращения (call) к са­ мому себе. Приемы такого рода использовались в печально известной атаке DAO 1 • Дополнительную информацию об атаках реентерабельности можно найти в ста­ тье Гаса Гимараеша 2 и рекомендациях по разработке смарт-контрактов Ethereum 3 •

Уя звимость Атаки такого рода могут происходить, когда контракт отправляет эфир по неиз­ вестному адресу (unknown address). Злоумышленник может аккуратно создать такой контракт с внешним адресом, который имеет вредоносный код в функции ' ru. wikipedia.org/wiki!Ihe_Dao_(opгaнизaция). - Прим. ред.

2

3

medium.coml@gus_tavo_guimlreentrancy-attack-on-smart-contracts-how-to-identify-the-exploitaЫe­ and-an-example-of-an-attack-4470a2d8dfe4. - Прим. авт. consensys.github. io/smart-contract-best-practices!known_attacks/#reentrancy. - Прим. авт.

240

Риски обеспечения безопасности и антишаблоны

fallback. Таким образом, когда контракт пошлет на этот адрес эфир, он ·вызовет активацию вредоносного кода. Обычно такой код вызывает функцию из уязви­ мого контракта, выполняя те операции, которые не были задуманы разработ­ чиком. Термин «реентерабельность» (англ. reentrancy) основан на том факте, что вредоносный код направляет (возвращает) маршрут выполнения «обрат­ но» в уязвимый контракт, вызывая из него его же функцию. Чтобы вам было понятнее, рассмотрим простой пример 9.1 уязвимого кон­ тракта, который играет роль хранилища средств, позволяя вкладчикам выво­ дить лишь по 1 эфиру в неделю. Пример 9. 1. EtherStore.sol 1 contract Ether S t o r e {

2

3 4

5

6

7

В

uint2 56 puЬlic wi thdrawa l L i rn i t = 1 ether ;

mappinq ( address => uint2 5 6 ) puЬlic l a s tWi thdrawT irne ; mappinq ( address => uint2 5 6 ) puЬlic b a l an ce s ; function depo s i t Funds ( ) puЬlic рауаЫе balances [ rns g . sende r ] += rnsg . va lue ;

9}

10 11

12

13

function wi thdrawFunds ( uint2 5 6

we i ToW i t hdraw ) puЬlic

requ i re ( ba l ances [ rns g . sende r ] >= _we i ToWithdraw } ; // огра ничива ем выв од

14

requ i re (_we i ToWithdraw = l a s tW i thdrawT irne [ rn s g . s e nde r ] + 1 weeks ) ;

18

balances [rnsg . sende r ] - = _we i ToWi thdraw ;

15 17

19

20 }

// огра ничива ем допустимую периодичность выв ода

requ i re (rnsg . sende r . ca l l . va l ue (_we i ToWithdraw ) ( ) ) ; l a s tWi thdrawT irne [ rns g . sende r ] = now ;

21 }

У этого контракта есть две публичные функции, d e p o s i t F u n d s и w i thdrawFunds. Первая просто увеличивает баланс отправителя, а вторая позволяет ему указать размер выводимой суммы (в wei). Эта функция должна завершиться успешно только в случае, если выводится меньше 1 эфира и если предыдущий вывод не выполнялся на протяжении последней недели. Глава 9. Безопасность смарт-контрактов

241

Уязвимость находится в строчке 17, в которой контракт шлет пользователю запрошенную сумму средств. Представьте, что злоумышленник создал контракт, показанный в примере 9.2.

Пример 9.2. Attack.sol 1 i.mport " Ethe rStore . s o l " ;

2

3 contract At t a c k { 4

5

6

7

8

9}

10 11 12

13 14

EtherStore puЬlic etherStore ; // инициализируем переменную e therS tore , присвоив ей адрес контракта

constructor ( address _e the rStoreAddre s s )

etherStore = EtherStore ( etherStoreAddre s s ) ;

function a t t a c kEtherStore ( ) puЬlic рауаЫе { / / а та куем ближайший эфир

requ i re (msg . value > = 1 ether ) ;

// переда ем эфир в функцию depos i t Funds ()

15

etherStore . depos i t Funds . va l ue ( l ether ) ( ) ;

17

etherStore . wi thdrawFunds ( l ether ) ;

16 18 } 19

20 21

22 )

// зде сь на чина ется магия

function c o l l e ctEther ( ) puЬlic {

msg . sende r . t r a n s f e r ( th i s . ba l an ce ) ;

23

24

25

26 27

28 }

// резервная функция , в которой происходит ма гия

function ( ) рауаЫе {

i f ( etherStore . ba l ance > 1 ether ) {

etherStore . wi thdrawFunds ( l ether ) ;

29}

30 }

242

Реентерабельность

Каким образом мог появиться эксплойт '? Прежде всего, злоумышленник со­ здал бы вредоносный контракт (скажем, по адресу О х О ... 1 2 3) с адресом кон­ тракта Ethe r S tore в качестве соль как параметра конструктора. Это инициа­ лизировало и направило бы переменную ethe r S t o r e к контракту, который подвергается атаке. Затем злоумышленник вызвал бы функцию a t t a c kE t h e r S t o re с неко­ торым количеством эфира не меньше 1 - пока что предположим, что это 1 е the r. В данном примере также предполагается, что ряд других пользователей вложили (направили) эфир в этот контракт, доведя его баланс до 1 0 ethe r. В результате случится следующее. 1. Attack.sol, строчка 15. Из контракта Ethe r S t o re вызывается функция depo s i t Funds с параметром m s g . va l u e, равным 1 ethe r (и боль­ шим количеством газа). Отправителем (ms g . s e n de r) является вредо­ носный контракт ( О х О ... 1 2 3). Таким образом, b a l a n c e s [ О х О . . 1 2 3 ] = 1 ether. 2. Attack.sol, строчка 17. Далее вредоносный контракт вызовет из контракта Ethe r S t o re функцию wi thdrawFunds с параметром 1 ether. Это позволит пройти все проверки (строчки 12-16 в контракте Е the r S to re ), так как ранее вывода средств не производилось. 3. EtherStore.sol, строчка 17. E t he r S t o re пошлет 1 ether обратно вре­ доносному контракту. 4. Attack.sol, строчка 25. Платеж, посланный вредоносному контракту, вы­ полнит функцию fallback.

5. Attack.sol, строчка 26. Общий баланс контракта E t he r S t o re составлял 1 0 ether; теперь он равен 9 e t h e r, проверка i f пройдет успешно. 6. Attack.sol, строчка 27. Функция fallback делает еще один вызов wi thdrawFunds, повторно входя Ethe r S t o re в контракт.

7. EtherStore. sol, строчка 11. Во время второго вызова функции w i t h d r a w F u n d s баланс вредоносного контракта все еще равен 1 e t h e r, так как строчка 18 пока не была выполнена. Таким образом, b a l a n c e s [ О х О • • 1 2 3 ] по-прежнему равно 1 e t h e r. То же самое справедливо и для переменной l a s tW i thdrawT ime. Снова мы про­ шли все проверки. 1

Э к с п л о й т (англ. exploit, эксплуатировать) - компьютерная программа, программный код или последовательность команд, использующие уязвимости в ПО и применяемые для про­ ведения атаки на информационную систему. - Прим. ред.

Глава 9. Безопа сность смарт-контра ктов

243

8. EtherStore.sol, строчка 17. Вредоносный контракт еще раз выводит 1 ether.

9. Шаги 4-8 повторяются, пока условие Ethe r S t o re . b a l ance > 1 не ста­ нет ложным; это продиктовано строчкой 26 в Attack.sol. 1 0. Attack.sol, строчка 26. Когда на балансе контракта Ethe rS tore останет­ ся 1 ether (или меньше), выполнение условия i f завершится неудач­ но. Благодаря этому смогут выполниться строчки 18 и 19 в контракте Ethe r S tore (для каждого вызова функции wi thdrawFunds). 1 1. EtherStore.sol, строчки 18 и 19. Будут установлены привязки balances и l a s tWi thdrawT ime, после чего выполнение закончится.

В итоге за одну транзакцию злоумышленник выведет из контракта Ethe r S to r e почти все средства, оставив 1 ether.

П ре вентивные меры Существует ряд распространенных методик, позволяющих избежать потенциальных уязвимостей реентерабельности в смарт-контрактах. Прежде всего при отправке эфира внешним контрактам используйте (по возможности) встроен­ ную функцию t r a n s f e r 1 • Вместе с внешним вызовом она отправляет газ в раз­ мере 2300; если принимающий адрес/контракт попытается обратиться к друго­ му контракту (то есть повторно войти в отправляющий контракт), этого будет недостаточно. Второй подход состоит в том, чтобы любая логика, меняющая переменные состояния, выполнялась до отправки эфира из контракта (или любого внешне­ го вызова). В примере Ethe r S t o re строчки 18 и 19 файла EtherStore.sol следует разместить выше относительно строчки 17. Любой код, выполняющий внешние вызовы по неизвестному адресу, рекомендуется оформлять в виде последней операции в локализованной функции или фрагменте выполнения. Этот шаблон проектирования называется checks-effects-interactions 2 (проверка - послед­ ствия - взаимодействие). Третья методика заключается в использовании мьютекса, то есть в добавле­ нии переменной состояния, которая блокирует контракт во время выполнения кода, предотвращая вызовы с повторным вхождением (reentrant calls). ' solidity. readthedocs.io/en/latest!units-and-global-variaЬ/es.htrnl#address-related. - Прим. авт.

2

solidi ty. readthedocs. io/е n/latest/secu ri ty-considerations. htrnl#use-the-checks-effects-i nterac tio ns­ pattern. - Прим. авт.

244

Реентерабельность

Применив все эти подходы (использование всех трех является необязатель­ ным, но мы делаем это в демонстрационных целях) к EtherStore.sol, мы получим контракт, защищенный от реентерабельности: 1 contract EtherStore {

2

// инициализируем mu tex

3

4

bool reEntrancyMutex = false ;

5

uint2 5 6 puЫic wi thdrawa l L imit = 1 ether ;

6

mapping ( addres s => uint2 5 6 ) puЫic l a s tWithdrawT ime ;

7

mapping ( address => uint2 5 6 ) puЫic b a l a n c e s ;

8

9

10

function depo s i t Funds ( ) puЬlic рауаЫе { b a l a n ce s [ ms g . sende r ] + = msg . va l u e ;

11 }

12

13

function w i t hdrawFunds (uint2 5 6 _we i ToWithdraw) puЬlic {

15

requ i re ( ba l ance s [ msg . sende r ] > = _we iToWi thdraw ) ;

14

16 17

18

19

requ i re ( ! reEntrancyMutex ) ; // ограничива ем выв од

requ i re ( _we i ToWi thdraw < = wi thdrawa l L imi t ) ;

// ограничива ем допустимую периодичность выв ода

requ i r e ( now > = l a s tW i t hdrawT ime [ ms g . sende r ] + 1 weeks ) ;

20

balances [ msg . sende r ] - = _we iToWi thdraw ;

22

/ / устанавлив а ем mu tex reEn t rancy перед внешним вызовом

24

msg . sende r . t r an s fe r ( _we i ToWi thdraw ) ;

26

reEntrancyMutex = false ;

21

23 25

27 }

l a s tWi thdrawTime [ ms g . sende r ] = now ;

reEntrancyMutex = true ;

// снима ем mu tex после внешнего вызова

28 }

Реал ь ный пример : DAO Атака DAO 1 стала одним из крупнейших взломов на ранних этапах развития Ethereum. Уязвимый контракт хранил на тот момент 150 миллионов долларов. 1

Decentralized Autonomous Organization - децентрализованная автономная организация. Прим. ред.

Глава 9. Безопасность смарт-контрактов

245

Реентерабельность сыграла существенную роль в атаке, которая впоследствии привела к хардфорку, создавшему новое ветвление - блокчейн Ethereum Classic (ЕТ С). Хороший анализ эксплойта ОАО находится по ссылке Ьit. ly/2EQaLCI. Больше информации об истории хардфорков Ethereum, хронологии взлома ОАО и рождении ЕТ С в результате хардфорка можно найти в дополнении Б.

Ари фм ети ческое переполнение и антипереполнение В виртуальной машине Ethereum для целых чисел предусмотрены типы данных фиксированного размера. Это означает, что целочисленную переменную мож­ но представить лишь с помощью определенного диапазона чисел. Например, u i n t 8 может хранить числа только в диапазоне [О,255]. Если попытаться со­ хранить в u i n t 8 значение 2 5 6, получится О. Неосторожность может привести к реализации уязвимости переменных в Solidity; например, если вы не проверя­ ете пользовательский ввод или выполняете вычисления, результаты которых вы­ ходят за рамки типов данных, предназначенных для их хранения. Подробности об арифметических переполнениях и антипереполнениях можно почитать в следующих статьях: «How to Secure Your Smart Contracts» 1 , «Ethereum Smart Contract Best Practices» 2 и «Ethereum, Solidity and integer overflows: programming Ыockchains like 1970» 3.

Уя звимость Переполнения и антипереполнения возникают, когда операция, требующая со­ хранения числа (или фрагмента данных) фиксированного размера, сохраняет значение, выходящее за пределы типа данных переменной. Например, в результате вычитания 1 из переменной типа u i n t 8 (беззна­ ковое целое число длиной 8 бит; то есть неотрицательное), равной О, получит­ ся число 2 5 5. Это антипереполнение (underflow). Мы присвоили число, ниже диапазона uin t 8, поэтому результат переходит в конец и оказывается равным мак­ симальному значению, которое может хранить этот тип данных. Аналогично, опе­ рация 2 л 8=2 5 6 оставит переменную u i n t 8 без изменений, пройдя всю ее длину. 1

medium.com//oom-networklhow-to-secure-your-smart-contracts-6-solidity-vulneraЬilities-and-how­ to-avoid-them-part-1 -c33048d4dl 7d. - Прим. авт.

2

consensys.gi th иЬ. i о/sma rt-co п tract- best-prac tices/kn own _а ttacks/ #i ntege r- overflow-and­ underflow. - Прим. ред.

3

randomoracle. wordpress.com/201 8/04/27/ethereum-solidity-and-integer-overflows-programming­ Ыockchains-like-1 970. - Прим. ред.

246

А рифметическое переполнение и антипереполнение

В качестве двух простых аналогий такого поведения можно привести автомобиль­ ный одометр, который измеряет пройденный путь (если достигнуть максимально­ го значения, 999999, он сбросится к 000000), и периодические функции в математи­ ке (добавление 2тт к аргументу функции s i n оставляет значение без изменений). Сложение чисел, превышающих допустимый диапазон типа данных, называется пе­ реполнением (overflow). Например, если прибавить 2 5 7 к переменной ui n t 8 со зна­ чением О, получится число 1. Переменные фиксированного размера можно считать циклическими: при добавлении чисел, превышающих максимальное возможное зна­ чение, мы опять начинаем с ноля; если же вычесть из ноля, отсчет продолжится с са­ мого большого числа. В случае с типами in t, которые могут хранить отрицательные числа, переход в конец происходит при достижении максимального отрицательного значения; если вычесть 1 из in t 8 со значением - 1 2 8, получится 1 2 7. Такого рода арифметические подвохи позволяют злоумышленникам исполь­ зовать код в своих целях, создавая неожиданные логические потоки выполнения. Рассмотрим пример 9.3 с контрактом T i me L o c k. Пример 9.3. TimeLock.sol 1 contract T ime Lock {

2

3 4

5

6

7 8

9}

10 11

12

13}

14 15

16 17 18

19

20 }

mapping ( address

mappinq ( address

=> =>

uint) puЬlic b a l a n ce s ;

uint ) puЬlic l o c kT ime ;

function depo s i t ( ) puЬlic рауаЫе {

balances [ ms g . sende r ] + = msg . value ;

l o c kT ime [ msg . sende r ] = now + 1 weeks ;

function i n c re a s e L o c kT ime ( uint l o c kT ime [ ms g . sende r ] + =

s e condsTo i nc r e a s e ) puЬlic {

s e condsTo i n c r ea s e ;

function wi thdraw ( ) puЬlic {

requ i re ( ba l ance s [ ms g . sende r ] > О ) ;

requi re ( now > l o c kT ime [ ms g . s e nder ] ) ;

balances [ ms g . sende r ] = О ;

msg . sende r . t r a n s f e r ( ba l a n ce ) ;

21 }

Глава 9. Безопасность смарт-контрактов

247

Этот контракт играет роль временного хранилища: эфир, который пользова­ тели в нем размещают, блокируется как минимум на неделю. При желании поль­ зователи могут продолжить этот период, но, положив эфир на баланс контракта, они могут быть уверены в том, что их средства будут безопасно храниться как минимум неделю. По крайней мере, так задумывал автор контракта. Если пользователь вынужден выдать свщ�: приватные ключи, контракт как этот поможет сделать так, чтобы на протяжении короткого периода време­ ни эфир будет недоступен. Но если пользователь разместил в этом контракте 1 О О ether и передал свои ключи злоумышленнику, тот может использовать пе­ реполнение, чтобы получить эфир (из контракта) в обход переменной lockTime. Злоумышленник может определить текущее значение l o c kT ime для адреса, ключи к которому он получил (это публичная переменная). Давайте назовемэто чис­ ло u s еrLосkТ imе. После этоrо он может вызвать функцию inсrеаsеLосkТ imе и передать в качестве аргумента 2 л 2 5 6 - u s erLockT ime. Это число будет при­ бавлено к текущему значению u s e rLockT ime и приведет к переполнению, в ре­ зультате чего l o c kT ime [ ms g . s ende r ] сбросится в О. Затем, чтобы получить свою награду, злоумышленник может просто вызвать функцию wi thdraw. В качестве еще одного примера (9.4) рассмотрим один из челленджей Ethernaut 1 • ОСТ ОРОЖНО, СПО Й ЛЕРЫ: на случай, если вы еще не выполняли челленджи Ethernaut, здесь приводится решение одного из них. Пример 9.4. Пример уязвимости антипереполнения из челленджа Ethernaut 1 pragma solidi ty л о . 4 . 1 8 ;

2

3 contract Token { 4

5

6

7

8

9

10 }

mappinq ( addres s => uint) b a l a n c e s ; uint puЫic t o t a l S upp l y ; function Token ( uint

i n i t i a l Supp l y ) {

bal an c es [ ms g . sende r ] = t o t a l Supp l y =

i n i t i a l S upp l y ;

11

12

function tran s fe r ( address _to , uint _va lue ) puЬlic returns (Ьооl ) {

' github.com/OpenZeppelin!ethernaut. - Прим. ред.

248

Арифметическое переполнение и анти nереnолнение

13

requ i re ( ba l ances [ ms g . s ende r ] - -

14

balances [msg . s ende r ] - = _va lue ;

16

return true ;

15

17 } 18

balance s [ _to ] + = _va lue ;

19

function balanceOf ( address

20

return balances [ _owner ] ;

balance ) { 21 }

value > = О ) ;

owne r ) puЫic constant returns ( uint

22 }

Это простой контракт токена с применением функции t r an s f е r. Он позво­ ляет участникам перемещать свои токены. Заметили ли вы ошибку в его коде? Изъян находится в функции t ra n s f e r. Выражение requ i re в строчке 13 можно обойти с помощью антипереполнения. Представьте себе пользователя с нулевым балансом. Он может вызвать функцию t r an s f e r со значением _ val ue больше ноля и тем самым пройти проверку requ i re в строчке 13. Дело в том, что баланс b a l a n c e s [ m s g . s e n de r ] равен О (и имеет тип u i nt 2 5 6), поэтому, как описывалось ранее, при вычитании из него любого положительно­ го значения (за исключением 2 л 2 5 6) получится положительное число. То же са­ мое касается и строчки 14, где на баланс поступает положительная сумма. Таким образом, в этом примере злоумышленник может получить «бесплатные» токены, воспользовавшись уязвимостью антипереполнения.

П ревентивные меры На сегодня в качестве защиты от уязвимостей (анти)переполнения принято ис­ пользовать или создавать математические библиотеки, которые заменяют стан­ дартные математические операторы сложения, вычитания и умножения (деле­ ние является исключением, так как оно не приводит к (анти)переполнениям, а в случае деления на О EVM откатывает все изменения). Проект OpenZeppelin 1 проделал отличную работу по созданию и аудиту безопасных библиотек для сообщества Ethereum. В частности, его библиотека SafeMath 2 позволяет избежать уязвимостей (анти)переполнения. 1 2

github.com/OpenZeppelin!openzeppelin-solidity. - Прим. ред. github. com/OpenZeppelin/openzeppelin-solidity!ЬloЫv 1 . 1 2. О/contracts!math!SafeMath.sol. Прим. ред.

Глава 9 . Безопасность смарт-контрактов

249

Чтобы продемонстрировать применение этих библиотек в Solidity, давайте подправим контракт T ime L o c k с помощью S a feMath. Версия контракта, за­ щищенная от переполнений, выглядит так: 1 library S a feMa th

2

3 4

5

6}

7

8

9

10 }

function mu l (uint2 5 б а , uint2 5 б Ь ) internal pure returns (uint2 5 6 ) if ( а

==

О)

return О ;

uint2 5 б с

=

as sert ( c / а

а * Ь; ==

return с ;

Ь) ;

11

12

function div (uint2 5 б а , uint2 5 б Ь ) internal pure returns (uint2 S б )

13

//a sse r t (b > О) ; //при делении н а О Sol i di ty а в тома тиче ски

14

uint2 5 б с

16

return с ;

15

17 } 18

19 20 21

22 } 23 24

генерирует исключение

function sub (uint2 S б а , uint2 5 б Ь ) internal pure returns (uint2 5 6 ) a s sert ( b < = а ) ;

Ь;

return а

function add (uint2 5 б а , uint2 S б Ь ) internal pure returns (uint2 S б ) uint2 S б с

27

return с ;

28 }

а / Ь;

// a ssert (a == Ь * с + а % Ь) ; // Это утв ержд ение в сегда истинно

25

26

=

a s se rt ( c

=

>=

а + Ь; а) ;

29} 30

250

А рифметическое переполнение и антипереполнение

3 1 contract T imeLo c k {

32

33 34

35

36 37

38

39 } 40

41

42

using S a feMath for uint ; / / используем библиотеку для типа u i n t

mapping ( address => uint2 5 6 ) puЬlic b a l ance s ;

mapping ( address => uint2 5 6 ) puЬlic l o c kT ime ; function depo s i t ( ) puЬlic рауаЫе { balances [ ms g . sende r ] l o c kT ime [ ms g . sende r ]

b a l a n c e s [ m s g . sende r ] . add ( m s g . va l ue ) ; now . add ( l weeks ) ;

function i n c re a s e L o c kT ime ( uint25 6 l o c kT ime [ m s g . sende r ]

se condsTo incre a s e ) ;

s e condsTo i n c re a s e ) puЬlic {

l o c kT ime [ ms g . sende r ] . add (

43 } 44 45

46 47

48

49

50 }

function w i t hdraw ( ) puЬlic {

requ i re ( b a l ance s [ ms g . sende r ] > О ) ;

requi re ( now > l o c kT ime [ ms g . sende r ] ) ;

b a l ances [ ms g . sende r ] = О ;

msg . sende r . t ra ns f e r ( b a l ance ) ;

51 }

Заметьте, что все стандартные математические операции были заменены ана­ логичными функциями из библиотеки S a f eMath. Контракт T imeL o c k боль­ ше не выполняет никаких операций, которые способны привести к (анти)пере­ полнению.

Реал ь ные примеры: PoWHC и переполнение пакетной передачи {CVE-201 8-10299) Контракт с токеном, использующий доказательство «слабых рук» (известный как PoWHC 1 ) , изначально задумывался как своеобразная шутка, он является Понци-схемой 2 и написан анонимным интернет-сообществом. К сожалению, его автор(ы), по всей видимости, никогда раньше не видели (анти)переполнений, в результате чего из этого контракта было выведено 866 ЕТ Н. Эрик Банисадр 1 Proof of Weak Hands Coin. - Прим. ред. 2

Англ.: Ponzi scheme. - Прим. ред.

Глава 9. Безопасность смарт-контрактов

251

сделал хороший обзор произошедшего антипереполнения (которое не сильно отличается от задачи Ethernaut, описанной ранее) в своей статье 1 • Еще одним примером 2 была реализация функции batchTran s fer ( ) в на­ боре контрактов для токенов ERC20. Она содержала уязвимость переполнения; вы можете почитать о ней подробнее в докладе PeckShield 3.

«Н еож иданный» эф и р

Обычно, когда эфир передается контракту, он должен вызвать из него резерв­ ную или какую-то другую функцию, которая в нем определена. У этого правила есть два исключения, когда эфир может находиться в контракте без выполнения какого-либо кода. Контракты, которые всегда выполняют код при получении средств, могут быть подвержены атакам с принудительной отправкой эфира. Подробнее об этом идет речь в следующих статьях: «How to Secure Your Smart Contracts» 4 и «Solidity Security Patterns - Forcing Ether to а Contract» 5.

Уя звимость Одна из распространенных техник безопасного программирования, которая по­ могает обеспечить корректность переходов состояния и проверяющих операций, называется инвариантной проверкой 6 • Он заключается в определении набора ин­ вариантов (метрик или параметров, которые не должны меняться) и проверке их неизменности после каждой операции (или группы операций). Обычно это ока­ зывается хорошим подходом в программировании, при условии, что проверяе­ мые инварианты действительно являются таковыми. В качестве примера инвари­ анта можно привести параметр t o t a l Supp l y токена ERC20 с фиксированной выдачей. Поскольку его никто не должен изменять, вы можете добавить про­ верку в функцию t r a n s f e r, чтобы удостовериться в неизменности значения tota l S upp l y, и гарантировать, что функция работает так, как нужно. ' Ыog.goodaudience. com!how-800k-evaporated-from-the-powh-coin-ponzi-scheme-overnight1 Ь025с33Ь530. - Прим. ред. 2 github.com!ethereum/EIPs/Ь/oЬ!master/EIPS/eip-20. md. - Прим. ред. 3

4

5 6

medium. com/@peckshield!alert-new-batchoverflow-bug-in-multiple-erc20-smart-contracts­ cve-2018- 1 0299-51 1 067dЬб536. - Прим. ред. medium.com//oom-networklhow-to-secure-your-smart-contracts-6-solidity-vulneraЬilities-and-how­ to-avoid-them-part-2-730db0aa4834. - Прим. ред. danielszego. Ыogspot.com/201 8/03/solidity-security-patterns-forcing. html. - Прим. ред. Англ.: invariant checking. - Прим. ред.

252

«Неожиданный» эфир

Существует один очевидный инвариант, который вам, наверное, хотелось бы задействовать, но который на самом деле может быть изменен внешними поль­ зователями (независимо от правил, предусмотренных смарт-контрактом). Это текущее количество эфира, хранимое в контракте. Часто разработчики, знако­ мящиеся с Solidity, ошибочно предполагают, что контракт может принимать или получатьэфир только через оплачиваемые функции 1 • Это недопонимание может привести к ошибочному представлению о балансе внутри контракта, что может повлечь тем самым ряд уязвимостей. Явным признаком такой ошибки является (некорректное) использование значения thi s . b a l ance. Есть два способа, как эфир можно (принудительно) послать на контракт, не используя оплачиваемую функцию и не выполняя любой его код. - Самоуничтожение/суицид. Любой контракт способен реализовать функ­ цию s e l fde s truct, которая удаляет весь его байт-код и отправляет все хранимые в нем средства на адрес, указанный в параметре. Если этот ад­ рес тоже принадлежит контракту, никакая функция (включая fallback) вызвана не будет. Таким образом, операцию s e l fde s t ru c t можно ис­ пользовать для принудительной отправки эфира любому контракту вне зависимости от кода, в нем может находиться, даже если у него нет опла­ чиваемой функции. Это означает, что любой злоумышленник может со­ здать контракт с функцией s e l fde s t ruct, послать ему эфир, вызвать s e l fde s truct ( t a rget ) и принудительно послать этот эфир целе ­ вому (атакуемому) контракту. В блоге Мартина Свенде есть замечатель­ ная статья 2, описывающая некоторые нюансы опкода для самоуничтоже­ ния (нюанс № 2 3 ) , а также оценка того, как клиентские ноды проверяли некорректные инварианты, что могло бы привести к катастрофическому сбою в сети Ethereum. - Заранее отправленный эфир. Еще один способ поместить эфир в кон­ тракт состоит в предварительной загрузке на адрес контракта с эфиром. Адреса контрактов являются детерминистическими; они вычисляются из хеша Keccak-256 4 адреса, созданного контрактом и одноразовом коде (nonce), который использовался при создании контракта. Они имеют вид addre s s = s h a З ( r lp . encode ( [ a ccount addre s s , t r a n s a c t i on _ n o n c e ] ) ) (некоторые забавные примеры использования этого 1

Англ.: рауаЬ!е functions. - Прим. ред.

2

swende.se!Ыog/Ethereum_quirks_and_vulns.html. - Прим. авт.

3

В ориг.: Quirk #2. - Прим. ред.

' Который обычно считается синонимом SНА-3. - Прим. ред.

Глава 9. Безопасность смарт-контрактов

253

подхода можно найти в статье Адриана Мэннинrа Keyless Ether 1 ) . Это означает, что кто угодно может узнать, какой адрес будет у контракта еще до того, как тот будет создан, и послать ему эфир. Сразу после создания у контракта будет ненулевой баланс. Давайте рассмотрим некоторые проблемы (pitfalls), к которым могут приве­ сти вышеизложенные факты. Возьмем для этого излишне упрощенный контракт, показанный в примере 9.5. Пример 9.5. EtherGame.sol 1 contract EtherGame {

2

3 4 5 6 7 8 9

uint puЬlic payoutMi l e S t o n e l

3 ether ;

uint puЬlic mi l e S tone l Reward

2 ether ;

uint puЬlic m i l e S t o ne 2 Reward

3 ether ;

uint puЬlic payoutMi l e S tone2

5 ether ;

uint puЬlic fina lMi l e S tone = 1 0 ether ; uint puЬlic fin a l Reward = 5 ether;

10

шappinq ( addres s = > uint ) redeemaЬleEthe r ;

12

function p l a y ( ) puЫic рауаЫе

11

13

// Пользова тели пла тят 0 , 5 эфира . На определенных этапах кредитуем их счета .

requ i re (msg . value == 0 . 5 ether ) ; // к�ая игра стоит 0 , 5 эфира

14

uint currentBalance = t h i s . b a l ance + msg . value ;

16

requ i re ( currentBal ance < = finalMi l e S tone ) ;

18

i f ( currentBalance == payoutMi l e S tone l ) {

15 17

19

20 } 21

22

23 }

24

25

// убежда емся в отсутствии игроков после окончания игры // если это новый этап , кредитуем счет игрока redeemaЫ eEthe r [ms g . sende r ] += m i l e S tone l Rewa r d ;

else i f ( currentBalance = = payoutMi l e S tone 2 )

redeemaЫ eEthe r [ ms g . sende r ] += m i l e S tone 2 Rewa r d ;

else if ( cu r rentBa l ance == fina lMi l e S tone ) {

redeemaЬ leEther [ms g . sende r ] += fin a l Rewa r d ;

' Ыog.sigrnaprirne. io/solidity-security.htrn/#keyless-eth. - Прим. авт.

254

« Неожиданный» эфир

26)

return ;

27

28 ) 29

function c l a imReward ( ) puЫic {

30 31

// убежда емся в том, что игра за кончила сь

requ i re ( th i s . balance

32

33

==

fina lMi l e S tone ) ;

/ / убежд а емся в том , что на гр ада выпла чена requ i r e ( redeemaЬ leEthe r [ ms g . sende r ] > О ) ;

34

redeemaЬleEthe r [ms g . sende r ]

35

36

=

О;

msg . sender . t r ans f e r ( t r a n s f e rVa l ue ) ;

3 7 _)

38 ) Этот контракт представляет собой простую игру (которое само собой при­ водит к так называемому состоянию гонки), в которой каждый игрок отправ­ ляет 0,5 эфира на адрес контракта в надежде, что именно он первым достигнет один из трех этапов. Все этапы деноминированы в эфире. По завершении игры пользователь, который первым дошел до определенного этапа, может потребо­ вать часть эфира. Игра заканчивается достижением финального этапа ( 1 0 ЕТН), после чего пользователи могут запросить свои награды.

E t h e r Game связана с некорректным использова­ th i s . b a l a n c e в строчках 14 (и, следовательно, 1 6 ) и 32. Неугомон­

Проблема контракта нием

ный злоумышленник может принудительно послать небольшую сумму (ска­ жем, 0, 1 ЕТН) через функцию

s e l fde s t ru c t (которую мы обсуждали ранее),

чтобы ни один из следующих игроков не смог достичь нового этапа. Благода­ ря этому взносу в размере 0 , 1 ЕТН значение

t h i s . b a l an c e никогда не бу­

дет кратным 0,5, поскольку честные игроки могут отправлять лишь платежи, равные 0,5 ЕТН. Из-за этого не выполнится ни одно из условий i f в строчках 1 8 , 2 1 и 24. Что еще хуже, злоумышленник, пропустивший новый этап, может в отмест­ ку принудительно послать 10 ЕТН (или аналогичное количество эфира, кото­ рое сделает баланс контракта больше, чем

fina lMi l e S t one) и тем самым на­

всегда заблокировать выплату вознаграждений в контракте. Это связано с тем,

r e qu i r e в строчке 32 (то есть из-за того, что t h i s . bal ance больше fin a lMi l e S tone) функция c l a imRewa r d всегда будет от­ что из-за выражения катываться.

Глава 9. Безопасность смарт-контрактов

255

П ревентивные меры Уязвимость такого рода обычно является следствием неправильного использо­ вания thi s . b a l ance. Логика контракта по возможности должна избегать за­ висимости от конкретных значений баланса контракта, поскольку ими можно искусственно манипулировать. Если ваша логика основана на th i s . bal ance, вы должны быть готовы к неожиданным изменениям баланса. Если вам необходимо знать точный объем поступающего на счет эфира, вы можете использовать самоопределяющуюся переменную, которая инкременти­ руется в оплачиваемые функции (рауаЫе functions) и позволяет безопасно отсле­ живать депонируемые средства. На эту переменную не будет влиять эфир, при­ нудительно отправленный через вызов s e l fde s t ruct. С учетом вышесказанного исправленная версия контракта EtherGarne мог­ ла бы выглядеть так: 1 contract EtherGame {

2

3

uint puЬlic payoutM i l e S tone l

5

uint puЬlic payoutMi l e S tone2

4

6

3 ether ;

uint puЬlic mi l e S t one l Reward

2 ether ;

uint puЬlic m i l e S t on e 2 Reward

3 ether ;

5 ether ;

7

uint puЬlic finalMi l e S t one = 1 0 ether;

9

uint puЬlic depo s i tedWe i ;

8

10

uint puЬlic fina l Reward = 5 ether ;

11

mappinq ( address

13

function p l a y ( ) puЬlic рауаЫе {

12 14

15

16

17

18

19

20} 21

22

23}

24

256

=>

uint ) redeemaЫ eEthe r ;

requ i re (msg . value == 0 . 5 ether ) ;

uint currentBalance

=

depo s i tedWei + msg . value ;

// убежда емся в отсутствии игроков после окончания игры requ i re ( currentBalance < = fina lMi l e S tone ) ;

i f ( currentBalance == payoutMi l e S t o ne l ) {

redeemaЫ eEthe r [msg . sende r ] += m i l eStone l Rewa rd;

else i f ( cu r r e n t B a l a n c e

==

p a youtMi l e S t o n e 2 )

redeemaЬ l e E t he r [ ms g . s e n de r ] + = mi l e S t o n e 2 Re w a r d ;

else i f ( cu r re n t Ba l a n ce

« Неожиданный » эфир

fin a l M i l e S t o n e )

rede emaЬ l eE t h e r [ m s g . s e nde r ] + = fin a l Rewa r d ;

25

26}

depo s i tedWe i + = m s g . v a l u e ;

27

return ;

28

29} 30 31

32

function c l a imReward ( ) puЬlic {

33

/ / убежда емся в том , что игра закончила сь requ i re ( depo s i t edWe i = = fin a lMi l e S t o n e ) ;

34

//

35

requi re ( redeemaЫ e E t he r [ ms g . s e n de r ] > О ) ;

37

m s g . s ende r . t r an s fe r ( t r a n s f e rVa l ue ) ;

36 38 }

убежда емся в том, что у ·на с есть средства для выпла ты на грады

redeemaЫ e E t h e r [ m s g . s e n de r ] = О ;

39}

Здесь мы создали новую переменную depo s i tedEther, которая отслежи­ вает эфир, поступивший на счет известным нам способом. Именно она исполь­ зуется в нашем тестировании. Вы можете заметить, что t h i s . b a l ance боль­ ше нигде не упоминается.

Д ругие примеры Несколько примеров уязвимых контрактов приводится в «закулисном» состяза­ нии по программированию на Solidity 1 . Там же можно найти развернутые при­ меры целого ряда проблем, затронутых в этом разделе.

DELEGATECALL Операционные коды CALL и DE LEGATECALL полезны тем, что позволяют раз­ работчикам Ethereum разбивать свой код на модули. Стандартный внешний вызов контракта делается с помощью опкода CALL, который выполняет код в среде внешнего контракта/функции. Опкод DE L E GATECALL почти ничем не отличается, только код, выполняемый по заданному адресу, работает в среде 1

Underhanded Solidity Coding Contest; github.com/Arachnid!uscc/tree!master/submissions-201 7. -

Пр им. авт.

Глава 9 . Безопасность смарт-контрактов

257

вызывающего контракта, а параметры m s g . s e nde r и m s g . va l ue остаются неизменными. Это позволяет создавать и развертывать библиотеки, код кото­ рых будет доступен для использования в будущих контрактах. Отличия между этими двумя опкодами являются простыми и интуитивно понятными, однако применение DELEGATECALL может привести к неожидан­ ному поведению кода. Дополнительный материал можно найти в вопросе Loi.Luu на Ethereum Stack Exchange 1 и в документации по Solidity 2.

Уяз в и мость В результате того, что DELEGA TECALL сохраняет свою «небезопасную» приро­ ду, создание пользовательских библиотек, устойчивых к уязвимостям, оказыва­ ется не так просто, как можно было бы подумать. Код самих библиотек может быть безопасным и неуязвимым; но при выполнении в среде других приложе­ ний у него могут возникнуть новые проблемы с безопасностью. Давайте рассмо­ трим такую ситуацию на достаточно сложном примере с использованием чисел Фибоначчи. Взгляните на библиотеку в примере 9.6, которая может генерировать числа Фибоначчи и другие последовательности подобного рода (примечание: это мо­ дифицированный код из Ьit. ly/2MReuii). Пример 9.6. FibonacciLib.sol 1 // библиоте чный контра кт : вычисляет последова тельность , похожую

на числа Фибона ччи

2 contract Fibona c c i L i b

// инициализа ция ста ндартной последова тельности Фибона ччи

3

uint puЬlic s t a r t ;

4

5

uint puЬlic c a l cu l atedFibNumЬe r ;

6

7

// изменяем 0 - е число в последова тельности

function s e t S t a r t (uint

8

start =

9

start ;



s t a r t ) puЬlic {

' ethereum.stackexchange.com!questions/3667/difference-between-call-callcode-and-delegatecall. Прим. авт.

2

solidity. readthedocs.io/en/latest/introduction-to-smart-contracts. html#delegatecall-callcode-and­ libraries. - Прим. авт.

258

DELEGAТECALL

10) 11

12

13

14 ) 15

16 17

18

19

20 )

function s e t F ibona c c i ( uint n ) puЫic { c a l c u l a tedF ibNurnЬe r

=

fibona c c i ( n ) ;

function fibona c c i ( uint n ) internal returns ( uint) { if ( n

==

О ) return s t a r t ;

else i f ( n

==

1 ) return s t a r t + l ;

else return fibona c c i ( n - 1 ) + fibona c c i ( n - 2 ) ;

21 )

Эта библиотека предоставляет функцию, которая генерирует п-е число в по­ следовательности Фибоначчи. Благодаря этому пользователи могут менять на­ чальное число в последовательности ( s t a rt) и вычислять п-й элемент в произ­ водном числовом ряду. Теперь давайте рассмотрим контракт, который использует эту библиотеку (см. пример 9.7). Пример 9. 7. FibonacciBalance.sol 1 contract Fibona c c i B a l ance {

2

3

address puЫic fibona c c i L i b r a r y ;

5

uint puЬlic c a l c u l atedFibNurnЬe r ;

4

6

// текущее число Фибона ччи для выв ода // на чальное число в по следова тельности Фибона ччи

7

uint puЬlic s t a r t = 3 ;

9

// селектор функции Фибона ччи

8

10

11

12

13 14

15 ) 16 17

uint puЬlic wi thdrawa l Counte r ; bytes4 constant fib S i g = bytes4 ( sh a 3 ( " s e t F ibonac c i ( u i nt 2 5 6 ) " ) ) ; / / конструктор : пополняет контракт эфиром

constructor ( address _fibonac c i Li b r a r y ) puЬlic рауаЫе { fiЬona c c i Library

_fibonac c i L i b ra r y ;

function w i t hdraw ( ) {

Глава 9. Безопасность смарт-контрактов

25 9

18

wi thdrawa lCounter += 1 ;

20

// который выв одит средства - - этим мы зада ем

19

// получа ем число Фибона ччи для текущего пользова теля ,

ca l c u l a t edFibNumber

21

requ i re ( fibonac c i Library . de legate c a l l (fib S i g ,

22

msg . sende r . t ran s fe r ( c a l c u l atedFibNumЬer * 1 ether ) ;

wi thdrawa l Counte r ) ) ; 23 } 24

25

// да ет возможность п,ользова телям вызыва ть библиоте чные функ-

ции Фибона ччи

26 27

28 }

function ( ) puЫic

{

requ i re ( fibona c c i Library . de legateca l l (msg . data ) ) ;

29}

Контакт позволяет участнику вывести (withdraw) из него эфир в размере, равном п-му числу Фибоначчи, где п - порядок, в котором участник выводит средства; то есть первый частник получает 1 ЕТ Н, второй - тоже 1 ЕТ Н, тре­ тий - 2 ЕТ Н, четвертый - 3 ЕТ Н, пятый - 5 ЕТ Н и т. д. (пока баланс контрак­ та не станет меньше числа Фибоначчи, которое выводится). Этот контракт содержит рядэлементов (содержимого), которые требуют неко­ торого разъяснения. Во-первых, обратите внимание на любопытную переменную fib S i g. Она хранит первые четыре байта хеша Keccak-256 (SНА-3), полученного из строки ' s e t F ibona c c i ( u i n t 2 5 6 ) ' . Это так называемый селектор функ­ ции 1 ; его передают в с а 1 1 da ta, чтобы указать, какая функция смарт-контракта будет вызвана. Он используется внутри функции de l egat e ca l l в строчке 21, чтобы объявить о нашем намерении запустить функцию fiЬonacci ( u i n t 2 5 6 ) . Еiце одним аргументом вызова de l egate c a l l служит параметр, которым мы передаем функции. Во-вторых, мы предполагаем, что в конструкторе указан кор­ ректный адрес библиотеки Fibona c c i L ib (в разделе «Ссылки на внешние кон­ тракты» на с. 270 обсуждаются некоторые потенциальные уязвимости, связан­ ные с подобного рода инициализацией ссылки на контракт). Заметили какие-либо ошибки в этом контракте? Если его развернуть, напол­ нить эфиром и вызвать функцию w i thdr а w, он, скорее всего, откатит изменения. Вы могли заметить, что переменная состояния s t а r t используется как в библиотеке, так и в главном вызывающем контракте. В контракте библиотеке 1

function selector; solidity. readthedocs. io/en//atest/aЬi-spec.html#function-selector. - Прим. авт.

260

DELEGATECALL

она указывает на начало последовательности Фибоначчи и равна О, тогда как в Fibona c c i B a l ance ей присвоено значение 3. Вам также могло броситься в глаза то, что функция fallback в вызывающем контракте пропускает к библиоте­ ке любые вызовы, что позволяет выполнить библиотечную функцию s e t S t a r t. Если вспомнить, что состояние контракта сохраняется, все выглядит так, буд­ то эта функция позволит поменять значение переменной s t a r t в локальном контракте Fibonna c c i B a l ance. Если это действительно так, мы можем уве­ личить выводимую сумму, поскольку c a l c u l a t e dFibNumЬ e r зависит от пе­ ременной s t a r t (как видно в контракте библиотеки). На самом же деле функ­ ция s e t S t a r t не позволяет (и не может) модифицировать переменную s t a r t в контракте FibonacciBal ance. Уязвимость, закравшаяся в данный контракт, куда хуже, чем простое изменение состояния s t a rt. Прежде чем переходить к самой проблеме, давайте немного отвлечемся и попытаемся понять, как именно переменные состояния хранятся в контрак­ те. Состояние хранимых значений (которые сохраняются между отдельными транзакциями) выглядит как последовательность слотов, упорядоченных в со­ ответствии с объявлением переменных в контракте (здесь есть некоторые ню­ ансы сложности; для лучшего понимания проконсультируйтесь, сверьтесь с до­ кументацией по Solidity 1 ). В качестве примера рассмотрим библиотечный контракт. У 'него есть две переменные состояния: s t a r t и c a l cu l a t e d F i bNumb e r. Они находятся в хранилище контракта в первом (s l o t [ О ] ) и, соответственно, втором слоте ( s l o t [ 1 ] ). Функция s e t S tart принимает ввод и присваивает его переменной s t art. Таким образом, состояние слота s l o t [ О ] меняется на то, которое мы предоставим в качестве ввода для функции s e t S t a r t. Аналогичным образом функция s e t Fibon a c c i присваивает переменной c a l cu l atedFibNumber результат вычисления fibona c c i ( n ) . Опять же, это просто присваивание сло­ ту хранилища s l o t [ 1 ] значения fiЬоn а с с i ( n ) . Теперь давайте рассмотрим контракт F i Ь о n а с с i В а 1 а n с е . Слот s l o t [ О ] теперь связан с адресом fib o n a c c i L i b r a r y, а s l o t [ 1 ] с c a l c u l a t edFibNumb e r. Уязвимость возникает из-за этих неправильных привязок. Вызов de lega t e ca l l сохраняет контекст контракта. Это означает, что код, выполняемый с помощью de l egate c a l l, будет работать с состояни­ ем (то есть хранилищем) вызывающего контракта. Теперь обратите внимание на строчку 21 в функции w i t h d r aw, в ко­ торой мы выполняем f i b o n a c c i L i b r a r y . de l e g a t e c a l l ( f i b S i g , w i t h d r a w a l C o u n t e r ) . Этот код вызывает функцию s e t F i b o n a c c i , 1

solidity. readthedocs.io/en!latest/miscellaneous.html#layout-ofstate-variaЬles-in-storage. - Прим. авт.

Глава 9. Безопасность смарт-контра ктов

261

которая, как уже обсуждалось, модифицирует слот s 1 о t [ 1 ] ; в нашем текущем контексте этот слот равен с а 1 cu 1 а t edF ibNumЬe r. Мы этого ожидали ( то есть после выполнения меняется значение c a l cu l a t e dF i bNumber). Но, как вы помните, переменная s t a r t в контракте Fibona c c i L ib находится в слоте s l ot [ О ] , который в текущем контракте хранит aдpec fibonacci Library. Это означает, что выполнение функции fibon a c c i приведет к неожиданному ре­ зультату. Это связано с тем, что она использует переменную s t a r t ( s lot [ О ] ), которая в контексте текущего вызова равна адресу fibona c c i L ib r a r y (ко­ торый может получиться довольно большим, если его интерпретировать как u i n t). Таким образом, функция w i thdraw, скорее всего, откатится, так как у нее будет (достаточного. - Ред.) u i n t ( fibona c c i L ib r a r y ) размера эфира, который вернет c a l cu l atedFibNumber. Что еще хуже, контракт Fibon a c c i B a l ance позволяет пользователям вы­ зывать все функции fibon a c c i L ib r a ry, используя функцию fallback в строч­ ке 26. Как уже говорилось ранее, это относится и к функции s e t S tart, кото­

рая позволяет кому угодно изменить или установить s 1 о t [ О ] . В данном случае слот s l ot [ О ] равен fibonac c i L i b r a r y адресу. Поэтому злоумышленник мо• жет создать вредоносный контракт, преобразовать адрес в u i n t (это доволь­ но легко сделать в Python, используя выражение i n t ( ' < addre s s > ' , 1 6 ) ), и затем вызвать s e t S t a r t ( < a t t a c k c o n t r a c t addre s s as u i n t > ) . Это позволит заменить fib ona c c i L ib r a r y адресом вредоносного контрак­

та. Дальше этот контракт будет запускаться каждый раз, когда пользователь вызывает w i thdraw или функцию fallback (что может привести к хищению средств на балансе контракта), потому что мы изменили актуальный адрес для fibona c c i L ib r a r y. Пример такого вредоносного контракта показан ниже : 1 contract At t a c k {

2 3 4

5

6

7

uint s t orage S l ot O ; / / указывает на fibona c c i Library

uint s torage S l ot l ; / / ука зыв ае т на c a l cu l atedFibNumЬe r / / выполня етс я , е сли не найдена указанная функция

function ( ) puЬlic {

s t o rage S l o t l = О ; / / мы присваиваем c a l cu l a t edFibNumЬe r О ,

В

/ / поэтому при вызове wi thdraw мы не отправляем никакое ко-

личество эфира наружу 9

в е сь эфир

< a t t a c ke r_addres s > . t r a n s f e r ( th i s . ba l ance ) ; / / мы забираем

10}

11 }

262

DELEGAТECALL

Обратите внимание на то, что этот вредоносный контракт модифицирует c a l c u l a t e dF i bNumb e r, изменяя s l o t [ 1 ] . В принципе, злоумышленник мог бы изменить любые другие хранимые слоты по своему выбору, что позво­ лило бы ему атаковать этот контракт всеми возможными способами. Совету­ ем вам открыть эти контракты в Remix 1 и поэкспериментировать с различными вредоносными контрактами и изменениями состояния с помощью этих вызо­ вов de legat e c a l l. Необходимо также отметить, что под сохранением состояния в вызове de l ega t e c a l 1 мы имеем в виду слоты хранилища, на которые ссылаются пе­ ременные, а не имена этих переменных в контракте. Этот пример показывает, что даже простая ошибка может позволить злоумышленнику увести весь кон­ тракт вместе с его эфиром.

Превентивные меры В языке Solidity есть ключевое слово l ibrary, предназначенное для реализации библиотечных контрактов (подробности см. в документации 2 ) . Благодаря этому библиотечный контракт не имеет состояния и не может сам себя уничтожить. Та­ кие ограничения смягчают трудности, связанные с контекстом хранилища и про­ демонстрированные в этом разделе. Кроме того, отсутствие состояния защищает их от атак, в которых злоумышленник напрямую модифицирует хранимые слоты библиотеки, чтобы повлиять на контракты, которые полагаются на библиотечный код. При использовании DELEGATECALL старайтесь внимательно следить за кон­ текстом потенциального вызова как в библиотечном, так и в вызывающем контрак­ тах (calling contract), и по возможности не добавляйте состояние в свои библиотеки.

Реальный пример: кошелек Parity с «мулыисиг» подписями (второй взлом) Второй взлом кошелька Parity с «мультисиг>►-подписями является примером того, как даже хорошо написанный код бибилиотеки может быть заражен экплойтом, если запустить его извне в неожиданном назначении. Есть несколько хороших объяснений с разборами данной атаки на кошелек Parity, включая статьи «Parity Multisig Hacked. Again>► и «An In-Depth Look at the Parity Multisig Bug» 4 • 3

' remix.ethereum.org. - Прим. авт. 2 3 4

solidity. rea'dthedocs. io/en!latest/contracts.html?highlight=library#/ibraries. - Прим. авт. medium.com/chain-cloud-company-Ьlog!parity-multisig-hack-again-b46771eaa838. - Прим. авт. hackingdistributed.com/201 7/07/22/deep-dive-parity-bug. - Прим. авт.

Глава 9 . Безопасность смарт-контрактов

263

В дополнение к этим материалам давайте посмотрим на контракты, которы­ ми воспользовались злоумышленники. Контракты библиотеки и кошелька мож­ но найти на GitHub 1 • Библиотечный контракт выглядит так: 1 contract Wa l l etLibrary is Wa l l etEvents {

2 3 4

5

// генерируем исключение , если контракт уже инициализирован .

6

modifier o n l y_un i n i t i a l i z e d { if ( m_numOwne rs > О ) throw ;

В

// конструктор : просто переда ем ма ссив владельцев в mul t i owned,

7

9

10 11

12

13

14 } 15

16 17

18

19}

// а лимит в dayl imi t

function i n i tWal l e t ( addres s [ ] o n l y_un i n i t i a l i z e d

owne r s , uint

uint _da y l imi t )

i n i tDayl imit (_da y l imi t ) ; i n i tMu l t i owned ( owne r s ,

;}

requ i re d ,

requ i r ed ) ;

// уничтожа ет контракт , отправляя все по адресу '_ t o ' .

function ki l l ( acldress _to ) onlymanyowners ( sha3 (msg . data ) ) external { s u i c i de ( t o ) ;

20 21

22

23 }

Пример контракта кошелька: 1 contract Wa l l et is Wa l l e tEve n t s {

2

3

1

github. com/paritytech!parity-ethereum/ЫoЬ/b640df8fЬb964da7538eef268dffc 125Ь081 a82f/jslsrc/ contracts/sn ippets/enhanced-wallet.sol. - Прим. авт.

264

DELEGAТECALL

4

5

// МЕ ТОДЫ

7

// вызыв а ется, когда не подходит ни одна другая функция

6

8

9

10 11

12

13

14 }

function ( ) рауаЫе {

// отправляе тся ли ка кая-то сумма средств ? i f (msg . value > О )

Depo s i t ( ms g . sende r , msg . v a lue ) ;

else if (msg . data . l ength > О )

_wa l l e t L i b r a r y . de legateca l l (msg . dat a ) ;

15

16 17 18

// ПОЛЯ

19

address constant _wa l l etLibrary =

20

21 }

0xcafecafecafecafecafeca fecafecafecafecafe ;

Обратите внимание на то, что контракт Wa l l e t, в сущности, делегирует все вызовы контракту Wa l l e t L ib r a r y, используя вызов de l e g a te ca l l. Кон­ станта адреса _wa l l e t L ib r a r y выступает в этом фрагменте кода в качестве наполнителя для развернутого контракта Wa l l e t L ib r a r y (который находил­ ся по адресу О х 8 6 3 DF 6 B Fa 4 4 6 9 f 3 e ad0bE 8 f 9 F2ME 5 l c 9 1A 9 0 7 b 4 ). Это должен быть простой и малозатратный при развертывании контракт W а 1 1 е t, кодовая база и основная функциональность которого находилась в библиотеке Wa l l e t L i b r a r y. К сожалению, Wa l l e t L i b r a r y является контрактом и обладает собственным состоянием. Уже заметили, в чем здесь проблема? Вызовы можно отправлять напрямую контракту W а 1 1 е t L i Ь r а r у. В частности, его можно инициализировать и стать его владельцем. По фак­ ту это и произошло: пользователь вызвал из W a l l e t L i b r a r y функцию i n i tWa l l e t, в результате чего эта библиотека перешла не нему во владе­ ние. Впоследствии тот же пользователь вызвал функцию k i 1 1. Поскольку он был владельцем, его вызов прошел проверку в модификаторе и привел к тому, что библиотечный контракт самоуничтожился. На эту библиотеку ссылают­ ся все существующие контракты Wa l l e t, и ни у одного из них нет мето­ да для изменения этой ссылки, поэтому вся их функциональность, включая возможность выводить эфир, потеряна вместе с Wa l l e t L ib r a r y. В итоге Глава 9. Безопа сность смарт-контра ктов

265

весь эфир в кошельках Parity этого типа был мгновенно утрачен, без шанса на восстановление.

Видимость по умолчанию У функций на языке Solidity есть спецификаторы видимости 1 , которые определя­ ют, как их можно вызывать: внешними пользователями, другими производными контрактами, только внутри или только извне. Существует четыре таких специ­ фикатора, и все они подробно описаны в документации по Solidity2 • По умолча­ нию функции являются публичными (puЫ i c), что позволяет пользователям вызывать их извне. Далее вы увидите, как неправильное применение специфика­ торов видимости может привести к очень опасным атакам на смарт-контракты.

Уязвимость По умолчанию функции являются публичными, поэтому, если не указать их ви­ димость, они будут доступны для вызова внешними пользователями. Пробле­ ма возникает в случаях, когда разработчик опускает спецификаторы видимости для функций, которые должны быть приватными (или доступными для вызова только из самого контракта). Давайте рассмотрим тривиальный пример: 1 contract HashForEther {

2

3

function wi thdrawW i n n i ngs ( )

4

// Победитель , е сли в се 8 последних ше стнадца теричных

символов адреса - - нули

requ i re ( uint32 (msg . sende r )

5

О) ;

s endWi n n i ng s ( ) ;

6

7} 8

9

10

11 }

function

sendW i n n i ngs ( ) {

msg . sende r . t rans f e r ( th i s . ba l ance ) ;

12 } 1 2

Анr.: visibllity specifiers. - Прим. авт. solidity.readthedocs.io/en//atest/contracts.html?highlight=library#visibllity-and-getters. - Прим. авт.

266

Видимость по умолчанию

Этот контракт написан в виде игры с баунти-вознаграждением (bounty game). Чтобы выиграть эфир на балансе контракта, пользователь должен сгенерировать адрес Ethereum, у которого восемь последних шестнадцатеричных символов рав­ ны О. Если ему это удалось, он может вызвать функцию wi thdrawWinni ngs, чтобы получить свое вознаграждение. К сожалению, здесь не указана видимость функций. В частности, функция _ sendWinnings является публичной (по умолчанию), поэтому любой адрес может ее вызвать эту функцию и похитить из ASCI I ,

{ char : = sub ( 0 x 6 0 , sub ( 0 x 7 A , cha r ) ) }

/ / игнорируем пробелы

21

i f i s z e r o ( eq ( ch a r , Ох2 0 ) )

23

{ mstoreB ( add ( add ( text , 0x2 0 ) , mul ( i , l ) ) , add ( char , 2 6 ) ) }

22

24 ) 25 ) 26

/ / приба вляем 2 6 к cha r !

emit Re s u l t ( text ) ;

Глава 9. Безопасность смарт-контрактов

273

27 } 28

29 30

31 32

33 34

// ра спмфро в ыва ем строку методом ro t l З function rot l 3 De c rypt ( strinq text ) puЫic uint2 5 6 l ength = bytes ( t ext ) . lengt h ; for ( var i = О ; i < l ength ; i + + ) byte char

=

assemЫy {

bytes ( t ext ) [ i ] ;

char : = byte ( 0 , cha r )

35 36

i f and ( gt ( cha r , 0 x 6 0 ) , l t ( cha r , 0 x бE ) )

37

{ ch a r : = add ( 0 x 7 B , s ub ( cha r , 0 x б l ) ) ) i f i s z e r o ( eq ( ch a r , О х 2 0 ) )

38 39

s ub ( ch a r , 2 6 ) ) )

{ ms t o re 8 ( add ( add ( t e xt , 0 x 2 0 ) , mu l ( i , l ) ) ,

40 )

41 ) 42

43} 44 }

emit Re s u l t ( t e xt ) ;

Этот контракт реализует шифр ROT26, который сдвигает каждый символ на 26 позиций (то есть ничего не меняет 1 ) . Опять же, вам не обязательно по­ нимать ассемблерную вставку в этом контракте. Вместо этого злоумышленник мог бы просто сослаться на следующий контракт: 1 contract P r i n t {

2

event P r i n t ( string text ) ;

3

4 5

function r o t l ЗEncrypt ( string tex t ) puЫic {

emit Print ( text ) ;

6)

7}

Если передать адрес любого из этих контрактов в конструктор, функция e n c r yp t P r ivateData сгенерирует событие, которое выведет нешифрован­ ные приватные данные (т. е. сделает их открытыми).

1

В английском алфавите 26 букв. - Прим. ред.

274

Ссыл ки на вн еш н ие контракты

В этом примере библиотечный контракт был задан в конструкторе, но ча­ сто случается так, что привилегированный пользователь (например, владелец) может поменять адрес этого контракта. Если контракт, на который ссылаются, не содержит указанного вызова, вместо него выполняется функция fallback. На­ пример, если бы в строчке encryp t i onLibrary . r o t l З E n c r ypt ( ) библио­ тека encrypt i onLibrary выглядела как: 1 contract B l a n k {

2

3

4

event P r i n t ( s tring text ) ; function ( ) {

emit P r i n t ( " He re " ) ;

5

// если поместить сюда вредоносный код , он будет выполнен

6}

7}

то было бы сгенерировано событие с текстом He re. Таким образом, если у поль­ зователей есть возможность менять библиотечные контракты, они теоретиче­ ски могут заставить других пользователей неосознанно запустить произволь­ ный код. г --

·1

Представленные здесь контракты предназначены лишь для демонстрации и не являются примером надлежащего шифрования. Не следует с их помощью шифровать свои данные.

П ревентивные меры Как продемонстрировано выше, безопасные контракты, развернутые определен­ ным образом, могут (в некоторых случаях) стать вредоносными. В ходе публич­ ного аудита владелец контракта может быть вынужден развернуть его так, что у того обнаружатся уязвимости или вредоносный код. Существует ряд методик, помогающих предотвратить такие ситуации. Один из подходов заключается в создании контрактов с помощью ключевого слова new. В предыдущем примере конструктор можно было бы написать так: constructor ( ) {

encryp t i onLibrary

new Rot l З Encrypt i o n ( ) ;

Глава 9. Безопасность смарт-контрактов

275

Таким образом, экземпляр контракта, на который ссылаются, создается во время развертывания, из-за чего развертывающая сторона не может заме­ нить контракт Rot l ЗE n c r ypt i on, не внеся в него изменения. Еще одним решением может быть явное указание адреса внешнего контракта. Общее правило в отношении кода, вызывающего внешний контракт: всегда следует тщательно его проверять с помощью аудита. Желательно, чтобы разра­ ботчик, работающий с внешними контрактами, делал их адреса контрактов пуб­ личными (этого не происходит в примере ловушки в следующем разделе); это позволит пользователям быстро анализировать код, на который ссылается кон­ тракт. С другой стороны, если вы видите приватную переменную с адресом кон­ тракта, это может быть признаком поведения вредоносного кода (как показано в реальном примере). Если адрес контракта, который используется для вызова внешних функций, можно менять, имеет смысл (в контексте децентрализован­ ной системы) реализовать механизм временного блокирования или механизма голосования, чтобы пользователи могли видеть, какой код может быть изменен; вы также можете предоставить участникам возможность принять или откло­ нить адрес нового контракта.

Реал ь ный пример: ловушка реентерабел ь ности Недавно в мейннете был развернут целый ряд ловушек (honeypots 1 ) . Они пы­ таются перехитрить злоумышленников, которые хотят использовать контрак­ ты в своих целях. В итоге взломщик теряет свой эфир в пользу контракта, кото­ рый он хотел атаковать. Ниже показан пример такой атаки, когда в конструкторе ожидаемый контракт заменяется вредоносным. Его код можно найти здесь 2 : 1 pragma solidity л о . 4 . 1 9 ;

2

3 contract Pr ivate Bank 4

5

mappinq ( address => uint ) puЬlic b a l a n ce s ;

7

Log Trans ferLog ;

uint puЫic MinDepo s i t = 1 ether ;

6

8

function P r i vate_Ban k ( address

9 1

log)

Honeypot (с англ. «горшочек с медом») - публичный ресурс, представляющий собой при­ манку для злоумышленников. - Прим. ред.

' etherscan. io!address/Ox95d34980095380851 902ccd9alfЬ4c81 3c2cb639#code. - Прим. авт.

276

Ссылки на внешние контракты

10 11

12 } 13 14

15

16

17 18

19

Trans ferLog

Log ( l og ) ;

function Depos i t ( )

puЫic

рауаЬlе if (msg . value > = MinDepo s i t ) balances [ms g . sende r ] +=msg . va l u e ;

20

Trans ferLog . AddМe s s age ( ms g . sende r , msg .

21

value , " Depo s i t " ) ; 22 } 23 }

24

25

26 27 28

function C a s hOut ( uint _am ) if ( am u n l o c k Т ime ) ; это позволило бы любому пользователю финализировать кон­ тракт по истечении периода времени, указанного в u n l o c kTime. Такой смяг­ чающий (риски) подход можно задействовать и в третьем примере. Если для пе­ рехода к новому состоянию требуются внешние вызовы, следует учитывать их возможный сбой; также можно добавить временной механизм продвижения со­ стояния, на случай если нужный вызов никогда не будет выполнен. Конечно, у этих рекомендаций есть централизованные альтер­ нативы. Например, может быть добавлен пользователь ma i n t enanceU s e r, который при необходимости способен решить проблемы с векторами DоS-атак. Обычно у такого рода контрактов ввиду наличия широких полномочий у поль­ зователя, есть вопросы доверия.

Реальный пример: GovernMental GovernMental 1 - это старая Понци-схема, которая в какой-то момент накопила довольно существенную сумму (1100 ЕТ Н). К сожалению, она была подвержена DоS-уязвимостям, упомянутым в данном разделе. Пользователь etherik в своем посте на Reddit 2 описывает то, как для вывода эфира данный контракт требовал удаления большого количестваэлементов массива. Газ, который был необходим для этого удаления, превышал лимит, установленный на тот момент, поэтому ' governmental.github. io/GovernMental. - Прим. авт. 2

www. reddit. com/rlethereum/oomments/4ghzhv!governmentals_l 1 00_eth_jackpot_payout_is_ stuck. - Прим. авт.

Глава 9. Безопасность смарт-контрактов

29 1

вывести 1100 ЕТ Н было нельзя. Этот контракт имеет адрес О х F 4 5 7 1 7 5 5 2 f 1 2 E f 7 cb 6 5 e 9 5 4 7 6 F2 1 7 Е а 0 0 8 1 6 7 Ае 3 1 , и в транзакции O x 0 d 8 0 d 6 7 2 0 2bd9 cb 6 7 7 3 d f 8 dd2 0 2 0 e 7 1 9 0 a l b 0 7 9 3 e 8 e c 4 f c 1 0 5 2 5 7 e 8 1 2 8 f 0 5 0 6b 2 мoжнo видеть, что для получения всей суммы потребовалось 2,5 миллиона единиц газа (когда лимит на газ в блоке поднялся достаточно высоко для того, чтобы такая транзакция стала возможной).

М анипуля ц ии с временно й метко й блока Исторически временные метки блоков (Ыосk timestamps) имели множество раз­ ных применений, таких как энтропия для случайных чисел (подробности опи­ саны в разделе «Иллюзия энтропии» на с. 269), временное блокирование средств и различные условные выражения для изменения состояния, которые зависят от времени. Майнеры имеют возможность слегка корректировать временные метки, что может таить в себе опасность, если эти метки используются непра­ вильно в смарт-контрактах. В качестве дополнительного материала по теме можно почитать документацию по Solidity3 и вопрос Джориса Бонтье на Ethereum Stack Exchange (см. ссылку4).

Уязвимость Поле Ы о с k . t ime s t amp и его alias (псевдоним) подвержены манипуляциям со стороны майнеров, если у них есть для этого стимул. Давайте напишем про­ стую игру, представленную в примере 9.11, которая имеет уязвимость для май­ неров и которую они смогут использовать в своих целях. Пример 9. 1 1 . roulette.sol 1 contract Rou l et t e {

2

uint puЫic pastBlockTime ; // можно делать одну ставку в каждом блоке

3

1

etherscan. io!address/Oxf4571 7552f12epcb65e95476f21 7ea0081 67ae3. - Прим. авт.

2

etherscan. io/tx/0x0d80d67202bd9cb6773df8dd2020e71 90al b0793e8ec4fcl 05257e8128f0506b. Прим. авт.

3

solidity. readthedocs. io/en!latest/units-and-global-variaЬ/es.htm/#Ыock-and-transaction-properties. Прим. авт.

4

ethereum.stackexchange.com/questions/413/can-a-contract-safely-rely-on-Ыock-timestamp. - Прим. авт.

292

Манипуляции с временной меткой блока

4

тракт

5

6

7

8

constructor ( ) puЬlic рауаЫе { } // изна чально финансируем кон-

// функция fa l lba ck для размещения ставок

function ( ) puЬlic рауаЫе {

requ i re (msg . value == 1 0 ether ) ; // для уча стия нужно отпра -

вить 1 0 Е ТН 9

дом блоке 10 11

12

13 }

requ i re ( now ! = p a s t B l o c kTime ) ; // только 1 транзакции в кажp a s t B l o c kT ime = now ;

if ( now% 1 5 == О ) { // победитель

msg . sende r . t r an s fe r ( th i s . ba l a n ce ) ;

14 }

15 }

Этот контракт ведет себя как простая лотерея. В каждом блоке можно сделать 1 транзакцию и ставку в размере 10 ЕТ Н, чтобы получить шанс выиграть баланс кон­ тракта. Предполагается, что последние две цифры Ь 1 ock . t imes tamp распределе­ ны равномерно. Если бы это было так, вероятность выигрыша составляла бы 1/15. Но, как мы знаем, майнеры в случае необходимости могут корректировать временные метки. В данном конкретном случае, если в контракт поступит до­ статочное количество эфира, майнер, вычисляющий блок, заинтересован в том, чтобы временная метка была равна Ы о с k . t ime s t amp, или чтобы остаток от деления now на 15 был равен О. Это может позволить ему выиграть эфир, за­ блокированный в контракте, и получить награду за блок. Поскольку только один человек может сделать ставку в каждом блоке, этот контракт также подвержен уязвимости фронтраннинга (подробнее об этом см. раздел «Состояние гонки и фронтраннинг» на с. 284). На практике временные метки увеличиваются монотонно, поэтому майнеры не могут выбирать их произвольным образом (метка должна отставать от своих предшественников). Они также не могут установить время блока слишком да­ леко в будущее, поскольку такой блок, скорее всего, будет отклонен сетью (узлы сети не станут проверять блоки с еще не наступившим временем).

Превентивные меры Временные метки блоков не следует использовать для энтропии или генера­ ции случайных чисел, то есть они не должны служить определяющим фактором Глава 9. Безопасность смарт-контрактов

293

(либо напрямую, либо как один из параметров) в выборе победителя игры или изменении важного состояния. Логика, чувствительная ко времени, иногда является необходимой; напри­ мер, для разблокирования контрактов (временно блокирование), завершения процедуры ICO по прошествии нескольких недель или соблюдения срока годно­ сти. Для оценки времени иногда рекомендуется использовать Ы о с k . number 1 и среднее время вычисления блока; если на один блок уходит 10 секунд, за одну неделю можно получить примерно 6 О 4 8 О блоков. Таким образом, указание но­ мера блока, на котором должно измениться состояние контракта, может ока­ заться более безопасным подходом, так как майнеры не могут легко манипули­ ровать данными номерами. Эта стратегия применяется в контракте ВАТ ICO 2 • Это может оказаться излишним, необязательным, если контракты не сильно зависят от манипуляций временными метками со стороны майнеров, но о такой уязвимости следует помнить в ходе разработки.

Реальный пример: GovernMental GovernMentaP, упомянутая выше старая Понци-схема тоже была уязвима к ата­ ке на основе временных меток. Контракт производил выплату последнему при­ соединившемуся (как минимум на одну минуту) игроку в каждом раунде. Таким образом, майнер, участвовавший в игре, мог откорректировать временную от­ метку (сдвинув ее вперед, чтобы все выглядело так, будто минута уже прошла), благодаря чему он выглядел бы как последний игрок, присоединившийся на пе­ риод более одной минуты (хотя в реальности это было вовсе не так). Подробнее об этом можно почитать в статье Тани Багриновской History of Ethereum Security Vulnerabllities, Hacks and Тheir Fixes 4.

Н ето чности в названи я х констру кторо в Конструкторы - это специальные функции, которые часто выполняют важные, требующие привилегий задачи в момент инициализации контракта. До версии ' solidity. readthedocs. io/en//atest/units-and-global-variaЬ/es.htm/#Ыock-and-transaction-properties. Прим. авт.

' etherscan. io/address/0x0d8775f648430679a709e98d2b0cb6250d2887ef#code. - Прим. авт. 3 governmental.github. io/GovernMental. - Прим. авт. 4

applicature.com!Ыog!Ыockchain-technologylhistory-ofethereum-security-vulneraЬilities-hacks-and­ their-fixes. - Прим. авт.

294

Неточности в названиях конструкторов

0.4.22 Solidity конструкторы определялись в виде функций с тем же именем, что и у контракта, в который они были включены. Таким образом, если в процессе разработки имя контракта менялось, конструктор тоже должен быть переиме­ нован, иначе он становился обычной функцией, доступной для вызова. Как вы можете себе представить, это могло привести (и приводило) к некоторым инте­ ресным взломам контрактов. Чтобы лучше понять эту проблему, вы можете попытаться решить челленджи Ethernaut 1 (в частности, уровень Fallout).

Уязвимость Если переименовать контракт или допустить опечатку в имени конструктора, в результате которой он будет называться не так, как сам контракт, конструктор станет действовать, как обычная функция. Это может привести к тяжелым по­ следствиям, особенно если конструктор выполняет привилегированные опера­ ции. Рассмотрим следующий контракт: 1 contract OwnerWa l l et {

2

3

4

5

6

7} 8

9

1О 11

12

13 14

15 }

address puЬlic owne r ; // конструктор

function owne rWa l l e t { address owner

=

owne r ;

owne r ) puЬlic {

// Функция fa l lba ck . Собира ем эфир .

function ( ) рауаЫе { }

function wi thdraw ( ) puЬlic { requ i re (msg . sender

==

owne r ) ;

msg . sende r . t r a n s f e r ( th i s . ba l an ce ) ;

16}

Этот контракт собирает эфир и позволяет вывести его только своему вла­ дельцу; для этого предусмотрена функция w i thd r aw. Проблема возникает из-за того, что конструктор назван не совсем так, как контракт: у него отличается ' github.com!OpenZeppelin!ethernaut. - Прим. авт.

Глава 9 . Безопасноаь смарт-контрактов

2 95

первая буква! Из-за этого пользователь может вызвать функцию owne rWa 1 1 et, назначить себя владельцем и затем забрать из контракта весь эфир, используя вызов wi thdraw.

Превентивные меры Эта проблема решена в компиляторе Solidity версии 0.4.22. В этой версии появи­ лось ключевое слово con s t ru c t o r для задания конструктора, благодаря кото­ рому вам больше не нужно следить за тем, чтобы имена функции и контракта совпадали. Это ключевое слово рекомендуется использовать для задания кон­ структоров, чтобы избежать проблем с их наименованием.

Реальный пример: Rublxi Rubixi 1 - это еще одна схема реализации финансовой пирамиды 2, у которой была подобного рода уязвимость. Изначально она называлась Dynami c P yram i d, но перед развертыванием (в сети) контракт был переименован. Имя конструк­ тора осталось прежним, что позволяло любому участнику стать создателем. Ин­ тересное обсуждение этой ошибки можно найти на форуме Bitcointalk (см. ссыл­ ку 3). В конечном счете пользователи получили возможность бороться за статус создателя, чтобы заполучить отчисления этой пирамиды. Больше подробно­ стей об этом конкретной уязвимости можно найти в заметке History of Ethereum Security Vulnerabllities, Hacks and Their Fixes 4 •

Хранение неинициализированных указателе й EVM размещает данные либо в хранилище, либо в памяти. При разработке кон­ трактов очень рекомендуется иметь четкое представление о том, как это делает­ ся и какими являются локальные переменные функций по умолчанию (то есть дефолтными настройками они обладают. - Ред.). Это связано с тем, что некор­ ректная инициализация переменных может привести к появлению уязвимостей в контрактах. 1

etherscan. io/address/Oxe8271 9202e5965Cf5D9B6673B7503a3b92DE20be#code. - Прим. авт.

2

Понци-схема. - Прим. ред.

3

Ьitcointalk. orglindex.php?topic=l400536.60. - Прим. авт.

4

applicature.com/Ь/og!Ыockchain-technologylhistory-ofethereum-security-vulneraЬilities-hacks-and­ their-fixes. - Прим. авт.

2 96

Хранение неинициализированны х указателей

Больше подробностей о хранилище и памяти в EVM можно найти в докумен­ тации по Solidity, посвященной местоположению данных, а также размещению переменных состояния в хранилище и памяти (см. ссылки 1 • 2• ) . 3

Этот раздел основан на замечательном посте Стефана Бейера 4 • Дополнительный материал по теме можно найти в обсужде­ нии в ветке на Reddit (см. ссылку 5), вдохновленном Стефаном.

Уязвимость Локальные переменные по умолчанию могут находиться в хранилище или памя­ ти, в зависимости от их типа. Неинициализированные локальные переменные хранилища могут содержать значения других переменных в хранилище того же контракта; это может привести к непреднамеренным уязвимостям или стать причиной сознательного взлома. Давайте рассмотрим относительно простой контракт регистратора имен в примере 9.12. Пример 9. 12. NameRegistrar.sol 1 // З а блокированный регистра тор имен

2 contract NameReg i s trar { 3

Ьооl puЫic u n l o c ked

4

на не обновляются

false ; // регистра тор за блокирован, име -

5

6

struct NameRe cord { // привязыва ем хеши к адре сам

7

8

Ьytes32 name ;

9}

address mappedAddre s s ;

1

solidity. readthedocs.io/en//atest/types.htm/#data-location. - Прим. авт.

2

solidity. readthedocs.io/en//atest/miscellaneous.htm/#/ayout-of-state-variaЬ/es-in-storage. - Прим. авт.

3

solidity. readthedocs.io/en//atest/miscellaneous.htm/#/ayout-in-memory. - Прим. авт.

4

medium.com/cryptronics/storage-allocation-exploits-in-ethereum-smart-contracts-1 6c2aa312743. Прим. авт.

5

www. reddit. com/r/ethdevlcomments/7wp363/how_does_this_honeypot_work_it_seems_like_a. Прим. авт.

Глава 9. Безопасность смарт-контрактов

2 97

10 11

// записыв а е т адрес того , кто зарегистрировал имена

12

mapping ( address => NameRe co r d ) puЫic reg i s te redNameRecord;

14

mapping (bytes32 = > address ) puЫic r e s o lve ;

16

function reg i s t e r (bytes32

13 15

17

// получа ет адреса из хешей

// Созда ем новую запись NameRecord

18

NameRe cord newRe c o r d ;

20

newRecord . mappedAddre s s

19

21

22

23 24

25

name , address _mappedAddre s s ) puЬlic

newRecord . name =

name ;

_mappedAddres s ;

re s o lve [ name ] = _mappedAddre s s ; reg i s te redNameRe cord [ ms g . sende r ]

newRecord ;

requ i re ( un l o c ked ) ; / / регистрация ра зрешена , е сли контра кт

разблокирован 26 }

27 }

У этого простого регистратора имен есть только одна функция. Когда кон тракт разблокирован (un l o c ked), он позволяет кому угодно зарегистрировать имя (в виде хеша типа byt e s 3 2) и привязать его к адресу. Изначально реги­ стратор заблокирован, а выражение requ i re в строчке 25 не дает ему добав­ лять записи с именами. На первый взгляд контракт выглядит бесполезным, так как реестр никак нельзя разблокировать! Однако существует уязвимость, кото­ рая позволяет зарегистрировать имя в обход переменной un l o c ked. Прежде чем приступить к обсуждению этой уязвимости, нам нужно сна­ чала понять принцип работы хранилища на языке Solidity. Если не вдаваться в технические подробности (с которыми можно ознакомиться в документации по Solidity), переменные состояния хранятся в виде последовательных слотов в соответствии с тем, как они объявлены в контракте (их можно сгруппировать вместе, но так как в данном примере мы этого не делаем, вам не стоит об этом беспокоиться). Таким образом, переменная un l o c ked находится в s l ot [ О ] , regi s t e redNameRe c o r d в s l o t [ 1 ] , re s o l ve в s l ot [ 2 ] и т. д. Каждый из этих слотов занимает 32 бита (у ассоциативных массивов есть дополнитель­ ные сложности, но пока что мы их проигнорируем). Булево (логическое) значе­ ние u n l o c ked будет выглядеть как О х О О О ... О (64 ноля, если не считать О х) для 2 98

Хранение неинициализированны х указателей

f a l s e или О х О О О ... 1 (63 ноля) для t rue. Как видите, в этом примере впустую тратится значительная часть хранилища. Следующим важным фактом является то, что в архитектуру Solidity по умол­ чанию заложено размещение в хранилище сложных типов данных (такие как s t ruct) во время их инициализации как локальных переменных. Таким обра­ зом, в хранилище попадает значение newRecord в строке 18. Уязвимость вызва­ на тем, что переменная newRe c o r d не инициализирована. Находясь по умолча­ нию в хранилище, она привязана к слоту s 1 о t [ О ] , который в данный момент содержит указатель на u n l o c ked. Обратите внимание на то, что после этого в строках 19 и 20 полям newRe c o r d . name и newRe c o r d . mappedAddre s s присваиваются значения _ name и, соответственно, _mappe dAddr e s s ; в ре­ зультате обновляются слоты s l o t [ О ] и s l o t [ 1 ] , что приводит к изменению un l o c ked и хранимого слота, связанного с reg i s t e re dNameRe c ord. Это означает, что для изменения переменной u n l o c ked достаточно моди­ фицировать параметр byte s 3 2 _ name в функции regi s t e r. Таким образом, если последний байт переменной _name не равен нолю, последний байт слота s l ot [ О ] будет изменен и тем самым поменяет значение u n l o c ked на t rue. Такие значения _ name приводят к успешному выполнению вызова requ i re в строке 25, так как мы сделали переменную u n l o c ked истинной. Попробуйте сделать это в Remix. Функция успешно выполнится, если вы используете пара­ метр _name такого вида: OxO O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O O l

Превентивные меры Компилятор Solidity выводит предупреждения для неинициализированных пе­ ременных хранилища; разработчики ,смарт-контрактов должны серьезно от­ носиться к таким предупреждениям. Текущая версия Mist (0.10) не дает ском­ пилировать подобные контракты. Чтобы обеспечить предсказуемое поведение сложных типов (данных), часто рекомендуется (использовать!) явно указывать спецификаторы memo ry или s t o rage.

Реальные примеры: ловушки OpenAddressLottery и CryptoRoulette Для изъятия эфира у неудавшихся взломщиков была развернута ловушка (honeypot) под названием Ope nAdd r e s s L o t t e r y 1 , которая использовала ' etherscan. io/address/Ox74 1f1 923974464efd0aa70e77800ba5d9ed1 8902#code. - Прим. авт.

Глава 9 . Безопасность смарт-контрактов

299

описанный выше нюанс с неинициализированными хранимыми переменными. Это довольно сложный контракт, поэтому, если вас интересует его анализ, мо­ жете почитать его анализ в треде на Reddit 1 , в котором дается вполне четкое объ­ яснение данной атаке. Этот трюк для сбора эфира использует еще одна ловушка, honeypot CryptoRoulette 2 • Если вам не удается понять принцип работы данной атаки, то почитайте обзор этого и других контрактов в заметке An Analysis of а Couple Ethereum Honeypot Contracts 3.

П лавающая запятая и точность Текущая на момент написания этой книги версия 0.4.24 Solidity не поддержива­ ет числа с фиксированной и плавающей запятыми 4 • Это означает, что значения с плавающей запятой должны быть сформированы из целочисленных типов. Не­ корректная реализация этого подхода может привести к ошибкам и уязвимым «дырам» в контрактах. Дополнительный материал можно найти в материале _f:there­ um Contract Security Techniques and Tips 5 •

Уязвимость Поскольку в Solidity нет типа с фиксированной запятой, разработчикам прихо­ дится создавать собственные реализации, используя стандартные целочислен­ ные типы данных. В ходе этого процесса можно столкнуться с целым рядом про­ блем. Некоторые из них мы попробуем рассмотреть в этом разделе. Давайте начнем с примера кода (чтобь1 не усложнять, проигнорируем про­ блемы с (анти)переполнением, которые мы обсуждали ранее в данной главе):

' Ьit. /y/2OgxPtG. - Прим. авт.

2

etherscan. io/address/Ox8685631276cfcfl 7a973d92fбdcl 1 645e51 58c0c#code. - Прим. авт.

3

medium.comlcoinmonkslan-analysis-of-a-couple-ethereum-honeypot-contracts-5c07c95b0a8d. Прим. авт.

4

Анrл.: fixed-point and floating-point. - Прим. авт.

5

github.com/ethereum/wiki/wiki/Safety#beware-rounding-with-integer-division. - Прим. авт.

300

Плавающая запятая и точность

1 contract FunW i t hNumb e r s { .

2

uint constant puЫic tokens PerEth = 1 0 ;

4

mappinq ( addres s => uint ) puЬlic b a l a n ce s ;

6

function buyTokens ( ) puЬlic рауаЫе {

3 5

7

uint constant puЬlic we i PerEth = l e l 8 ;

// преобра зуем wei в e th и умножим резуль та т на обменный

курс токена 8

9

10 } 11

12

13

uint t o kens = msg . va l u e / we i PerEth * tokensPerEth ; balances [msg . sende r ] += t o kens ;

function s e l l To kens ( uint t o kens ) puЬlic {

requ i re ( ba l ances [ ms g . sende r ] >= t o ke ns ) ;

14

uint eth = t o ken s / t o ken s Pe rEth ;

16

msg . sender . t r an s fe r ( e th * we i Pe rE th ) ;

15

17 }

balance s [msg . sende r ] -= t o ken s ;

18 }

/

У этого простого контракта для покупки/продажи токенов есть явные про­ блемы. Математические вычисления покупки/продажи сделаны правильно, од­ нако из-за нехватки чисел с плавающей запятой мы получим ошибочные ре­ зультаты. Например, если при покупке токенов в строчке 8 значение меньше 1 ether, начальная операция деления даст О, и итоговый результат умножения тоже будет равен О (например, если поделить 2 0 0 we i на l e l 8 we i Pe rE th, по­ лучится О). Точно так же при продаже любого количества токенов меньше 1 О мы получим О ether. На самом деле округление здесь всегда выполняется в мень­ шую сторону, поэтому продажа 2 9 токенов даст 2 e the r. Проблема этого контракта в том, что значения всегда округляются до бли­ жайшего количества эфира (то есть l e 1 8 we i). Это может создать трудности с десятичными дробями, если при работе с токенами ERC20 вам нужна повы­ шенная точность.

П ревентивные меры Очень важно поддерживать подходящий уровень точности в своих смарт­ контрактах, особенно при работе с соотношениями (ratios) и обменными курса­ ми (rates), отражающими финансовые решения. Глава 9 . Безопасность смарт-контрактов

301

Вы должны сделать так, чтобы любые соотношения и проценты, которые вы используете, поддерживали большие числители в дробях. Например, в нашем примере мы использовали обменный курс t o ke n s Pe rEth. Вместо него лучше было бы применить более крупное число, we i Pe r T o ke n s. Для вычисления со­ ответствующего количества токенов можно было бы выполнить msg . s ende r / we i Pe r T o ke n s. Таким образом, мы получили бы более точный результат. Также не стоит забывать о порядке выполнения операций. В нашем примере для покупки токенов использовалось выражение m s g . v а 1 u е / we i PerEth * to kenPe rEth. Обратите внимание на то, что деление выполня­ ется перед умножением (в отличие от некоторых других языков, язык Solidity гарантирует выполнение операций в том порядке, в котором они записаны). В этом примере мы бы добились более высокой точности, если бы умножение шло перед делением, то есть msg . va l u e * t o kenPe rEth / we i PerE th. Наконец, при определении произвольной точности для чисел стоит приве­ сти значения к более высокой точности (это может быть хорошей мерой); затем, после выполнения математических операций, результат можно вывести с той точностью, которая требуется. Обычно для этого используется тип u i n t 2 5 6 (он оптимален для потребления газа); в его диапазон входит примерно 60 поряд­ ков величины, и некоторые из них можно выделить для точности математиче­ ских операций. Иногда все переменные в Solidity стоит хранить с высокой точ ностью и затем приводить их к менее точным типам во внешних приложениях (так, по сути, работает переменная de c ima l s в контракте токена ERC20). В ка­ честве примера того, как это делается, можно привести проект DS-Math 1 • В нем используются причудливые термины (wads [шарики] и rays [лучи]), но сама кон­ цепция будет полезной для ознакомления.

Реал ь ный пример : Ethstick Контракт E t h s t i c k 2 не использует повышенную точность; но при этом он работает с wei. Таким образом у этого контракта будут возникать проблемы с округлением, но только на уровне единиц wei. У него есть и другие, более серь­ езные недостатки, но они связаны с внедрением энтропии в блокчейн (см. раз­ дел «Иллюзия энтропии» на с. 269). Дополнительную информацию о контрак­ те E t h s t i c k можно получить в еще одной заметке Питера Вессенеса Ethereum Contracts Are Going to Ве Candy for Hackers 3 • 1

github.com/dapphuЬ!ds-math. - Прим. авт.

2

etherscan. io!address/OxЬA6284cA 128d72B25f1 353FadD06Aa1 45D9095Af#code. - Прим. авт.

3

vessenes.com/ethereum-contracts-are-going-to-be-candy-for-hackers. - Прим. авт.

302

Плавающая запятая и точность

Аутенти ф икация с помо щ ью Тх. Origin В языке Solidity есть глобальная переменная tx . o r i g i n, пронизывающая весь стек вызовов. Она �одержит адрес учетной записи, который инициировал вы­ зов (или транзакцию). Использование ее для аутентификации сделает смарт­ контракт уязвимым к атакам на основе фишинга 1 • Дополнительные сведения можно найти в вопросе пользова­ теля dbryson на форуме Ethereum Stack Exchange 2 , заметке Пи тера Вессенеса Тх. Origin and Ethereum Oh Му! 3 и заметке Кри­ са Ковердейла Solidity: Тх Origin Attacks 4.

Уязвимость Контракты, производящие авторизацию с помощью переменной tx . o r i g i n, обычно подвержены атакам фишинга, которые обманным путем предлагают пользователям выполнить аутентификацию с уязвимым контрактом 5 • Рассмотрим простой контракт в примере 9.13. Пример 9. 13. PhishaЫe.sol 1 contract Ph i s haЫe

2

address puЫic owne r ;

4

constructor ( address

3

owner =

5

6}

7

owne r ;

owne r )

function ( ) puЫic рауаЫе { } // собира ем эфир

В 9

10 1

function wi thdrawAl l ( address

recipient ) puЫic {

Англ.: phishing. - Прим. авт.

' ethereum.stackexchange. сот/questions/ 1 891 !whats-the-difference-between-msg-sender-and-tx3 4 5

origin. - Прим. авт.

vessenes.com/tx-origin-and-ethereum-oh-my. - Прим. авт. medium.com!coinmonks/solidity-tx-origin-attacks-5821 1 ad95514. - Прим. авт.

Аутентификация на стороне ресурса (контракта) злоумышленника как раз означает реали­ зацию «фишинга», атаки с помощью технологий фишинга. - Прим. ред.

Глава 9 . Безопасность смарт-контрактов

303

11

requ i re ( t x . o r i g i n

==

owne r ) ;

recipi ent . t ra n s f e r ( th i s . ba l ance ) ;

12

13)

14 )

Обратите внимание на строку 11, в которой контракт авторизует функцию w i thdrawA l l с помощью t x . o r i g i n. Это позволяет злоумышленнику со­ здать вредоносный контракт следующего вида: 1 import " Phi shaЫe . s o l " ;

2

3 contract AttackContract { 4

5

6

Ph i s haЫe ph i s haЬ l e C o n t r a c t ;

addres s a t t a c ke r ; // Адрес злоумьшшенника для получения

средств

7

8

cons tructor ( Ph i s haЫe _ph i shaЫeCon t r a c t , address

a t t a c ke rAddre s s ) 9

10

11)

12

13 14

15)

{

ph i s haЫ e C o n t r a c t attacker

=

_ph i s h a Ь l e C on t r a c t ;

_a t t a c ke rAddr e s s ;

function ( ) рауаЫе {

ph i s haЬ l e C o n t r a c t . w i thdrawAl l ( a t t a c ke r ) ;

16)

Злоумышленник может «подсунуть)) (вам) этот контракт под видом собствен­ ного частного адреса и применить методы социальной инженерии к жертве (вла­ дельцу контракта Ph i shaЫ е), склонить ее к отправке на данный адрес какого-то рода транзакции - возможно, посылая определенное количества эфира. Неосто­ рожный пользователь может не заметить, что по этому адресу находится код, хотя злоумышленник может сказать, что это кошелек с множественными подписями или это какое-то продвинутое приложение для хранения эфира (помните, что ис­ ходный код открытых контрактов недоступен по умолчанию). В любом случае, если жертва пошлет транзакцию с достаточным количе­ ством газа по адресу At t a c kC o n t r a c t, она вызовет функцию fallback, кото­ рая в свою очередь вызовет из контракта Ph i s haЫ e функцию w i thdrawAl l 304

Аутентификация с помощью Тх. Origin

с параметром a t t a c ke r. Это приведет к перечислению всех средств с кон­ тракта Ph i s haЫ e на адрес a t t a c ke r. Все благодаря тому, что адрес, кото­ рый инициализировал вызов, принадлежал жертве (то есть владельцу контрак­ та Ph i s haЫ e). Таким образом, переменная t x . o r i g i n будет равна own e r, и условие requ i re в строчке контракта Ph i s haЫ e будет выполнено.

П ре вентивные меры Переменную t x . o r i g i n не следует использовать для авторизации в смарт­ контрактах. Но это не означает, что ее всегда нужно избегать. У нее есть вполне разумные применения. Например, если вы хотите запретить вызов текущего контракта со стороны внешнего кода, можете реализовать выражение requ i re вида requ i re ( tx . o r i g i n == msg . s ende r ) . Это не даст использовать про­ межуточные контракты для вызова текущего, благодаря чему он будет доступен только для обычных адресов, у которых нет кода (внутри).

Б иблиотеки для контрактов Вам доступно множество готового кода - как в виде вызываемых библиотек (libraries), хранящихся в блокчейне (on-chain), так и в качестве обычных биб­ лиотек шаблонов (off-chain, вне блокчейн-сети). Платформенные библиотеки, если их развернуть, представляют собой смарт-контракты, скомпилированные в байт-код, поэтому в реальных условиях их следует использовать с особой осто­ рожностью. Тем не менее устоявшиеся библиотеки имеют множество преиму­ ществ; например, они позволяют пользоваться последними обновлениями, эко­ номят вам деньги и помогают экосистеме Ethereum, снижая общее количество активных контрактов в блокчейн-сети. Наиболее популярным ресурсом в Ethereum является пакет OpenZeppelin (см. ресурс по ссылке 1 ) . Это богатая библиотека с широким диапазоном контрак­ тов: от реализаций токенов ERC20 и ERC721 до разного рода моделей краудсейла (продажи токенов) и простой логики, которую часто можно встретить в контрак­ тах (например, OwnaЫe, PausaЫe или Limi tBa l ance). Контракты в данном репозитории прошли обширное тестирование и в некоторых случаях даже являют­ ся стандартными реализациями (кейсов) де-факто. Они бесплатны в использова­ нии, а их разработкой и сопровождением занимается компания Zeppelin2 совмест­ но с постоянно растущей группой независимых разработчиков (контрибьютеров). 1

openzeppelin.org. - Прим. авт.

2

zeppelin.solutions. - Прим. авт.

Глава 9 . Безопасность смарт-контрактов

305

Та же компания предлагает ZeppelinOS 1 - открытую платформу 2 серви­ сов и инструментов для безопасной разработки и администрирования прило­ жений на основе смарт-контрактов. ZeppelinOS предоставляет надстройку над EVM, которая помогает разработчикам запускать обновляемые DАрр-прило­ жения, связанные с оn-сhаin-библиотекой хорошо протестированных контрак­ тов, которые сами умеют обновляться. В одном блокчейне Ethereum могут со­ существовать разные версии данных библиотек, а предложение и продвижение улучшений в том или ином направлении осуществляется с помощью системы поручительства 3 • Эта платформа также предоставляет внешние (off-chain) ин­ струменты для отладки, тестирования, развертывания и мониторинга децентра­ лизованных приложений. Проект ethpm пытается организовать различные ресурсы, которые развива­ ются в экосистеме Ethereum, предоставляя систему управления пакетами. В его реестре можно найти дополнительные примеры: - Веб-сайт: www.ethpm.com - Репозиторий: www.ethpm. com/registry - GitHub: github.com!ethpm - Документация: docs. ethpm.com

Выводы Разработчику смарт-контрактов необходимо очень много знать о предметной области и понимать ее. Следуя лучшим практикам в проектировании смарт­ контрактов и написании кода, вы сможете избежать многих серьезных проблем и ловушек. Возможно, самый фундаментальный принцип компьтерной безопасности со­ стоит в максимизации использовании готового, доверенного кода 4. В криптогра­ фии этот принцип стал настолько важным, что даже превратился в присказку: «Не изобретайте собственное шифрование» 5. В случае со смарт-контрактамиэто сводится к как можно более активному использованию общедоступных библио­ тек, тщательно проверенных сообществом. 1

zeppelinos.org. - Прим. авт.

2

Англ.: open source platform. - Прим. ред.

3

Анrл.: vouching system. - Прим. ред.

4

Кода, которому можно доверять. - Прим. ред.

5

В ориr.: Don't roll your own crypto. - Прим. ред.

ГЛАВА 1 0

То кены

Слово «токен» (англ. token) происходит от староанглийского tacen, которое означает знак или символ. Оно часто используется по отношению к эмити­ руемым частным образом монетам (coins), сущностям, которые сами по себе имеют незначительную ценность. Это могут быть транспортные билеты с ба­ лансом (номинированные в токенах), жетоны для прачечных или игровых ав­ томатов 1 . В наши дни под «токеном» на блокчейне 2 все чаще понимают основанную на блокчейне абстрактную единицу, которой можно владеть (пользователю) и которая может представлять активы (физические активы), валюту (средство обмена) или права доступа 3. Незначительная ценность «токенов» во многом связана с ограниченным ис­ пользованием их физических аналогов 4. В реальном мире токены часто реги­ стрируются для деятельности определенных бизнес-компаний, организаций или территорий, обычно имеют только одно функциональное назначение и не могут легко обмениваться (между собой) 5 • В блокчейне эти ограничения не действу­ ют - или, если быть точным, их можно полностью поменять. Многие токены имеют несколько назначений в глобальном масштабе и подлежат обмену между собой или на другие валюты 6 на мировых рынках ликвидности. Вместе с огра­ ничениями на использование и владение в прошлом осталась и идея «незначи­ тельной ценности». 1

Англ.: transportation tokens, laundry tokens, arcade game tokens. - Прим. ред. 2 Или токеном, выпущенным с использованием блокчейн-технолоrий. - Прим. ред. 3

Англ.: assets, currency, access rights. - Прим. ред.

4

То есть использование тех же токенов на блокчейне в «реальном» физическом мире. - Прим. ред.

5 От англ.: exchangeaЫe. - Прим. ред. 6

Под валютами имеются в виду различные крипrовалюты и токены. Криптовалюты - это активы, выпущенные на распределенном реестре или блокчейне. Токены - это активы, вы­ пущенные в рамках блокчейн-сети, но не являющиеся основным платежным токеном, как например, эфир в рамках блокчейн-сети Ethereum является криптовалютой, а активы стан­ дарта ERC20 - токенами. - Прим. ред.

Глава 10. Токены

307

В этой главе мы рассмотрим разные способы применения токенов и то, как они создаются. Мы также обсудим их характеристики, такие как взаимозаменя­ емость и свойства. В конце будут представлены стандарты и технологии, на ко­ торых они основаны, а также примеры построения, создания собственных то­ кенов.

С посо б ы применени я токенов Наиболее очевидной областью применения токенов являются частные цифро­ вые валюты 1 • Но это лишь один из возможных вариантов. Токены можно за­ программировать для выполнения множества разных функций, которые часто имеют много общего. Например, токен может одновременно выражать право го­ лоса, право доступа и владение ресурсом 2 • В следующем списке функционально­ го применения токенов валюта занимает лишь один из пунктов. - Валюта (или цифровая валюта). Токен может служить разновидностью ва­ люты, ценность которой определяется в ходе частной торговли (обмена). - Ресурс. Токен может представлять ресурс, добытый или произведенный в экономике совместного потребления 3 или среде с общими ресурсами\ например, токен хранилища 5 или токен процессора 6 представляет ресур­ сы, которыми можно делиться по(в) сети. - Актив (или физический актив) 7 • Токен может представлять владение вну­ тренним или внешним, материальным или виртуальным активом - на­ пример, золотом, недвижимостью, автомобилем, нефтью, энергией, веща­ ми в многопользовательской иrре 8 и т. д. - Доступ 9 • Токен может предоставлять право владения или права до­ ступа к цифровой или физической собственности, такой как форум Или «криптовалюты». - Прим. ред. 2 Англ.: voting right, access right, ownership of а resource. - Прим. ред. 1

3

Англ.: sharing economy. - Прим. ред.

Англ.: resource-sharing environment. - Прим. ред. 5 Англ.: storage token. - Прим. ред.

4

6 7 8 9

Англ.: CPU token. - Прим. ред. Англ.: asset. - Прим. ред. Англ.: MMOG items. - Прим. ред. Англ.: access. - Прим. ред.

308

Способ ы применения токенов

с обсуждениями, эксклюзивный веб-сайт, отдельный номер в гостинице или арендованный автомобиль. - Капитал (или акции) 1 . Токен может представлять долю акционера (вла­ дельца) в цифровой организации (англ. Digital Organization), например в DAO, или юридическом лице, например корпорации. - Голосование 2 • Токен может представлять право голоса в цифровой или правовой системе. - Предмет коллекционирования. Токен может представлять цифровой (на­ пример, CryptoPunks) или физический предмет коллекционирования (на­ пример, картину). - Идентификатор 3. Токен может представлять цифровой идентификатор 4 (например, аватар) или юридически значимое удостоверение, в т. ч. лич­ ности 5 (например, национальный паспорт). - Свидетельство. Токен может представлять удостоверение (сертификат) или подтверждение факта, выданное некими органами власти или децен­ трализованной системой репутации (например, запись о браке, свидетель­ ство о рождении, диплом колледжа). - У тилитарность 6. Токен может быть использован для оплаты услуг или до­ ступа к ним (внутри информационной 7системы . - Ред.). Часто один токен несет в себе несколько из этих функций. Иногда их слож­ но различить, поскольку их физические эквиваленты всегда были неразрывно связаны между собой. Например, в реальности водительские права (свидетель­ ство) одновременно являются удостоверением личности (идентификатором), и эти два свойства данного документа нельзя разделить. В цифровом же мире функции, которые прежде были неразделимы, можно использовать по отдель­ ности и развивать независимо друг от друга (например, использовать аноним­ ное свидетельство).

1 2

Англ.: equity. - Прим. ред. Англ.: voting. - Прим. ред.

Англ.: identity. - Прим. ред. 4 Англ.: digital identity. - Прим. ред. 3

5

6 7

Англ.: Iegal identity. - Прим. ред. Англ.: utility. - Прим. ред. Или полезность для использования внутри информационной системы. - Прим. ред.

Глава 10. Токены

309

Токены и взаимозамен я емост ь В «Википедии» 1 говорится: «В экономике взаимозаменяемость (fungibllity) - та­ кое свойство общего блага 2 или товара, когда отдельные единицы, по сути, явля­ ются (взаимно)заменяемыми друг на друга». Токены являются взаимозаменяемыми, если мы можем заменить любую от­ дельную единицу (unit) токена на другую единицу без какого-либо изменения его ценности (value) или функции. Строго говоря, если историю происхождения токенов можно проследить, их нельзя считать полностью взаимозаменяемыми. Возможность отслеживания происхождения может привести к созданию черных или белых списков, в ре­ зультате чего их взаимозаменяемость понижается или вовсе устраняется. Невзаимозаменяемыми (non-fungiЬle) называют такие токены, которые пред­ ставляют уникальные вещи материального или виртуального характера и, следова­ тельно, не являются взаимозаменяемыми. Например, токен, представляющий вла­ дение определенной картиной Ван Гога, не является эквивалентом токена, который представляет владение картиной Пикассо, хотя оба они могут быть частью системы «токенов владельцев» (предметов искусства). Точно так же токен, представляющий определенный цифровой коллекционный предмет, такой как CryptoКitty, не явля­ ется взаимозаменяемым по отношению к другому предмету CryptoКitty. У каждого уникального токена есть уникальный идентификатор, такой как серийный номер. Примеры взаимозаменяемых и невзаимозаменяемых токенов будут пред­ ставлены позже в этой главе.



Стоит отметить, что «взаимозаменяемые» токены часто ис­ пользуются в значении «непосредственного обмена их на деньги» 3 (например, токен в казино можно «обналичить», а токены в прачечной - как правило, нет). Это не то, что мы понимаем здесь под этим термином.

Р иск контрагента Риск контрагента заключается в том, что другая сторона транзакции может не выполнить свои обязательства. Некоторые типы транзакций подвержены 1

ru. wikiреdiа. оrg/wiki/Взаимозаменяемость_(экономика). - Прим. ред.

' Англ.: commodity. - Прим. ред. 3

)\нrл.: directly exchangeaЫe for money. - Прим. ред.

310

Токены и взаимозаменяемость

дополнительному риску, поскольку в них участвует больше двух сторон. На­ пример, если вы обладаете сертификатом (депозитным сертификатом) на дра­ гоценный металл и хотите его кому-нибудь продать, в такой транзакции будет как минимум три участника: продавец, покупатель и хранитель (custodian) цен­ ного металла. Тот, кто хранит физический актив, вынужден выступать одной из сторон совершения сделки; это добавляет риск контрагента к любой транзак­ ции, связанной с этим активом. В целом, когда активом торгуют опосредованно на бирже, путем обмена токенами владения, хранитель актива привносит допол­ нительный риск - риск контрагента. Действительно ли у него есть этот металл? Признает (и позволит) ли он передачу владения путем отправки токена (напри­ мер, сертификата, акта покупки-продажи, удостоверения права собственности или цифрового токена)? В мире цифровых токенов, представляющих активы, как и в реальном мире, важно понимать, кто хранит (держит) физический актив, представленный токеном, и какие правила на него распространяются.

Токены и и х сво й ства (назначение) Англ. слово intrinsic {свойственный) происходит от латинского intra, что озна­ чает «изнутри» (внутри . - Ред.). Некоторые токены представляют цифровые сущности, которые являются внутренними по отношению к блокчейну. Такие цифровые активы регулируют­ ся правилами консенсуса, точно так же, как и сами токены. Это имеет важные по­ следствия: токены, представляющие внутренние активы (intrinsic assets), не под­ вержены риску контрагента. Если у вас есть ключи к CryptoКitty, никакая друrая сторона не хранит этот экземпляр (CryptoКitty) за вас - вы владеете им напря­ мую. Срабатывают правила консенсуса в блокчейне, и ваше владение (то есть контроль над) приватными ключами эквивалентно владению самим активом без какого-либо посредника. С друrой стороны, многие токены используются для представления внешних предметов, таких как объекты недвижимости, корпоративные голосующие ак­ ции, торговые марки и золотые слитки. Владение этими предметами, которые не находятся «внутри» блокчейна, регулируется законами, обычаями и нормами, отделенными от правил консенсуса, которые управляют токеном. Иными сло­ вами, эмитенты и владельцы токенов могут по-прежнему зависеть от реальных контрактов без приставки «смарт» 1 • Как результат, эти внешние активы (по отно­ шению к блокчейну . - Ред.) подвержены дополнительному риску контрагента, 1

Англ.: non-smart contracts. - Прим. ред.

Глава 10. Токены

311

поскольку они удерживаются хранителями, записываются во внешние реестры или регулируются законами и нормами за пределами среды блокчейна. Одной из важнейших свойств токенов, основанных на блокчейне, является возможность преобразования (внешних) активов во внутренние, что устраня­ ет риск контрагента. Хорошим примером этому может служить переход от вне­ шнего, капитала (equity) корпорации к токенам-активам или токенам голосова­ ния в DAO или похожей (внутренней) организации.

Назначение токенов: утилитарные токены и то кены -акции На сегодня почти все проекты в Ethereum запускаются с каким-нибудь токе­ ном. Но всегда ли это оправданно? Имеет ли использование токенов какие­ либо недостатки или мы с вами увидим то, как воплощается в жизнь лозунг «токенизируй все))? В принципе, токены могут рассматриваться как основной управленческий или организационный инструмент. Но на практике интеграция блокчейн-платформ, включая Ethereum, с существующими общественными ин­ ститутами пока показывает, что их применение (и их способность быть приме­ нимыми) имеет множество ограничений. Для начала давайте проясним роль токенов в новых проектах (вновь запу­ скаемых на блокчейн-платформе . - Ред.). Большинство проектов используют две основные формы токенизации, где токены обозначают «утилитарность)) (это утилитарные токены) или «капитал)) (это токены-акции). Очень часто эти две роли совмещены. У тилитарным называют токен, использование которого необходимо для по­ лучения доступа к услуге, приложению или ресурсу. Например, он может пре­ доставлять доступ к общему хранилищу или таким сервисам, как социальные сети. Токены-акции представляют долю (акции) в управлении или владении корпо­ ративным капиталом 1 , например в стартапе. Данные токены могут быть неголо­ сующими акциями, использоваться для распределения дивидендов и прибыли; но они также могут подразумевать участие в управлении децентрализованной автономной организацией с помощью комплексной системы управления на ос­ нове решений, принимаемых держателями токенов.

1

В редакции научного редактора. - Прим. ред.

312

Назначение токенов : утилитарные токены и токены -акции

Это утка ! 1 Многие стартапы сталкиваются со сложной проблемой: токены отлично подхо­ дят для реализации механизма фандрайзинrа (сбора средств) 2, наравне с этим выпуск публичных ценных бумаг (акций) регулируется законами в большин­ стве стран. Выдавая токены-акции за утилитарные, многие стартапы пытаются обойти эти ограничения и собрать деньги в ходе публичного размещения (име­ ется в виду процедура публичного размещения монет (или токенов), часто на­ зываемая сокр. ICO. - Ред.) они оформляют это в виде предварительной про­ дажи «ваучеров для доступа к услуге» или утилитарных токенов (utility tokens), как мы их называем. Время покажет, удастся ли этим тонко замаскированным предложениям убедить регуляторов «не реаrировать» 3 • Как говорится в популярной пословице: «если нечто ходит как утка и кряка­ ет как утка, то это утка». Вряд ли регуляторов получится запутать этими семан­ тическими приемами; напротив, они, скорее всего, воспримут такую правовую нечистоплотность как попытку ввести общественность в заблуждение.

Утилитарные токены: ко му они нужны? Реальная проблема состоит в том, что утилитарные токены создают для старт­ апов существенные риски и барьеры на пути их адаптации. Возможно, в дале­ ком будущем все действительно будет токенизировано, но в настоящее время люди, которые разбираются в токенах и хотят их использовать, являются отно­ сительно небольшим сообществом и без того небольшого рынка криптовалют 4. С позиции стартапа каждая инновация представляет собой риск и рыночный фильтр. Это выбор наименее известного пути, «в обход» традиций. Это одино­ кое путешествие. Если стартап старается внедрить инновации в новую техноло­ гическую нишу, такую как хранилище данных с пиринrовыми сетями, то он уже одинок. Если добавить к этому использование утилитарных токенов в качестве инновации и предоставление услуг с помощью них, то это создает дополнитель­ ные риски и увеличивает барьеры их адаптации. Это все равно что сойти с и без

1

В ориг.: It's а Duck! - Прим. ред.

2

И краудфандинrа. - Прим. ред.

3

Практика работы, например, Комиссии по ценным бумагам США (US SEC) показала, что при наличии у выпущенных токенов признаков «капитала» (акций) они однозначно квали фицируется регулятором как ценные бумаги (security). - Прим. ред.

4

В январе 202 1 года капитализация криптовалюты ЕТН составила 84- 155 млрд долл. США. -

Прим. ред.

Глава 10. Токены

313

того одинокой тропинки инновации пирингового хранилища и начать проби­ раться через дикие заросли. Представьте, что каждая инновация - это фильтр. Она ограничивает выход на рынок определенным сегментом, на котором могут появиться первые пользо­ ватели (early adopters) данной инновации. Добавление второго фильтра усили­ вает эффект, ограничивая доступность целевого рынок еще сильнее. Вы просите первых пользователей перейти сразу на две совершенно новые технологии: но­ вое, только что созданное приложение (платформу и сервис) и экономику на ос­ нове токенов (токеномику, реализованную в данном приложении. - Ред. ). С точки зрения стартапа каждая инновация создает риски, которые повыша­ ют вероятность его провала. Если взять и так рискованную идею стартапа и до­ бавить к ней утилитарный токен, вы получите совокупность рисков, связан­ ных с платформой (Ethereum), экономикой в целом (биржей, ликвидностью), нормативно-правовой базой (регулирующие органы) и технологией (смарт­ контрактами, стандартами токенов). Это слишком большой набор рисков для одного стартапа. Сторонники концепции «токенизации всего» могут возразить, что вместе с этим проект получает рыночный энтузиазм, ранних пользователей, техноло­ гию, инновацию и ликвидность всей токеномики. Это тоже верно. Вопрос лишь в том, смогут ли эти преимущества и энтузиазм перевесить риски и неопреде­ ленность. Тем не менее в мире криптовалют действительно зарождаются некоторые из самых инновационных бизнес-идей. Если регуляторы не успевают адапти­ ровать законодательство и организовать поддержку новых бизнес-моделей, предприниматели и окружающие их талантливые специа_листы будут искать возможности работы в юрисдикциях других стран, более дружелюбных к крип­ товалютам. Это уже происходит. В начале этой главы во время знакомства с токенами мы использовали обще­ употребительное определение «токенов»: «сущности, которые сами по себе име­ ют незначительную ценность». Причина незначительной ценности большинства токенов связана с тем, что их можно использовать только в очень узком функ­ циональном контексте: в автобусах одной компании, в одной прачечной, в од­ ном зале игровых автоматов, в одном отеле или одном магазине. Ограниченные ликвидность и применимость, высокие расходы их обмена сводят ценности то­ кена к его стоимости. Поэтому, если вы добавите в свою платформу утилитар­ ный токен, который можно использовать только в пределах данной платформы и небольшого рынка, вы тем самым воссоздадите условия, которые нивелиру­ ют ценность физических токенов. Это и в самом деле может быть правильным подходом к инкорпорированию токенизации в ваш проект. Однако если для 314

Назначение токенов : утилитарные токены и токены-акции

использования вашей платформы пользователю необходимо обменять что-то на ваш утилитарный токен, использовать его, затем по окончании совершить об­ ратный обмен, чтобы получить что-то более полезное, то вы фактически созда­ ете частную валюту. Комиссии при обмене цифровых токенов на порядки ниже, чем в реальном мире, где у их аналогов нет своего рынка, но платить все же приходится. Утилитарные токены, применяемые в целой отрасли народного хо­ зяйства, могут оказаться очень интересными и, вероятно, довольно ценными. Но если для успеха вашего стартапа вы понимаете, что необходимо внедрить новый отраслевой стандарт, то это может означать то, что вы уже потерпели неудачу. Одним из преимуществ развертывания сервисов на платфор­ мах общего пользования, таких как Ethereum, является воз­ можность подключения смарт-контрактов (и, следовательно, утилитарных токенов) ко всем проектам, что повышает потенциальную ликвидность и ( степень утилитарного) приме­ нения токенов. Это решение должно иметь подходящие предпосылки. Внедряйте и адапти­ руйте токен, если ваше приложение не может без него работать, если он устра­ няет фундаментальный рыночный барьер или решает проблемы с доступом. То­ кен не следует использовать, если для вас это единственный способ быстрого сбора денег, если вы вынуждены делать вид, что это не публичное размещение ценных бумаг.

То кены в Ethereum Блокчейн-токены существовали д о появления Ethereum. Первая валюта, выпу­ щенная на блокчейн, это Bitcoin, - сама в некотором роде является токеном. На основе Bitcoin и других криптовалют разработано много разных платформ с токенами, которые предшествовали Ethereum. Однако прорыв в этой области произошел в тот момент, когда на платформе Ethereum был представлен первый стандарт токенов. Виталик Бутерин предложил токены в качестве одного из наиболее очевид­ ных и полезных применений программируемого блокчейна общего пользова­ ния, такого как блокчейн Ethereum. На самом деле в первый год существования данной платформы Виталик и другие разработчики носили футболки с логоти­ пом Ethereum и примера смарт-контракта на спине. Существовало несколько

Глава 10. Токены

315

разновидностей этих футболок, но самая популярная из них демонстрировала реализацию токена. Прежде чем погружаться в подробности создания токенов на Ethereum, важ­ но получить общее представление о том, как эти токены работают на данной платформе. Они отличаются от эфира, поскольку протокол Ethereum ничего о них не знает. В отличие от отправки токенов или даже владения ими, переда­ ча эфира является неотъемлемой (внутренней функцией) частью работы сети. Баланс эфира в учетных записях Ethereum существует на уровне протокола, то­ гда как за баланс токенов в тех же учетных записях отвечают смарт-контракты. Чтобы создать на платформе Ethereum новый токен, вы должны написать новый смарт-контракт, который после развертывания будет заниматься всем, включая владение, перевод средств и обеспечение права доступа. Вы можете написать смарт-контракт, который станет выполнять все необходимые действия любым удобным для вас способом, однако соблюдение существующего стандарта, на­ верное, будет самым разумным решением. Ниже мы рассмотрим такие стандар­ ты. В конце этой главы мы обсудим их достоинства и недостатки.

Ста ндарт токенов ERC20 Первый стандарт токенов, ERC 1 , был предложен в ноябре 2015 года Фабианам Фоrельштеллером. Заявка (issue) на GitHub, в которой он был опубликован, авто­ матически получила номер 20, что положило начало. названию токена «ERC20». На сегодня подавляющее большинство токенов выпущено на основе данного стандарта. В итоге запрос комментариев ERC20 превратился в предложение о внесении улучшений в Ethereum № 20 (EIP-20) 2, и на него до сих пор ссылают­ ся по его изначальному названию ERC20. ERC20 - это стандарт взаимозаменяемых токенов. Это означает, что разные единицы ERC20 обладают идентичными свойствами и могут заменять друг друга. Стандарт ERC20 3 определяет общий интерфейс для контрактов, реализую­ щих токены, чтобы использование и доступ к любому совместимому токену были унифицированными. Этот интерфейс состоит из ряда функций, которые должны присутствовать в каждой реализации токенов данного стандарта, а так­ же некоторые дополнительные функции и атрибуты, которые могут быть добав­ лены разработчиками. 1

Ethereum Request for Comments - запрос комментариев в Ethereum. - Прим. ред.

2

Ethereum lmprovement Proposal 20. - Прим. ред.

3

github.com/ethereum/EIPs!ЫoЬ!master/EIPS/eip-20.md. - Прим. авт.

316

Токены в Ethereum

Обязательные функции и события в ERC20 Контракт токена, соответствующий стандарту ERC20, должен предоставлять как минимум следующие функции и события. - t o t a l S upp l y. Возвращает количество единиц (units) данного токена, которые существуют на данный момент. Общее число находящихся в об­ ращении токенов ERC20 может быть постоянным или плавающим. - b a l anceOf. Возвращает баланс токенов на заданный адрес. - tran s fe r. Переводит заданное количество токенов на заданный адрес, вычитая его из баланса учетной записи, с которой выполняется данный перевод. - trans ferFrom. При наличии отправителя, получателя и количества пе­ реводит токены с одной учетной записи на другую. Используется в соче­ тании с функцией app rove. - app rove. Разрешает заданному адресу выполнить несколько переводов в размере, не больше указанного. Токены берутся из учетной записи, вы давшей данное разрешение. - a l l owance. Возвращает остаток средств, которые заданной учетной за­ писи позволено списать со счета владельца. - Т r an s f е r. Событие, срабатывающее при выполнении успешного перево­ да (вызова t r a n s f e r или t r an s fe rFrom; сумма может быть нулевой). - App roval. Событие, которое регистрируется в журнале при успешном вызове approve.

Дополнительные функции в ERC20 Помимо обязательных вызовов, перечисленных в предыдущем разделе, в стан­ дарте предусмотрены следующие дополнительные функции. - n ame. Возвращает понятное человеку название токена (например, US Dollars). - s ymbol. Возвращает читаемое человеком условное обозначение токена (например, USD). - de c ima l s. Возвращает количество цифр после запятой, которые ис­ пользуются для обозначения единиц размерности токена. Например, если de c i ma l s равно 2, то перед выводом пользователю сумма делит­ ся на 100.

Глава 10. Токены

317

Определение интерфе й са ERC20 в Solidity Вот как выглядит спецификация интерфейса ERC20 в Solidity. contract ERC 2 0 {

function t o t a l Supp l y ( ) constant returns ( uint theTot a l Suppl y ) ; function b a l anceOf ( address

function t r a n s f e r ( address

owne r ) constant returns (uint balance ) ;

to , uint _va lue ) returns (bool succe s s ) ;

function t rans fe r From ( address

returns (bool s u c ce s s ) ;

function approve ( address

f rom , address

owne r , address

returns (uint rema i n i ng ) ;

event T r a n s f e r ( address indexed uint _va l ue ) ;

value )

spende r , uint _value )

returns (bool succe s s ) ;

function a l l owance ( address

to , uint

spende r ) cons tant

f rom , address indexed _t o ,

event Approva l ( address indexed _owne r , address indexed uint _va l ue ) ;

spende r ,

Структуры данных в ERC20 Если изучить реализацию ERC20, можно заметить, что она содержит две струк­ туры данных: одна для отслеживания балансов, а другая - для отслеживания -сумм 1, которые позволено тратить. В Solidity они реализованы в виде ассоциа­ тивных массивов 2 . Первый массив хранит внутреннюю таблицу балансов токена, отсортирован­ ную по владельцу. Это позволяет контракту токена следить за тем, кто им владе­ ет. Каждый перевод -это вычитание из одного баланса и прибавление к другому: mappinq ( address => uint2 5 6 ) b a l ance s ;

Вторая структура данных представляет собой ассоциативный массив с чу­ жими средствами, доступными для траты. Как вы увидите в следующем разделе, владелец токена ERC20 может делегировать полномочия другим пользователям, позволяя им потратить определенную сумму средств (a l l owance) с баланса владельца. Контракт ERC20 отслеживает эти суммы с помощью двумерного ас­ социативного массива, в котором первичным ключом является адрес владельца 1 2

Англ.: allowances. - Прим. ред. Англ.: data mapping. - Прим. ред.

318

Токены в Ethereum

токена, связанный с адресом расходующей стороны и объемом средств, кото­ рый позволено потратить: mappinq ( address => mappinq ( address => uint2 5 6 ) ) puЫic a l l owed ;

Работа с ERC20: «transfer», «approve и transferFrom » Стандарт токенов ERC20 предусматривает две функции для перевода средств. Вам, наверное, интересно почему? ERC20 поддерживает два способа переда­ чи токенов. Первый является довольно простым и представляет собой единую транзакцию с функцией t r an s f е r. Именно этот способ применяется для пере­ вода токенов между кошельками. Подавляющее большинство транзакций с то­ кенами основаны на вызове t r a n s f e r. Выполнение контракта с переводом средств делается очень просто. Если Элис захочет передать Бобу 10 токенов, ее кошелек отправит транзакцию по адресу контракта токена; эта транзакция вызовет функцию t r an s fe r с адресом Боба и значением 1 О в качестве аргументов. Контракт токена изменит балансы Элис (-10) и Боба (+10) и сгенерирует событие T r a n s f e r. Второй подход состоит из двух транзакций и использует функцию app rove, за которой идет t r an s fe rFrom. Это позволяет владельцу токенов делегиро­ вать контроль над ними другому адресу. Этот способ чаще всего применяется для передачи полномочий контракту, распределяющему токены, но он также мо­ жет использоваться биржами. Напр�мер, если компания продает токены во время проведения ICO, она может одобрить (app r ove) адрес контракта для групповой продажи, что­ бы распределить определенное количество токенов (по результатам успешно­ го проведения ICO . - Ред.). Затем этот контракт может перечислить средства (tran s fe r From), находящиеся на балансе контракта токена, каждому покупа­ телю (инвестору). Это проиллюстрировано на рис. 10.1. Первичное размещение монет (или токенов) (от англ. Initial Coin Offering, сокр. ICO) - это механизм краудфандинга, ко­ торый используется компаниями и организациями 1 для сбора средств с помощью продажи токенов. Этот термин происходит от первичного публичного размещения (IPO 2 ) - процесса, в ходе которого публичная компания предлагает инвесторам 1

2

Также как и пользователями, и группами пользователей без формальной регистрации ор­ ганизации. - Прим. ред. От англ.: Initial PuЬlic Offering. - Прим. ред.

Глава 10. Токены

31 9

купить акции (доли в компании) на фондовой бирже. В отли­ чие от строго регулируемого рынка инвестиций с помощью IPO, процедура ICO является открытой, глобальной и сума­ тошной. То, что в этой книге приводятся примеры и объясне­ ния процедур ICO различных проектов, вовсе не означает, что мы поддерживаем этот вид фандрайзинга.

AliceCoin

Элис

AlicelCO

Рис. 1 0. 1. Двухшаговый перевод токенов ERC20 на основе approve и transferFrom Для комбинации app rove и t r an s f e r From необходимо две транзакции. Представим, что Элис хочет позволить контракту Al i c e I C0 продать 50% всех токенов AliceCoin, распределив их между Бобом и Чарли. Для начала Элис за­ пускает контракт Al i ce C o i n типа ERC20, перечисляя все токены AliceCoin на собственный адрес. Затем она запускает контракт Al i c e I C0, который мо­ жет отдавать токены в обмен на эфир. Дальше Элис инициирует процесс на ос­ нове approve и tran s f e rFrom. Она отправляет контракту Al i ceCoin тран­ закцию, вызывая функцию app rove с адресом контракта Al i c e I C0 и 50% от t o t a l S upp l y в качестве аргументов. В результате этого сработает событие App roval. Теперь контракт Al i ce I C0 сможет продавать AliceCoin. Когда контракт Al i c e I C0 получает эфир от Боба, он должен послать в от­ вет AliceCoin. В контракте Al i ce I C0 прописан курс обмена между AliceCoin и эфиром, который Элис установила в момент создания AliceICO. Он опреде­ ляет, сколько токенов получит Боб за то количество эфира, которое он послал контракту Al i c e I C0. Когда Al i c e I C0 вызывает из Al i ce C o i n функцию t r a n s f e r Fr om, он устанавливает адрес Элис в качестве отправителя и ад­ рес Боба - в качестве получателя; затем с помощью курса обмена он опреде­ ляет, сколько токенов AliceCoin будет передано Бобу в поле val ue. Контракт Al i ceCo i n перечисляет баланс с адреса Элис на адрес Боба и инициирует со­ бытие Tran s fe r. Контракт Al i c e I C0 может вызывать t rans ferFrom cкoль­ кo угодно раз, при условии, что он не превысит разрешенный лимит, который 320

Токены в Ethereum

установила Элис. С помощью функции a l 1 owance контракт Al i се I СО может вычислить, какое количество токенов AliceCoin у него осталось для продажи.

Реализации ERC20 Токен, совместимый с ERC20, можно уместить в 30 строчек кода на языке Solidity, однако большинство реализаций являются куда более сложными. Это вызвано борьбой с потенциальными уязвимостями контрактов. В стандарте EIP-20 упо­ минается две реализации. - Consensys EIP20 1 • Простая и понятная реализация токена, совместимого с ERC20. - OpenZeppelin Standard Token 2 • Эта реализация совместима с ERC20 и отли­ чается дополнительными мерами предосторожности. Она лежит в осно­ ве библиотек OpenZeppelin, которые реализуют более сложные котракты с токенами, совместимыми с ERC20, имеют лимиты на сбор средств, аук­ ционы, графики инвестирования и другие возможности.

Запуск собственно го токена ERC20 Давайте создадим и запустим наш собственный токен. В этом примере мы бу­ дем использовать фреймворк Truffle. Предполагается, что вы уже установили и сконфиrурировали пакет truffle и знакомы с основными принципами его ра­ боты (подробности описаны в разделе «Truffle» на с. 467). Наш токен будет называться Mastering Ethereum Token и иметь обозначение « МЕТ ». Вы можете найти этот пример в репозитории данной книги на GitHub 3.

Для начала создадим и инициализируем директорию проекта Truffle. Выпол­ ните эти команды, акцептуйте ответы по умолчанию на все вопросы:

1

github.corn/ConsenSys/Тokens/ЫoЬ!rnaster/contracts/eip20/EIP20.sol. - Прим. авт.

' gith u b. co rn/OpenZeppelin/openzeppelin -solidity/Ьlo Ь!v l . 1 2. 0/con tra c ts/toke n/ER C20/ StandardToken.sol. - Прим. авт. 3

github. сот/ethereurnbook/ethereurnbook/tree/developlcode/truff/,elMEToken. - Прим. авт.

Глава 10. Токены

321

$ Dlkdir МEToken $ cd МEToken

METoken $ truflle init METoken $ npm init

У вас должна получит ь с я следующая с труктура директории : ME T o k e n /

+ - - - - contracts

' ---- Migrations . sol

+---- migrat ions

' - - - - 1 i n i t i a l_mi g r a t i o n . j s

+ - - - - p a c kage . j s o n +---- test

+ - - - - t ruffie - c onfig . j s t ruffie . j s

Отредактируйте конфигурационный файл tru.ffle.js или tru.ffle-config.js, чтобы настроить окружение Truffle, или скопируйте последний из репозитория 1 • Если вы используете tru.ffle-config.js из примера, не забудьте создать в папке METoken файл.епv с приватными (закрытыми) ключами для тестирования и раз­ вертывания в публичных тестовых сетях Ethereum, таких как Ropsten или Kovan. Вы можете экспортировать свой приватный ключ для тестнета из MetaМask. После этого ваша структура-директория должна выглядеть так: METoke n /

+---- contracts

, ____ M i g r a t i ons . s o l

+ - - - - mi g r a t i o n s

, _ _ _ _ 1 i n i t i a l_mi g r a t i o n . j s

+ - - - - pac kage . j s on +---- test

+---- t ruffie-config . j s

+ - - - - t r uffie . j s

' - - - - . env * new fil e *

' github. сот!ethereumbook/ethereumbook!ЫoЫdeveloplcode/trujfle/METoken/trujfle-config.js.

Прим. авт.

322

Токены в Ethereum

& ' ,

Используйте только те тестовые ключи и мнемоники, которые не участвуют в хранении средств в главной сети Ethereum. Никогда не применяйте в тестировании ключи, связанные с ре­ альными деньгами.

Мы импортируем для нашего примера библиотеку OpenZeppelin, которая реализует некоторые важные проверки безопасности и может быть легко рас­ ширена: $ npm install openzeppelin-sol idi ty@ l . 12 . 0

+ openz eppe l i n - s o l i di t y @ l . 1 2 . 0

added 1 pac kage f rom 1 contributor and audited 2 3 8 1 packages i n 4 . 0 7 4 s

Пакет open z ерре 1 i n - s о 1 i d i t у добавит в директорию node_modules при­ мерно 250 файлов. Библиотека OpenZeppelin далеко не ограничивается токеном ERC20, и мы будем использовать лишь малую ее часть. Теперь давайте напишем контракт нашего токена. Создадим новый файл, METoken.sol, и скопируем пример кода из GitHub 1 • Наш контракт, представленный в примере 10.1, выглядит очень просто, так как всю свою функциональность он наследует из библиотеки OpenZeppelin. Пример 1 0. 1 . METoken.sol: контракт на языке Solidity, реализующий токен ERC20 1 pragma solidi ty л о . 4 . 2 1 ;

2

3 import ' z eppe l i n - s o l i d i t y / contract s / to ke n / E RC2 0 / S tandardToken . s o l ' ; 4

5 contract METoken is StandardToken 6

string puЬlic constant name = ' Ma s t e r i ng Ethe reum Token ' ;

В

uint8 puЬlic constant de cima l s = 2 ;

7

string puЬlic cons tant s ymЬo l = ' МЕТ ' ;

9

10 11 1

uint cons tant

i n i t i a l supp l y = 2 1 0 0 0 0 0 0 0 0 ;

function METoken ( ) puЬlic {

github.comlethereumbook/ethereumbook!ЫoЫdevelop/code/truffle/METoken/contracts/METoken. sol. - Прим. авт.

Глава 10. Токены

323

12

t o t a l S upp l y_

13

b a l a n c e s [ m s g . s e n de r ]

14

emit Trans f e r ( address ( O ) , msg . s e n de r ,

i n i t i a l s upp l y ; =

i n i t i a l s upp l y ; i n i t i a l s upp l y ) ;

15 1 161

Здесь мы определяем опциональные переменные name, s уmЬо 1 и dec imal s. Мы также определяем переменную _ i n i t i а l _ s uрр 1 у со значением, равным 21 миллиону токенов; учитывая два порядка деления, у нас в сумме получится 2,1 миллиарда единиц (units). Функция инициализации контракта (конструктор) присваивает переменной t o t a l Supp l y значение _ i n i t i a l _ s upp l y и ука­ зывает последнее в качестве баланса учетной записи (ms g . s ende r), которая создает контракт MET o ken. Теперь воспользуемся утилитой t ruffle, чтобы скомпилировать код METo ken: $ truJlle compile C omp i l ing . / c o n t r a c t s / ME T o k e n . s o l ... C omp i l i ng . / co n t r a c t s /Mi g r a t i o n s . sol ... Comp i l ing open z eppe l i n - s o l i d i ty / cont r a c t s /math / S a feMa th . s o l ... Comp i l i n g open z eppe l i n - s o l i d i ty / c o n t r a c t s / t o k e n / E RC 2 0 / Ba s i cToken . s o l ... Comp i l i ng open z eppe l i n - s o l i di t y / c o n t ra c t s / t o ken / ERC 2 0 / E RC 2 0 . sol ... Comp i l ing open z eppe l i n - s o l i d i t y / c o n t r a c t s / t o k e n / E RC 2 0 / E RC 2 0 B a s i c . s o l .. . Comp i l ing open z eppe l i n - s o l idi ty/ cont racts / t o ken/ ERC2 0 / S tandardToken . s o l .. .

Как видите, вместе с нашим кодом компилируются все необходимые контрак­ ты, которые t ruffle берет из библиотек OpenZeppelin. Давайте напишем миграционный скрипт для развертывания контрак­ та METo ken. Создадим в папке METoken!migrations новый файл под названием 2_dep lay_contracts.js. Скопируем в него содержимое примера из репозитория GitHub 1 : 1 var ME T o ken

a r t i f a c t s . requ i r e ( " ME T o ken " ) ;

2 3 modu l e . exp o r t s = function ( deploye r )

{

4

// Развертывание контракта METoken в ка честве единственного задания

5

dep l o ye r . dep l o y ( METoken ) ;

61 ; 1

github.com/ethereumbooklethereumbook/ЫoЫdevelop!code/tru.ffle/METoken/migrations/2_dep loy_ contracts.js. - Прим. авт.

324

Токены в Ethereum

Прежде чем развертывать код в одной из тестовых сетей Ethereum, давайте сначала проверим все на локальном блокчейне. Запустите блокчейн ganache либо из командной строки с помощью утилиты ganache - c l i либо из графи­ ческого пользовательского интерфейса.

n"'"' 8•f3616361Sf•1ef7ed8f4a8f192H9a8bf633fe1a2398cell81bf"c•3dc7Ьddae

--....

----

,,

IAfUIO 26981

8•be9298d59678M12e68ed&.tfedЫ736•f•ad2977cfb2176b9b8ad•1ScSdc9f8 8.Ullftl

1•1я.а

11 .... 8•d7bc86d31bee32f13988f1c1eaЬce•e3a1b5d578З•taз19cdb15Зa•72ee8c956

....

--,,

__

.,.....,.

,....,__,.

,1м1

. .....

....

8•b2e98a856dc6ad8e65•683921fc613c796a83b89df6768ec1dЫBS.ea•••B••b

...,.,

,....,

tJ,tllllO

Рис. 1 0.2. Развертывание METoken в ganache После этого мы можем развернуть наш контракт MET o ken и убедиться в том, что все работает как следует: $ tru!lle miqrate - -network qanache

U s i ng networ k ' ganache ' .

Running migrat i o n : 1 i n i t i a l_mi g r a t i o n . j s Deploying Migrat i ons _

... ОхЬ 2е 9 0 а0 5 6dc 6ad8e 654 6 8 З 9 2 1 f с 6 1 З с 7 9 6 а 0 ЗЬ 8 9df 6 7 6 0 e c l dЫ О 8 4 еа 4 а 0 8 4 еЬ

Migrat ions : O x 8 cda f 0 cd2 5 9 8 8 7 2 5 8b c l 3 a 9 2 c 0 a 6da 9 2 б 9 8 6 4 4 c 0

Saving succe s s fu l migrat i o n to networ k ...

... O xd7bc 8 6d3 lbee 3 2 f а З 9 8 8 f l c l eabce 4 О З a l b 5 d 5 7 0 З 4 О а З а 9с dЬа 5 З а 4 7 2 е е 8 с 95 6

Saving a r t i facts_

Runn ing migra t i on : 2_de p lo y_cont r a c t s . j s Deploying METo ken _

_ O xbe 9 2 9 0 d5 9 6 7 8b 4 1 2 e 6 0 e d 6 a e fedЫ 7 3 6 4 f 4 ad2 9 7 7 c fb2 0 7 6b 9 b 8 ad4 1 5 c 5 dc 9 f 0

METoken : O x 3 4 5 c a 3e 0 1 4 aa f 5 d c a 4 8 8 0 5 7 5 9 2 e e 4 7 3 0 5 d 9 b 3 e l 0

Saving succe s s fu l m i g r a t i o n t o netwo r k...

_ O x f 3 6 1 6 3 6 1 5 f 4 l e f 7 e d8 f 4 a 8 f l 92 1 4 9 a 0 b f 6 3 3 fe l a2 3 9 8 ce 0 0 lb f 4 4 c 4 3 dc 7bdda 0

Saving a r t i fac t s_

Глава 10. Токены

325

В консоли ganache должно быть видно, что развертывание нашего контрак­ та создало четыре новые транзакции (см. рис. 10.2).

Работа с MEТoken в консоли Truffle Мы можем взаимодействовать с нашим контрактом в блокчейне ganache, ис­ пользуя консоль Truffle. Это интерактивное окружение на языке JavaScript, ко­ торое предоставляет доступ к среде Truffle и блокчейну (через wеЬЗ). В данном случае мы подключим консоль Truffle к блокчейну ganache: $ truflle console - -network ganache

t ruffle ( ganache ) >

Строка приглашения t ruffle ( ganache ) > говорит о том, что мы подклю­ чились к блокчейну ganache и готовы к вводу наших команд. Консоль Truffle поддерживает все команды t ruffle, поэтому мы можем использовать операции c omp i l e и m i g r a t e прямо в консоли. Для запуска этих команд все готово, поэтому давайте перейдем непосредственно к самому контракту METo ken, ко­ торый в среде Truffle существует в виде объекта JavaScript. Введите МEToken в командной строке, чтобы вывести все определение контракта: t ruffle ( ganache ) > МEToken

{ [ Func t i o n : TruffleCont ract ] s t a t i c me thods :

[ ... ] currentProvide r : HttpProvider {

ho s t : ' http : / / l o c a l h o s t : 7 5 4 5 ' , t imeout : О ,

u s e r : undefined ,

pas sword : undefined , heade r s : undefined, send : [ Func t i on ] ,

sendAs ync : [ Func t i on ] ,

a l readyWrapped : t rue } ,

netwo r k i d : ' 5 7 7 7 ' }

326

Токены в Ethereum

У объекта ME T o ke n есть несколько атрибутов, таких как адрес контракта (указанный во время развертывания с помощью команды migrate): truffle ( ganache ) > МEToken . address

' 0 x 3 4 5 c a 3 e 0 1 4 a a f 5 dca4 8 8 0 5 7 5 9 2 ee 4 7 3 0 5 d 9 b 3e l 0 '

Для взаимодействия с развернутым контрактом необходимо использо­ вать асинхронные вызовы в виде «обещаний» (promise) в формате JavaScript. Получим экземпляр контракта с помощью dep l o ye d и вызовем функцию tota l Supp l y: truffle ( ganache ) > МEToken . deployed ( ) . then ( instance => instance . totalSupply О )

Bi gNumЬe r { s : 1 , е : 9 , с :

[2100000000] }

Теперь воспользуемся учетной записью, созданной в блокчейне gana che, чтобы проверить, сколько токенов METoken у нас на счете, и перевести часть из них на другой адрес. Для начала давайте получим адреса учетных записей: truffle ( ganache ) > let accounts

undefined

truffle ( ganache ) > weЬЗ . eth . qetAccounts ( (err , res ) => { accounts = res } )

undefined

truffie ( ganache ) > accounts [ 0 ]

' Q x 6 2 7 3 0 6 0 9 0 abab 3 a бe l 4 0 0 e 9 3 4 5b c 6 0 c 7 8 a 8 b e f 5 7 '

Список a c c o un t s содержит все учетные записи, созданные в блокчейне ganache, а account [ О ] - это учетная запись, которая развернула контракт ME To ken. На ее счете должны находиться токены METoken, поскольку кон­ структор контракта ME To ken перечислил весь их запас на адрес, на котором создан контракт. Давайте в этом убедимся: truffie ( ganache ) > МEToken . deployed ( ) . then ( instance => undefined

{ instance . balanceOf ( accounts [ 0 ] ) . then ( console . loq) } )

truffie ( ganache ) > BiqNumЬer { s : 1 , е : 9 , с : [ 2 1 00 0 0 0 0 00 ] }

В завершение давайте переведем 1000,00 единиц METoken с a c count [ О ] на account [ 1 ] , вызвав из контракта функцию t r an s fe r: Гл ава 10. Токены

327

t r uffie ( ganache ) > МEToken . deployed ( ) . then ( instance => undefined

{ instance . transfer ( accounts [ l ] , 1 0 0 0 0 0 ) ) )

t r uffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

undefined

{ instance . balanceOf ( accounts [ 0 ] ) . then ( console . loq) } )

t r uffie ( ganache ) > BiqNumЬer ( s : 1 , е : 9 , с : [ 2 0 9 9900000 ) }

undefined

t ruffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

undefined

{ instance . balanceOf ( accounts [ l ] ) . then ( console . loq) } )

t r uffie ( ganache ) > BiqNumЬer { s : 1 , е : 5 , с : [ 1 0000 0 ) }

Точность METoken равна двум знакам после запятой; это озна­ чает, что в контракте 1 METoken равен 100 единицам. Чтобы передать 1000 токенов ME Token, мы указываем в вызове t r an s fe r значение 1 0 0 0 0 0. Как можно видеть в консоли, на счетах account [ О ] и account [ 1 ] теперь находится 20 999 ООО МЕТ и, соответственно, 1000 МЕТ. Если перейти в графический интерфейс ganache, показанный на рис. 10.3, можно увидеть транзакцию, которая вызвала функцию t r an s f е r.

--

8•б2 738б898аЬаЬ3абе1 488е9345Ьс68с78а8Ьеf57 е . ее ЕТН

"'"""

5 1 647

__ ...,.

8•f12b5dd4ead5f743cбbaa648tle2 16288e89b68da

1еееееееееее

8'SUIIТ 6721975

-·""" 4

1Х"1А fц9t59cЬЬНf8ttet68&6ttННtHl88f 1 7f.521 Sl@btfOc7331tf1d888CS7tlod77216b7328М618888иt6НН&etee8181Nfll&e""88t6tleNtH8118t888tt8118Ь.8

Рис. 1 0.3. Передача METoken в ganache

Отправка токенов ERC20 на адрес контракта Итак, мы создали токен ERC20 и перевели некоторое количество его единиц с од­ ной учетной записи на другую. Все учетные записи, которые мы использовали в этих примерах, имеют внешних владельцев; это означает, что они управляются 328

Токены в Ethereum

приватными ключами, а не контрактами. Но что случится, если мы отправим МЕТ на адрес контракта? Давайте посмотрим! Для начала развернем в нашем тестовом окружении еще один контракт. В этом примере мы воспользуемся нашим первым контрактом - Faucet.sol. Ско­ пируйте его в директорию contract проекта METoken. Структура директорий дол­ жна выглядеть так: METoken/

+ - - - - con t rac t s

+--- - Faucet . s o l

+ - - - - METoken . s o l

Migrat i on s . s o l

Также добавим поддержку миграции, чтобы развернуть Fau c e t отдельно от ME T o ken : var Faucet

a r t i f a c t s . requ i r e ( " Fa u ce t " ) ;

modu le . expo r t s = function ( deploye r )

{

// Развер тыв а ние кон тра кта Fa uce t в ка честв е единс твенного зада ния

dep l o ye r . dep l o y ( Faucet ) ; };

Давайте скомпилируем и перенесем контракты из консоли T ruffle : $ truJВe console - -network ganache

t r uffie ( ganache ) > compile

Comp i l i n g . / c o n t r a c t s / Faucet . s o l ...

Writing a r t i f a c t s t o . / bu i l d/ contracts truffle ( ganache ) > migrate U s i ng ne two rk ' ganache ' . Running migrat i o n : 1 i n i t i a l_mi g r a t i o n . j s Dep loying Migra t i o n s _

_ O x 8 9 f бa 7bd2 a 5 9 6 8 2 9 c 6 0 a 4 8 3 e c 9 9 6 6 5 c 7 a f 7 l e 6 8 c 7 7 a 4 1 7 fab5 0 3 c 3 9 4 fcd7 a 0 c 9

Migrat i on s : O x a l c c ce 3 6 fb 8 2 3 8 1 0 e 7 2 9dce2 9 3b 7 5 f 4 0 fb б e a 9 c 9

Saving a r t i facts_

Глава 10. Токены

329

Runn ing migration : 2_depl o y_contrac t s . j s Rep l a c ing METoken_

-· O x 2 8 d0 da2 6 f 4 8 7 6 5 f 6 7 e l 3 3 e 9 9dd2 7 5 fa c б a 2 5 fdfe c 6 5 9 4 0 6 0 fd l a 0 e 0 9a 9 9b4 4ba

METoken : O x 7 dбbf 9d5 9 1 4 d3 7b cba 9d4 6 d f 7 1 0 7 e 7 l c 5 9 f 3 7 9 l f

Saving a r t i fac t s_,

Running migration : 3 dep l o y_faucet . j s Dep loying Faucet_,

_ O x б fb f 2 8 3b c c 9 7 d 7 c 5 2 d 9 2 fd9 l f бa c 0 2 d5 6 5 f 5 fded4 8 3 a б a 0 f 8 2 4 f 6 6edc б fa 9 0 c 3 Faucet : O xЫ 8 a 4 2 e 9 4 6 8 f 7 f l 3 4 2 fa 3 c 3 2 9 e c 3 3 9 f 2 5 4 b c 7 5 2 4

Saving a r t i f a c t s _

Отлично. Теперь пошлем контракту Faucet токены МЕТ: truffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

{ instance . transfer ( Faucet . address , 1 0 0 0 0 0 ) } )

truffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

{ instance . ЬalanceOf (Faucet . address) . then (console . loq) } )

t r uffie ( ganache ) > BigNumЬer { s : 1 , е : 5 , с : [ 1 0000 0 ) }

Итак, мы перечислили контракту Fau cet 1000 МЕТ. Как нам теперь выве­ сти эти токены? Как вы помните, контракт Faucet.sol является довольно простым. Он со­ держит лишь одну функцию w i thdr aw, предназначенную для вывода эфи­ ра. Он не умеет выводить МЕТ или любой другой токен ERC20. Если вызвать wi thdraw, то он попытается послать эфир, но, поскольку на счете у Faucet нет никакого эфира, эта попытка окажется неудачной. Контракт ME T o ke n знает, что у Fau c e t есть баланс, но для перечисле­ ния этого баланса он должен получить вызов t r an s fe r с адреса контракта. Нам нужно каким-то образом заставить контракт Fau c e t вызвать функцию t r an s f e r из METo ken. Если вам интересно, как это сделать, можете не тратить свое время: у этой проблемы нет решения. Токены МЕТ, переданные контракту F a u c e t, будут навсегда заблокированы. Их может передать только сам контракт F a u c e t , но у него нет кода для вызова функции t r an s f е r из контракта токена ERC20. Эту проблему можно было предвидеть, но, скорее всего, вы этого не сделали. На самом деле в ту же ловушку попали сотни пользователей Ethereum, которые случайно перечислили различные токены контрактам, не совместимым с ERC20. По некоторым оценкам таким образом «застряли» и были навечно утеряны то­ кены общей стоимостью примерно 2,5 миллиона долларов США. 330

Токены в Ethereum

Одна из ситуаций, в которой можно нечаянно потерять свои токены ERC20, заключается в попытке переслать средства бирже или другому сервису. Пользо­ ватели копируют адрес Ethereum на веб-сайте биржи и думают, что они могут просто отправить на него свои токены. Однако многие биржи публикуют при­ нимающие адреса, которые на самом деле принадлежат контрактам. Эти кон­ тракты предназначены лишь для получения эфира, а не токенов ERC20; чаще всего все переданные им средства помещаются в «холодное хранилище ►► или дру­ гой центращ,:зованный кошелек. Вопреки многочисленным предупреждениям о том, что не следует отправлять токены на данный адрес, многие пользователи допускают эту ошибку и- теряют свои деньги. Демонстра ция п роцесса approve и transferfrom Наш контракт Fau cet не поддерживает ERC20. Токены, переданные его функ­ ции tran s f e r, будут утрачены. Давайте перепишем этот контракт так, чтобы он мог работать с токенами ERC20. В частности, мы превратим его в faucet для выдачи МЕТ всем желающим. Для этого примера мы создадим копию директории truffie (назовем ее METoken_METFaucet), инициализируем t r uffle и npm, установим зависимости OpenZeppelin и скопируем контракт METoken.sol. Подробные инструкции мож­ но найти в разделе miqrate

Us i ng netwo r k ' ganache ' . Running m i g r a t i o n : l_i n i t i a l_mi g r a t i o n . j s Dep loying Migrat i o n s _

_ O x 7 9 3 5 2 b 4 3 e 1 8 cc 4 6b 0 2 3 a 7 7 9 e 9 a 0 d l бb 3 0 f 1 2 7 b f a 4 0 2 6 6 c 0 2 f 9 8 7 1 d 6 3 c 2 6 5 4 2 c 7

Migrations : O x aa 5 8 8 d 3 7 3 7 b б l lba fd7bd7 1 3 4 4 5b 3 1 4bd4 5 3 a 5 c 8

Saving a r t i f a c t s -

Running m i g r a t i o n : 2_depl o y_contra c t s . j s Rep l a c i ng METo ken _

_ O xc 4 2 a 5 7 f 2 2 cddf 9 5 f б f 8 c 1 9d7 9 4 c 8 a f 3b2 4 9 1 f 5 6 8 b 3 8 b 9 6 fe f 1 5Ы 3 b б e 8 bfff2 1

METoken : O x f 2 0 4 a 4 e f 0 8 2 f 5 c 0 4bb 8 9 f 7 d 5 e 6 5 6 8b 7 9 6 0 9 6 7 3 5 a Rep l a c i ng METFaucet_

-· Oxd9 6 1 5 cae2 fa 4 f l e 8 a 3 7 7 de 8 7 f 8 6 1 6 2 8 3 2 c f 4 d3 1 0 9 8 7 7 9e б e 0 0 d f l ae 7 f l b 7 f 8 6 4

METFauce t : O x 7 5 c 3 5 c 9 8 0 c 0 d 3 7 e f 4 6d f 0 4 d3 1 a 1 4 0b 6 5 5 0 3 c 0 eed

Saving a r t i f a c t s _.

truffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

{ instance . approve (МETFaucet . address , 1 0 0 0 0 0 ) } )

truffie ( ganache ) > МEToken . deployed ( ) . then ( instance =>

{ instance . balanceOf (weЬЗ . eth . accounts [ l ] ) . then ( console . 109) } )

truffie ( ganache ) > BiqNuшЬer { s : 1 , е : О , с : [ О ] }

truffie ( ganache ) > МETFaucet . deployed ( ) . then ( instance =>

{ instance . withdraw ( l 000 , { from : weЬЗ . eth . accounts [ l ] } ) } )

334

Токены в Ethereum

truШe ( ganache ) > МEToken . deployed ( ) . then ( instance =>

{ instance . balanceOf (weЬЗ . eth . accounts [ l ] ) . then ( console . loq) } )

truШe ( ganache ) > BiqNumЬer { s : 1 , е : 3 , с : [ 1 000 ) )

Как видно по результатам, мы можем использовать процесс на основе вызо­ вов app rove и t r an s f e r From, чтобы разрешить одному контракту переда­ вать токены, определенные в другом контракте. Если правильно этим воспользо­ ваться, токены ERC20 можно будет применять в учетных записях ЕОА и других контрактах. При этом ответственность за правильное обращение с токенами ERC20 ло­ жится на пользовательский интерфейс. Если пользователь по ошибке попытает­ ся передать токены ERC20 контракту, который не умеет их принимать, они бу­ дут потеряны.

П роблемы с токенами ERC20 Темпы внедрения токенов стандарта ERC20 оказались действительно бурными. Были запущены тысячи проектов - как для экспериментов с новыми возмож­ ностями, так и для сбора средств в различных краудфандинговых кампаниях, процедурах ICO. Но, как мы уже видели на примере отправки токенов на адрес контракта, существуют некоторые потенциальные проблемы. Одна из не таких очевидных проблем состоит в том, что токены ERC20 вскры­ вают тонкие отличия между собой и эфиром. Если эфир передается с помощью транзакции, пунктом назначения которой служит адрес получателя, передача то­ кенов происходит в рамках определенного состояния контракта токена, и этот контракт, а не адрес получателя, выступает в роли «пункта назначения». Кон­ тракт токена отслеживает балансы и генерирует события. На самом деле при пе­ реводе токенов получателю не оправляется никакой транзакции. Вместо этого адрес получателя добавляется в ассоциативный массив внутри самого контрак­ та токена. Транзакция, передающая эфир, изменяет состояние конечного адре­ са. Транзакция, передающая токен, изменяет только состояние его контракта, но не адреса получателя. Даже кошелек с поддержкой ERC20 не может узнать о балансе токенов, пока пользователь вручную не добавит соответствующий контракт в список «отслеживаемых». Некоторые кошельки следят за контракта­ ми самых популярных токенов, чтобы определить балансы на счетах, которыми они управляют, но это лишь небольшая часть существующих контрактов стан­ дарта ERC20. На самом деле пользователь вряд ли захочет отслеживать все балансы в контрактах всех возможных токенов ERC20. Многие из них больше похожи Глава 10. Токены

335

на почтовый спам, нежели на что-то полезное. Чтобы привлечь пользователей, они автоматически создают балансы для учетных записей, которые работают с эфиром. Если у вас есть Ethereum-aдpec с длинной историей использования эфира, особенно если он был создан в рамках предварительной продажи (токе­ на), вы обнаружите на его счете множество бесполезных токенов, появившихся из ниоткуда. Конечно, на самом счете нет никаких токенов; это просто контрак­ ты токенов включают в себя ваш адрес. Эти балансы видны только в случае, если контракты токенов отслеживаются обозревателем блоков или кошельком, кото­ рые вы используете для просмотра своего адреса. Токены ведут себя не так, как эфир. Эфир отправляется с помощью вызо­ ва s e n d и принимается любой оплачиваемой функцией контракта или лю­ бым адресом с внешним владельцем. Токены отправляются с помощью вызова t r an s f e r или комбинации app rove и t r an s fe r From, которые существу­ ют только в контракте ERC20 и не вызывают никаких оплачиваемых функций в контракте получателя (по крайней мере, не в стандарте ERC20). По принци­ пу своей работы токены напоминают криптовалюту, такую как эфир, но ввиду определенных отличий эта иллюзия развеивается. Рассмотрим еще одну проблему. Чтобы отправить эфир любому контракту в Ethereum, вам нужно обменять его часть на газ. Для отправки токенов тоже нужен эфир. За газ, необходимый для транзакции, нельзя заплатить токенами, и контракт токена не может внести оплату вместо вас. В отдаленном будущем это может измениться, но пока такая логика может довольно странным образом отразиться на применении токенов. Представьте, к примеру, что вы использу­ ете биржу или сервис ShapeShift для конвертации некоторого количества Bitcoin в токены. Вы «получаете» токены в кошелек, который отслеживает их контракт и выводит ваш баланс. Они выглядят, как любая друrая криптовалюта в вашем кошельке. Но если вы попробуете отправить токен, ваш кошелек сообщит о том, что для этого необходим эфир. Это может показаться странным - в конце кон­ цов, вам не нужен был эфир для получения токена. Может быть, у вас и вовсе нет эфира. Возможно, вы даже не знаете, что это был токен стандарта ERC20 в сети Ethereum; вы могли подумать, что это криптовалюта с собственным блокчейном. Вот так и развеивается иллюзия. Некоторые из этих проблем характерны только для токенов ERC20, а дру­ гие, более общие, относятся к границам абстракций и интерфейсов внутри Ethereum. Одни проблемы можно решить путем изменения интерфейса токе­ на, тогда как друrие могут потребовать изменений в фундаментальных структу­ рах Ethereum (таких как отличие между учетными записями ЕОА и контрактами или между транзакциями и сообщениями). Некоторые проблемы могут ока­ заться не совсем «решаемыми»; возможно, некоторые нюансы придется скрыть 336

Токены в Ethereum

за пользовательским интерфейсом, чтобы, несмотря на внутренние отличия, все выглядело однородным. В следующих разделах мы рассмотрим различные предложения, призванные решить некоторые из этих проблем.

ERC223: предложенный стандарт интерфе йса для контракта токена Предложение стнадарта ERC223 - это попытка решить проблему с непредна­ меренной передачей токенов контракту (который может их не поддерживать) путем определения того, принадлежит ли конечный адрес контракту. Соглас­ но ERC223, контракты, написанные для приема токенов, должны реализовать функцию с именем t o ken F а l lba с k. Если пунктом назначения перевода является контракт, который не поддерживает токены (то есть у которого нет функ­ ции t o kenFa l lbac k), перевод не исполнятся. Чтобы определить, принадлежит ли конечный адрес контракту, в реализа­ ции стандарта ERC223 применен довольно необычный подход, где использует­ ся небольшая вставка байтокода: function i s C o n t r a c t ( addres s contract )

{

addr ) private view returns (bool i s

uint l e n g t h ; аssешЫу

// получа ем размер кода по заданному адресу ; нужна

а ссемблерная в ставка

l e n g t h : = e x t c ode s i z e ( addr )

return ( length>0 ) ;

Спецификация интерфейса ERC223 выглядит так: interface ERC 2 2 3 T o ke n {

uint puЬlic t o t a l Supp l y ;

function b a l a n c eO f ( address who ) puЬli c view returns ( uint ) ; function name ( ) puЬli c view returns ( s trinq _n ame ) ;

function s ymbo l ( ) puЫic view returns ( string

function de c ima l s ( ) puЬlic view returns (uint8

s ymbo l ) ;

de cimal s ) ;

function t o t al S upp l y ( ) puЬlic view returns (uint2 5 6

s uppl y ) ;

Глава 10. Токены

337

function trans fer ( address to , uint value ) puЬlic returns (bool o k ) ;

function trans f e r ( address to , uint value , bytes dat a ) puЬlic returns

(bool o k ) ;

function t r a n s f e r ( address to , uint value , bytes dat a , string cus tom

f a l lbac k )

puЬlic returns (bool o k ) ;

event Tran s fe r ( address indexed from, address indexed t o , uint value , bytes indexed data ) ;

Стандарт ERC223 не получил широкого распространения. В его обсуждении 1 затрагиваются такие темы, как обратная совместимость и сравнение реализации изменений на уровне интерфейса контракта и пользовательского интерфейса. Обсуждение продолжается.

ERC777: предложе н ный стандарт интерфе йса для контракта токена Стандарт ERC777 2 - это еще одно предложение по улучшению стандарта кон­ трактов токенов. Оно ставит перед собой сразу несколько целей, включая сле­ дующие. - Предложить интерфейс, совместимый со стандартом ERC20. - Передавать токены с помощью функции s e n d, как это делается при передаче эфира. - Обеспечить совместимость со стандартом ERC820 для регистрации кон­ трактов токенов. - Позволить контрактам и адресам самим выбирать, какие токены они бу­ дут отправлять через функцию t o k e n s T o S e nd, которая вызывается пе­ ред отправкой. - Сделать возможным уведомление контрактов и адресов о получении токе­ на путем вызова функции t o ke n s Re c e i ved на стороне получателя; сни­ зить вероятность блокирования токенов внутри контрактов за счет требо­ вания реализации в контрактах функции t o ke n s Re c e i ved.

1

github.com/ethereum/EIPs/issues/223. - Прим. авт.

2

eips.ethereum.org/EIPS/eip-777. - Прим. авт.

338

Токены в Ethereum

- Позволить существующим контрактам использовать прокси-контракты для функций t o k e n s T o S e n d и t o ke n s Re c e i ved. - Унифицировать отправку средств контрактам и учетным записям ЕОА. - Обеспечить выполнение специальных событий для генерации и сжигания токенов. - Позволить операторам (доверенным третьим сторонам, которые выпол­ няют роль верифицированных контрактов) перемещать токены от име­ ни их держателя. - Предоставлять метаданные внутри транзакции по передаче токенов в по­ лях u s e r Data и ope r a t o r Da t a. Текущее обсуждение ERC777 можно найти на GitHub 1 • Спецификация интерфейса контрактов ERC777 выглядит так: interface ERC 7 7 7 To ken {

function name ( ) puЫic constant returns ( string ) ;

function s ymЬo l ( ) puЫic constant returns ( string ) ;

function t o t a l Suppl y ( ) puЬlic constant returns ( uint25 6 ) ; function granu l a r i t y ( ) puЬlic constant returns ( uint2 5 6 ) ;

function balanceOf ( address owner ) puЬlic constant returns (uint2 5 6 ) ; function send ( address t o , uint2 5 6 amount , bytes use rDat a ) puЬlic ; function autho r i z eOpe rat o r ( address ope r a t o r ) puЬlic ;

function revo keOpe r a t o r ( address ope r a to r ) puЬlic ;

function i s OperatorFo r ( address operat o r , address tokenHolde r ) puЬlic constant returns (bool ) ;

function ope ratorSend ( address from, address t o , uint2 5 6 amount ,

bytes u s e r Data , bytes ope ratorDa t a ) puЬlic ;

event Sent ( address indexed ope r a t o r , address indexed from,

address indexed to , uint2 5 6 amount , bytes u s e rData , bytes ope ratorData ) ;

event Minted ( address indexed ope rato r , address indexed to , uint2 5 6 amount , bytes ope ratorData ) ;

event Burned ( address indexed ope rato r , address indexed from, 1

github. com/ethereum/EIPs/issues/777. - Прим. авт.

Глава 10. Токены

33 9

uint25 6 amount , bytes us e rData , bytes ope ratorData ) ;

event Autho r i z edOpe rato r ( address indexed ope r a to r ,

address indexed t o kenHo l de r ) ;

event Revo kedOpe rator ( address indexed ope r a to r , address indexed

tokenHo l de r ) ;

Хуки в ERC777 Спецификация хука 1 отправителя токенов в ERC777: interface ERC 7 7 7 TokensSender (

function t oke n s ToSend ( address ope r a to r , address from, address to ,

ope ratorData ) puЫic ;

uint value , bytes use rDa t a , bytes

Реализация данного интерфейса взаимодействия требуется для любого адре­ са, в который включается возможность по получению уведомлений об отправ­ ке или озможность отмены отправки токенов. Адрес, для которого контракт реализует этот интерфейс (он может принадлежать как самому контракту, так и кому-то другому), должен быть зарегистрирован с помощью ERC820. Спецификация хука получателя токенов в ERC777: interface ERC 7 7 7 TokensRe c ipient function t o ken sRe c e i ved (

address ope r a to r , address from, address t o ,

uint amount , bytes u s e rDa t a , bytes ope ratorData

puЬlic ;

К получателю токенов применяется та же логика, что и к интерфейсу отпра­ вителя, но с одним ограничением: принимающие контракты обязаны реализо­ вать данный интерфейс взаимодействия, чтобы предотвратить блокирование токенов. Если принимающий контракт не зарегистрирует адрес, реализующий этот интерфейс, передача· токенов завершится неудачно. Важная деталь состоит в том, что для каждого адреса можно зарегистриро­ вать по одному отправителю и получателю токенов. Следовательно, при каждой 1

Англ.: hook. - Прим. ред.

340

Токены в Ethereum

отправке или получении токенов стандарта ERC777 вызываются одни и те же хук-функции. Чтобы менять поведение в зависимости от того или другого вида токенов, в этих функциях можно использовать адрес отправителя сообщения, который принадлежит контракту определенного токена. С другой стороны, одни и те же хуки отправителя и получателя токенов мож­ но зарегистрировать для нескольких адресов. В этом случае хуки могут распознать отправителя и предполагаемого получателя с помощью параметром f rom и t o. В предложении стандарта ERC777 прилагается эталонная реализация (см. ссылку '). Этот стандарт опирается на «параллельное» предложение реги­ стрирующего контракта (registry contract), детализируемое в стандарте ERC820. Обсуждение ERC777 частично посвящено сложности внедрения сразу двух крупных изменений: нового стандарта токенов и стандарта регистрации. Дис­ куссии продолжаются.

ERC721 : стандарт (актов) невзаимозаменяемых токенов Все стандарты, рассмотренные выше, относятся к взаимозаменяемым токенам, экземпляры которых идентичны. Стандарт ERC20 отслеживает только итого­ вый баланс каждой учетной записи и не содержит (явного) упоминания проис­ хождения токенов. Предложение использования ERC721 2 описывает стандарт невзаимозаменя­ емых токенов, которые еще называют актами (англ. deed) 3. Из оксфордского словаря: акт: юридический документ, который подписан и выдан, в особенности в отношении прав собственности или юридических прав.

Использование слова «акт» должно отражать часть определения, касающую­ ся «прав собственности», хотя такого рода токены не признаются юридическими документами ни в одной стране (пока). Возможно, в будущем права собственно­ сти на основе цифровых подписей на платформе блокчейна получат юридиче­ скую силу. Уникальные токены отслеживают владение какой-то невоспроизводимой ве­ щью. Эта вещь может быть виртуальной (как артефакт в игре или цифровой кол­ лекционный предмет) или физической, право владения которой отслеживается ' github.com/0xjac/ERC777/ЬloЬ/master/contracts/examples/ReferenceToken.sol. - Прим. авт. github.com/ethereum/EIPs/ЫoЬ/master/EIPS/eip-721. md. - Прим. авт.

2

3

Или dееd-токены. - Прим. ред.

Глава 10. Токены

341

с помощью токена (например, дом, машина или произведение искусства). Акты могут также олицетворять вещи с отрицательной ценностью, такие как ссуда (долг), залог, �ервитут и т. д. Стандарт ERC721 не накладывает никаких ограни­ чений и не имеет никаких ожиданий относительно природы вещей, владение ко­ торыми отслеживается с помощью акта; он лишь требует, чтобы их можно было однозначно идентифицировать, что в данном случае достигается за счет 256-бит­ ного идентификатора. Подробности о стандарте и его обсуждение можно найти в двух разных ме­ стах на Github: - Изначальное предложение 1 • - Продолжение обсуждения (предложения) 2 • Чтобы получить представление об основных отличиях между стандартами ERC20 и ERC721, достаточно взглянуть на внутреннюю структуру данных, ко­ торая используется в ERC721: // Привязка ID а кта к владельцу

mappinq ( uint2 5 6 => address ) private deedOwne r ;

ERC20 отслеживает балансы, принадлежащие каждому владельцу; при этом владелец выступает первичным ключом в ассоциативном массиве. Для сравне­ ния: ERC721 отслеживает идентификатор акта, который используется в качестве первичного ключа в ассоциативном массиве, и то, кто им владеет. Из этого эле­ ментарного отличия вытекают все характеристики уникальных токенов. Спецификация интерфейса контрактов ERC721: interface ERC 7 2 1 / * он же ERC721 */ { event Trans f e r ( address indexed

from, address indexed

event Approva l ( address indexed

owne r , address indexed _approved ,

uint25 6

deed i d ) ;

uint2 56

deed i d ) ;

event Approva l Fo rAl l ( addres s indexed ope rat o r , bool

function b a l anceOf ( addres s

owne r ) external view returns

' github.com/ethereum/EIPs/issues/721. - Прим. авт. github.com/ethereum/EIPs/pull/841. - Прим. авт.

342

Токены в Ethereum

owne r , address indexed

approved ) ;

( uint2 5 6 _ba l ance ) ;

2

to ,

function own e r0 f ( uint2 5 6 _de e d i d ) external view returns ( address

owne r ) ;

function trans fe r ( address

to , uint2 5 6 _deed i d ) external рауаЫе ;

function trans f e r From ( address external рауаЫе ;

from, address

function approve ( addres s _approve d , uint2 5 6 рауаЫе ;

t o , uint2 5 6

dee d i d )

dee d i d ) external

function se tApprova l F o rAl l ( address _ope rateo r , boolean _approve d ) рауаЫе ;

function s uppo r t s i nt e r face (bytes4 i n t e r face I D ) external view returns (bool ) ;

ERC721 поддерживает два дополнительных интерфейса: один для метадан­ ных, а другой - для перечисления актов и владельцев. Дополнительный интерфейс для метаданных ERC721: interface ERC 7 2 1Metadata / * он же ERC 72 1 */ {

function name ( ) external pure returns ( string _name ) ; function s ymbo l ( ) external pure returns ( s tring

s ymЬol ) ;

function deedUr i ( uint2 5 6 _deedi d ) external view returns ( s tring

deedUri ) ;

Дополнительный интерфейс для перечислений в ERC721: interface ERC 7 2 1 En ume r aЬ l e / * он же ERC 72 1 * / {

function t o t a l Supp l y ( ) external view returns ( uint2 5 6 function deedB y i n de x ( uint2 5 6 ( uint2 5 6 _dee di d ) ;

coun t ) ;

i ndex ) external view returns

function countO fOwne r s ( ) external view returns ( uint2 5 6

count ) ;

function owne r B y i ndex ( uint2 5 6 ( addres s _owne r ) ;

i ndex ) external view returns

function deedO fOwne r B y i ndex ( addres s _owne r , uint2 5 6 external view

returns ( uint2 5 6

i ndex )

dee d i d ) ;

Глава 10 . Токены

343

И спол ьзование стандартов токенов В предыдущем разделе сделан краткий обзор нескольких предложений и двух широко распространенных стандартов для контрактов токенов. Но для чего именно они нужны? Стоит ли их использовать и если да, то как? Следует ли добавлять возможности, которые в этих стандартах не предусмотрены? Какие из них лучше выбрать? На некоторые из этих вопросов мы попробуем ответить ниже.

Что такое стандарты токенов ? Каково их наз начение ? Стандарт токена - это минимальная спецификация для его реализации. Это означает, что для совместимости, скажем, с ERC20 вам необходимо реализовать как минимум те функции и то поведение, которые описаны в стандарте ERC20. Вы также можете добавить другие возможности, реализовав функции, которые не входят в этот стандарт. Основное назначение этих стандартов заключается в поощрении совмести­ мости между контрактами. Таким образом, все кошельки, биржи, пользователь­ ские интерфейсы и другие инфраструктурные компоненты могут предсказуемо взаимодействовать с любым контрактом, соблюдающим спецификацию стан дарта. Иными словами, если развернуть контракт, который следует стандарту ERC20, пользователи всех существующих кошельков смогут сразу же присту­ пить к торговле вашим токеном без каких-либо обновлений ПО кошелька или усилий с вашей стороны. Стандарты должны быть описательными, а не предписывающими. Вы сами вольны выбирать то, как именно будут реализованы эти функции - внутрен няя работа контракта не имеет отношения к стандарту. К контрактам могут предъявляться определенные функциональные требования, определяющие по­ ведение в тех или иных условиях, но выбор реализации остается за вами. При­ мером этого является поведение функции t r an s f e r, когда значение равно нолю.

Стоит ли испол ьзовать эти стандарты ? Каждый разработчик сталкивается с дилеммой, выбором между использовани­ ем существующих стандартов и введением инноваций, которые выходят за рам­ ки накладываемых ими ограничений. Эту дилемму не так просто решить. Стандарты неизбежно ограничивают вашу способность к инновациям, предоставляя вам узкий коридор, по которому 344

Использование стандартов токенов

вы должны двигаться. С другой стороны, основные стандарты стали результа­ том опыта разработки сотен приложений, и они отлично вписываются в подав­ ляющее большинство сценариев. Вместе с этим возникает еще более важный вопрос: какова выгода от совме­ стимости и широкого распространения? Выбрав существующий стандарт, вы получите доступ ко всем системам, которые умеют с ним работать. При откло­ нении от стандарта следует учитывать затраты на самостоятельную разработку всей вспомогательной инфраструктуры или на то, чтобы другие начали поддер­ живать вашу реализацию в качестве нового стандарта. Тенденция прокладывать собственный путь и игнорировать существующие стандарты известна как «син­ дром неприятия чужой разработки» 1 и прямо противоречит культуре открытого ПО. С другой стороны, чтобы сопутствовать прогрессу и развитию инновации, нам иногда приходится отходить от традиций. Это непростой выбор, требую­ щий тщательного взвешивания! Согласно «Википедии», «синдром неприятия чужой разработ­ ки» -это позиция в социальной, корпоративной или органи­ зационной культурах, при которой избегается использование или покупка уже существующих разработок, исследований, стандартов или знаний из-за их внешнего происхождения и необходимости затрат, таких как «роялти».

Безопасность, основанная на з релости Помимо выбора стандарта существует также параллельный выбор реализации. Остановившись на такой спецификации, как ERC20, вы должны решить, как бу­ дет реализована совместимая с ней архитектура. Существует ряд «эталонных» реализаций, которые широко используются в экосистеме Ethereum, но вы так­ же можете написать собственный аналог с нуля. Опять ж�, этот выбор олице­ творяет собой дилемму, которая может иметь серьезные последствия для рис­ ков безопасности. Существующие реализации «проверены в бою». Хотя их безопасность нельзя формально доказать, многие из них подкреплены стоимостью токенов в размере нескольких миллионов долларов. Они подвергались многократным и мощным атакам, но пока у них не было обнаружено никаких существенных уязвимостей. Написание собственной реализации является непростой задачей, ведь кон тракт может быть взломан с помощью множества неочевидных способов атак. 1

"Not Invented Here" syndrome; ru.wikipedia.orglwiki!Cuндpoм_нenpuяmuя_чужой_разработки

Глава 10. Токены

345

Намного безопаснее использовать хорошо проверенные и широко использу­ емые решения. В наших примерах мы применяем реализацию стандарта ERC20 от OpenZeppelin, так как она с самого начала была сосредоточена на обеспече­ нии принципа безопасности. Если вы используете готовую реализацию, вы можете ее расширить. Но, опять же, будьте осторожны. Сложность - враг безопасности. Каждая строчка кода, которую вы добавляете, увеличивает поверхность атаки на ваш контракт и может породить уязвимость, которая в какой-то момент себя про­ явит. Проблема может оставаться незамеченной вами до тех пор, пока контракт не получит достаточно много средств и кто-то его не взломает. Выбор стандартов и реализаций является важным, но не един­ ственным аспектом обеспечения безопасности смарт­ контрактов. См. главу 9.

Расширение стандарто в интерфейса то кенов Стандарты токенов, обсуждаемые в этой главе, предоставляют минимальный интерфейс с ограниченными возможностями. Многие проекты создали их рас­ ширенные реализации, чтобы получить функции, необходимые в их приложе­ ниях. Некоторые из этих функций перечислены ниже. - Управление владельцами. Способность выдавать определенным адресам или их наборам (в случае с множественными подписями) специальные возможности, такие как занесение в черный (и белый) список, минтинr, восстановление и т. д. - Сжигание (burning). Возможность преднамеренно уничтожать «сжигать)) токены путем отправки их по адресу, который не сможет их потратить, или удаления баланса и уменьшения запасов. - Минтинr (minting, генерация новых токенов). Возможность увеличения общего количеств токенов с предсказуемой скоростью или за счет фиду­ циарных денег создателя токена. - Краудфандинr. Возможность выставления токенов на продажу - напри­ мер, через аукцион, рыночный сбыт, реверсивный аукцион и т. д. - Ограничения. Возможность установить заранее определенные и неизме­ няемые лимиты на общий запас (как генерация новых токенов, только на­ оборот). 346

Расширение стандартов интерфейса токенов

- Функции восстановления (с помощью backdoors). Функции, позволяющие восстановить денежные средства, отменить переводы или стереть токен. Могут быть активированы с заданного адреса или набора адресов. - Занесение в белый список (whitelisting). Возможность делать действия (та­ кие как передача токенов) доступными только для определенных адресов. Чаще всего используется для предоставления токенов «аккредитованным инвесторам» после согласования с правилами разных юрисдикций. Обыч­ но предусматривается механизм для обновления белого списка. - Занесение в черный список (Ьlacklisting). Возможность ограничить пере­ дачу токенов путем запрета определенных адресов. Для обновления чер­ ных списков обычно есть отдельная функция. У многих из этих функций есть эталонные реализации - например, в биб­ лиотеке OpenZeppelin. Некоторые из них предназначены лишь для определен­ ных сценариев и реализованы всего в нескольких токенах. На сегодня у интер­ фейсов к этим функциям нет общепринятых стандартов. Как уже упоминалось ранее, решение по добавлению в стандарт токена но­ вой функциональности представляет компромисс между инновациями - рис­ ками и совместимостью - безопасностью.

То кены и процедура выпуска токенов ( ICO) Появление токенов стало по-настоящему громким событием в экосистеме Ethereum. Вполне вероятно, что они станут очень важными компонентами всех аналогичных платформ с поддержкой смарт-контрактов. Тем не менее важность и будущее влияние этих стандартов не следует прини­ мать за одобрение текущих предложений токенов (token offerings). Как и любые другие технологии, находящиеся на раннем этапе развития, почти все продукты и компании первой волны потерпят неудачу, а некоторые эффектно провалятся. Многие токены, которые на сегодня предлагаются на платформе Ethereum, явля­ ются едва замаскированными скамом (мошенничеством), финансовыми пира­ мидами и недобросовестным сбором денег 1 • Главное (для нас) - отделить долгосрочное видение и влияние данный тех­ нологии, которое, вероятно, будет огромным, от краткосрочного пузыря про­ дажи токенов в помощью процедуры ICO, изобилующих аферами. Стандарты 1

Англ. scams, pyramid schemes, money grabs.

Глава 10. То кены

347

токенов и сама платформа переживут текущую токен-манию и, скорее всего, они изменят мир.

Выводы В Ethereum токены являются очень мощной концепцией, которая может стать фундаментом для реализации множества важных децентрализованных прило­ жений. В этой главе мы рассмотрели разные типы токенов и их стандарты. Вы создали свой первый токен и сопутствующее приложение для него. Мы еще вер­ немся к этой теме в главе 12, в которой невзаимозаменяемый токен будет реали­ зован в DАрр-приложении с аукционом.

ГЛАВА 1 1

Оракул ы Эта глава посвящена оракулам 1 - системам, которые могут предоставлять внешние источники данных для смарт-контрактов на Ethereum. Термин «оракул» про­ исходит из греческой мифологии и обозначает человека, который общается с бо­ гами и может видеть будущее. В контексте блокчейна оракул является системой, способной отвечать на вопросы, выходящие за рамки Ethereum. В идеале оракул не должен требовать никакого доверия, поскольку он работает в соответствии с децентрализованными принципами.

Зачем нужны оракулы ? Ключевым компонентом платформы Ethereum является виртуальная машина EVM, ограниченная правилами консенсуса, ее способностью выполнять про­ граммы и обновлять состояние блокчейна на любой из нод децентрализованной сети. Чтобы поддерживать консенсус, работа EVM должна быть полностью де­ терминистической и основываться только на разделяемом между всеми участ­ никами состоянии Ethereum и подписанных транзакций. Это имеет два важных последствия: во-первых, отсутствие внутреннего источника энтропии, с кото­ рым могли бы работать смарт-контракты и виртуальная машина; во-вторых, внешние данные можно вводить только в виде полезной нагрузки (data payload) в транзакциях. Давайте проанализируем эти два последствия. Чтобы понять, почему в EVM запрещены по-настоящему случайные функции, предоставляющиеэнтропию для смарт-контрактов, попробуем представить, чем бы закончилась попытка дости­ жения консенсуса после выполнения такой функции. Допустим, нода А выпол­ няет команду и сохраняет в своем хранилище число 3 от имени смарт-контракта; вместе с тем нода Б выполняет тот же смарт-контракт и сохраняет вместо это­ го 7. Таким образом ноды А и Б приходят к разным выводам о том, каким дол­ жно быть итоговое состояние, хотя оба они выполнили одинаковый код в одном и том же контексте. Таким образом, при каждом выполнении смарт-контракт 1 Англ.: oracles. - Прим. ред.

Глава 1 1 . Оракулы

349

может выдавать разные результаты. Следовательно, у сети с множеством неза­ висимых нод, разбросанных по всему миру, нет возможности прийти к децен­ трализованному консенсусу относительно итогового состояния. На практике экспоненциальное накопление транзакций с эфиром очень быстро привело бы к значительно худшим последствиям по принципу домино. Заметьте, что для многих приложений недостаточно псевдослучайных функ­ ций, как те, что занимаются криптоrрафически безопасным хешированием (они являются детерминистическими и, следовательно, могут быть частью EVM). Возьмем, к примеру, азартную игру, которая симулирует подбрасывание монеты и делает выплаты согласно ставкам; она должна сделать так, чтобы орел и решка выпадали случайно. В этом случае майнер, участвующий в игре, может получить преимущество, включая свои транзакции только в те блоки, которые станут вы­ игрышными. Как же обойти эту проблему? Все поды могут согласовать содер­ жимое подписанных транзакций, поэтому внешнюю информацию, включая ис­ точники энтропии, сведения о цене, прогнозы погоды и т. д., мо;жно оформить в виде данных транзакций, отправляемых в сеть. Однако таким данным попро­ сту нельзя доверять, поскольку они получены из источников, которые невоз­ можно проверить. Следовательно, мы лишь отсрочили проблему. В качестве ре­ шения попытаемся воспользоваться оракулами, которым посвящена оставшаяся часть этой главы.

Примеры оракулов и и х использование В идеале оракулы предоставляют способ получения внешней (то есть «реаль­ ной», off-chain, находящейся за пределами блокчейна) информации, такой как результаты футбольных матчей, цена золота или по-настоящему случайные числа. Эта информация предназначена для использования смарт-контрактами на платформе Ethereum и не требует (или почти не требует) к себе никакого до­ верия. Оракулы также могут применяться для безопасной и прямой передачи данных клиентской стороне DАрр-приложений. Стоит рассматривать оракулы в качестве механизма, который служит мостом между реальным (off-chain) ми­ ром и смарт-контрактами. Область применения смарт-контрактов существенно расширяется, если позволить им использовать условия на основе событий (из) реального мира и данных. Вместе с тем оракулы привносят внешние риски в мо­ дель безопасности Ethereum. Представьте себе контракт с «умным завещанием», который распределяет активы в случае смерти человека. Этот пример часто об­ суждается в среде разработчиков смарт-контрактов и подчеркивает риски, ко­ торые несут в себе доверенные оракулы. Если наследство, контролируемое этим 350

Примеры оракулов и их использование

контрактом, имеет достаточно высокую ценность, у злоумышленника появляет­ ся очень мощный стимул для взлома оракула и инициирования распределения активов до смерти его владельца. Стоит отметить, что некоторые оракулы предоставляют данные из опреде­ ленных закрытых источников - например, академические сертификаты или правительственные удостоверения. Источник таких данных (университет или министерство) является полностью доверенным, а сами данные - субъектив­ ными (истина определяется только путем обращения к стороне, ответственной за источник). Следовательно, такую информацию нельзя предоставлять без до­ верия (то есть, не доверяя источнику), так как объективной истины, которую можно было бы проверить независимым образом, не существует. Таким обра­ зом, подобные источники данных подходят под наше определение «оракулов», так как они тоже могут служить мостом данных для смарт-контрактов. Данные, которые они предоставляют, обычно принимают форму сющетельств, таких как паспорта или записи о наградах. В будущем подобные цифровые свидетельства станут одной из главных причин успеха блокчейн-платформ, особенно в контек­ сте таких родственных проблем как (проверка) идентификация личности или репутации 1 • Поэтому важно исследовать то, как они могут быть предоставлены с помощью блокчейн-платформ. Ниже перечислены примеры некоторых данных, которые можно сделать до­ ступными с помощью оракулов. - Случайные числа/энтропия из физических источников, таких как кван­ товые/тепловые процессы: например, чтобы честно выбирать победителя лотереи с помощью смарт-контракта. - Параметрические триггеры со стихийными бедствиями в качестве ин­ дексов: например, которые инициируют смарт-контракты с гарантиями 2 на случай катастрофы, такими как измерения по шкале Рихтера для гаран­ тий, связанных с землетрясениями. - Данные об обменных курсах: например, для точной привязки стоимости криптовалют к фиатным валютам. - Данные с рынков капитала: например, вычисление цен для потребитель­ ских корзин токенизированных активов или ценных бумаг. - Значения контрольных показателей: например, внедрение процентных ставок в «умные» финансовые деривативы. 1

Англ.: verifying identity or reputation. - Прим. ред.

2

Англ.: bonds. - Прим. ред.

Глава 1 1 . Оракулы

351

- Статические/псевдостатические данные: идентификаторы безопасности, коды стран, коды валют и т. д. - Данные о времени и интервалах: для триггеров событий, основанных на точных измерениях времени. - Данные о погоде: например, подсчет страховых выплат в зависимости от прогнозов погоды. - Политические события: для предсказания поведения рынка. - Спортивные события: для предсказания поведения рынка и контрактов для фэнтези-спорта 1 • - Геолокационные данные: например, те, которые используются при отсле­ живании цепочки поставок. - Проверка ущерба: для страховых контрактов. - События, возникающие в других блокчейнах: функции интероперабильности (совместимости). - Рыночная цена эфира: например, для оракулов, предоставляющих цену на газ в фиатной валюте. - Статистика авиаперелетов: например, может использоваться сообщества­ ми и клубами для коллективного выкупа авиабилетов. В следующих разделах мы исследуем некоторые способы реализации ораку­ лов, включая базовые шаблоны проектирования, вычислительные и децентра­ лизованные оракулы, а также реализации клиентов на языке Solidity.

Ш аблоны про е ктиро вани я оракуло в Любой оракул по определению предоставляет несколько ключевых функций, включая возможность: - собирать данные извне блокчейна (off-chain); - передавать данные внутри блокчейна с помощью подписанных сообщений; - делать данные доступными за счет размещения их в хранилище смарт­ контракта. 1

Вид игр, в которых участники собирают воображаемые или виртуальные спортивные команды, состоящие из спортсменов, имеющих реальные прототипы, чьи статистические результаты используются для подсчета результатов игр. - Прим. ред.

352

Шаблоны проектирования оракулов

Как только данные попадают в хранилище смарт-контракта, другие смарт­ контракты могут обращаться к ним с помощью сообщений, вызывающих функ­ цию «извлечения» (retrieve function) из контракта оракула; они также становят­ ся доступны подам Ethereum и клиентам, подключенным к сети, которые могут напрямую «вглянуть на» хранилище оракула. Все оракулы можно разделить на три основные категории по принципу их рабо­ ты: «запрос - ответ», «издатель - подписчик» и «немедленное(вопрос) - чтение» 1 • Начнем с самых простых оракулов, основанных на принципе (mемедленное(во­ прос) - чтение». Они предоставляют данные, которые необходимы только для принятия незамедлительного решения. Например, ((Какой адрес у ethereumbook. info?» или ((Является ли этот человек совершеннолетним!» Те, кому нужны подоб­ ного рода данные, обычно запрашивают их прямо перед использованием и, воз­ можно, больше к ним не возвращаются. Такие оракулы могут предоставлять дан­ ные об организациях (или данные, выданные этими организациями), такие как учебные сертификаты, телефонные коды, официальное членство в институтах, идентификаторы аэропортов, суверенные удостоверения личности 2 и т. д. Ин­ формация помещается в хранилище контракта оракула, откуда ее могут достать другие смарт-контракты при помощи вызова. Она также может обновляться. Со­ держимое хранилища оракула для прямых обращений со стороны приложений с поддержкой блокчейна (например, подключенных к клиенту Ethereum); для это­ го не нужно ничего согласовывать или платить за газ, как в случае с транзакция­ ми. Такой способ использования оракулов подходит для магазина, который желает проверить возраст покупателя, приобретающего алкогольный напиток. Он также подходит для организаций или компаний, которым самостоятельно пришлось бы заниматься размещением и обслуживанием серверов, отвечающих на подобные запросы. Следует отметить, что оракул, скорее всего, не будет хранить все данные, которые он выдает (например, для повышения эффективности его работы или со­ блюдения конфиденциальности). Университет может подготовить сервис ораку­ ла для подтверждения дипломов и успеваемости выпускников. Однако хранение всего содержимого этих документов (возможно, с длинным перечнем всех окон­ ченных курсов и полученных оценок) было бы излишним; достаточно ограни­ читься их хешами. Та же ситуация может возникнуть, если правительство захочет разместить удостоверения личности граждан на платформе Ethereum. Очевидно, что подробности должны быть конфиденциальными. Опять же, для эффективной организации такой услуги в хранилище смарт-контракта можно поместить лишь корневой хеш данных (деликатно, используя деревья Меркла с солью). 1

Англ. ( 1 ) request-response, (2) puЬlish-subscribe, (3) immediate-read. - Прим. ред. 2 Англ.: self-sovereign IDs. - Прим. ред.

Глава 11. Оракулы

Ш

Следующий вариант оракула реализует принцип «издатель - подписчик», в сущности, он предоставляет сервис распространения данных, которые, как ожи­ дается, могут меняться (возможно, регулярно и часто), где обновления информа­ ции могут периодически проверяться смарт-контрактом (on-chain) на блокчейне или отслеживаться внесетевым (off-chain) актором. Этот подход похож на шаблон проектирования, который используется в RSS-потоках, WebSub и подобных меха­ низмах, где оракул получает новую информацию и сигнализирует 1 о ее доступно­ сти тем, кто является «подписчиками». Заинтересованные стороны должны либо периодически обращаться к оракулу, проверяя наличие изменений, либо отслежи­ вать и реагировать на обновления контрактов оракула. В качестве примера мож­ но привести котировки цен, метеорологические данные, экономическую или со­ циальную статистику, данные о дорожном движении и т. д. В мире веб-серверов периодические проверки являются очень неэффективными, но в контексте пи­ ринговых блокчейн-платформ они вполне уместны: клиенты Ethereum должны поддерживать все изменения состояний, включая обновления хранилища кон­ тракта, поэтому проверка данных на изменения представляет собой локальный вызов к синхронизирующему клиенту. Особенно упрощают поиск обновлений оракула логи событий Ethereum, поэтому такой шаблон проектирования можно считать своего рода рush-сервисом. Однако если запросы изменений направляет смарт-контракт (что может быть необходимо в некоторых децентрализованных приложениях (в которых, к примеру, исключена возможность активации поощре­ ния), то это может существенно повысить расходы на газ. Самой сложной является категория оракулов, работающих по принципу «за­ прос - ответ»: где объемы хранения данных слишком большие, чтобы хранить их в смарт-контракте; к тому же предполагается, что пользователям в любой мо­ мент может понадобиться лишь небольшая их часть от общего набора данных. Эта модель также подходит для компаний, которые занимаются поставкой дан­ ных. На практике такой оракул можно реализовать в виде системы (on-chain) смарт-контрактов на блокчейне и внесетевой (off-chain) инфраструктуры для мониторинга запросов, извлечения и возврата данных. Запрос данных из де­ централизованного приложения обычно выполняется асинхронно и состоит из нескольких этапов. На первой стадии учетная запись ЕОА общается с децен­ трализованным приложением, в результате чего вызывается функция, опреде­ ленная в смарт-контракте оракула. Эта функция инициирует запрос к оракулу, предоставляя аргументы, описывающие запрашиваемые данные вместе с до­ полнительной информацией, которая может включать в себя функции обрат­ ного вызова и параметры расписания. Когда эта транзакция пройдет проверку, 1

Получает «сигналы с флагом» (англ. flag signals). - Прим. ред.

354

Шаблоны проектирования оракулов

запрос к оракулу можно будет наблюдать в виде события EVM, сгенерирован­ ного соответствующим контрактом, или изменения состояния; аргументы могут быть извлечены и использованы для обращения к внешнему (off-chain) источ­ нику данных. Оракул также может потребовать плату для обработки запроса, оплаты газа для выполнения функции обратного вызова и получения прав до­ ступа для работы с данными. Наконец, владелец оракула подписывает итого­ вую информацию, подтверждая ее подлинность в текущий момент, после чего она передается в виде транзакции децентрализованному приложению, которое выполнило данный запрос (либо напрямую, либо через контракт оракула). В за­ висимости от параметров расписания оракул может регулярно распространять дополнительные транзакции, осуществляя обновление данных (например, со­ бирая информацию о котировках цен в конце дня). Ниже приводятся шаги, которые предпринимает оракул, работающих по прин­ ципу запрос-ответ. 1. 2. 3. 4.

Получаем запрос от DАрр-приложения. Делаем парсинг (анализ) запроса. Проверяем наличие платежа и предоставление прав доступа к данным. Извлекаем подходящие данные из внешнего (off-chain) источника (и шифруем их при необходимости). 5. Подписываем транзакцию(-и) с включенными в нее данными. 6. Передаем транзакцию(-и) в сеть. 7. Помещаем в расписание любые необходимые в дальнейшем транзакции, такие как уведомления и т. д.

Возможен также ряд других схем реализации (шагов); например, данные можно запрашивать и получать непосредственно из учетной записи ЕОА, из­ бавляясь от необходимости в смарт-контракте оракула. Точно так же запросами и ответами можно обмениваться с аппаратным IоТ-датчиком 1 • Таким образом, роль оракула может играть человек, программное или аппаратное обеспечение. Описанный здесь шаблон «запрос - ответ>► часто встречается в клиент­ серверных архитектурах. Он хорошо подходит для обмена сообщениями, позво­ ляя приложениям поддерживать двунаправленное взаимодействие, но в опреде­ ленных ситуациях его лучше не применять. Например, если «умная гарантия» основана на запросах - ответах и должна получать процентные ставки, то ора­ кулу, вероятно, придется выполнять ежедневные запросы, чтобы данные всегда 1

Аппаратным датчиком Интернета вещей, англ. lпterпet ofThings-enaЫed hardware sensor. -

Прим. ред.

Глава 1 1 . Оракулы

355

были в актуальном состоянии. У читывая то, что процентные ставки меняют­ ся редко, здесь более уместным может быть использование типа шаблон «изда­ тель - подписчик» - в особенности если принимать во внимание ограничен­ ную пропускную способность блокчейн-сети Ethereum. Согласно данному шаблону «издатель - подписчик», издатели (в данном слу­ чае оракулы) не шлют сообщения получателям напрямую; вместо этого сообще­ ния категорируются на отдельные классы. Подписчики могут выразить свою заин­ тересованность к одному или нескольким классам, извлекая только те сообщения, которые их интересуют. В этом случае оракул может записывать процентную став­ ку в свое внутреннее хранилище каждый раз, когда та изменяется. Подписавшие­ ся DАрр-приложения могут просто считывать ее значения из контракта оракула, снижая тем самым нагрузку на сеть и минимизируя затраты на хранение. Если использовать шаблон трансляции (или распространения, или много­ адресного сообщений), то оракул может публиковать все сообщения в канал, ко­ торый просматривается подписанными на него контрактами в разнообразных режимах (подписки). Например, оракул может публиковать сообщения в канал, выделенный криптовалютной биржей для курсов валют. Подписанный на него смарт-контракт может запросить его полное содержимое, если ему нужен вре­ менной ряд для, скажем, вычисления скользящего среднего значения; а другому контракту, например, могут понадобиться только последние курсы для вычис­ ления спотовой цены 1 • Шаблон трансляции подходит для ситуаций, когда ора­ кулу не нужно знать, какие именно контракты на него подписаны.

Подлинность данн ых Если предположить, что исходные данные, запрашиваемые DАрр-приложением, являются одновременно надежными и заслуживающими доверия (серьезное допу­ щение), без ответа остается следующий вопрос: как мы можем доверять механиз­ му типа «запрос - ответ», если он может функционировать отдельно от оракула? Вполне существует возможность подменены данных в процессе их передачи, поэто­ му очень важно, чтобы их целостность можно было подтвердить за пределами блок­ чейна (то есть off-chain). Существует два распространенных метода аутентификации данньп:2: доказательства подлинности3 и доверенная среда выполнения (сокр. ТЕЕ) 4. 1

Цена, по которой продается реальный товар, ценные бумаги или валюта в данное время и в данном месте на условиях немедленной поставки. - Прим. ред.

2

Проверки подлинности данных. - Прим. ред.

3

Англ.: authenticity proofs. - Прим. ред.

4

Англ.: trusted execution environment. - Прим. ред.

356

Подлинность данных

Доказательства подлинности - это криптографические гарантии того, что данные не были искажены или подделаны. Они основаны на разнообразных методиках подтверждения (таких как доказательство, подписанное цифровой подписью), которые, в сущности, делегируют доверие не механизму передачи данных, а тому, кто их удостоверяет (то есть подтверждающему провайдеру). Проверяя подлинность информации внутри блокчейна, смарт-контракты мо­ гут удостовериться в их целостности перед тем, как начинать с ними работать. В качестве примера сервиса, использующего разнообразные доказательства под­ линности, можно привести проект Oraclize 1 • Одно из таких доказательств, кото­ рое в настоящий момент доступно для данных, запрашиваемых из главной сети Ethereum, называется TLSNotary. Оно позволяет предоставить третьей стороне свидетельство о том, что между клиентом и сервером был передан веб-трафик по HT TPS. Протокол HT TPS сам по себе является безопасным, но он не умеет подписывать данные. В итоге доказательства такого типа полагаются на дока­ зательства подписи TLSNotary (создаваемые с помощью PageSigner). TLSNotary использует TLS протокол 2 с поддержкой мастер-ключа, который подписывает данные, доступ к которым был произведен. Доказательство распределяется ме­ жду тремя участниками процесса: сервером (оракулом), а также проверяемой (на стороне Oraclize) и проверяющей сторонами. В качестве аудитора Oraclize использует экземпляр виртуальной машины Amazon Web Services (сокр. AWS), неизменность которого с момента установки можно подтвердить. Этот экзем­ пляр AWS хранит секретный ключ TLSNotary, позволяя предоставить доказа­ тельства правдивости. Этот подход дает более надежную защиту от подмены данных по сравнению с механизмом типа «запрос - ответ», однако при этом он полагается на допущение о том, что сама компания Amazon не станет изменять экземпляр собственной виртуальной машины. Town Crier 3 - это система оракулов с потоком проверенных на подлинность данных, основанная на подходе Т ЕЕ; в этом случае для обеспечения гарантий целостности данных используются аппаратные анклавы безопасности. Town Crier применяет технологию Software Guard eXtensions (сокр. SGX) от Intel, что­ бы убедиться в подлинности ответов на НТ Т Р-запросы. SGX предоставляет га­ рантии целостности, защищая приложения внутри анклава от искажения со сто­ роны любых друrих процессов на уровне CPU 4. Этот механизм также способен 1 2

www.oraclize. it. - Прим. авт. Протокол защиты транспортного уровня TLS ( от англ. Traпsport Layer Security). - Прим. ред.

www. town-crier.org. - Прим. авт. 4 CPU (ceпtral processing unit), центральное обрабатывающее устройство (процессор) - элек­ тронный блок либо интегральная схема, исполняющая код программы. - Прим. ред. 3

Глава 1 1 . Оракулы

357

обеспечить конфиденциальность, делая состояние приложения, запущенного в анклаве, недоступным для выполнения других процессов. Наконец, SGX позво­ ляет выдавать свидетельства, генерируя подписанное доказательство того, что приложение (безопасно идентифицируемо по хешу его создания) действительно выполняется в рамках анклава. Проверив эту цифровую подпись, децентрализо­ ванное приложение может доказать, что экземпляр Town Crier находится в без­ опасности внутри анклава SGX. Это в свою очередь доказывает, что экземпляр Town Crier не был подделан и что сгенерированные им данные являются подлин­ ными. Благодаря поддержке конфиденциальности Town Crier может также рабо­ тать с частными данными, позволяя шифровать запросы с помощью публичного ключа. Поскольку механизм запросов/ответов оракула работает внутри анклава, такого как SGX, мы можем считать, что он безопасно выполняется на доверенном оборудовании третьей стороны и гарантирует, что запрашиваемые данные воз­ вращаются в подлинном виде (при условии, что мы доверяем Intel/SGX).

Вычислитель н ые оракулы 1 До этого момента мы обсуждали оракулы только в контексте запрашивания и доставки данных. Но оракулы можно использовать для выполнения произ­ вольных вычислений; это особенно полезно для платформы Ethereum с ее лими­ том на газ для каждого блока и относительно высокой ценой вычислений. Вместо того чтобы полностью полагаться на результаты запроса, оракулы могут произ­ водить вычисления с набором входящих данных и возвращать результат, кото­ рый было бы нецелесообразно рассчитывать внутри блокчейна. Например, с по­ мощью такого оракула можно было бы рассчитать сложную регрессию, чтобы оценить доходность контракта гарантии. Если вы готовы доверять централизованному, но аудируемому сервису, то можете снова воспользоваться системой ораклов - Oraclize. Сервис, кото­ рый она предоставляет, позволяет децентрализованным приложениям запра­ шивать результаты вычислений, выполненных на изолированн·ой виртуальной машине AWS. Экземпляр AWS берет сконфигурированный пользователем файл Dockerfile, запакованный в архив и загруженный в файловую систему IPFS 2 (см. раздел «Хранилище данных» на с. 372), и генерирует из него исполняемый кон­ тейнер. Во время запроса Oraclize извлекает этот архив с помощью его хеша, 1

Англ.: computation oracles. - Прим. ред. 2 Inter-Planetary File System (межпланетная файловая система) - контентно-адресуемый, од­ норанrовый rипермедийный протокол связи. - Прим. ред.

358

В ы числительные оракулы

после чего инициализирует и выполняет контейнер Docker в AWS, передавая приложению любые предоставленные параметры в виде переменных окруже­ ния. Находящееся в контейнере приложение производит вычисления с ограни­ чением по времени и записывает результат в стандартный вывод (output), отку­ да его сможет получить Oraclize и вернуть децентрализованному приложению. На сегодня Oraclize предлагает эту услугу в виде сервера AWS типа t2.micro, по­ этому, если вычисления имеют какую -то особую ценность, вы можете убедить­ ся в том, что был выполнен нужный вам контейнер Docker. Тем не менее это ре­ шение нельзя назвать по-настоящему децентрализованным. Концепция криптлета (англ. cryptlet) как стандарта проверяемых истин ора­ кула 1 была формализована в рамках более общего фреймворка ESC компании Microsoft. Криптлеты выполняются внутри зашифрованной капсулы, которая абстрагирует инфраструктуру, такую как ввод/вывод, и подключена к серви­ су CryptoDelegate; благодаря этому все входящие и исходящие сообщения ав­ томатически подписываются, проверяются и подтверждаются на подлинность. Криптлеты поддерживают распределенные транзакции, поэтому логика кон­ тракта может комплексной, мультиэтапной, мультисетевой (выполняться в раз­ ных блокчейнах. - Ред. ) и включать транзакции внешней системы на ACID май­ нере. Это позволяет разработчикам создавать переносимые, изолированные и приватные заключения об истинности для применения в смарт-контрактах. Криптлеты имеют следующий формат: puЬlic c l a s s Samp l eContractC rypt l e t : C r ypt l e t puЬlic Samp l eCont r a c t C r ypt l e t ( Gu i d i d , Guid b i nding i d , s tring

name , string address , I Conta i n e r S e r v i c e s hostCont a i ne r , bool contract ) base ( id , binding i d , name , address , hostCon t a i ne r , contract )

Me s s ageAp i = new C r yp t l etMe s s ageAp i ( GetType ( ) . Fu l lName , new S amp l eContractConst ructo r ( ) )

В качестве более децентрализованного решения можно взять систему TrueBit, которая предоставляет решения для масштабируемых и верифицируемых офф­ чейн (off- chain, или находящихся вне блокчейн-сети) вычислений. Она основа­ на на механизме так называемых решателей 2 и верификаторов 3 (проверочных Англ.: verifiaЫe oracle truths. - Прим. ред. 2 Англ.: solvers. - Прим. ред. 1

3

Англ.: verifiers. - Прим. ред.

Глава 1 1 . Оракулы

359

устройств), которые имеют стимулы для выполнения вычислений и, соответ­ ственно, их проверки. Если решение ставится под сомнение, то ончейн (on-chain, в сети) запускается пошаговый процесс, который проверяет отдельные этапы вычисления - своего рода проверочная игра (verification game). Игра прохо­ дит раунд за раундом, каждый из которых рекурсивно проверяет все меньшие и меньшие фрагменты вычисления. В конце концов, процесс доходит до заклю­ чительного раунда, на котором проблема становится достаточно тривиальной для того, чтобы судьи (майнеры в сети Ethereum) смогли вынести итоговый вер­ дикт относительно того, является ли решение правильным. TrueBit, в сущности, является реализацией рынка вычислений, позволяющая децентрализованным приложениям покупать расчеты верификации, которые выполняются за преде­ лами сети, но основываясь на Ethereum, и зависят от правил проверочной игры. Теоретически это позволяет недоверительным смарт-контрактам безопасно вы­ полнить любую вычислительную задачу. Широкий диапозон применения имеют такие системы, как TrueBit: от машин­ ного обучения до проверки доказательства работы (PoW). Примером последней является мост Doge-Ethereum, который использует TrueBit для проверки РоW в Dogecoin (Scrypt); эта функция требует много памяти и вычислительных ресур­ сов, которые выходят за рамки лимита на газ в блоках Ethereum. Благодаря деле­ гированию этого процесса системе TrueBit стала возможной безопасная провер­ ка транзакций Dogecoin внутри смарт�контрактов в тестнете Rinkeby (Ethereum).

Децентрализованные оракулы В то время как централизованные данные или вычисления оракулов достаточны для многих приложений, они представляют собой отдельные точки сбоя в сети Ethe�eum. Был предложен целый ряд схем вокруг идеи децентрализованных ораку­ лов как концепции обеспечения доступности данных и создание сети индивидуаль­ ных источников (поставщиков) данных с системой агрегации данных на блокчейне. Проект ChainLink 1 предложил сеть децентрализованных оракулов, состоя­ щую из трех ключевых смарт-контрактов (репутационного контракта, контрак­ та сопоставления заказов, контракта агрегирования 2 ) и внесетевого (off-chain) реестра поставщиков данных. Репутационный контракт используется для отсле­ живания производительности поставщиков данных. Баллы в контракте репута­ ции используются для заполнения внесетевого реестра. Контракт сопоставления ' www.smartcontract.com/link. - Прим. авт.

2

Англ.: reputation contract, order-matching contract, aggregation contract. - Прим. ред.

360

Децентрализованные оракул ы

заказов выбирает предложения оракулов с помощью репутационного контрак­ та. Затем он делает окончательным соглашение на уровне сервиса, которое вклю­ чает параметры запроса и количество необходимых оракулов. Это означает, что покупателю не нужно взаимодействовать с отдельными оракулами напрямую. Контракт агрегирования собирает ответы от разных оракулов (поданные с по­ мощью схемы обязательства), подсчитывает итоговый коллективный результат запроса и, наконец, передает его обратно репутационному контракту. Одна из основных трудностей такого децентрализованного подхода состоит в формулировании функции агрегации. ChainLink предлагает вычислять взве­ шенный ответ, позволяя оценивать валидность (правильность) ответа каждого оракула. Определение результата, который можно считать невалидным 1 (непра­ вильным), является нетривиальной задачей, основываясь на предпосылке того, что элементы данных некорректны, они имеют отклонение от ответов других оракулов. Давая оценку ответу на основе его местоположения относительно дру­ гих результатов, мы рискуем отбросить корректные ответы в пользу тех, которые находятся ближе к среднему значению. Таким образом, помимо стандартного на­ бора контрактов агрегирования ChainLink также позволяет данные контракты, внося в них дополнения. Похожая идея реализована в протоколе SchellingCoin. Где разные участники сообщают значения (value), а в качестве «корректного» ответа берется их медиа­ на. Участникам, сообщающим значения, необходимо предоставить залог, кото­ рый в итоге перераспределяется между теми участниками, чьи значения были ближе к полученной медиане. Тем самым поощряются ответы со значениями, «похожие» на другие. Общепринятое значение (известное как точка Шеллинга 2 ) должно быть близким к правильному значению, при этом опрашиваемые сто­ роны могут считать общепринятое значение естественной и очевидной (его) це­ лью, вокруг которой следует координироваться. Недавно Джейсон Тойч из проекта TrueBit предложил новый подход к обеспе­ чению доступности данных во внесетевых децентрализованных оракулах. Дизайн его предложения основан на блокчейне с алгоритмом специализированного (вы­ деленного) доказательства работы (dedicated PoW), который способен корректно определить, доступны ли зарегистрированные данные во время заданной эпохи (epoch). Майнеры пытаются загрузить, разместить (в хранилище) и распростра­ нить все зарегистрированные данные, гарантируя тем самым их локальную доступ­ ность. Операции в данной системе очень затратны, в том смысле, что каждая нода при выполнении майнинга должна хранить и распространять все регистрируемые 1 2

Англ.: Invalid. - Прим. ред. Англ.: Schelling point. - Прим. ред.

Глава 1 1 . Оракулы

361

в ней данные, однако данная система позволяет повторное использование храни­ лища за счет удаления данных по истечении срока их регистрации.

Клиентские интерфейсы оракулов в Solidity В примере 11.1 продемонстрировано, как с помощью Oraclize можно постоян­ но запрашивать из АРI-интерфейса котировку ETH/USD и сохранять результа­ ты удобным образом. Пример 1 1 . 1 . Использование Oraclize для получения курса обмена ETHIUSD из внешнего источника /*

Курс обмена

E TH/USD, который загружа ется с помощью Cryp toCompare API

Этот контракт сохраняет в хранилище ко тировку ETH/USD,

*/

которая обновляется каждые 1 0 минут .

pragma solidз.ty л о . 4 . 1 ;

illlport " g i thub . com/ o r ac l i z e / ethe reum-ap i / o r a c l i z eAP I . s o l " ; /*

Префикс «ora cl i z�_ » ука зыв а е т на то , что методы уна следованы

о т « u s i n gOra cl i ze» */

contract EthUsdP r i c e T i c ke r is u s i ngOra c l i z e { uint puЬlic e t hU s d ; event newOra c l i zeQuery ( s tring de s c r ipt i on ) ; event newC a l lba c kRe s u l t ( s tring r e s u l t ) ; function EthUsdP r i c e T i c ke r ( ) рауаЫе (

// сигнализирует о генера ции TLSN доказа тель ства и ра змещении // в хранилище IPFS

o r a c l i z e_s e t P r oo f ( p ro o f Type_TLSNotary // выполняет запрос

362

Клиентские интерфейсы оракулов в Solidity .

p r o o f S t o rage I PFS ) ;

quer y T i c ke r ( ) ;

function _ca l l ba c k ( bytes32 _que r y i d , s trinq

puЫic {

r e s u l t , bytes _proo f )

if ( msg . sender ! = o r a c l i z e cbAddre s s ( ) ) throw ;

newC al lbackRe s u l t ( r e s u l t ) ; /*

* Парсит строку с резуль та том , превра ща я в беззна ковое целое * число для использования (on -cha i n ) внутри блокчейна .

* Использует в спомога тельный метод «pa rsein t » из «usingOra cl i ze», * позв оляя резуль та ту, такому как « 1 2 3 . 4 5 » , быть * конв ертированным в зна чение 1 23 4 5 . */

ethUsd

pa r s e i n t (_re s ul t , 2 ) ;

// запрашив а ем котировку цены с помощью функции обра тного вызова

que r yT i c ke r ( ) ;

function que r yT i c ke r ( ) puЫic рауаЫе {

if ( o rac l i z e_ge t P r i c e ( " URL " ) > t h i s . ba l ance )

newOrac l i z eQue r y ( " Orac l i z e que r y was NOT sent , p l e a s e add

J else

s ome ЕТН t o cove r for the que r y fee " ) ;

newOr ac l i z eQue ry ( " Orac l i z e que r y was sent , s tanding Ьу f o r t h e a n s w e r ... " ) ;

// параметры запроса (задержка в секундах , тип источника // данных , аргумент источника данных)

// формируют JSONPa th , чтобы получить определенную ча сть //

ответа JSON API

orac l i z e_que ry ( б 0 * 1 0 , " URL " ,

" j s on ( https : / /min-api . c r yptocompa r e . com/ da t a / p r i ce ? \

f s ym = ETH & t s yms = U S D , EUR , GBP ) . US D " ) ;

Глава 1 1 . Оракулы

363

Чтобы интегрироваться с Oraclize, контракт E t h U s dP r i c e T i c ke r дол­ жен быть потомком контракта u s i ngOra c l i z e, который определен в файле oraclizeAPI. Данные запрашиваются с помощью функции o r a c l i z e que r y, унаследованной от u s i ngOra c l i z e. Это перегруженная функция, она ожида­ ет получить как минимум два аргумента: - поддерживаемый источник данных, который будет использоваться: URL, WolframAlpha, IPFS или вычисления; - аргумент для заданного источника данных, в который включены помощ­ ники для парсинга на JSON или ХМL. Функция qu e r yT i c ke r делает запрос котировки. Для выполнения за­ проса Oraclize взымает небольшую комиссию в эфире, которая покрывает за­ траты газа, необходимого для обработки результата и передачи его в функцию _ca l lb a c k, а также сопутствующую доплату за выполнение данной услуги. Конкретная сумма зависит от источника данных и типа доказательства подлин­ ности, которое нужно предоставить (если он указан). После извлечения данных учетная запись, которая управляется системой Oraclize 1 и которая имеет пол­ номочия по выполнению данной функции, вызывает функцию _ca l lba c k ( обратного вызова ) ; ей передается значение ответа и уникальный аргумент que r y i d, который, к примеру, можно использовать для поддержания работы и отслеживания множества отложенных обратных вызовов от Oraclize. Поставщик финансовых данных Thomson Reuters, помимо прочего, пре­ доставляет для Ethereum сервис оракула под названием BlockOne IQ, кото­ рый позволяет запрашивать рыночную и справочную информацию из смарт­ контрактов, запущенных в приватных или разрешенных 2 блокчейн-сетях 3 • В примере 11.2 показан интерфейс такого оракула и контракт клиента, который к нему обращается. Пример 1 1.2. Контракт, запрашивающий рыночные данные у сервиса BlockOne IQ pragma solidity л о . 4 . 1 1 ; contract O r a c l e {

uint256 puЬlic divi s o r ;

1 2 3

Анrл.: Oraclize-controlled account. - Прим. ред. Анrл. permissioned. - Прим. ред. Блокчейн с разным уровнем разрешений. - Прим. ред.

364

Клиентские интерфейсы оракулов в Solidity

function i n i tReque s t (

uint2 56 que ryType , function ( uint2 5 6 ) external onSucce s s , function ( uint2 5 6

external onFa i lure ) puЬlic returns ( uint2 5 6 i d ) ;

function addArgumentToRequ e s t U i n t ( uint2 5 6 i d , bytes32 name ,

uint256 a r g ) puЬli c ;

function addArgumentToReque s t S t r i ng ( uint2 5 6 i d , bytes32 name ,

bytes32 arg )

puЬlic ;

function executeReque s t ( uint2 5 6 i d ) puЬlic ;

function getResponseU i n t ( uint25 6 i d , bytes32 name ) puЬlic constant returns ( uint2 5 6 ) ;

function getRespon s e S t r i ng ( uint25 6 i d , bytes32 name ) puЬlic

constant

returns (bytes32 ) ;

function getResponseEr r o r ( uint2 5 6 i d ) puЬlic constant

returns (bytes32 ) ;

function de leteRe spon s e ( uint25 6 i d ) puЬlic constant ;

contract OracleB l I QC l ient { Oracle private orac l e ;

event LogEr ro r (bytes32 de s c r ip t i on ) ; function O r a c l eB l I QC l i ent ( addres s addr ) puЬlic рауаЬlе { o r a c l e = O r a c l e ( addr ) ;

get i ntraday ( " I BM" , now ) ;

function ge t i nt raday ( bytes32 r i c , uint2 5 6 t ime s t amp ) puЬlic {

uint2 56 i d = o r a c l e . i n i tRequ e s t ( 0 , th i s . handl eSucce s s , thi s .

handl eFa i lure ) ;

o r a c l e . addArgumentToReque s t S t r ing ( i d , " s ymЬo l " , r i c ) ;

o r a c l e . addArgumentToReque s t U i n t ( i d , " t ime s tamp " , t ime s t amp ) ; o r a c l e . executeReque s t ( i d ) ;

function handl eSucce s s ( uint2 5 6 i d ) puЬlic {

Глава 1 1 . Оракулы

365

a s s e r t (msg . sender == address ( o r a c l e ) ) ;

bytes32 r i c = o r a c l e . getRespon s e S t r i ng ( i d , " s ymЬo l " ) ; o r a c l e . getResponseUint ( id , " open " ) ;

uint25 6 open

uint25 6 h i gh uint25 6 l ow

o r a c l e . getResponseUint ( i d , " h igh" ) ;

=

o r a c l e . getResponseUint ( i d , " l ow " ) ;

uint2 56 c l o s e = o r a c l e . getResponseUint ( i d , " c l o s e " ) ; uint2 56 Ь i d uint2 5 6 a s k

o r a c l e . getResponseUint ( i d , " b i d " ) ;

o r a c l e . getResponseUint ( i d , " a s k " ) ;

uint2 5 6 t ime s t amp

=

o r a c l e . getResponseUint ( i d , " t ime s t amp " ) ;

o r a c l e . de l e teResponse ( i d ) ;

// Дела ем что -нибудь с данными о котировках

function handl e Fa i lure ( uint2 5 6 i d ) puЫic { a s ser t (msg . sender == addres s ( o r a c l e ) ) ;

bytes32 e r r o r

=

o r a c l e . getRe s po ns e Er r o r ( i d ) ;

o r a c l e . de l e teResponse ( i d ) ;

emit LogE r r o r ( e r r or ) ;

Запрос данных инициируется с помощью метода i n i tReque s t, который помимо двух функций обратного вызова позволяет указать тип запроса (в этом примере нас интересует внутридневная цена). Результатом будет идентифика­ тор типа u i n t 2 5 6, который затем можно использовать для предоставления дополнительных аргументов. Функция a ddAr gume n t T oReque s t S t r i ng указывает символьный идентификатор RIC 1 для акций IВМ, а функция addArgument ToRequ e s tUint позволяет задать временную метку. Таким об­ разом, передав ссылку (alias) на Ы о с k . t ime s tamp, мы получим текущую ко­ тировку для IBM. Затем функция executeReque s t делает еще один запрос, после обработки которого контракт оракула выполнит функцию обратного вы­ зова o n Su c c e s s с идентификатором запроса, что позволит извлечь итоговые данные; если в ходе извлечения произойдет сбой, обратный вызов onFa i lure вернет вместо этого код ошибки. В случае успеха можно извлечь такие поля с данными, как open, h i gh, l ow, c l o s e (OHLC 2 ) , и цены Ь i d/a s k. 1 2

Анrл.: Reuters Instrument Code. - Прим. ред. OHLC - сокращенное обозначение котировок, которые у казываются для элементарной диаграммы какого-либо ценового графика. - Прим. ред.

366

Клиентские интерфейсы оракулов в Solidity

Выводы Как видите, оракулы предоставляют смарт-контрактам критически важную услугу: они привносят внешние факты (данные) для выполнения контрактов. Вместе с этим оракулы, конечно, создают существенный риск: они считаются доверенными источниками, тем не менее могут быть взломаны, что, в свою оче­ редь, скомпрометирует выполнение всех смарт-контрактов, которым они по­ ставляют данные. В целом, перед использованием оракула следует тщательно продумать мо­ дель доверия. Предполагая, что оракулу можно доверять, вы можете подвергнуть рискам безопасности свой смарт-контракт, сделав его уязвимым к потенциально ложному вводу (input) данных. Тем не менее оракулы могут оказаться очень по­ лезными, если тщательно взвесить все допущения, связанные с безопасностью. Децентрализованные оракулы могут решить некоторые из этих проблем и предоставить смарт-контрактам Ethereum доверенные внешние данные. Будь­ те внимательны, начиная исследование моста между Ethereum и «реальным ми­ ром», который предлагают оракулы.

ГЛ АВА 1 2

Децент рализ ованные п риложения (DApps) В этой главе мы познакомимся с миром децентрализованных приложений (англ. decentralized applications, или сокр. DApps). То видение, которое сфор­ мировали основатели Ethereum, с самого начала было шире концепции «смарт­ контрактов»: ни много ни мало переосмысление Интернета и создание нового мира DАрр-приложений с метким названием web3. Смарт-контракты -это спо­ соб децентрализации управляющей логики и функций оплаты в приложениях. Идея Web3 DАрр-приложений состоит в децентрализации остальных аспектов функциональности приложений: хранилища, механизма обмена сообщениями, системы наименований и т. д. (см рис. 12.1).

•'

,. ,. ,. ,. ,. ,. ,. ,. ,. ,. ,. t

'

Ethereum КОНТРАКТЫ

t

t '

t 1

1-; .--.._.'B roW1ier • · · ·· · · ···••·• ······· ·············· · · · · · · · · · · · · ·· · ·······

Рис. 12. 1. Web3: децентрализованный Интернет на основе смарт-контрактов и Р2Р технологий

368

1

Децентрализованные приложения (DApps)

�!\

• Децентрализованные приложения • - амбициозная и футу­ ристическая концепция, однако термин DApp часто применяют к любым смарт-контрактам с клиентским веб-интерфейсом. Некоторые из таких приложений являются высокоцентрали­ зованными (не лучше ли их назвать CApps 1 , централизован­ ными приложениями? - Ред.). Остерегайтесь ложных DApp!

В этой главе мы разработаем и развернем демонстрационное DАрр-приложе­ ние - платформу для аукционов. Его исходный код можно найти в репозито­ рии данной книги в каталоге code/auction_dapp 2 • Мы рассмотрим каждый аспект этого проекта и посмотрим, как сделать его максимально децентрализованным. Но для начала давайте внимательнее рассмотрим характеристики и преимуще­ ства DАрр-приложений.

Ч то такое DАрр - приложение ? DАрр-приложение - это полностью или частично децентрализованное прило­ жение. Перечислим все его аспекты, которые теоретически можно децентрализовать: - серверное ПO 3 (логика приложения); - клиентское ПО 4 ; - хранилище данных; - взаимодействие с помощью сообщений; - разрешение имен. Каждый из этих элементов может быть в той или иной мере централизован­ ным или децентрализованным. Например, клиентскую часть можно разрабаты­ вать в виде веб-сайта, который выполняется на центральном сервере, или как мобильное приложение, запускаемое на вашем устройстве. Серверные компо­ ненты и хранилище могут находиться на частных серверах и использовать про­ приетарные базы данных; но вместо этого можно применять смарт-контракты и Р2Р-хранилища. 1

Англ.: centralized applications. - Прим. ред.

' github.com/ethereumbook/ethereumbook/tree!developlcode!auction_dapp. - Прим. авт. 3

Англ.: backend software. - Прим. ред.

4

Англ.: frontend software. - Прим. ред.

Глава 12. Децентрализованные приложения (DApps)

369

У DАрр-приложений есть множество преимуществ, которыми не может по­ хвастаться типичная централизованная архитектура. - Устойчивость. Поскольку бизнес-логика контролируется смарт­ контрактом, серверная часть DАрр-приложения является полностью рас­ пределенной и управляется блокчейн-платформой. В отличие от приложе­ ний, запущенных на центральном сервере, DApp никогда не простаивает и всегда остается доступным (при условии, что сама платформа продол­ жает работать). - Прозрачность (транспарентность) 1. Тот факт, что DАрр-приложение на­ ходится в блокчейне, позволяет кому угодно просмотреть его код и полу­ чить более четкое представление о его функциях. Любое взаимодействие с ним будет навсегда сохранено в блокчейн. - Сопротивляемость к цензуре 2 • Пока пользователь имеет доступ к узлу Ethereum (или держит его запущенным у себя, если это необходимо), он может взаимодействовать с DApp без какого-либо централизованного контроля. Никакой поставщик услуг или даже владелец смарт-контракта не может изменить код, развернутый в сети. В экосистеме Ethereum в ее нынешнем виде очень мало по-настоящему де­ централизованных приложений - большинство по-прежнему так или иначе ис­ пользует в своей работе централизованные сервисы и серверы. Ожидается, что в бу�ущем любой элемент любого DАрр-приложения сможет функционировать полностью децентрализованным образом.

Серверная часть 3 (смарт-контракт) DАрр-приложения используют смарт-контракты для хранения бизнес-логики (программного кода) и связанного с ней состояния. Это как бы замена сервер­ ной стороны, которая входит в состав обычных приложений. Конечно, не все так просто. Основное отличие в том, что все вычисления, выполняемые смарт­ контрактом, являются очень дорогими и по возможности должны быть мини­ мизированы. Поэтому важно определить, какие аспекты приложения нуждают­ ся в доверенной и децентрализованной платформе исполнения. 1 2 3

Англ.: transparency. - Прим. ред. Англ.: censorship resistance. - Прим. ред. Англ.: backend. - Прим. ред.

370

Что такое DА рр - приложение?

Платформа смарт-контрактов Ethereum позволяет создать архитектуру, в ко­ торой сеть из смарт-контрактов может вызывать и обмениваться между собой данными и динамически читать/делать записи собственных состояний перемен­ ных; при этом их сложность ограничена лишь лимитом на газ, который уста­ новлен в блоке. Когда вы развернете свой смарт-контракт, вашу бизнес-логику вполне смогут использовать многие другие разработчики. Одним из важных аспектов, которые следует учитывать при проектирова­ нии смарт-контрактов, является невозможность изменения их кода после раз­ вертывания. Их можно полностью удалить, если они содержат доступный опкод SELFDE S TRUCT, но помимо этого код нельзя никак модифицировать. Еще одна особенность архитектуры смарт- контрактов связана с размером DАрр-приложения. На развертывание и использование по-настоящему боль­ шого монолитного смарт-контракта может потребоваться большое количество газа. В связи с этим некоторые приложения могут выносить вычисления пределы блокчейна, то есть делать их внесетевыми, и использовать внешние источники данных. Но помните, что организация основной бизнес-логики DАрр-приложе­ ния в зависимости от внешних данных (например, данных из централизованно­ го сервера) потребует доверия к источникам данных со стороны пользователей.

Клиентская часть 1 (пользовательский веб-интерфейс) В отличие от бизнес-логики DАрр-приложения, которая требует от разработчи­ ка понимания EVM и новых языков вроде Solidity, клиентский интерфейс децен трализованного приложения может использовать стандартные веб-технологии (такие как HTML, CSS, JavaScript и др.). Это позволяет традиционным разработ­ чикам применять знакомые им инструменты, библиотеки и фреймворки. Взаи­ модействие с Ethereum, такое как подписание сообщений, отправка транзакций и управление ключами, часто осуществляется из веб-браузера с помощью таких расширений как MetaMask (см. главу 2). Децентрализованное приложение можно написать и для мобильного устрой­ ства, но информации по созданию мобильных интерфейсов DApp не так уж мно­ го. Это в основном связано с нехваткой легких мобильных клиентов, поддержи­ вающих управление ключами доступа. Клиентская часть (frontend) обычно подключается к Ethereum через JаvаSсriрt­ библиотеку web3.js, которая поставляется в комплекте с интерфейсными ресур­ сами и подается веб-сервером браузеру.

1

Англ.: frontend. - Прим. ред.

Глава 12. Децентрализованные лриложения (DApps)

371

Хранили ще данных В связи с высокой ценой и текущим низким лимитом на газ смарт-контракты плохо подходят для хранения или обработки больших объемов информации. Поэтому большинство DАрр-приложений используют внесетевые (off-chain) сервисы хранения данных, то есть они хранят массивные данные за предела­ ми блокчейна Ethereum, на предназначенных для этого платформах. Такие плат­ формы могут быть как централизованными (например, типичная облачная база данных), так и децентрализованными, размещенными на таких Р2Р-платформах, как IPFS, или решении Swarm экосистемы Ethereum). Децентрализованное Р2Р хранилище идеально подходит для хранения и рас­ пространения больших статических файлов, таких как изображения, видео и ре­ сурсы клиентского веб-интерфейса (HTML, CSS, JavaScript и т. д.). Далее мы рас­ смотрим несколько доступных вариантов. IPFS Межпланетная файловая система (IPFS) 1 - это децентрализованное хранили­ ще с прямой доставкой контента, которое распределяет хранимые объекты ме­ жду участниками Р2Р-сети. «Прямая доставка контента» 2 означает, что каждый фрагмент (файл) контента хешируется, и этот хеш затем используется для его идентификации. Вы можете извлечь любой файл из любого поды (узла) IPFS, запросив его по хешу. Цель IPFS - заменить НТ Т Р в качестве протокола для доставки веб-прило­ жений. Файлы хранятся в IPFS, и в отличие от веб-приложений, размещаемых на единственном сервере, могут быть извлечены с помощью любой поды дан­ ной системы. Больше информации о IPFS можно найти на сайте ipfs. io.

Swarm Swarm - это еще одна Р2Р-система хранения данных с прямой доставкой кон­ тента, похожая на IPFS. Она создана Ethereum Foundation как часть пакета про­ грамм и инструментов Go-Ethereum. Как и IPFS, Swarm распределяет и реплицирует хранимые файлы между своими подами. Доступ к любому файлу Swarm можно выполнить по хешу. Вместо центрального веб-сервера Swarm позволя­ ет использовать доступ к веб-сайту через децентрализованную Р2Р-систему.

1 2

Англ.: Inter-Planetary File System. - Прим. ред. Англ.: content addressaЫe. - Прим. ред.

372

Что та кое DАрр -приложение?

Домашняя страница проекта тоже находится в Swarm; доступна через вашу ноду Swarm или шлюз ': swarm-gateways.net/bzz:/theswarm. eth/.

Децентрали зова нные протоколы взаимодействия на основе сооб щений Еще одним важным компонентом любого приложения является внутри­ процессная коммуникация, или взаимодействие. Это означает возможность обмениваться сообщениями между приложениями, между отдельными экзем­ плярами приложения и между пользователями приложения. Традиционно это достигалось с помощью применения центрального сервера. Но существует мно­ жество децентрализованных решений, которые обеспечивают обмен сообще­ ниями по пиринговой сети, - альтернатив централизованным протоколам. В контексте DАрр-приложений для Р2Р-обмена сообщениями самым упомина­ емым из них является Whisper 2, который входит в состав пакета программ и ин­ струментов Go-Ethereum. Заключительный аспект приложения, которое можно децентрализовать, состоит в разрешених имен (наименований). На рассмотрении сервиса имен Ethereum мы подробно остановимся позже в данной главе; а сейчас давайте рас­ смотрим пример DАрр-приложения.

Пример просто го деце нтрализо ванного приложени я : аукцион В этом разделе мы начнем создавать демонстрационное DАрр-приложение, что­ бы изучить различные рабочие инструменты. В приложении будет реализован децентрализованный аукцион. Наше приложение-аукцион позволит пользователю зарегистрировать уни кальный токен (deed, или «акт»), который представляет собой уникальный ак­ тив, такой как дом, машина, торговая марка и т. д. Владение токеном после его регистрации делегируется приложению-аукциону, благодаря чему он может быть выставлен на продажу. Аукцион регистрирует (или «листит», от aнrл. list. Ред.) токены, предлагая другим пользователям сделать ставки (Ьids) для приоб­ ретения любого из зарегистрированных токенов. Во время проведения каждо­ го аукциона пользователи могут подключиться к чату, специально созданному 1

Англ.: gateway. - Прим. ред.

' github.com/ethereum/wiki/wiki/Whisper. - Прим. авт.

Глава 12 . Децентрализованные приложения (DApps)

373

для конкретного аукциона. По окончании торгов владение токеном передается победителю аукциона. Общая схема торгов аукциона представлена на рис. 12.2. Основные компоненты приложения-аукциона перечислены ниже: - смарт-контракт, реализующий выпуск невзаимозаменяемых 1 токенов стандарта ERC721 (DeedRepo s i t o r y); - смарт-контракт, реализующий аукцион (Au c t i onRepo s i tory) для про­ дажи актов; - клиентский веб-интерфейс на JavaScript-фpeймвopкe Vue/Vuetify; - библиотека webЗ.js для подключения к блокчейнам экосистемы Ethereum (с помощью MetaMask или других клиентов); - клиент Swarm для хранения ресурсов таких как изображения; - клиент W hisper для создания чатов для каждого аукциона для участников.

D

2. Создаем аукцион

(март-контракт аукционного дома Передаем актив учетной записи

Создатепь аукциона

1. Передаем владение контракту аукционного дома

З. Ставка (передаем эфир контракту) Покупатель

озвращаем эфир

Передаем актив обратно учетной

D

Токен ЕRШ 1

Рис. 12.2. DАрр-приложение: пример простого децентрализованного аукциона

Исходный код данного приложения-аукциона можно найти в репозитории книrи 2 •

1 2

dееd-токены. - Прим. ред. github.com/ethereumbook!ethereumbook/tree!develop!code!auction_dapp. - Прим. авт.

374

Пример простого децентрализованного приложения: аукцион

Децентрализова нный аукцион: смарт-контракты на стороне сервера Наш пример децентрализованного аукциона опирается на два смарт-контракта, которые необходимо развернуть на блокчейне Ethereum: Auc t i onRepo s i tory и DeedRepo s i t o r y. Давайте для начала рассмотрим контракт DeedRep o s i t o r y, показанный в примере 12.1. Это взаимозаменяемый токен, совместимый с ERC721 (см. раз­ дел «ERC721: стандарт (актов) взаимозаменяемых токенов» на с. 341). Пример 12. 1 . DeedR epository.sol: dееd-токен стандарта ERC721 для использования в аукционах pragma solidity � о . 4 . 1 7 ;

import " . /ERC 7 2 1 / ERC7 2 1 To ken . s o l " ;

/** * @ t i t l e Репозиторий а ктов ERC 72 1

* Этот контракт содержит список а ктов

пользов а телями .

(deeds ) , зарегистрированных

* Это демонстра ция того , ка к можно сгенерирова ть 1 и добавить токены

в репозиторий . */

contract DeedRepo s i to r y i s ERC 7 2 1 Token {

/** * @dev Созда ется DeedRepos i t ory с именем условным обозна чением

* @pa ram строка тория

* @pa ram строка

пате представляет на звание репозитория

symbol представляет условное обозна чение репози­

*/

function DeedRepo s i to r y ( s tring _name , s tring puЫic ERC 7 2 1 To ken ( name ,

s ymЬo l ) { }

s ymЬo l )

/** * @dev Публичная функция для регистрации нового а кта * @dev Вызыва ем генератор ERC 72 1 Token * @pa ram 1

(deed)

t oken id u i n t25 6 представляет определенный а кт

Анrл.: mint. - Прим. ред.

Глава 12. Децентрализованные приложения (DApps)

375

* @pa ram строка

*/

uri с метаданными/uri

function reg i s t e rDeed ( uint2 5 6 _mi n t ( ms g . sende r ,

t o ken i d , string _u r i ) puЫic {

tokeni d ) ;

addDeedМetada t a (_to ken i d , _u r i ) ;

eщi.t DeedRe g i s tered (msg . sende r ,

t o ken i d ) ;

/**

* * * *

@dev Публичная функция для добавления метаданных в а кт @pa ram

t okenid представляет определенный акт

@pa ram текст

*/

uri описыв а е т характеристики заданного акта

@ re t urn были ли метаданные а кта добавлены в репозиторий

function addDeedMe t adata ( uint2 5 6 _token i d , string

returns (bool ) {

u r i ) puЬlic

setTokenURI (_token i d , _ur i ) ;

return true ;

/**

* @dev Если токен/а кт был зарегистрирован, генерируется событие * @pa ram Ьу представляет адрес регистра тора * @pa ram token id u i n t 2 5 6 представляет определенный а кт */

event DeedRegi s t e red ( address _Ьу , uint25 6

t o ken i d ) ;

Как видите, контракт DeedRepo s i t o r y является простой реализацией то­ кена, совместимого с ERC721. Наш децентрализованный аукцион использует контракт DeedRepo s i tory для выпуска и отслеживания токенов для каждого аукциона. Работа самого аук­ циона оркестрируется контрактом Au c t i onRep o s i t o r y. Данный контракт слишком длинный, чтобы приводить его (код) полностью, но в примере 12.2 по­ казаны его основное определение и структуры данных. В полном виде контракт доступен в репозитории книги на GitHub (см. ссылку 1 ). 1

github. сот/ethereurnbook/ethereurnbook/ЫoЫdevelop/code/auction_dapplbackend!contracts/ AuctionRepository.sol. - Прим. авт.

376

Пример простого децентрапизованноrо припожения: аукцион

Пример 12.2. AuctionRepository.sol: главный смарт-контракт децентрализованного аукциона contract Auc t i onRepo s i to r y { // Ма ссив со всеми а укционами

Au ct i o n [ ] puЬlic a u c t i o n s ;

// Привязка индекса а укциона к ставкам (bi ds ) пользова телей

111appin9 ( uint2 5 6 => B i d [ ] ) puЬlic a u c t i onBids ; // Привязка владельца к списку его аукционов

mappinq ( address => uint [ ] ) puЬlic a u c t i onOwne r ;

ки)

// Структура ставки : владелец ставки (покупа тель ) и ра змер (став struct B i d {

address from;

uint2 5 6 amount ;

// Структура а укциона содержит в сю необходимую информа цию

struct Auc t i o n { strinq name ;

uint25 6 Ы o c kDeadl i ne ; uint2 5 6 s t a r t P r i ce ; strinq metadat a ;

uint25 6 deedi d ;

address deedRepo s i t o r yAddre s s ; address owne r ;

bool act ive ;

bool fina l i z e d ;

Контракт Au c t i onRep o s i tory управляет всеми аукционами и содержит следующие функции: getCount ( )

get Bids Count ( uint _au c t i o n i d )

Глава 12. Децентрализованные приложения (DApps)

377

ge tAu c t i o n s O f ( addres s

owne r )

get Cu r re n tB i d ( uint _au c t i o n i d )

getAu c t i o n s CountO fOwne r ( addres s _owne r ) g e tAu c t i onBy i d ( uint

c r e a t eAu c t i o n ( addres s s tring

uint2 5 6

auct i o n i d )

deedRepo s i t o r yAddr e s s , uint2 5 6

au c t i o nT i t l e , s tring _me t a da t a ,

s t a r t P r i ce , uint _Ы o c k Deadl i n e )

app r oveAndT r a n s fe r ( addres s addres s

c a n c e lAu c t i o n ( uint

dee d i d ,

f rom , addres s

to,

dee dRep o s i t o r yAddr e s s , uint2 5 6

auct i o n i d )

deed i d )

fina l i z eAu ct i o n ( uint _au c t i o n i d ) b i dOnAuc t i on ( uint _au c t i on i d )

Вы можете развернуть данные контракты в любом блокчейне Ethereum на ваш выбор (например, в сети Ropsten), используя t ruffie в репозитории книги: $ cd code/auction_dapp/backend $ truJlle init

$ truJlle compile

$ truJlle migrate - -network ropsten

Уп равление DАрр-п риложением Если пройтись по коду двух смарт-контрактов децентрализованного аукцио­ на, можно заметить один важный момент: у них нет специальной учетной записи или роли с особыми привилегиями по отношению к DАрр-приложению. У каж­ дого аукциона есть владелец со специальными возможностями, но у самого приложения-аукциона нет привилегированного пользователя. Это сделано намеренно, чтобы децентрализовать управление приложением и отказаться от любого контроля по окончании его развертывания. У некото­ рых приложений есть одна или несколько учетных записей со специальными возможностями, такими как удаление контракта, перезапуск или изменение его конфигурации, или «наложение вето» на определенные операции. Обычно по­ добные функции управления добавляются в приложение для того, чтобы избе­ жать потенциальных проблем, которые могут возникнуть из-за ошибки в коде. Функции управления особенно сложно реализовать, поскольку это пал­ ка о двух концах. С одной стороны, привилегированные учетные записи таят в себе опасность; в случае взлома они могут подорвать защиту всего приложе­ ния. С другой стороны, если таких учетных записей нет совсем, мы теряем воз­ мqжности восстановления в случае нахождения ошибки. Мы уже видели оба 378

Пример простого децентрализованного приложения : аукцион

этих риска, проявленных в децентрализованных приложениях Ethereum. В случае с DAO (см. раздел «Реальный пример: DAO» на с. 245 и дополнение А) у контракта имелись определенные привилегированные учетные записи, которые назывались «кураторами» 1 , и они были ограничены в своих возможностях. Во время атаки они не смогли прервать вывод средств злоумышленником. Если взять более свежий пример, децентрализованная биржа Bancor стала жертвой крупной кражи из-за компромитации привилегированной учетной записи2 • Оказалось, что биржа была не настолько децентрализованной, как предполагалось изначально. При разработке DАрр-приложения следует определиться с тем, хотите ли вы сделать смарт-контракты по-настоящему независимыми, запуская их без даль­ нейшего контроля, или же вам нужны привилегированные учетные записи с по­ тенциальным риском их копрометации. Опасность риска присутствует в любом из вариантов, но в долгосрочной перспективе настоящие децентрализованные приложения не смогут предоставлять привилегированный доступ для отдель­ ных учетных записей - это противоречит децентрализации.

Децентрализова нный аукцио н: клиентский пол ьзовател ьски й интерфейс Развернув контракты децентрализованного аукциона, вы сможете взаимодей­ ствовать с ними с помощью вашей любимой консоли JavaScript и web3.js (или другой wеЬ3-библиотеки). Однако большинству пользователей нужен простой и удобный интерфейс. Пользовательский интерфейс нашего приложения осно­ ван на JavaScript фреймворке Vue2/Vuetify3. Исходный код можно найти в папке code/auction_dapplfrontend репозитория книги 4. Данная директория имеет следующие структуру и содержимое: frontend/

1 - - bu i l d

1 - - bu i l d . j s

1 - - check-ve r s i on s . j s 1 - - l ogo . png 1 - - ut i l s . j s

1 - - vue - l oade r . c o n f . j s

1

Анrл.: curators. - Прим. ред.

2

Учетной записи с функциями управления. - Прим. ред.

3

Фреймворк Google. - Прим. ред.

4

github.com/ethereumbook/ethereumbook. - Прим. авт.

Глава 12. Децентрализованные приложения (DApps)

379

1 - - webpack . ba s e . c o nf . j s 1 - - webp a c k . dev . c o nf . j s

webpa c k . prod . co nf . j s

1 - - config

1 - - dev . env . j s 1 - - i ndex . j s

prod . env . j s

1 - - i ndex . html

1 - - pac kage . j s on

1 - - pac kage - l oc k . j s on 1 - - README . md

1 -- src

1 - - App . vue

1 - - components 1

1 - - Auct i o n . vue Home . vue

1 -- c onfig . j s

1 - - contracts

1 - - Auc t i onRepos i t o r y . j s on DeedRepo s i tory . j son

1 - - ma i n . j s 1 - - mode l s

1 - - Auc t i onRepos i t o r y . j s 1 - - ChatRoom . j s

DeedRepo s i to r y . j s

router

i ndex . j s

После развертывания контрактов DeedRepos i tory и AuctionRepos i tory укажите их адреса в файле конфигурации frontend!src!conjig.js. Клиентскому при­ ложению также понадобится доступ к поде Ethereum, который предоставляют интерфейсы JSON-RPC и WebSockets. Сконфигурировав клиентскую часть, за­ пустите ее с помощью веб-сервера на своем локальном компьютере: $ npm install

$ npm run dev

В результате запустится клиентская часть приложения-аукциона. Она будет доступна в любом веб-браузере по адресу localhost:8080. 380

Пример простого децентрализованного приложения : аукцион

Если все пройдет удачно, вы должны увидеть страницу, показанную на рис. 12.3. Это пример децентрализованного аукциона, запущенного в веб­ браузере.

Recent Auctlons

·--

1 --- 1

-�--,

Рис. 12.3. Пользовательский интерфейс децентрализованного аукциона

Дальней ш ая децентрализация приложени я -аукциона Наш � приложение DApp уже довольно децентрализованное, но совершенству нет предела. Контракт Au c t i o n Re p o s i t o r y выполняется полностью независимо (от кого-либо) и открыт для всех желающих. После развертывания его нельзя остановить, а аукцион не подлежит контролю. У каждого аукциона есть отдель­ ный чат, в котором все участники могут общаться без какой-либо цензуры или идентификации. Все метаданные аукциона, такие как описание и прикрепленное к нему изображение, хранятся в Swarm; благодаря этому их сложно подвергнуть цензуре или заблокировать. Кто угодно может взаимодействовать с DАрр-приложением. Для этого до­ статочно вручную сформировать транзакции или запустить у себя клиентскую часть на основе Vue на локальной машине. Сам код DАрр-приложения является открытым и коллективно разрабатывается в публичном репозитории. Мы можем сделать это приложение более децентрализованным и устойчи­ вым двумя способами: - хранить весь код приложения в Swam или IPFS; - обращаться к приложению по имени, используя сервис имен Ethereum. Глава 12. Децентрализованные приложения (DApps)

381

Первый вариант мы исследуем ниже, а ко второму вернемся в разделе «Сер­ вис имен Ethereum (ENS)» на с. 386.

Хран е ни е де це нтрализо ванного аукциона в Swarm Ранее в этой главе (см. с. 372) мы уже ознакомились со Swarm. Наше децентра­ лизованное приложение уже использует эту систему для хранения изображе­ ний иконок аукционов. Это намного эффективнее, чем пытаться хранить дан­ ные в Ethereum (что довольно дорого). К тому же это намного более устойчивое решение по сравнению с централизованным сервисом, таким как файловый или веб-сервер. Но на этом можно не останавливаться. Мы можем поместить в Swarm всю клиентскую часть DАрр-приложения и выполнять ее напрямую на ноде Swarm, а не на веб-сервере.

Подготовка Swarm Для начала вам следует установить Swarm и инициализировать свою ноду. Swarm входит в пакет программ и инструментов Go-Ethereum (Ethereum Foundation). Инструкции по установке Go-Ethereum приведены в разделе «Go-Ethereum (Geth)» на стр. 93. Вы также можете установить бинарную версию Swarm, сле­ дуя инструкциям документации по Swarm 1 • Завершив установку, вы можете проверить корректность работы с помощью команды ve r s i o n: $ swarm version

Ve r s i on : 0 . 3

G i t Comrni t : 3 7 6 8 5 9 3 0 d 9 5 3bcbe 0 2 3 f 9b c 6 5Ы 3 5 a 8 d8 b 8 f 1 4 8 8

G o Ve r s i on : go l . 1 0 . 1 OS : l i nux

Перед началом работы клиент Swarm должен знать, как ему подключиться к экземпляру Geth, чтобы получить доступ к JSON-RPC API. Для начала работы следуйте инструкциям (см. ссылку 2).

1 2

swarm-guide. readthedocs. io/en/latest/installation.html. - Прим. авт. Getting Started guide; swarm-guide. readthedocs. io/en/latest/gettingstarted.html. - Прим. авт.

382

Хранение децентрализованного аукциона в Swarm

При запуске Swarm вы должны увидеть примерно следующее: Maximum peer count

ЕТН = 2 5 LES = O t o t a 1 = 2 5

conne c t i ng to ENS API

u r l = http : / / 1 2 7 . 0 . 0 . 1 : 8 5 4 5

Starting pee r - t o -peer node swarm [ 5 9 5 5 ] : [ 1 8 9В Ы оЬ data ] Starting Р 2 Р netwo r king

UDP l i stener up

instance = swarm/vO . 3 . 1-22 5 1 7 1a 4 / l inux...

s e l f = enode : / / f 5 0 c 8 e 1 9ff8 4 lbcd5 c e 7 d2 d...

oaddr = 9 c 4 0 b e 8 b 8 3 e 6 4 8 d5 0 f 4 0 ad3_ uaddr = e

Updated bzz local addr S ta r ting Swarm s e rvice

9 с 4 0Ье8Ь hive s t a r t i ng

detected an e x i s t ing s t o r e . t r y i n g to load p e e rs hive 9 с 4 0Ье 8Ь : pee r s l o aded

Swarm ne twor k s t a rted on b z z addre s s : 9 c 4 0 be 8 b 8 3 e 6 4 8 d5 0 f 4 0 ad3d3 5 f_ Pss sta rted

Streame r started

u r l = / home / ubuntu / . ethe reum/ b z z d . ipc

I PC endpoint opened

s e l f = enode : / / f 5 0 c 8 e 1 9ff8 4 lbcd5 ce 7 d2 d_.

RLPx l i s tener up

Чтобы убедиться в корректной работе своей поды Swarm, вы можете подклю­ читься к веб-интерфейсу его локального шлюза: localhost:8500. На вашем экране должна появиться страница, показанная на рис. 12.4. Вы также должны иметь возможность запросить любой хеш Swarm или ESN имя.

owo,

Enter the hash or ENS of а Swarm-hosted file below: { _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ___ _

--

�: Serwrless НOldng lncenllvlsed реемо-рееr Storage and Conteot Dlstt\Ьud1111

Рис. 12.4. Шлюз Swarm на локальном компьютере Глава 12. Децентрализованные приложения (DApps)

383

Загрузка файлов в Swarm Запустив локальную ноду и шлюз Swarm, вы сможете загрузить туда свои фай­ лы, и они будут доступными на любой другой ноде; для обращения к ним доста­ точно указать их хеш. Давайте протестируем это и загрузим файл: $ swarm up code/auction_dapp/READМE . md

e c l 3 0 4 2 c 8 3ffc2 fb 5 c b 0 aa 8 c 5 3 f 7 7 0 d 3 6 c 9b 3 b 3 5 d 0 4 6 8 a 0 c 0 a 7 7 c 9 7 0 1 6bb 8 d 7 c

Система Swarm загрузила файл README. md и вернула хеш, по которому он будет доступен из любого другого узла. Например, вы можете использовать пуб­ личный шлюз Swarm 1 • Загрузка одного файла выглядит довольно просто, но загрузить всю клиент­ скую часть DАрр-приложения будет немного сложнее. Дело в том, что различные ресурсы децентрализованного приложения (HTML, CSS, JavaScript, библиотек и т. д.) содержат ссылки друг на друга. Веб-сервер обычно переводит URL-aдpeca в локальные пути и выдает подходящие файлы. Чтобы добиться того же в Swarm, мы можем упаковать наше DАрр-приложение. В директории децентрализованного аукциона находится скрипт для упаков­ ки всех ресурсов: $ cd code/auction_dapp/_frontend $ npm run build

> frontend@ l . 0 . 0 build /home/aantonop/Dev/ethereumЬook/code/auction_

dapp/frontend

> node build/build . j s

Hash : 9ee l 3 4 d 8 db 3 c 4 4 dd5 7 4 d

Ve r s i o n : webp a c k 3 . 1 0 . 0 T ime : 2 5 6 6 5ms

Asset

Size

s t a t i c / j s /vendor . 7 7 9 1 3 f 3 1 6a a f l 0 2 ce c l l . j s s t at i c / j s / app . 5 3 9 6eadl 7 8 9 2 9 2 2 4 2 2 d 4 . j s

1 . 2 5 МВ

5 0 2 kB

s t a t i c / j s /man i f e s t . 8 7 4 4 7 dd4 f 5 e 6 0 a 5 f 9 6 5 2 . j s

1 . 5 4 kB

s t at i c / c s s / app . 0 e 5 0 d б a l d2 Ы e d 4 daa 0 3 d 3 0 6ced7 7 9 c c . c s s

1 . 1 3 kB

s t at i c / c s s / app . 0 e 5 0 d б a l d2 Ь l e d 4 daa 0 3 d3 0 6ced7 7 9 c c . c s s . map s t at i c / j s / vendor . 7 1 9 1 3 f 3 1 6aa f l 0 2 ce c l l . j s . map 1

Ьit. /y/2zn WUP9. - Прим. авт.

384

Хранение децентрализованного аукциона в Swarm

4 . 7 4 МВ

2 . 5 4 kB

sta ti c / j s / app . 5 3 9 6eadl 7 8 9 2 9 2 2 4 2 2 d 4 . j s . map

8 9 3 kB

s t a t i c / j s /man i fe s t . 8 7 4 4 7 dd4 f 5 e 6 0 a 5 f 9 6 5 2 . j s . map index . html

1 . 1 5 kB

7 . 8 6 kB

Bui ld complete . Эта команда создаст новую директорию, code!auction_dapp!frontend!dist, ко­ торая будет содержать всю клиентскую часть DApp, полностью упакованную в одном месте: di s t /

1 - - index . html static

1 -- css

1 - - app . O e 5 0 d б a l d2 Ы e d 4 daa 0 3d3 0 6ced7 7 9 c c . c s s

js

app . O e 5 0 d б a l d2 Ы e d 4 daa 0 3d 3 0 6ced7 7 9 c c . c s s . map

1 - - app . 5 3 9 6 eadl 7 8 9 2 9 2 2 4 2 2 d 4 . j s

1 - - app . 5 3 9 6eadl 7 8 9 2 9 2 2 4 2 2 d4 . j s . map

1 - - man i fe s t . 8 7 4 4 7 dd4 f 5 e 6 0 a 5 f 9 6 5 2 . j s

1 - - man i fe s t . 8 7 4 4 7 dd4 f 5 e 6 0 a 5 f 9 6 5 2 . j s . map 1 - - vendor . 7 7 9 1 3 f 3 1 6a a f l 0 2 ce c l l . j s

vendor . 7 7 9 1 3 f 3 1 6 aa f l 0 2 ce c l l . j s . map

Теперь вы можете загрузить все приложение в Swarm, используя команду

up и параметр -recur s i ve. Мы также укажем, что index.html является путем по умолчанию (de faul tpath) для загрузки DАрр-приложения: $ swarm - -bzzapi http : / / localhos t : 8500 - -recursive \ -- defaultpath dist/ index . html up dist/

Теперь весь наш децентрализованный аукцион размещен в Swarm и досту­ пен там же по URL-aдpecy: bzz://аЬ 1 64cf37dc 1 0647e43a233486cdeffa8334b026e32a480dd9cbd020cl 2d4581

Итак, мы немного продвинулись в децентрализации нашего приложения и сдедали так, что его стало сложнее использовать. Такой URL-aдpec куда менее дружественен к пользователям, чем привычное доменное имя auction_dapp.com. Глава 12. Децентрализованные приложения (DApps)

385

Неужели мы вынуждены пожертвовать удобством в угоду децентрализации? Не­ обязательно. В следующем разделе мы рассмотрим сервис имен Ethereum, кото­ рый поддерживает понятные адреса, сохраняя при этом децентрализованную сущность нашего приложения.

Се р вис им е н Ethereum ( ENS) Вы можете спроектировать лучший смарт-контракт в мире, но он будет бесполез­ ным, если у него не окажется доступа к хорошему интерфейсу для пользователей. В традиционном Интернете существует система доменных имен (англ. Domain Name System, сокр. DNS), которая преобразует понятные человеку на­ звания, вводимые в браузере, в IР-адреса или другие идентификаторы, скрытые от нас. В блокчейне Ethereum у этой проблемы есть свое, децентрализованное решение - сервис имен Ethereum (сокр. ENS 1 , или сервис именования). Например, адрес для пожертвований Ethereum Foundation выглядит как Ох fB 6 9 1 6 0 9 S ca l d f 6 0bB7 9Се 9 2 сЕ 3 Е а 7 4 c 3 7 c 5 d 3 5 9, но в кошельке с поддерж­ кой ENS он имеет простой вид (в виде читаемого имени. - Ред.) ethereum.eth. ENS - это больше чем просто смарт-контракт; это фундаментальное DАрр­ приложение, предоставляющее децентрализованный сервис имен. Более того, ENS поддерживается рядом других приложений для регистрации, управления, аукционов зарегистрированных имен. ENS демонстрирует как DАрр-приложе­ ния могут работать совместно. ENS - это приложение, созданное для обслужи вания других децентрализованных приложений, поддерживаемое их экосисте­ мой приложений Ethereum, встраиваемое в них, и т. д. В этом разделе мы рассмотрим принцип работы ENS. Мы покажем, как мож­ но создать собственное имя и привязать его к кошельку или адресу Ethereum, как встроить ENS в другое приложение DApp и как упростить использование ресур­ сов для своих DApps, предоставляя им ENS имена.

И стория появления сервиса имен Ethereum Регистрация имен стала первым применением блокчейна, не связанным с валю­ той. Пионером в этой области стал проект Namecoin. В документе White paper Ethereum 2 в качестве одного из демонстрационных приложений приводится си­ стема регистрации из двух строчек кода, похожая на Namecoin. 1 2

Англ.: Ethereum Name Service. - Прим. ред. White paper, или «белая бумага»; github.com/ethereum/wiki/wiki!White-Paper. - Прим. ред.

386

Сервис имен Ethereum (ENS)

Ранние версии клиента Geth и клиента на языке С++ (Ethereum) содержали встроенный контракт name reg ( больше уже не используется. - Ред.). Было на­ писано множество предложений и документов ERC с описанием сервиса имен, но серьезная работа над проектом реестра имен началась только в 2016 году, ко­ гда Ник Джонсон присоединился к Ethereum Foundation и взял эту инициати­ ву в свои руки. Сервис ENS был запущен 4 мая 2017 года (в так называемый день «Звездных войн» 1 ) после неудачной попытки запуска в 14 марта (в «день числа Пи» 2 ) .

Спецификация ENS ENS в основном описывается в трех предложениях по улучшению Ethereum: EIP-137, определяющем основные функции ENS, EIP-162, в котором описывает­ ся система аукционов для корневого домена .eth, и EIP-181, которая описывает спецификацию обратной регистрации адресов. ENS придерживается философии проектирования по принципу «сэндвича» 3 : очень простой слой внизу, на который опираются более сложные, но заменяе­ мые коды, с очень простым слоем на самом верху, распределяющим все средства по отдельным учетным записям.

Н ижни й слой : имена владел ь цев и сопоставителе й ENS работает с «нодами», а не с человеко-читаемыми именами. Для преобразо­ вания имени в формат ноды используется алгоритм Namehash. Базовый слой ENS представляет собой простой, но искусно написанный кон­ тракт (менее 50 строчек кода), который определен как ERC137. Он позволяет только владельцу нод указывать информацию об их именах и создавать их суб­ ноды (или подузлы, аналог поддоменов в DNS). К нижнему слою принадлежат только те функции, которые позволяют вла­ дельцу указывать сведения о собственной ноде (такие как сопоставитель, время жизни и передача владения) и создавать новые субноды.

Алгоритм Namehash Namehash -это рекурсивный алгоритм, который преобразует любое имя в хеш, позволяющий идентифицировать данное имя. 1 2

3

Англ.: Star Wars Day. - Прим. ред. Англ.: Pi Day. - Прим. ред. Англ.: sandwich design. - Прим. ред.

Глава 1 2. Децентрализованные приложения (DApps)

387

«Рекурсивный» означает, что для решения задачи мы сначала решаем более мелкую подзадачу и затем возвращаемся к оригинальной проблеме, используя полученный результат. Namehash рекурсивно хеширует элементы имени, генерируя уникальную строку фиксированной длины (или «ноду») для любого валидноrо ввода доме­ на. Например, узел Namehash для s ubdoma i n . examp l e . eth выглядит как keccak ( ' ' node ) + keccak ( ' < s ubdoma i n > ' ) . Под­ задача, которую мы должны решить, состоит в вычислении ноды для example . e th, то есть keccak ( ' < . eth> ' узел ) + ke ccak ( ' ' ) . Но вна­ чале мы должны вычислить ноду для e th, которая равна ke c c a k ( ) + ke c c a k ( ' ' ) .

Корневая нода (англ. root node) - это то, что мы называем «базовым приме­ ром)) нашей рекурсии. О чевидно, что мы не можем определить ее рекурсивно, иначе алгоритм никогда не завершится! Корневая нода имеет вид О х О О О О О О О

000000000000000000000000000000000000000000000000000000000

(32 нулевых байта). Таким образом, если свести все это вместе надой s ubdoma i n . examp l e . e th, будет ke c c a k ( ke c c a k ( k e c c a k ( О х О ... О + ke c c a k ( ' e th ' ) ) +

ke c c a k ( ' examp l e ' ) ) + ke c c a k ( ' subdoma i n ' ) ) .

В обобщенном виде функцию Namehash можно определить так ( база корне­ вой поды или пустое имя, за которым следует рекурсивный шаг): namehash ( [ ] ) = О х О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О О 000000000000

namehash ( [ l abe l , ... ] ) = kec c a k2 5 6 ( namehash ( _ ) + keccak2 5 6 ( l abe l ) )

На языке Python это можно выразить следующим образом: de f nameha s h ( name ) : i f name == ' ' :

return ' \ О ' * 3 2

else :

l abe l ,

, rema i nder = name . pa r t i t i on ( ' . ' )

return shaЗ ( nameha s h ( rema i nde r ) + s h a З ( l abe l ) )

И мя ma s te r i n g - e the reum . eth будет обработано так: namehash ( ' ma s t e r i ng-ethereum . e th ' )

� shaЗ ( n ameha s h ( ' eth ' ) + shaЗ ( ' ma s t e r i ng-ethe reum ' ) )

388

Сервис имен Ethereum (ENS)

⇒ shaЗ ( s haЗ ( namehash ( ' ' ) + s h a З ( ' e th ' ) ) + s h a З ( ' ma s t e r i ng-e thereum ' ) ) ⇒ shaЗ ( shaЗ ( ( ' \ 0 ' * 3 2 ) + s h a З ( ' e th ' ) ) + s h a З ( ' ma s t e r ing-ethereum ' ) )

Конечно, у субдоменов могут быть свои субдомены: после s ubdoma i n . examp l e . e t h могло бы идти имя s ub . s ubdoma i n . e x amp l e . e th, затем s ub . s ub . s ubdoma i n . e x amp l e . e t h и т. д. Namehash полагается только на само имя, поэтому, чтобы каждый раз не тратить ресурсы на его получение, ноду для заданного имени можно предварительно вычислить и затем вставить ее в контракт; таким образом мы �ожем пропустить работу со строками и сра­ зу приступить к поиску ENS заnисей независимо от того, из скольких элементов состоит («сырое») необработанное имя 1 •

Как выбрать валидное имя Имена состоят из набора меток (labels), записанных через точку. Допускаются большие и маленькие буквы, однако все метки должны проходить через про­ цесс нормализации UTS #46 2 , который перед хешированием переводит их в ниж­ ний регистр. В связи с этим одинаковые имена в разном регистре имеют одно и тот же окончание в имени хеша (Namehash). Метки и домены могут иметь произвольную длину, для совместимости с устаревшими версиями DNS рекомендуется соблюдать следующие правила: - ни одна из меток не должна превышать 64 символа; - все ENS имена должны занимать не больше 255 символов; - метки не должны начинаться или заканчиваться дефисом; они также не должны начинаться с цифры.

Владение корневой нодо й Одним из последствий этой иерархической системы является ее зависимость от владельцев корневой поды, у которых есть возможность создавать домены верхнего уровня (англ. top-level domain, сокр. TLD). В конечном счете процесс принятия решений относительно новых TLD доме­ нов должен стать децентрализованным, но на момент написания данной книги корневая нода контролируется четырьмя из семи подписей, принадлежащих лю­ дям из разцых стран (по аналогии с семью держателями ключей в системе DNS). В итоге для внесения любого изменения требуется согласие большинства вла­ дельцев, то есть как минимум четырех из семи.

1

Анrл.: raw name. - Прим. ред.

2

См. подробнее docs.ens.domains/contract-api-reference!name-processing. - Прим. ред. Глава 12. Децентрализованные приложения (DApps)

389

На сегодня предполагается, что целью держателей ключей является достиже­ ние консенсуса (согласия) с сообществом для того, чтобы: - мигрировать и сделать апгрейд временное владение TLD доменом .eth на более постоянный контракт после того, как система пройдет проверку; - позволить осуществлять добавление новых Т LD-доменов, если сообще­ ство посчитает это нужным; - передать владение корневой нодой с «мультисигом» более децентрализо­ ванному контракту, когда такая система будет согласована, протестиро­ вана и внедрена; - предоставить средство (способ) для борьбы с любыми ошибками (в коде) и уязвимостями в реестрах верхнего уровня.

Сопоставитеп и Базовый ENS контракт не может добавлять к именам метаданные; эта функция принадлежит так называемым контрактам-сопоставителям 1 • Это создаваемые пользователями контракты, которые могут отвечать на вопросы об именах: на­ пример, какой адрес Swarm связан с приложением, на какой адрес поступают платежи, отправляемые приложению (это может быть эфир или токены), или какой хеш у данного приложения (чтобы убедиться в его целостности).

Средний слой: ноды (узла) .eth На момент написания данной книги единственным доменом верхнего уровня, который можно уникальным образом зарегистрировать в смарт-контракте, яв­ ляется домен .eth.



В настоящее время идет работа над тем, чтобы позволить вла­ дельцам традиционных доменов DNS претендовать на владе­ ние в системе ENS. Теоретически это может работать и для до­ менов .сот, но пока это реализовано только для домена .xyz и только в тестнете Ropsten 2 •

Домены .eth распределяются с помощью системы аукционов. При этом нет никаких приоритетов или зарезервированных списков - имя можно приобре­ сти исключительно с помощью системы (ENS). Система аукционов представляет 1 2

Англ.: resolver contracts. - Прим. ред. medium.com/the-ethereum-name-service/how-to-claim-your-dns-domain-on-ens-eб00ef2d92ca. Прим. авт.

390

Сервис имен Ethereum (ENS)

собой довольно сложный фрагмент кода (более 500 строчек), в котором можно найти большую часть изменений (и ошибок!), внесенных в ENS на ранних ста­ диях разработки. Но вместе с тем этот код можно изменять и обновлять, не под­ вергая рискам средства (подробнее об этом чуть позже).

Аукционы В икри Имена распределяются через видоизмененный аукцион Викри 1 • В традицион ной версии этого аукциона каждый участник делает «слепую» ставку (Ьid), раз­ мер которой раскрывается одновременно со всеми другими; в этот момент право на покупку получает владелец, предложивший максимальную сумму, но в ка­ честве цены выбирается вторая по размеру ставка. Таким образом, участникам имеет смысл делать ставки не меньше реальной стоимости за имя, так как чем больше (ставку) они поставят, тем будут выше их шансы на победу; вместе с тем это не влияет на цену, которую им придется заплатить. При реализации аукциона на блокчейне он потребует некоторых изменений. - Чтобы участники не могли делать ставки, которые они не планируют опла­ чивать, они должны заранее заблокировать сумму в размере, не меньшем их собственной ставки. Это гарантирует, что ставка является обоснованной. - Поскольку в блокчейне нельзя скрыть никакие секреты, участники аук­ циона должны выполнить по меньшей мере две транзакции (в процессе исполнения схемы обязательств 2 ) , чтобы спрятать оригинальную сумму и имя, на которую они поставили ставку. - Поскольку в децентрализованной системе нельзя раскрыть все ставки од­ новременно, участники должны сделатьэто сами. В противном случае они теряют свои заблокированные средства. Если бы данного условия (потери заблокированных средств) не было, то один участник мог бы сделать мно­ жество ставок и раскрыть лишь одну или две из них, превращая тем са­ мым слепой аукцион в традиционный, где возрастает цена. Учитывая все вышесказанное, аукцион состоит из четырех этапов. l . Начало торгов. Этот этап нужен для того, чтобы транслировать свое на­ мерение о регистрации имени. В этот момент устанавливаются все край­ ние сроки. Имена хешируются, чтобы об открытии аукциона знали толь­ ко те, у кого в словаре находится соответствующее имя. Это придает 1

Англ.: Vickrey auction. - Прим. ред.

2

Англ.: commit-reveal process. - Прим. ред.

Глава 12. Децентрализованные приложения (DApps)

391

процессу некую конфиденциальность, что полезно в ситуациях, когда вы не хотите, чтобы кто-то узнал подробности о вашем новом проекте. Вы можете открыть сразу несколько фиктивных аукционов, чтобы тот, кто за вами следит, не мог сделать ставку во всех аукционах, открытых вами. 2.

Создание слепой ставки 1 • Это нужно сделать до истечения крайнего сро­ ка, привязав определенное количество эфира к хешу секретного сооб­ щения (которое помимо прочего содержит хеш имени, реальный размер ставки и соль). Вы можете заблокировать более крупную сумму, чем та, которую вы ставите, чтобы скрыть вашу настоящую оценку стоимости.

3. Раскрытие ставки. В этот период вы должны выполнить транзакцию, ко­ торая раскрывает вашу ставку. С помощью этой информации будут вы­ числены две самые высокие ставки, после чего неудачливые участники получат обратно свой эфир. Текущий победитель вычисляется заново при раскрытии каждой ставки; таким образом, тот, кто является побе­ дителем в момент истечения крайнего срока, выигрывает торги. 4. Последующая очистка. Если вас признали победителем, вы можете за­ вершить аукцион, чтобы получить обратно разницу между своей и вто­ рой по размеру ставкой. Если вы забыли раскрыть свою ставку, вы можете произвести позднее раскрытие и вернуть небольшую часть по­ ставленных на аукционе средств.

Верхний слой: dееd-контракты (акты} Верхний слой ENS системы представляет собой еще один суперпростой кон тракт с единственной задачей холдирования (удержания) средств. На самом деле, если вы выиграете имя, ваши средства никуда не отправля­ ются, а просто блокируются на период владения именем (как минимум год). Это своего рода гарантированный выкуп: если владелец хочет отказаться от имени, он может продать его обратно системе и вернуть свой эфир (поэтому стоимость удержания имени равна издержкам от действий с положительным доходом). Конечно, как доказано практикой, хранение миллионов долларов в одном кон­ тракте является очень рискованным, поэтому в ЕNS-системе создается для каждого нового имени отдельный dееd-контракт (акт). Этот контракт очень простой (при­ мерно 50 строчек кода); он позволяет возвращать средства лишь одной определен­ ной учетной записи (владельцу акта), и вызывать его может только одна сущность (регистратор контракта). Данный подход коренным образом снижает возможности атаки, когда из-за ошибок в коде средства становятся подвержены риску. 1

Англ.: sealed Ьid. - Прим. ред.

3 92

Серв ис имен Ethereum (ENS)

Регистрация имени Как мы узнали в разделе «Аукционы Викри» на с. 391, регистрация имени в ENS системе состоит из четырех этапов. Сначала мы делаем ставку на любое доступ­ ное имя, затем через 48 часов мы ее раскрываем, чтобы закрепить это имя за со­ бой. На рис. 12.5 показана диаграмма с поэтапным процессом регистрации. Давайте зарегистрируем наше первое имя! Мы воспользуемся одним из нескольких удобных интерфейсов для поиска доступных имен, сделаем ставку на ethereumbo o k . e t h, раскроем ее и полу­ чим имя в свое владение. У децентрализованного приложения с сервисом имен (ENS) есть ряд веб-ин­ терфейсов, которые позволяют с ним взаимодействовать. В данном примере вос­ пользуемся интерфейсом MyCrypto 1 в сочетании с нашим кошельком MetaMask.

Домен доступен для торгов

П е ио

азме ения ста вок (72 часа)

День О, начало аукциона Аукцион начинается, когда 1) имя становится доступным

Участни к с самой в ы со кой ставкой побеждает. Чтобы завладеть име нем, он должен заплатить сумму, эк вивал е нтную второ й по разме·ру ставке . Чтобы стать владельцем, п о бедитель должен за вершить аукцио н ( примечание: это можно в л ю бой мо мент п осле о ко нчания то ргов)

П е иод а скрытия (48 часов)

День 3, День 5, на чало периода завершение торгов раскрытия �--�------� С н а чалом периода ра скрытия новые ставки больше не принимаются.

и 2) ауtщион кем-то открыт (примечание: аукцион можно открыть без размещения ставки}

Ставки должны быть раскрыты самим участником, иначе он теряет 99,S% от поставленного эфира

П осле аукциона пока на смену временному регистратору не придет постоянный)

В любой момент после окончания торгов владелец может подготовить контракт-сопоставитель для своего имени

-

Timeline

Рис. 12.5. Пошаговый процесс регистрации имени (ENS) Вначале следует убедиться в том, что нужное нам имя доступно. Во время на­ писания этой книги нам очень хотелось зарегистрировать домен ma s te r i ng . eth, но, увы, он уже был занят (см. рис. 12.6)! Поскольку регистрация в ENS сер­ висе действует только один год, мы, возможно, сможем зарегистрировать его в будущем. А пока что давайте поищем ethe reumЬ o o k . e t h (см. рис. 12.6).

1

mycrypto.com. - Прим. авт.

Глава 12. Децентрализованные приложения (DApps)

393

Е) MyCrypto New Wallet

Send

GA.,: 'IIICI"

◄ 1 Gwel

f Swap

Send Offline

Contracts

ENS

�:_���=_) t

DomainSale

NПWOJII(

-

ТХ Status

.�

LAMGU,t,G;f

Enpih

"'

View lnfo

Try the МyCrypto lktr. ►

Help

ENS

The E�m Name Scrvi« 1s а distr\Ьuted, open. and extenslble namin, system Ьased on the Etl'lf:reum Ыockcha1n. Once уоо have а name, 'fOU can �1 your fr�ds to и-nd ЕТН to нwtop1a. ettl lmteIO of Вх4ЬЬеЕ8ебе�В

ethereumbook

ethereumbook.eth i s ava i l a Ы e ! - Do you want ethereumbook.eth? Unlock your Wallet to Start an Auctlon

Рис. 12.6. Поиск ENS имен на сайте MyCrypto.com Отлично! Имя доступно. Чтобы его зарегистрировать, нам нужно пе­ рейти дальше к рис. 12.7. Давайте разблокируем MetaMask и начнем торги за ethe reumЬoo k . e t h.

Name et h ereumbook

.eth

Actual Bid Amount

'You must rememЬer this to claim your name later.'

0 . 01

ЕТН

Bid Mask

'Тhis is the amount of ЕТН you send when placing your bld. lt has no Ьearing on the 'actual' amount you bld (аЬоvе). lt is simply to hide your real bld amount lt must Ье >= to your actual Ьid. •

0 . 01

ЕТН

Secret Phrase

'You must remember this to claim yoar name later (feel free to change this)

Есе

Ыооd annual S t .1 гt t h

0.010000 ЕТН 3.95 ll О

Amount Gas Limit Gas Price

ENS Registr•r 6090Аб ...78Еf



685978 l 1 1

'------...,---- .:::_)

4U

ITS

WEI

Мах Transaction Fee

О.02�125 ЕТН 11.12 1.JSD

Max Total

0.038125 ЕТН 15.07 uso Data lnduded: 132 Ьytes

В!НИЭ i!Fihl Рис. 12.9. Транзакция MetaMask, содержащая вашу ставку В случае успеха после отправки транзакции вы сможете вернуться и рас­ крыть свою ставку в течение 48 часов, и запрошенное вами имя будет зареги­ стрировано по вашему адресу Ethereum.

Уп ра вл ение ENS и менем Вы можете управлять зарегистрированным вами ENS именем с помощью еще одного пользовательского интерфейса - ENS Manager 1 • Открыв его, введите в поисковой строке имя, которое вас интересует (рис. 12.10). Ваш кошелек Ethereum (такой как MetaMask) должен быть разбло­ кирован, чтобы приложение ENS Manager могло управлять данным именем с ва­ шего разрешения.

Рис. 12. 1 0. Веб-интерфейс ENS Manager 1

manager.ens.domains. - Прим. авт.

396

Сервис имен Ethereum (ENS)

С помощью этого интерфейса мы можем создать поддомены, установить кон­ тракт-сопоставитель (подробнее об этом позже) и привязать каждое имя к под­ ходящему ресурсу, такому как Swarm адрес клиентской части DApp.

Создание ENS поддомена Давайте сначала создадим поддомен для нашего примера с децентрализованным аукционом (рис. 12.11). Назовем его a u c t i on, чтобы полное имя выглядело как auc t i on . ethe reumЬoo k . eth.

"'

1

: · : •- : : :' • • · · · -

C,1•t D1..•ЫLI�

Name: ethereumЬook.eth

Owner: 0X.Sab7aбaЬe87f29S224fS17S37dl760a894e8laft

Resolver: Oxlda022710dfS002339274aadee8dS8218e9dбabS

auction

' 1

(

, 1 t • •1, ,, , p t нl , 1 ,',1 1 1

Рис. 12. 1 1. Добавление поддомена auction.ethereumbook.eth После создания поддомена мы можем ввести au c t i on . ethereumbo o k . eth в поисковую строку и начать управлять этим именем точно так же, как мы это делали ранее с ethe reumЬoo k . e t h.

Глава 12. Децентрализованные приложения (DApps)

397

Е NS-сопоставители В ENS сопоставление имени состоит из двух этапов. 1. Реестру для сопоставления передается хеш имени. Если подходящая за­ пись существует, реестр возвращает адрес ее сопоставителя. 2. Вызывается сопоставитель с использованием метода, который подходит для запрашиваемого ресурса. Сопоставитель возвращает искомый ре­ зультат. У данного двухэтапного процесса есть несколько преимуществ. Разделение функций сопоставителей и самой системы имен дает нам намного больше гиб­ кости. Владельцы имен могут применять контракты-сопоставители для сопо­ ставления любых типов или ресурсов, расширяя тем самым функциональность ЕNS-сервиса. Например, если в будущем вы захотите привязать геолокационный ресурс (долготу/широту) к ЕNS-имени, вы можете создать новый сопоставитель, который отвечает на запросы типа geo l o c a t i on. Никто не знает, какие при­ ложения окажутся востребованы в будущем. Благодаря кастомизированным со­ поставителям вы ограничены лишь собственной фантазией. Для удобства предусмотрен стандартный публичный контракт­ сопоставитель, рассчитанный на разнообразные ресурсы, включая адрес (для кошельков или контрактов) и содержимое (Swarm хеш для DАрр-приложений или исходный код контрактов). Поскольку мы хотим привязать наш децентрализованный аукцион к Swarm хешу, мы можем использовать публичный сопоставитель, который поддержива­ ет сопоставление содержимого, как показано на рис. 12.12; писать код и развер­ тывать сопоставитель не обязательно.

398

Сервис имен Ethereum (ENS)

Name: ethereumbook.eth

Owner: 0x5ab7aбabe87f295224f517537df760a894e81afc

Resolver: Oxlda022710df5002339274aadee8d58218e9dбab5

Ох ...

Ox5ffc014343cd97lb7eb70732021e26c35t l Sl' . nvmrc $ nvm ins tall

Выглядит неплохо. Теперь установим t ruffle: $ npm -9 ins tall truJВe

+ truffie @ 4 . 0 . 6

i n s t a l l ed 1 p a c kage i n 3 7 . 5 0 8 s

Интеграция готового проекта Truffle (Тruffle Вох) Если вы хотите использовать или создать децентрализованное приложение из готового шаблона, зайдите на веб-сайт Trutfle Boxes, выберите существующий проект Trutfle и затем загрузите и распакуйте его с помощью следующей команды: $ truJВe unЬox

вох_NАИЕ

Создание директории проекта truffle Чтобы использовать t r uffle, вам нужно создать и инициализировать директо­ рию проекта. t ruffle создаст в ней все необходимые файлы и папки. Обычно директори_и дают название, которое описывает проект. В этом примере мы вос­ пользуемся t ruffle для развертывания нашего контракта Fau c e t из раздела «Простой контракт: тестовый контракт faucet с эфиром» на с. 69, поэтому назо­ вем папку нашего проекта Faucet:

468

Фреймворки

$ mkdir Faucet $ cd Faucet

Faucet $

Находясь в директории Faucet, инициализируем t ruffle: Faucet $ trullle init

t ruffle создаст структуру директорий и некоторые файлы по умолчанию: Faucet

+ - - - - contracts

' - - - - Migra t i o n s . s o l

+ - - - - migra t i o n s

' - - - - 1 i n i t i a l_mi g r a t i o n . j s

+---- test

+ - - - - t ruffie- c onfig . j s truffie . j s

Помимо самой утилиты truffle мы будем использовать ряд вспомогательных пакетов на JavaScript (Node.js). Мы можем установить их с помощью nprn. Ини­ циализируем структуру директорий nprn и согласимся с параметрами по умол­ чанию, которые нам будут предложены: $ npm init pac kage name : ve r s i on :

( fauce t )

(1 . 0 . 0)

de s c r i pt i on : entry point :

test command :

( t ruffie - config . j s )

g i t repo s i to r y : keywords :

author :

l i cense :

( ISC)

AЬout to w r i te t o Fauce t / p a c kage . j s on :

" narne " : " fa u c e t " ,

Дополнение Г. Инструменты разработки, фреймворки и библиотеки

469

'' de s c r i pt i on '' : '' '' ,

" ma i n " : " t ruffle - config . j s " , " d i re c t o r i e s " : {

" te s t " : " te s t "

},

" te s t " : " e cho \ " E r r o r : по t e s t spec ified\ " & & e x i t 1 " },

" autho r " :

'' l i ce n s e '' : ' 1 i s c ''

I s t h i s o k ? ( ye s )

Теперь можно установить зависи �юсти, которые упростят работу с t ruffle: $ npm install dotenv trufВe-wallet-provider ethereumj s-wallet

Теперь внутри нашей директории Faucet находится папка node_modules с несколькими тысячами файлов. Прежде чем развертывать приложение DApp в облачной промышленной сре­ де или в системе непрерывной интеграции, необходимо определиться с полем e n g i n e s, чтобы ваш проект был собран с подходящей версией Node.js и соот­ ветствующими зависимостями. Подробнее об этом поле можно прочитать в до­ кументации 1 • Кон фи гура ци я truffle t ruffle создает несколько пустых конфигурационных файлов, таких как truffle. js и truffle-con.fig.js. В системах семейства Windows имя truffle.js может привести к конфликту, так как иногда этот файл запускается вместо утилиты truffle. Что­ бы этого избежать, мы удалим truffle.js и будем использовать truffle-con.fig.js (сде­ лаем это в поддержку пользователей Windows, которые уже и так достаточно на­ страдались): $ rm trufВe . j s 1

docs. npmjs.com/files/package.json#engines. - Прим. ред.

470

Фреймворки

Теперь отредактируем файл tru.ffle-con.fig.js, заменив его содержимое приме­ ром конфигурации, представленным ниже: modu le . expo r t s = { netwo rks : {

l oca l node : { // Сеть , к которой подключа ется наш локальный узел netwo r k_i d : " * " , // Допуска ется любой сетевой идентифика тор ho s t : " l ocalho s t " ,

port : 8 5 4 5 ,

);

Эта конфигурация служит хорошей отправной точкой. Она настраивает одну сеть Ethereum по умолчанию (под названием l o c a l node); это подразумева­ ет, что мы используем полноценный или легковесный клиент Ethereum, такой как Parity. Прочитав эту конфигурацию, t ruffle подключится к локальному узлу по RPC, используя порт 8 5 4 5, t ruffle будет работать с той сетью Ethereum, с ко­ торой соединен локальный узел: главной или тестовой, такой как Ropsten. Ло­ кальный узел также будет предоставлять функции кошелька. В следующих разделах мы сконфигурируем для t ruffle дополнительные сети, такие как локальный тестовый блокчейн ganache и провайдер сетевого доступа Infura. Чем больше сетей мы добавим, тем сложнее станет конфигурационный файл, но вместе с тем мы получим больше свободы в процессе тестирования и разработки. И спол ьзование truffle дл я развертыван ия контракта Итак, у нашего проекта Faucet есть базовая рабочая директория со сконфигу­ рированной утилитой t ruffle и ее зависимостями. Контракты будут храниться в поддиректории contracts нашего проекта. Там уже находится «вспомогатель­ ный» контракт Migrations.sol, который занимается автоматическим обновлением других контрактов. Вы узнаете, как с ним работать, в следующем разделе. Давайте скопируем контракт Faucet.sol (из примера 2. 1) в поддиректорию contracts. Структура директорий проекта будет выглядеть так: Faucet

+ - - - - contracts

+ - - - - Fau c e t . s o l

Migrations . so l

Дополнение Г. Инструменты разработки , фреймвор ки и библиотеки

1

471

Теперь мы можем скомпилировать этот контракт с помощью truffie: $ tru!Вe compile

Comp i l ing . / contract s / Faucet . s o l ...

Comp i l ing . / co n t r a c t s /Mi g r a t i o n s . s o l ...

W r i t i n g a r t i f a c t s to . /bu i l d/ cont r a c t s

Процесс миграции в Truffle: скрипты развертывания Truffie предоставляет систему развертывания, которую называют миграция. Если вы уже работали с другими фреймворками, вам уже могло встречаться нечто подобное: Ruby on Rails, Python Django и многие другие языки и фрейм­ ворки имеют команду migra te. Во всех этих фреймворках миграция предназначена для управления измене­ ниями в структуре данных при переходе на разные версии ПО. В Ethereum все немного иначе. Смарт-контракты не подлежат изменению, а для их развертывания нужен газ. В связи с этим Truffie предлагает механизм для отслеживания контрак­ тов, которые уже были развернуты (и их версий). В сложных проектах с десятка­ ми контрактов и запутанными зависимостями желательно избежать платы за раз­ вертывание тех частей, которые не изменились. При этом вам вряд ли захочется вручную следить за тем, какие версии тех или иных контрактов уже развернуты. Механизм миграции Truffie делает это все автоматически с помощью контракта Migrations.sol, который отслеживает развертывания всех остальных контрактов. У нас есть лишь один контракт, Faucet.sol, поэтому система миграции будет, мягко говоря, излишней. К сожалению, нам придется ее использовать. Но, на­ учившись с ней работать на примере даже одного контракта, мы можем начать оттачивать некоторые полезные навыки в нашем процессе разработки. Когда за­ дачи станут более сложными, ваши усилия окупятся. Миграционные скрипты хранятся в директории migrations. Сейчас там нахо­ дится лишь один скрипт, l_initial_migration.js, который развертывает сам кон­ тракт Migrations.sol: 1 var M i g r a t i o n s

2

a r t i f a c t s . requ i re ( " . /Mi g r a t i o n s . s o l " ) ;

З modu l e . exports = function ( dep l o ye r ) 4

5};

dep l o ye r . dep l o y ( M i g r a t i o n s ) ;

Чтобы развернуть Faucet.sol, нам понадобится еще один миграционный скрипт. Давайте назовем его 2_deploy_contracts.js. Он будет очень простым 472

Фреймворки

и похожим на l_deploy_contracts.js, но с несколькими небольшими изменениями. На самом деле вы можете просто скопировать содержимое l_initial_migration.js и поменять все упоминания Migra t i o n s на Fau c e t : 1 var Faucet = a r t i f a c t s . requ i re ( " . / Faucet . s o l " ) ;

2

3 modu le . expo r t s = function ( deploye r ) 4

5};

dep l o ye r . dep l o y ( Faucet ) ;

Этот скрипт инициализирует переменную Fau cet, которая идентифициру­ ет исходный код Faucet.sol на языке Solidity в качестве артефакта, определяюще­ го контракт Fau c e t . Затем он вызывает функцию dep l oy, чтобы развернуть этот контракт. Итак, все готово. Давайте воспользуемся командой tr uffie migrate, что­ бы развернуть наш контракт. С помощью apryмeнтa -network следует указать сеть, в которой будет производиться развертывание. В конфигурационном фай­ ле мы указали только одну сеть под названием l o c a l node. Убедитесь в том, что ваш локальный клиент Ethereum уже запущен, и затем введите: Faucet $ truJИe migrate - -network localnode

Поскольку для подключения к сети Ethereum и управления нашим кошель­ ком мы используем локальный узел, нам необходимо авторизовать транзакцию, которую создает t ruffie . У нас запущен клиент p a r i t у, подключенный к тесто­ вому блокчейну Ropsten, поэтому во время миграции мы увидим всплывающее окно в веб-консоли Parity, как показано на рис. Г. 1 .

Рис. Г. 1. Parity просит подтвердить развертывание Faucet До rтолнение Г. Инструменты разработки, фреймворки и библиотеки

1

473

Всего выполняется четыре транзакции: одна для развертывания Mi gr а t i ons, одна для обновления счетчика развертываний до 1 , одна для развертывания Fau cet и еще одна для обновления счетчика развертываний до 2 . Truffle просигнализирует о завершении миграции, покажет каждую транзак­ цию и выведет адреса контрактов: $ trulВe miqrate -network localnode

U s i ng netwo r k ' l o c a l node ' .

Running migra t i on : 1 i n i t i a l_mi g r a t i o n . j s Depl o y i ng Migrat i o n s _

... O x f a O 9 0 dЫ 7 9 d 0 2 3d2 abae 5 4 3b 4 a 2 l a l 4 7 9 е 7 0 са 7 d 3 5 a 4 6 9 a 5 dl a 9 8Ь f c бbd8 0 fe 8

Migrat i on s : O x 8 8 6 l c2 7 7 1 5 5 5 0bed8 3 6 2 c 0 3 4 5 addl 5 8 4 8 9df бdb 0

Saving s u c ce s s fu l migrat i on t o netwo r k ...

... О х 9 8 5 с 4 а 3 2 7 1 6 8 2 6ddbe 4 eae2 8 4 1 0 4 be f 8 bc 6 9 e 9 5 9 8 9 9 f 6 2 2 4 ба 1 Ы 7 с 9dfсdб с 0 3

Saving a r t i f a c t s _

Run n i ng m i g r a t i o n : 2_depl o y_contract s . j s Dep l o y i n g Faucet_

... Oxecdbee f 7 7 f 0 5 5 8 e dc 6 8 9 4 4 0 е 3 4 Ь 7 ЬЬа 0 а 3Ьа 7 a 4 5 e 4 b 6 8 0 b 0 7 lb4 7 с 3 0 а 9 3 0 е 9 d б Faucet : Oxd0 l c d 8 e 7 bd2 9e 4 bff8 c l 6 9 3 f 5 9eee4 6 1 3 7 a 9 f 3 0 0

Saving s u c ce s s fu l migrat i o n t o networ k_.

_ O x l l f 3 7 6bd7 3 0 7 edddfd4 0 dc 4 a l 4 c 3 f 7 cb 8 4 b б c 9 2 l a c2 4 6 5 6 0 2 0 6 0 b 6 7 d0 8 f 9 fd 8 a

Saving arti facts_

И спользова ние консоли Truffle Truffle предоставляет консоль JavaScript, с помощью которой можно взаимодей­ ствовать с сетью Ethereum (через локальный узел), развернутыми контрактами и провайдером кошельков. В нашей текущей конфигурации ( l o c a l node) в ка­ честве узла и провайдера кошельков выступает локальный клиент Parity. Давайте запустим консоль Truffle и попробуем выполнить какие-нибудь команды: $ trulВe console -network localnode

t r uffie ( l o c a l n o de ) >

Truffle выводит строку приглашения, в которой указана выбранная сетевая конфигурация ( l o c a lnode).

474

Фреймвор ки

Важно помнить и осознавать, какую сеть вы используете. Слу­ чайное развертывание тестового контракта или выполнение транзакции в главной сети Ethereum было бы нежелательным. За такую ошибку можно дорого заплатить! Консоль Trufile поддерживает автодополнение, что облегчает исследование си­ стемного окружения. Если ввести часть команды и нажать ТаЬ, Trufile автомати­ чески ее завершит. При двойном нажатии ТаЬ будут выведены все возможные варианты, если нашему вводу соответствует сразу несколько команд. Если же на­ жать ТаЬ два раза в пустой строке, Trufile выведет список всех доступных команд: truffie ( l ocal node ) >

Array Boolean Date E r r o r Eva l E r r o r Fun c t i o n I nfin i t y JSON Math NaN NumЬe r Obj ect Range E r r o r Re ferenceError RegExp S t r i ng S yntaxE r r o r TypeError URI E r r o r de codeURI de c odeURI Component encodeURI

encodeURI C omponent eval i s Fi n i te i sNaN p a r s e F l oa t p a r s e i nt undefined ArrayBuffe r Buffe r DataView Faucet F l o a t 3 2Array F l o a t 6 4Array GLOBAL

I n t 1 6Ar ray I n t 3 2Array I n t 8Array I n t l Мар Migra t i o n s Promi s e Proxy

Refle ct Set StateManager S ymЬ o l U i n t 1 6Array Uint 3 2Array Uin t 8Array Uint B C l ampedArray WeakМap WeakSet WebAs s emЬ l y XMLHttpReque s t

a s s e r t a s yn c hooks buffe r c h i l d_proce s s

c l e a r immed iate c l e a r i nte rval c l earT imeout c l u s t e r cons o l e c r ypto dgram

dns doma i n e s cape eve n t s f s g l ob a l http http2 https modu l e net o s path

pe r f_hooks proce s s punycode que rys t r i ng readl ine repl requ i re root set immediate s e t i n t e rval s e t T imeout s t ream s t r i ng de coder tls t t y

unes cape u r l ut i l v 8 vm w е Ь З z l ib defineGet t er

_proto_

define S e tt e r

l o o kupGe t t e r

l o o kupS e t t e r_

constructor hasOwnPrope r t y i s PrototypeOf prope r t y i s Enume raЫe

toLoca l e S t r i ng toSt r i ng valueOf

Подавляющее больши н ство фун кций, отн осящихся к кошелькам и узлам, реализован ы в объекте wеЬ З , который является экземпляром библиотеки web3 . js. Объект wеЬ З извлекает RРС-и н терфейс к н ашему узлу Parity. Вы также мо­ жете заметить два других объекта со з н акомыми н азван иями: M i g r a t i o n s и Fau c e t . О н и представляют кон тракты, которые м ы только что разверн ули .

Дополнение Г. Инструменты разработки, фреймворки и библиотеки

475

Для взаимодействия с ними воспользуемся консолью Truffle. Давайте для нача­ ла проверим наш кошелек, используя объект wеЬЗ: t ruffie ( l ocalnode ) > weЬЗ . eth . accounts

[ ' 0 x 9 e 7 1 3 9 6 3 a 9 2 c 0 2 3 1 7 a 6 8 l b 9bb 3 0 6 5 a 8 2 4 9de l 2 4 f ' ,

' 0 xdb 5 dc l a l 3 e 3 a 5 5 c f 3 b 4 5 8 7 cd 8 d l e 5 fdeb 6 7 3 8 1 4 5 ' ]

Наш клиент Parity содержит два кошелька, которые хранят некоторое коли­ чество тестового эфира в сети Ropsten. В атрибуте wеЬЗ . eth . accounts нахо­ дится список всех учетных записей. Мы можем проверить баланс первой из них с помощью функции getBa l ance: t ruffie ( l ocalnode ) > weЬЗ . eth . getвalance (weЬЗ . eth . accounts [ O ] ) . toNumЬer ( ) 191198572800000000

truffie ( l oc a l node ) >

webЗ.js представляет собой крупную JаvаSсriрt-библиотеку, которая предо­ ставляет полноценный интерфейс к системе Ethereum через провайдера, такого как локальный клиент. Мы исследуем web3.js более подробно в дополнении Д. А пока давайте попробуем обратиться к нашим контрактам: truffie ( l oc a l node ) > Faucet . address

' 0 xd0 l cd 8 e 7bd2 9e 4bff8 c 1 6 9 3 f 5 9e e e 4 6 1 3 7 a 9 f 3 0 0 '

t ruffie ( l oc a l node ) > weЬЗ . eth . getвalance ( Faucet . addres s ) . toNumЬer ( ) о

truffie ( l o c a l node ) >

Далее воспользуемся функцией s endTran s a c t i on, чтобы отправить тесто­ вый эфир контракту Faucet. Обратите внимание на то, как метод wеЬЗ . toWe i автоматически преобразует единицы эфира. Ручной ввод 18 нолей был бы уто­ мительным и опасным, поэтому всегда лучше использовать автоматическое пре­ образование значений. Вот как мы отправим нашу транзакцию: t ruffie ( l ocalnode ) > weЬЗ . eth . sendTransaction ( { from : weЬЗ . eth . accounts [ O ] ,

, to : Faucet . address , value : weЬЗ . toWei ( 0 . 5 , ' ether ' ) } ) ;

' 0 x f 1 3 4 c 7 5b 9 8 5 dc 0 e 0 c2 7 c2 f 0 4 1 2 2 5 1 e 0 8 6 0 eb5 3 0 a 5 0 5 5 e 6 6 0 f2 1 e 7 4 8 3 ab3 3 6 8 0 8 '

476

Фреймворки

Если вернуться к веб-интерфейсу Parity, можно увидеть всплывающее окно с просьбой подтвердить транзакцию. После того как транзакция будет сгенери­ рована, мы сможем увидеть баланс контракта Fau cet: truffie ( l oc a l node ) > weЬЗ . eth . qetвalance ( Faucet . address) . toNumЬer ( )

500000000000000000

Теперь давайте вызовем функцию w i thdraw, чтобы вывести из контракта немного тестового эфира: truffie ( l ocalnode ) > Faucet . deployed ( ) . then ( instance =>

{ instance . withdraw (weЬЗ . toWei ( 0 . 1 , ' ether ' ) ) } ) . then ( console . loq)

Нам опять нужно будет подтвердить транзакцию в веб-интерфейсе Parity. Если снова проверить баланс контракта Faucet, можно заметить, что он умень­ шился. При этом наш тестовый кошелек получил 0,1 ЕТ Н: truffie ( l oc al node ) > weЬЗ . eth . qetвalance ( Faucet . address) . toNumЬer ( ) 400000000000000000

truffie ( l o c a l node ) > Faucet . deployed ( ) . then ( instance =>

{ instance . withdraw (weЬЗ . toWei ( l , ' ether ' ) ) } )

StatusErro r : T r a n s a c t i o n : 0 xe l 4 7 ae 9 e 3 6 1 0 3 3 4 _ 8 6 1 2b 9 2 d3 f 9 c exi ted with a n e r r o r ( s tatus О ) .

Embark GitHub: github.com/embark-frameworklembarkl Документация: embark.status. im/docsl Репозиторий пакетов npm: www. npmjs.com/package/embark Embark - это фреймворк, облегчающий разработку и развертывание де­ централизованных приложений. Он интегрируется с Ethereum, IPFS, Whisper и Swarm, предлагая следующие возможности: - автоматическое развертывание контрактов с доступом к ним из кода на JavaScript; - отслеживание изменений и обновление контрактов с повторным развер­ тыванием (если это необходимо); Дополнение Г. Инструменты разработки, фреймворки и библиотеки ,

1

477

- управление и взаимодействие с разными блокчейнами (например, тесто­ вым, локальным, главным); - управление сложными системами, состоящими из взаимозависимых контрактов; - хранение и извлечение данных, включая файлы, хранящиеся в IPFS; - упрощение процесса развертывания всего приложения в IPFS или Swarm. - отправка и получение сообщений через Whisper. Вы можете установить Embark с помощью npm: $ npm -q install emЬark

OpenZeppelin GitHub: github.com!OpenZeppelin!openzeppelin-solidity Веб-сайт: openzeppelin.orgl Документация: openzeppelin.orglapi/docs/open-zeppelin.html OpenZeppelin 1 - это фреймворк с открытым исходным кодом, состоящий из безопасных смарт-контрактов на языке Solidity. Он развивается сообществом под руководством команды Zeppelin 2 и имеет больше ста внешних участников. Приоритетом этого проекта является безопас­ ность, для достижения которой применяются шаблоны проектирования и реко­ мендуемые методики, ставшие отраслевым стандартом. Этот фреймворк опира­ ется на весь тот опыт, который разработчики Zeppelin получили в ходе аудита 3 огромного количества контрактов, а также на результаты тестирования и ауди­ та со стороны сообщества, которое использует OpenZeppelin в качестве основы для настоящих приложений. Это самое распространенное решение для разработки смарт-контрактов в Ethere­ um. На сегодня этот фреймворк содержит обширную библиотеку контрактов, вклю­ чая реализации токенов ERC20 и ERC72 I, многие разновидности модели групповых распродаж и экземпляры простой бизнес-логики, которую часто можно встретить в таких контрактах, как OwnaЫe, Pau s aЬle и Limi tBalance. Некоторые кон­ тракты из этого репозитория стандартными реализациями де-факто. ' openzeppelin.org. - Прим. ред. zeppelin.solutions. - Прим. ред. ' Ыog.zeppelin.solutions/tagged/security. - Прим. ред.

2

478

Фреймворки

Данный фреймворк доступен под лицензией МIТ, а все его контракты написаны с помощью модульного подхода, чтобы облегчить их повторное использо­ вание и расширение. Это аккуратные и простые компоненты, готовые к исполь­ зованию в вашем проекте для Ethereum. Давайте подготовим пакет OpenZeppelin и создадим простую групповую распродажу на основе одного из его контрак­ тов. Вы сами увидите, насколько легко это делается. Этот пример также подчер­ кивает важность повторного использования компонентов вместо написания их вручную. Для начала нам нужно установить в нашей рабочей среде библиотеку ope n z eppe l i n - s o l i d i t y. На момент написания этой книги последней вер­ сией была 1.9.0, поэтому мы используем именно ее: $ mkdir sample-crowdsale $ cd sample-crowdsale

$ npm install openzeppelin-solidi ty@ l . 9 . 0 $ mkdir contracts

На сегодня OpenZeppelin включает в себя разные простые контракты токе­ нов, которые соответствуют стандартам ERC20, ERC72.l и ERC827, но имеют раз­ ные показатели эмиссии, лимитов, наделения полномочиями, жизненного ци­ кла и т. д. Давайте напишем токен ERC20 с возможностью майнинга; то есть начальный запас будет равен О, а новые токены могут быть созданы владельцем (в нашем случае это контракт групповой распродажи) и проданы покупателям. Для этого создадим файл contracts/SampleToken.sol со следующим содержимым: pragma solidity 0 . 4 . 2 3 ; import ' openz eppe l i n - s o l id ity/ contrac t s / token/ERC 2 0 /MintaЬleToken . so l ' ; contract Samp l e T o ken is MintaЬleToken { strinq puЬlic name

" SAМPLE TOKEN " ;

strinq puЫic s ymЬo l = " SАМ " ;

uintB puЬlic de c ima l s = 1 8 ;

OpenZeppelin уже содержит контракт M i n t aЫ e T o ke n, который мож­ но использовать в качестве основы для нашего токена, поэтому мы определим лишь код, уникальный для нашего случая. Вслед за этим создадим контракт Допопнение Г. Инструменты разработки, фреймворки и бибпиотеки

479

групповой распродажи. Опять же, OpenZeppelin предоставляет широкий спектр подобных решений. На сегодня доступны контракты для различных сценариев, связанных с распределением, эмиссией, ценами и проверкой действительно­ сти. Давайте представим, что вы хотите задать желаемый результат для своей распродажи и, если он Н'е будет достигнут на момент ее завершения, вы обя­ зуетесь вернуть деньги всем своим инвесторам. Для этого подойдет контракт Re fundaЬ l e C r owds a l e 1 • Если же вам нужна групповая распродажа с воз­ растанием цены, чтобы поощрять ранних покупателей, можете воспользовать­ ся контрактом I n c r ea s i ng P r i c e C r owds a l e 2 • Вы также можете завершить транзакцию в момент, когда средства на счете контракта достигнут определен­ ной суммы (CappedC rowds a l e 3 ) , установить время завершения с помощью контракта T imedC rowds a l e 4 или создать белый список покупателей, иcпoль­ з� Wh i t e l i s tedC r owds a l e � Как уже упоминалось ранее, контракты в OpenZeppelin являются простыми компонентами, созданными специально для того, чтобы их можно было объединять; чтобы понять, каким образом расширить контракт C rowds a l e 6 , просто почитайте его исходный код. В ходе нашей групповой распродажи мы должны ге­ нерировать новые токены каждый раз, когда наш контракт получает эфир, поэто­ му давайте возьмем за основу MintedC rowds a l e 7 • А чтобы сделать этот при­ мер более интересным, давайте добавим контракт Po s t De l i ve ryC rowdsale 8 , чтобы токены можно было выводить только по окончании распродажи. Для это­ го мы запишем в файл contracts/SampleCrowdsale.sol следующий код:

1

github. com/OpenZeppelin/openzeppelin-solidity/ЫoЬ!v 1 . 9. О/contracts/crowdsa/e/distribution/ RefundaЬ/eCrowdsale.so/. - Прим. ред.

' gith u b. com/OpenZeppelin/ ope nzeppelin-solidity/Ы o Ь!v 1 . 9. О/ con tracts/crowdsa /e/price/ IncreasingPriceCrowdsale.sol. - Прим. ред. 3

gi th иЬ. сот/ OpenZeppelin/openzeppeli n-so/idi tу/ЫоЫv 1 . 9. О/contracts/crowdsa/e/validation/ CappedCrowdsale.sol. - Прим. ред.

4

github. com/OpenZeppelin/openzeppelin-so/idity/ЫoЬ!v 1 . 9. О/contracts/crowdsale/validation/ ТimedCrowdsale.sol. - Прим. ред.

5

github. com/OpenZeppelinlopenzeppelin-solidity!ЫoЬ!v 1 . 9. О/contracts/crowdsale/validation/ WhitelistedCrowdsale.sol. - Прим. ред.

6

github. com/OpenZeppelin/openzeppelin-solidity/Ь/oЬ!v 1. 9. 0/contracts/crowdsa/e/Crowdsale.sol. Прим. ред.

7

gith ub. com/OpenZeppelinlopenzeppelin-solidity/ЫoЬ!v 1 . 9. О/contracts/crowdsa/e/em ission/ MintedCrowdsale.sol. - Прим. ред.

• github. com/OpenZeppe/in/openzeppelin-solidity!ЫoЬ!v 1 . 9. О/contracts/crowdsa/e/distribution/ PostDeliveryCrowdsale.sol. - Прим. ред.

480

Фреймворки

pragma solidity 0 . 4 . 2 3 ; import ' . / Samp l eToken . s o l ' ;

import ' openz eppe l i n - s o l i d i t y / contra c t s / c rowds a l e / emi s s i o n /

MintedCrowds a l e . s o l ' ;

import ' openz eppe l i n - s o l i d i t y / contra c t s / c rowds a l e / \ d i s t r ibut i on / Po s t D e l iveryC rowdsa l e . s o l ' ;

contract Samp l eCr owds a l e is PostDel iveryC rowds a l e , MintedCrowd s a l e { constructor ( uint2 56

ope n i ngT ime ,

uint2 56

rate ,

uint2 56 address

c l o s i ngTime

wa l l e t ,

MintaЫeToken

puЬlic

token

C rowds a l e ( rate , _wa l l e t ,

t o ken )

PostDel iveryC rowds a l e ( open i ngT ime ,

c l o s i ngTime )

И снова нам не пришлось писать почти никакого кода; мы просто воспользо­ вались проверенными в реальных условиях контрактами, которые предоставило сообщество OpenZeppelin. Однако следует отметить, что данный случай отлича­ ется от того, с которым мы имели дело в контракте S amp l eToken. Если открыть автоматические тесты Crowdsale 1 , можно заметить, что они выполняются в изоля­ ции. Когда вы интегрируете разные модули в единый, более крупный компонент, тестирования каждого из них по отдельности будет недостаточно, поскольку взаи­ модействие между ними может вызвать неожиданное поведение. В частности, вы увидите, что множественное наследование, которое мы здесь используем, может удивить разработчика, если тот не понимает всех тонкостей Solidity. Наш демон­ страционный контракт S ampleC rowds a l e довольно простой, и он будет рабо­ тать именно так, как мы того ожидаем, потому что используемый нами фреймворк 1

github.com!OpenZeppelin!openzeppelin-solidity/tree!vl . 9.0/test!crowdsale. - Прим. ред.

Дополнение Г. И нструменты разработки, фреймворки и библиотеки

481

создан специально для облегчения подобных сценариев; но не следует слишком расслабляться из-за этой простоты. При интеграции компонентов фреймворка OpenZeppelin для построения более сложных решений необходимо проводить полное тестирование всех аспектов своего кода, чтобы гарантировать надлежа­ щее взаимодействие между разными модулями. Наконец, получив удовлетворительный результат и тщательно протестиро­ вав свой код, вам нужно его развернуть. OpenZeppelin хорошо интегрируется с Truffle, поэтому мы можем написать миграционный файл следующего содержа­ ния (migrations!2_deploy_contracts.js}. Подробности объясняются в разделе «Про­ цесс миграции в Truffle: скрипты развертывания» на с. 472. const S amp l eCr owds a l e const Samp l e T o ken modu l e . exports

=

=

=

a r t i f a c ts . requ i re ( ' . / Samp leC rowds a l e . s o l ' ) ;

a r t i f a c ts . requ i re ( ' . / Samp l e T o ken . s o l ' ) ;

function ( deploye r , netwo r k , account s ) {

const open i ngT ime

=

webЗ . e th . getB l o c k ( ' l a t e s t ' ) . t ime s t amp + 2 ; // 2

const c l o s i ngT ime

=

ope n i ngT ime + 8 6 4 0 0 * 2 0 ; // 20 дней

сек . в будущее const rate

=

new web З . B igNumЬ e r ( l 0 0 0 ) ;

const wa l l e t = accounts [ l ] ; return dep l oyer

. then ( ( ) = > { })

return dep l o ye r . deploy ( S amp l e T o ken ) ;

. then ( ( ) => {

return dep l oye r . deploy ( S ampl e C rowds a l e , ope n i ngT ime , c l o s i ngTime , rate ,

wa l l et , );

S amp l e T o ken . addre s s



482

Это был лишь краткий обзор нескольких контрактов из соста­ ва OpenZeppelin. Чтобы научиться большему и внести свой вклад, вы можете присоединиться к сообществу этого фрейм­ ворка.

Фреймворки

ZeppelinOS GitHhub: github.com/zeppelinos Веб-сайт: zeppelinos.org Блог: Ыog.zeppelinos. org ZeppelinOS 1 - это «открытая, распределенная платформа инструментов и сер­ висов поверх EVM, предназначенная для разработки и администрирования при ложений на основе смарт-контрактов безопасным образом». В отличие от кода OpenZeppelin, который нужно заново развертывать вме­ сте с каждым приложением, в котором он используется, код ZeppelinOS находит­ ся вне блокчейна. Как и в OpenZeppelin, приложениям, которым нужна опреде­ ленная функциональность (скажем, токен ERC20), не нужно изменять структуру и проводить аудит ее реализации. Но помимо этого использование ZeppelinOS позволяет напрямую взаимодействовать с реализацией токена в блокчейне очень похоже на то, как настольная программа общается с компонентами ОС, в которой она работает. В основе ZeppelinOS лежит очень гибкий контракт, известный как прокси. Он может служить оберткой для любого другого контракта, делая доступным его ин­ терфейс без необходимости реализовывать соответствующие геттеры и сеттеры; он также может обновлять этот контракт без потери состояния. С точки зрения Solidity прокси выглядит как обычный контракт, чья бизнес-логика содержится внутри библиотеки, которую в любой момент можно заменить с сохранением ее состояния. Связь прокси с его реализацией полностью автоматизирована и скры­ та от разработчика. Это позволяет сделать обновляемым практически любой кон­ тракт, не изменяя его кода. Больше о механизме прокси ZeppelinOS можно почи­ тать в блоге 2, а примеры его использования находятся на GitHub 3 • Разработка приложений с помощью ZeppelinOS похожа на то, как npm помо­ гает в написании кода на JavaScript. AppManager предоставляет пакеты для всех версий приложения. Пакет - это всего лишь директория с контрактами, у каж­ дого из которых есть один или несколько обновляемых прокси. AppManager предоставляет прокси не только для контрактов приложения, но и для реализа­ ций ZeppelinOS (в виде стандартной библиотеки). Полноценный пример нахо­ дится на странице exampleslcomplex4. 1

github.com/zeppelinos. - Прим. ред.

2

Ыog.zeppelinos. org/upgradeabllity-using-unstructured-storage. - Прим. ред.

3

github.com/zeppelinos!zos-liЬ!tree!master/examples. - Прим. ред.

4

github.com!zeppelinos/zos/tree/master/examples/lib-complex. - Прим. ред.

Дополнение Г. Инструменты разработки, фреймворки и библиотеки

483

Проект ZeppelinOS все еще разрабатывается. Он стремится предоставить обшир­ ный набор дополнительных возможностей, таких как инструменты для разработчи­ ков, планировщик с автоматизацией фоновых операций внутри контракта, награды за участие в разработке, площадка для обмена денежными средствами между прило­ жениями и многое другое. Все это описано в белом документе ZeppelinOS 1 •

Утилиты

EthereumJS helpeth: утилита командной строки

GitHub: github. сот/ethereumjs/helpeth

h e l p e t h - это инструмент командной строки для манипуляции ключами и транзакциями, который существенно упрощает жизнь разработчикам. Он входит в набор библиотек и инструментов EthereumJS: Usage : helpeth [ command ] Commands :

s i gnMe s s age

ve r i fyS i g < s ig>

ve r i f y S i g Pa r ams < r > < s >

createTx < t o >



a s s emЬ leTx < to > < da t a > < r > < s >

p a r s eTx

keyGe nerate [ f o rma t ]

keyConve rt

[ i capdi rect ]

keyDeta i l s

b i p 3 2 De t a i l s addre s s De t a i l s < addre s s > u n i tConve rt < f rom> < to > 1

zeppelinos.orglzeppelin_os_whitepaper.pdf - Прим. ред.

484

Утилиты

S i gn а me s s age

Ve r i f y s i gnature

Ve r i f y s i gnature paramet ers S i gn а t ra n s a c t io n

AssemЬle а transaction from its components

Parse raw t r a n s a c t i o n

Generate new key

Conve rt а key to VЗ ke ys t o re format

P r i n t key de ta i l s

P r i n t key de ta i l s for

а given path

P r i n t de ta i l s about an

addre s s

Conve rt between Ethe reum

units

Opt i ons :

- р , -private

P r i va t e key a s а hex s t r i ng

- pas swo rd-prompt

Prompt f o r t h e p r ivate k e y pas sword [ bo o l e a n ]

- pas sword

Pas sword f o r the p r ivate key

- k , -keyfil e

Enc oded key fil e

- mnemo n i c

Mnemo n i c f o r H D key de r i vat i on

- help

S h o w help

- show-private - ve r s ion

[ string] [ string]

[ string]

Show p r i v a t e k e y de t a i l s

[ bo o l e a n ]

Show ve r s i on numb e r

[ bo o l e a n ]

[ s t r i ng ]

[ bo o l e a n ]

dapp.tools Веб-сайт: dapp. tools/ dapp.tools - это комплексный пакет инструментов разработки, ориентирован­ ных на блокчейн и созданных в духе философии Unix. В их число входят: - Dapp - простой пользовательский инструмент для создания новых децен­ трализованных приложений, выполнения модульных тестов в Solidity, от­ ладки и развертывания контрактов, запуска тестовых сетей и т. д. - Seth - используется для составления транзакций, обращения к блокчей­ ну, преобразования форматов данных, выполнения удаленных вызовов и подобных ежедневных задач. - Hevm -это реализация EVM на языке Haskell с гибким консольным отладчи­ ком для Solidity. Используется для тестирования и отладки приложений DApp. - evmdis - дизассемблер для EVM; выполняет статический анализ байт­ кода, предоставляя более высокоуровневые абстракции по сравнению с операциями EVM.

SputnikVM SputnikVM 1 - это полноценная расширяемая виртуальная машина для разных блокчейнов на основе Ethereum. Она написана на языке Rust и может использо­ ваться в качестве двоичного файла, пакета cargo или разделяемой библиотеки. Ее также можно интегрировать через FFI, Protobuf и JSОN-интерфейс. У нее есть отдельный исполняемый файл, spu t n i kvm-dev, предназначенный для тести­ рования; он эмулирует большую часть JSON-RPC API и майнинга блоков. 1

github.com/etcdevteam/sputnikvm. - Прим. ред.

Дополнение Г. Инструменты разработки, фреймворки и библиотеки

485

Б и бл иоте ки

webЗ.js

web3.js - это АРI-интерфейс на языке JavaScript, совместимый с Ethereum. Он разработан фондом Ethereum Foundation и предназначен для взаимодействия с клиентами через JSON-RPC. GitHub: github.com!ethereum!web3.js Пакет в репозитории npm: www.npmjs.com/package!web3 Документация для web3.js API О.2х.х: Ьit.ly/2Qcyq1 C Документация для web3.js API 1 .0.0-beta.xx: Ьit.ly/2CT33p0

wе ЬЗ. ру wеЬЗ.ру - это библиотека на языке Python для взаимодействия с блокчейном Ethereum. Поддерживается фондом Ethereum Foundation. GitHub: github.com!ethereum!web3.py PyPi: pypi.python.org!pypi!web3!4.0.0b9 Документация: web3py. readthedocs. io/

EthereumJS EthereumJS - это набор библиотек и утилит для Ethereum. GitHub: github.com/ethereumjs Веб-сайт: ethereumjs.github. io/

webЗj web3j - это библиотека для Java и Android, предназначенная для интеграции с клиентами Ethereum и работы со смарт-контрактами. GitHub: github.com!web3j!web3j Веб-сайт: web3j. io Документация: docs. web3j. io

486

1

Библиотеки

EtherJar EtherJar - это еще одна Jаvа-библиотека для интеграции с Ethereum и работы со смарт-контрактами. Она предназначена для серверных проектов, основанных на Java 8+, и предоставляет низкоуровневый доступ к RPC, структура данных Ethereum и смарт-контрактам, а также высокоуровневую обертку вокруг них. GitHub: github. com/infinitape!etherjar

Nethereum Nethereum - это библиотека для интеграции Ethereum c.NET. GitHub: github.com!Nethereum/Nethereum Веб-сайт: nethereum. com! Документация: nethereum. readthedocs. io!en!latest/

ethers.js ethers.js - это компактная, завершенная, полнофункциональная и хорошо про­ тестированная библиотека для Ethereum с лицензией МIТ. На ее расширение и поддержку фонд Ethereum Foundation выделил грант DevEx. GitHub link: github.com!ethers-io!ethers.js Документация: docs. ethers. io

Платформа Emerald Платформа Emerald предоставляет библиотеки и компоненты пользовательско­ го интерфейса для создания децентрализованных приложений поверх Ethereum. Emerald JS и Emerald JS UI содержат набор модулей компонентов React для по­ строения приложений и веб-сайтов на JavaScript; Emerald SVG Icons -это набор пиктограмм, связанных с блокчейном. Помимо кода на языке JavaScript Emerald содержит библиотеку для Rust, которая позволяет управлять закрытыми ключа­ ми и подписями транзакций. Все библиотеки и компоненты Emerald доступны под лицензией Apache License версии 2.0. GitHub: github. сот!etcdevteam!emerald-platform Документация: docs. etcdevteam. сот Дополнение Г. Инструменты разработки, фреймворки и библиотеки

1

487

Тестиро вание смарт- контрактов В разработке смарт-контрактов применяется несколько распространенных фреймворков для тестирования. Они собраны в табл. Г.1. Таблица Г. 1 . Краткий перечень фреймворков для тестирования смартконтрактов

Фреймворк Я зык(-и) тестирования

Фреймворк Эмулятор тестирования бл окчейна

Веб-сайт

Truffle

JavaScript/Solidity Mocha

TestRPC/Ganache

https://truffleframework.coml

Embark

JavaScript

Mocha

TestRPC/Ganache

https:/lembark.status.im/docsl

Dapp

Solidity

ds-test (моди- ethrun (Parity) фикация)

https://dapp.tools/dapp/

Populus

Python

pytest

https://populus.readthedocs.io

Python chain emulator

- Truffle. Позволяет писать модульные тесты на JavaScript (на основе Mocha) или Solidity. Эти тесты выполняются в сети Ganache. - Embark. Интегрируется с Mocha для выполнения модульных тестов, напи санных на JavaScript. Сами тесты предназначены для контрактов, развер­ нутых в TestRPC/Ganache. Этот фреймворк автоматически развертывает смарт-контракты каждый раз, когда те изменяются. Он делает это только тогда, когда это действительно нужно. Embark включает в себя библиоте­ ку для быстрого запуска и тестирования ваших контрактов в EVM с по­ мощью таких функций как a s s e r t . equ a l. Команда emb a r k t e s t за­ пускает файлы с тестами в директории test. - Dapp. Использует код на языке Solidity (библиотеку под названием ds-te s t) и библиотеку e thrun (основанную на Parity и написанную на Rust) для вы­ полнения байт-кода Ethereum с последующей проверкой его корректности. Библиотека ds - t e s t предоставляет функции-утверждения для проверки корректности и события для вывода данных в консоль. В число функций утверждения входят: a s s e r t (bool condi t i o n )

a s s e rtEq ( addre s s а , addre s s Ь )

a s s e rtEq ( byte s 3 2 а , byte s 3 2 Ь ) a s s e rtEq ( i nt а , i n t Ь )

488

1

Тестирование смарт-контрактов

a s s e r tEq ( u i n t а , u i n t Ь )

a s s e r t E q O ( by t e s а , byt e s Ь )

expectEvent s E x a c t ( addre s s t a r ge t )

Журнальные команды выводят данные в консоль, позволяя использо­ вать их для отладки: l o g s ( byte s )

l o g_byt e s 3 2 ( byt e s 3 2 )

l o g_named_byt e s 3 2 ( byte s 3 2 ke y , byte s 3 2 va l )

l o g_named_addre s s ( byte s 3 2 ke y , addre s s va l ) l og_named_i n t ( byt e s 3 2 ke y , i n t va l )

l og_named_u i n t ( byt e s 3 2 ke y , u i n t va l )

l o g named_de c i m a l i n t ( byte s 3 2 ke y , i n t va l , u i n t de c i ma l s )

l o g named_de c imal_u i n t ( byte s 3 2 ke y , u i n t va l , u i n t de c i ma l s )

- Populus. Использует Python и собственный эмулятор блокчейна для вы­ полнения контрактов, написанных на Solidity. Модульные тесты пишут­ ся на языке Python с помощью библиотеки pyte s t. Populus позволяет со­ здавать контракты, предназначенные непосредственно для тестирования. Файловые имена этих контрактов должны соответствовать шаблону Test*. sol и находиться где-нибудь внутри директории тестирования проекта, tests.

Тестирование в рамка х блокч ейна Обычно тестирование не следует проводить для уже развернутых контрактов, однако их поведение можно проверить с помощью клиентов Ethereum. Ниже пе­ речислены команды, которые позволяют оценить состояние смарт-контракта. Их нужно вводить в терминале geth, хотя они также поддерживаются в любой консоли на основе wеЬЗ. Чтобы получить адрес контракта по хешу его транзакции, используйте: eth . g e t T ra n s a c t i onRe c e i p t ( txha sh ) ;

Эта команда возвращает код контракта, развернутого по адресу соп t ra c t a ddre s s; ее можно использовать для подтверждения корректности развертывания: eth . getCode ( con t ra c t a ddre s s )

Дополнение Г. Инструменты разработки, фреймворки и библиотеки

489

Эта команда возвращает полные журнальные записи контракта, размещен­ ного по адресу, который указан в op t i ons. Полезно для просмотра истории вызовов контракта: eth . ge t P a s t Logs ( op t i on s )

Наконец, данная команда возвращает хранилище, размещенное по адресу a ddres s, со смещением posi t i on: eth . getStorageAt ( a ddre s s , posi t i on )

Ganache: локал ь ны й блокч ейн для тестирования Ganache - это локальный тестовый блокчейн, который можно использовать для развертывания контрактов, разработки приложений и выполнения те­ стов. Он доступен в виде приложения (с графическим пользовательским ин терфейсом) для Windows, macOS и Linux. Он также распространяется в виде утилиты командной строки под названием ganache - c l i . Подробности и ин­ струкции по установке настольной версии Ganache можно найти на странице truffleframework.com!ganache. Код ganache - c l i находится по адресу github.com!trufflesuite!ganache-cli!. Чтобы установить консольную версию gana che - c l i, используйте npm: $ npm install -g ganache-cli

С помощью ganache - c l i можно запустить локальный блокчейн для те­ стирования: $ ganache-cli \

networkid=З \

port= " 8545 " \ verbose \

gasLi.mit=S O O O O O O \

gasPrice=4 000000000 ;

Несколько замечаний по поводу этой команды. - Убедитесь в том, что значения флагов -netwo r k i d и -po r t совпадают с вашей конфигурацией в файле truffle.js.

490

Тестирование смарт-контрактов

- Убедитесь в том, что значение флага -g а s L imi t соответствует текущему лимиту на газ в главной сети (например, 8 ООО ООО единиц газа), который можно узнать на сайте ethstats. net. В противном случае вы рискуете полу­ чить исключение out of gas (недостаточно газа). Отметим, что -g as Р r i се со значением 4 О О О О О О О О О обозначает цену на газ в размере 4 Гвэй. - При желании можно использовать флar -mnemoni с, чтобы восстановить предыдущий НD-кошелек и связанные с ним адреса.

ДОПОЛ НЕНИЕ Д

Ру ко в од ств о п о раб оте с webЗ.js О писание

Это руководство основано на web3.js версии [email protected] и предлагается в качестве введения для работы с данной библиотекой. JаvаSсriрt-библиотека web3.js представляет собой набор модулей с определен­ ными функциями для работы в экосистеме Ethereum, а также API-интерфейс на языке JavaScript, который реализует общую спецификацию JSON RPC для дан­ ной блокчейн -сети. Этот скрипт необязательно запускать на своей локальной ноде, так как он ис­ пользует сервисы Infura 1 •

Простое взаимодействие с контрактами в асин хронном стиле с помо щ ью webЗ.js Убедитесь в том, что у вас установлена подходящая версия npm: $ npm -v 5.6.О

Инициализируйте npm, если вы еще этого не сделали: $ npm init

Установите базовые зависимости: ·$ npm i coппnand-line-args $ npm i wеЬЗ

$ npm i node-rest-client-promise

' infura.io. - Прим. ред.

492

1

О п исание

В результате ваши новые зависимости будут записаны в файл package.json.

Выполнение скрипта Node.js Простой запуск: $

node code/webЗ j s /weЬЗ-contract-basic-interaction . j s

Используйте собственный токен Infura (зарегистрируйтесь на сайте infura. io/ и сохраните АРI-ключ в локальном файле с именем infura_token) : $ node code/webЗ j s /webЗ -contract-basic-interaction . j s \ -- infuraFileToken /path/ to/file/wi th/infura_token

или: $ node code/weЬЗ j s /webЗ- contract-basic-interaction . j s \ /path/ to/file/with/infura_token

Эти команды прочитают ваш токен из файла и передадут его в качестве ар­ гумента для самого скрипта.

Обзор демонстрационного скрипта Далее мы просмотрим наш демонстрационный скрипт, web3-contract-basic­ interaction. Для получения базового провайдера wеЬЗ используется объект Web 3 : var wеЬЗ = new WebЗ ( i n fu ra_h o s t ) ;

После этого мы сможем взаимодействовать с wеЬЗ и попробовать некоторые простые функции. Давайте выведем версию протокола: web З . e th . getProtocolVe r s io n ( ) . then ( function ( protocolVe r s io n ) con s o l e . l og ( ' Protocol Ve r s i o n : $ { protoco1Ve r s i o n ) ' ) ;

})

Дополнение Д. Руководство по работе с webЗ.js

493

Теперь давайте проверим текущую стоимость газа: webЗ . e th . getGa s P r i c e ( ) . then ( function ( ga s P r i ce ) console . l og ( ' Ga s P r i c e : $ { ga s P r i ce } ' ) ;

} )

Какой последний сгенерированный блок в текущей цепочке? webЗ . e th . ge t Bl oc kNumЬ e r ( ) . then ( function ( Ы o c kNumbe r ) c o n s o l e . l og ( ' B l o c k Numbe r : $ { Ы o c kNumЬer } ' ) ;

} )

Взаимоде й ст вие с контракто м Теперь давайте попробуем организовать взаимодействие с контрактом. В этих примерах мы будем использовать контракт WE т Н 9 _ с о n t r а с t 1 в тестнете Kovan. Для начала инициализируем адрес нашего контракта: var our contract addre s s

" 0xd0AlE 3 5 9 8 1 1 3 2 2 d 9 7 9 9 1 E 0 3 f 8 6 3 a O C 3 0 C 2 c F0 2 9C " ;

Теперь мы можем проверить его баланс: webЗ . e th . getBa lance ( our_contract_addre s s ) . then ( function ( b a l ance ) {

c o n s o l e . l og ( ' Balance o f $ { ou r contract addre s s } : $ { ba l ance } ' ) ;

} )

и просмотреть его байт-код: web З . e th . getCode ( ou r contract addre s s ) . then ( function ( code ) c o n s o l e . l og ( code ) ;

} )

Дальше мы подготовим наше окружение для работы с АРI-интерфейсом обо­ зревателя Etherscan.

1

kovan.etherscan. io!address/0xd0al e35981 1322d97991 e03f863a0c30c2cf029c#code. - Прим. ред.

494

Взаимодействие с контрактом

Инициализируем URL-aдpec нашего контракта для работы с АРI-интерфей­ сом Etherscan в блокчейне Kovan: var e t h e r s c a n u r l =

" h ttps : / / kovan . e t he r s ca n . i o / ap i ?modu l e = c o n t r a c t & a c t i o n = ge t aЫ &

address=$ { ou r c o n t r a c t_addre s s } "

Инициализируем также RЕSТ-клиент для работы с АРI- интерфейсом Etherscan: var c l i e n t

requ i re ( ' node - re s t - c l i e n t - p r omi s e ' ) . C l i e n t ( ) ;

и получим объект

Promi s e для клиента:

c l i e n t . ge t P romi s e ( e t h e r s can u r l )

Получив действительный объект

P r omi s e , мы можем извлечь АВI -интер­

фейс нашего контракта через API Etherscan: . then ( ( c l i e n t_p romi s e ) our c o n t r a c t аЫ

=>

{

JSON . pa r s e ( c l i e n t_pr om i s e . da t a . r e s u l t ) ;

Теперь мы можем создать объект нашего контракта в виде следующего использования:

P r omi s e для по­

return new Promi s e ( ( re s o l ve , re j e c t ) => {

var o u r c o n t r a c t = new webЗ . e t h . C o n t r a c t ( ou r c o n t r a c t аЫ ,

o u r c o n t r a c t addres s ) ;

try {

// Если в се пройдет ка к нужно re s o lve ( ou r c o n t r a c t ) ;

catch ( е х )

{

// Если что -то пойдет не так reject (ex) ;

} ) ; } )

Допопнение Д . Руководство по работе с webЗ.js

495

Если

Promi s e нашего контракта успешно вернет результат, мы сможем на­

чать с ним взаимодействовать: . then ( ( our contract ) => {

Давайте проверим адрес нашего контракта: c o n s o l e . l og ( ' Ou r Contract address :

$ { our_cont ract . addr e s s ) ' ) ;

или: c o n s o l e . l og ( ' Ou r Contract address in anothe r way : $ { ou r contract . op t i o n s . address } ' ) ;

Теперь давайте обратимся к АВI-интерфейсу нашего контракта: c o n s o l e . l og ( " Ou r contract abi : " +

JSON . s t r i n g i f y ( ou r_cont r a c t . opt i o n s . j s o n l n t e r f a ce ) ) ;

Чтобы просмотреть общий объем средств, находящийся на контракте, мож­ но воспользоваться функцией обратного вызова: our contract . rnethods . t o t a l Supp l y ( ) . ca l l ( function ( e r r , t o t a l Supp l y ) { if ( ! e r r ) {

) else

c o n s o l e . l og ( ' To t a l Supp l y w i t h а c a l l ba c k : $ { total Suppl y ) ' ) ; c o n s o l e . l og ( e r r ) ;

}) ;

Вместо обратного вызова можно использовать возвращенный объект Р romi s е: ou r_c o n t ract . rne thods . t o t a l Supp l y ( ) . ca l l ( ) . t hen ( function ( t o t a l Supp l y ) {

con s o l e . l og ( ' To t a l Supp l y w i t h а p r orni s e : $ { t o t a 1 Supp l y ) ' ) ;

} ) . ca t c h ( function ( e r r ) c on s o l e . l o g ( e r r ) ;

}) ;

496

Взаимодействие с контрактом

Асинхронные операции с помо щ ью await Итак, в ы познакомились с базовым руководством п о работе с wеЬЗ. Теперь вы можете выполнить те же операции с помощью асинхронной конструкции awa i t. Просмотрите скрипт web3-contract-basic-interaction-asyncawait.js в дирек­ тории code/web3js 1 и сравните его с вышеприведенными примерами. Конструк­ ции async-await облегчают чтение кода и делают асинхронное взаимодействие похожим на последовательность блокирующихся вызовов.

' github.com/ethereumbook/ethereumbook!tree!develop!code!web3js. - Прим. ред.

ДОПОЛНЕНИЕ Е

С п исок сокра щенны х сс ылок В этой книге мы использовали сокращенные ссылки. Они занимают меньше ме­ ста на странице, и их проще вводить в браузере, если вы являетесь читателем печатного издания. Но такие сокращения могут устареть, а компании, которые предоставляют эти услуги, могут закрыться или заблокировать определенные адреса. Ниже приводится список всех ссылок в том порядке, в котором они ука­ заны в тексте.

Б езопасность смарт- контракто в Сокращенная Развернутая ссылка ссылка 20gvnng

solidity.readthedocs.io/en/latest/units-and-globa/-variaЬ/es.html#address-related

2EVo70v

solidity.readthedocs.io/en/latest/security-considerations.html#use-the-checks­ effects-interactions-pattern

2EQaLCI

hackingdistributed.com/20 1 6/06/1 8/analysis-of-the-dao-exploit/

2MOfВPv

consensys.github.io/smart-contract-best-practices/known_attacks/#integer­ overflow-and-underflow

2xvbx1 M

randomoracle.wordpress.com/20 1 8/04/27/ethereum-solidity-and-integer-overflowsprogramming-Ыockchains-like- 1 970/

2CUf7WG

github.com/ethereum/E/Ps/ЫoЬ/master/EIPS/eip-20.md

2RovrDf

solidity.readthedocs.io/enllatest/introduction-to-smart-contracts.html

2ААЕ1Ь8

ethereum.stackexchange.com/questions/3667/difference-between-calf-calfcodeand-de/egatecaf/

20i7UIH

solidity.readthedocs.io/en/latest/introduction-to-smart-contracts.html#de/egateca//callcode-and-/ibraries

2RmueMP

solidity.readthedocs.io/enllatest/abl-spec.html#function-selector

2Dg7GtW

medium.com/chain-cloud-company-Ыog/parity-multisig-hack-again-b4677 1eaa838

2(Uh2KS

ethereum.stackexchange.com/questions/1 9 1 /how-can-i-securely-generate-arandom-number-in-my-smart-contract

2Q5891x

Ыog.positive.com/predicting-random-numbers-in-ethereum-smart-contractse5358c6b8620

498

Безо п а сность смарт- контрактов

Сокращенная Развернутая ссыnка ссыnка 2JtdqRi

etherscan.io/address/0x95d3498009538085 7 902ccd9a 1 fb4c8 1 Зс2сЬ639#соdе

2Q58VyX

www.reddit.com/r/ethdev/comments/7x5rwr/tricked_by_a_honeypot_contract_or_ beaten_by/

2yKme14

vessenes.comlthe-erc20-short-address-attack-explained/

2yFOGRQ

medium.com/huzzle/ico-smart-contract-vulnerabllity-short-addressattack-3 1 ас9 1 77еЬбЬ

2CQjBhc

www.reddit.com/r/ethereum/comments/6r9nhj/cant_understand_the_erc20_short_ address_attack/

2Q5VIG9

solidity.readthedocs.io/en/latestlabl-spec.html

2Q1 ybpQ

vessenes.com/the-erc20-short-address-attack-explained/

2RnS1vA

hackingdistributed.com/20 1 6/06/1 6/scanning-live-ethereum-contracts-for-bugs/

2CSdF7y

solidity.readthedocs.io/en/latest/common-patterns.html

20fНalK

github.com/etherpot/contract/ЫoЬ/master/app/contracts/lotto.so/

2Jpzf4x

aakilfernandes.github.io/Ыockhashes-are-only-good-for-256-Ь/ocks

2ACsfi1

www.kingoftheether.com/thrones/kingoftheether/index.html

2ESoaub

www.kingoftheether.com/postmortem.html

2Q6E41P

consensys.github.io/smart-contract-best-practiceslknown_attacks/#race-conditions

2yl5Dv7

github.com/ethereum/wiki/wiki/Ethash

2SygqQx

hackingdistributed.com/20 1 7/08/28/submarine-sendsl

2EUILzb

hackernoon.comlfront-running-bancor-in- 1 50-lines-of-python-with-ethereum-api­ d5e2Ьfd0d798

20h8j7R

etherscan.io/address/0xf457 1 7552f12eflcb65e95476f2 1 7ea008 167ae3

20dUC9C

solidity.readthedocs.io/en/latest/units-and-global-variaЬ/es.html

2AAebFr

etherscan.ioladdress/0x0d8775f648430679a709e98d2b0cb6250d2887ef#code

2Q1 AMA6

applicature.com/Ьlog/history-of-ethereum-security-vulnerabllities-hacks-and-their­ fixes

2ESWG7t

etherscan.io/address/0xe827 1 9202e5965Cf5D98667387503a3b92DE20be#code

2ERIOpb

medium.com/cryptronics/storage-allocation-exploits-in-ethereum-smart­ contracts- 1 6c2aa3 12743

20gxPtG

www.reddit.com/r/ethdev/comments/7wp363/how_does_this_honeypot_work_it_ seems_like_al

20VkSL4

medium.com/coinmonks/an-analysis-of-a-coup/e-ethereum-honeypot­ contracts-5c07c95b0a8d

20gp21a

github.com/ethereum/wiki/wiki/Safety#beware-rounding-with-in teger-division

2SwDnEO

vessenes.com/ethereum-contracts-are-going-to-be-candy-for-hackers/

2qm7ocJ

vessenes.com/tx-origin-and-ethereum-oh-myl

2РЗКVА4

medium.com/coinmonks/so/idity-tx-origin-attacks-582 1 1 ad955 14

Дополнение Е. Список сокращенных ссылок

499

Токены Сокращенная ссыnка

Развернутая ссыnка

2CUf7WG

github.com/ethereum/E/Ps/ЫoЬ/master/EIPS/eip-20.md

2EUYCMR

github.com/ConsenSys/Тokens/ЫoЬ/master/contracts/eip20/EIP20.so/

2хРУсkб

github.com/OpenZeppelin/openzeppelin-solidity/ЬloЬ/v 1. 12.0/contracts/token/ ERC20/StandardToken.sol

Об авторах Андреас Антонопулос заслужил признание как автор бестселлеров, докладчик, преподаватель и один из главных мировых специалистов по Bitcoin и открыто­ му блокчейну. Андреас делает сложные темы доступными и понятными. Он из­ вестен по своим поразительным выступлениям, сочетающим экономику, психо­ логию, технологии и теорию игр с текущими событиями, историями из жизни и историческими прецедентами - непринужденно переводя сложные пробле­ мы технологий блокчейна из абстракции в реальным мир. В 20 14 году Андреас написал уникальную и инновационную книгу «Осва­ иваем Bitcon», которая многими расценивается как лучшее техническое руковод­ ство о технологиях, когда-либо изданное об этой технологии. Его вторая книга, «Интернет денег», которая описывает потенциальное воздействие технологий на человеческую цивилизацию, стала бестселлером на Amazon. Ее долгождан­ ный второй том вышел осенью 20 17 года, сотни копий которого были прода­ ны только за первый месяц. «Осваиваем Ethereum» стала его четвертой книгой. Гэвин Вуд на протяжении всей своей жизни интересуется пересечением тео­ рии игр, общества и технологий. Он начал программировать в возрасте девя­ ти лет; во времена Commodore Amiga он учился в Королевской грамматической гимназии в Ланкастере (Великобритания) и выигрывал призы как разработчик. Он получил докторскую степень в области компьютерных наук в Университете Йорка и закрепил за собой репутацию плодотворного программиста, возглав­ ляющего и участвующего во многих открытых проектах (наиболее известным из которых является KDE). Его профессиональная карьера включает в себя пери­ од в издательстве видеоигр (где он работал над переносимой, высокопроизводи­ тельной звуковой подсистемой) и Microsoft Research (где он писал встраиваемые проблемно-ориентированные языки на основе С++). Помимо своей непосред­ ственной работы он занимался преподаванием математики, искусства и англий­ ского языка для школьников и проектировал настольные игры (включая Milton Keynes), системы голосования и продвинутые инструменты программирова­ ния (кульминацией чего стала первая интегрированная среда разработки DSL на С++). После участия в двух других стартапах общий друг познакомил Гэвина Вуда с Виталиком Бутериным, и в течение нескольких недель он закончил напи сание первой рабочей реализации Ethereum. Он быстро стал техническим дирек­ тором и фактическим архитектором этой платформы. Ему принадлежат такие Об авторах

501

термины как EVM, РоА и Web 3.0, последний из которых позже перерос в его прогрессивную идею о децентрализованной веб-платформе. Гэвин Вуд создал язык разработки контрактов Solidity и написал «Желтый документ» - первую в своем роде формальную спецификацию протокола блокчейна, которая явля­ ется одной из ключевых характеристик, отличающих Ethereum от других подоб­ ных технологий. Через несколько месяцев после запуска Ethereum Гэвин Вуд вместе с несколь­ кими другими участниками покинул свой пост технического директора Ethereum Foundation и основал венчурную фирму Parity Technologies, чтобы продолжить развитие своего оригинального видения этой платформы. В настоящее время он помимо прочего является основателем и президентом WеЬЗ Foundation и рабо­ тает над Polkadot - блокчейном следующего поколения, который должен стать платформой для дальнейших инноваций в данной области.

В заве рш ен и е На обложке этой книги изображены китайские восковые пчелы (лат. Apis cerana). Этот вид распространен в странах Юго- Восточной Азии. Данное насекомое де­ монстрирует очень сложное поведение, которое в конечном счете приносит пользу всему улью. Каждая отдельная пчела работает свободно, соблюдая набор простых правил, и сообщает о важных находках с помощью феромонов и так на­ зываемого пчелиного танца. Этот танец передает ценную информацию, такую как позиция солнца и географические координаты цели относительно улья. Пче­ лы могут передавать полученные сведения дальше или использовать их в сво­ ей работе, выполняя тем самым децентрализованную волю роевого интеллекта. Несмотря на то, что пчелы используют кастовую систему и производят по­ томство с помощью пчеломатки, у них нет центральных власти, лидера. Высо­ коинтеллектуальное и сложное поведение, демонстрируемое колонией с мно­ готысячной популяцией, является результатом взаимодействия отдельных участников в как бы «социальной сети». Природа демонстрирует, что децентрализованные системы могут быть устой­ чивыми и достигать разных уровней сложности и невероятной утонченности без центрального управления, иерархии или сложных составляющих. Многие животные, изображенные на обложках издательства O'Reilly, нахо­ дятся под угрозой исчезновения; все они важны для нашей планеты. О том, как им помочь, можно узнать на сайте animals. oreilly. com. Титульная иллюстрация была взята из книги «Животная жизнь в море и на суше» 1 • На обложке используются шрифты URW Typewriter и Guardian Sans. Шрифт основного текста - Adobe Minion Pro; заглавный шрифт - Adobe Myriad Condensed; для кода был выбран шрифт Ubuntu Mono от Dalton Maag.

1

Animal Life In the Sea and Оп the Land. - Прим. ред.

Алфавитны й у казател ь D3Vp2p 43 А АВI-интерфейс контрактов 1 90 approve & transferFrom 329

в

Bamboo 187 BIP-39 99, 1 32, 1 33, 1 34, 1 35, 1 37, 1 40, 1 4 1 , 1 42 Опциональная кодовая фраза 1 40

с

Casper 44, 429, 43 1 , 432, 434, 448 Cipher Browser 99 cpp-ethereum 82 D DApp Клиентская часть 369 определение 367 Серверная часть 368 Хранилище данных 370 dapp.tools 483 DE LEGATECA LL 1 97, 255, 256, 26 1 , 446, 464 DоS-атака 286 Е ECDSA 25, 29, 33, 1 50, 1 68, 1 69, 1 70, 1 72, 1 75, 447 EIP-55 27, 1 0 1 , 1 23, 1 24, 1 25, 1 60, 447 ошибки в адресе 125 EIP- 1 55 1 72, 1 73, 1 74, 1 75, 449 Embark 475, 476, 486 Emerald Wallet 53, 83 ENS

504

1

Алфавитны й указатель

Создание поддомена 395 Сопоставители 396 ЕNS-именем Управление 394 ERC20 Дополнительные функции 3 1 5 Отправка токенов 326 Проблемы с токенами 333 Реализации 3 1 9 токен 3 1 9 функции и события 3 1 5 ERC223 99, 335, 336 ERC72 1 25, 27, 303, 339, 340, 34 1 , 372, 373, 374, 399, 476, 477 ERC777 336, 337, 338, 339 Хуки 338 Ethash 25, 3 1 , 44, 282, 429, 430, 43 1 Ethereum АВI-интерфейс контрактов 1 90 Адреса 1 20 Взаимодействие с контрактом 74 Виртуальная машина 40 1 Выбор кошелька 52 Запуск клиента 86 История ответвлений 435 клиенты 44, 8 1 компоненты 43 Культура разработки 48 мобильные кошельки 98 определение 36 Основы 5 1 Пополнение контракта 75 потребление газа 455 появление 39 Просмотр адреса контракта 74

разработка 4 1 Сервис имен 384 Сети 82 синхронизация с блокчейнами 94 Состояние 408 сравнение с Bitcoin 36 Стандарты 445 Токены 3 1 3 Удаленные клиенты 98 Форматы адресов 1 2 1 Экономическая безопасность 44 языки высокого уровня 1 85 Ethereum Classic 24, 4 1 , 53, 82, 88, 9 1 , 99, 147, 1 74, 1 75, 244, 43 1 , 435, 439, 44 1 , 442, 449 EthereumJS 482, 484 EthereumJS helpeth 482 Etherium Снятие средств с нашего контрак­ та 76 EtherJar 485 Etherscan 6 1 , 64, 65, 75, 78, 79, 1 66, 1 67, 1 68, 492, 493 ethers.js 485 EVM 40 1 Набор инструкций 404 Опкоды 455

G Ganache 26, 83, 85, 486, 488 Geth 26, 44, 59, 82, 86, 88, 89, 9 1 , 92, 94, 95, 97, 1 75, 380, 385, 426, 440 Запуск 95

н

Harmony 82 НD-кошельке Идентификатор ключа (путь) 1 46 НD-кошельки 1 3 1 , 1 32, 1 34, 1 42, 1 43, 1 45, 147 НD-кошельков Перемещение по древовидной струк­ туре 1 46

1 IPFS 26, 356, 362, 370, 379, 475, 476 J Jaxx 53, 99, 1 00, 1 34 JSON-RPC 95, 96, 97, 378, 380, 452, 483, 484

к

Keccak-256 27, 1 07, 1 1 8, 1 1 9, 1 20, 1 2 1 , 1 24, 1 63, 1 64, 1 69, 1 70, 1 72, 25 1 , 258, 405, 4 1 6, 422, 456 L LLL 1 86 Localhost 8545 59

м

Mantis 82 Mastercoin 39 MetaMask 53, 56, 57, 58, 59, 60, 6 1 , 62, 63, 64, 66, 70, 72, 73, 75, 76, 77, 78, 79, 80, 83, 99, 1 00, 1 34, 1 52, 320, 369, 372, 39 1 , 392, 394, 444 истории транзакций для определен­ ного адреса 64 Отправка эфира 62 Переключение между сетями 59 Получение эфира для тестирования 60 Создание кошелька 56 METoken 27, 3 1 9, 320, 32 1 , 322, 323, 324, 325, 326, 327, 328, 329, 330, 33 1 , 332 Mist 27, 99, 1 0 1 , 297, 399 MyCrypto 83, 99, 1 00, 1 34, 39 1 , 392 MyEtherWallet 53, 83, 99, 1 00, 1 34, 444

N Nethereum 485 Node.js 465, 466, 467, 468, 49 1

о

OpenZeppelin 233, 246, 247, 293, 303, 3 19, 32 1 , 322, 329, 344, 345, 476, 477, 478, 479, 480, 48 1

Алфавитны й указатель

1

505

Загрузка файлов 382 Сопоставление имени с хешем 397

р

Parity 27, 44, 59, 82, 86, 87, 88, 89, 90, 91, 94, 95, 97, 98, 154, 161, 162, 261, 264, 265, 286, 426, 440, 469, 471, 472, 473, 474, 475, 486, 507 Запуск 95 Pyethereum 82

s

seed Создание НD-кошелька 142 Serpent 28, 186, 404 Solidity 16, 24, 28, 29, 30, 31, 43, 67, 68, 70, 71, 72, 80, 166, 181, 182, 183, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 206, 207, 208, 212, 215, 222, 223, 224, 225, 226, 228, 231, 233, 234, 237, 244, 248, 250, 251, 255, 256, 259, 261, 264, 269, 276, 278, 282, 286, 290, 293, 294, 295, 296, 297, 298, 300, 301, 315, 316, 319, 321, 350, 360, 369, 404, 410, 415, 439, 471, 476, 479, 481, 483, 486, 487, 507 Выбор версии 188, 192 Загрузка и установка 188 Клиентские интерфейсы оракулов 360 Компиляция 190 Компиляция кода в байт-код 410 Модификаторы 224 Обработка ошибок 206 Определение интерфейса ERC20 315 Программирование 193 События 208 Среда разработки 189 Типы данных 193 Функции 198 SputnikVM 483 Status 99 Swarm 27, 28, 29, 40, 48, 101, 370, 372, 379, 380, 381, 382, 383, 388, 395, 396, 397, 398, 399, 475, 476

506

1

Алфавитны й указатель

т

Truffie 28, 211, 217, 221, 222, 319, 320, 324, 327, 332, 465, 466, 470, 472, 473, 474, 480, 486 Trust Wallet 99 V

Vyper 16, 20, 28, 186, 222, 223, 224, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235 Компиляция 232

w

Web 2.0 48 web3j 484 web3.js 48, 83, 210, 369, 372, 377, 473, 474, 484, 490 web3.py 484 Whisper 48

z

ZeppelinOS 304, 481, 482 А

Алгоритм Namehash 385 Алгоритм консенсуса 38, 43 антипереполнение 244 антишаблоны 236, 238 Арифметические операции с эллиптиче­ ской кривой 113 Арифметические переполнение 244 Ассемблерные вставки 226 Атака короткоrо адреса/параметра 276 Аукционы Викри 389, 391, 393 Аутентификация с помощью Tx.Origin 301 Б

базы данных 43 байт-код 43, 71, 232

байт-кода 67, 72, 185, 234, 404, 413, 414, 415, 416, 483, 486 Дизассемблирование 415 блока Контекст 196 блокчейн 36, 50, 53, 85, 94, 476 Запись 179 Создание контрактов 72

в

Видимость по умолчанию 264 виртуальная машина 37 виртуальная машина Ethereum 43 Виртуальная машина Ethereum 25, 401 вызов \«Сырой\» 215 вызов контракта 163, 255

г

газ Лимит 425 Газ 422 газа Отрицательный расход 425 Расход 424 Учет 423

д

Декораторы 230 деревья Патриции-Меркла 43 Детерминизм 117 Децентрализованная автономная орга­ низация 24, 436 децентрализованного аукциона 371 Хранение 380 Децентрализованное управление 376 децентрализованные приложения 40, 47, 99, 127, 304, 346, 366, 368, 384, 444, 475, 483, 485 Децентрализованные приложения 366, 367, 400 Децентрализованные протоколы взаи­ модействия 371

децентрализованные системы 54, 508 Децентрализованный аукцион клиентский пользовательский интер­ фейс 377 смарт-контракты 373 дочерних ключей Выведение усиленных 145

з

закрытого ключа Генерация из случайного числа 107 Закрытые ключи 54, 103, 106, 115, 146 Защита от коллизий 117 Значения seed 131, 132

и

идентификатор цепочки 174 Иллюзия энтропии 267, 290, 300

к

классов Наследование 226 ключ-значение тип данных 27, 30, 42 компиляции Защита от ошибок переполнения бу­ фера 233 Компиляция 232 Компиляция контракта Faucet 70 конечный автомат 38, 42, 45, 401 Конечный автомат 37, 43 Консенсус 31, 427, 428, 429 на основе доказательства выполне­ ния работы 428 на основе доказательства доли владе­ ния 429 консенсуса Принципы 432 консоли Truffie 324 контракта вызов 163 Код развертывания 414 Конструктор 200

Алфавитны й указатель

507

самоуничтожение 200 контрактам Передача значения учетным запи­ сям 162 Передача полезных данных 163 контрактов Вызов 212 Наследование 204 создание 165 контракты Жадные 223 Суицидальные 223 кошельки Браузерные 99 Детерминистические 131 Иерархические детерминистические 131 Недетерминистические (случайные) 129 Кошельки 127 кошельков Обзор 127 Криптография с открытым ключом 104, 109

л

локальной симуляции блокчейна 85

м

Манипуляции с временной меткой бло­ ка 290 Мнемоническая кодовая фраза 134 мнемонические коды 132 Мнемонические слова Генерация 135, 137

н

Наследование 226 Некорреляционность 117 Необратимость 117 Непроверяемые значение, возвращен­ ные из вызова CALL 278 Номиналы валюты 51, 63

508

Алфавитны й указатель

о

Объект address 196, 198, 215 одноразовые коды дублирование и отставание 155 одноразовый код Отслеживание 152 оракулов использование 348 Шаблоны проектирования 350 оракулы Вычислительные 356 Децентрализованные 358 Оракулы 347, 348 Отказ в обслуживании 286 открытого ключа восстановление 175 Генерация 114 открытые и закрытые ключи Расширенные 143 Открытые ключи 109, 146, 170

п

память с произвольным доступом 42 Параллелизм 156 Первичное размещение монеты 317 Передача значения учетным записям ЕОА 162 переход состояния 42 Пиринговая сеть 33, 43 Плавающая запятая и точность 298 Платформа Emerald 485 Подлинность данных 354 подписание автономное 176 подписи Префиксное значение 175 Проверка 170 полнота по Тьюрингу 44, 45 Полнота по Тьюрингу 30, 32, 45, 421 газ 421 полноценного узла Ethereum Аппаратные требования 86

полноценный узел 82 Полноценный узел Преимущества и недостатки 84 постусловия 229 Правила консенсуса 33, 43 Предусловия 229 Приведение типов 227 проблемой факторизации простых чи­ сел 104 Проверяемость 1 1 7 Протокол обмена клиентскими адреса­ ми 1 2 1

Ссылки на внешние контракты 258, 268 Стандарт токенов ERC20 3 1 4, 3 1 7 Структуры данных 43, 3 1 6

т

тестовая заглушка с эфиром 67 Тестовая сеть Kovan 59 Тестовая сеть Rinkeby 59, 452 Тестовая сеть Ropsten 59 токенов Расширение стандартов 344 Способы применения 306 стандарты 342 токены р Реентерабельность 2 1 5, 238, 244, 282, 43 7 Служебные 3 1 1 Риск контрагента 308 Токены 305 ICO 345 взаимозаменяемость 308 смарт-контракт 1 5, 20, 28, 30, 33, 35, 36, свойственность 309 39, 40, 46, 47, 49, 5 1 , 65, 66, 80, 106, 1 3 1 , транзакции 1 80, 1 8 1 , 1 82, 1 84, 1 8 � 1 86, 1 87, 1 95, 2 1 2, Значение и данные 1 60 2 1 9, 220, 222, 223, 224, 225, 229, 232, 233, Контекст 1 96 234, 235, 236, 237, 238, 268, 297, 303, 304, Контекст вызова 195 3 1 2, 3 1 3, 3 1 4, 344, 345, 347, 348, 349, 3 5 1 , Одноразовый код 1 5 1 352, 355, 358, 362, 365, 366, 367, 368, 369, Подписание 1 72 370, 373, 376, 377, 399, 404, 408, 426, 428, Получатель 1 59 442, 444, 450, 45 1 , 465, 476, 48 1 , 486, 496 происхождение 1 56 определение 1 82 Создание сырой 1 74 смарт-контракта Специальные 165 Жизненный цикл 1 83 Структура 1 49 на языке Solidity 1 87 сырой 1 73 смарт-контрактов Транзакции 43, 1 49 Безопасность 236 с несколькими подписями 1 80 Тестирование 486 транзакций Смарт-контракты 1 82, 1 83, 1 85, 223, 237, Газ 1 57 366, 470 Распространение 1 78 событий Перехват 2 1 О у универсальная вычислимость 45 События 208 универсальная машина Тьюринга 45 Сопоставители 388, 396 учетной записью с внешним владель­ Состояние гонки 282, 29 1 цем 66 Спецификация ENS 385

Алфавитны й указатель

509

ф

фронт-раннинг 282 функции вызов 163 Селектор 163 функций Модификаторы 203 Перегрузка 227 Порядок следования 230

х

хеширования 28, 31, 107, 116, 117, 118, 119, 120, 125, 126, 130, 138, 170, 197, 403, 417 Хранение неинициализированных ука­ зателей 294

ц

э

экземпляра 212 Создание нового 212 экземпляру Обращение к существующему 214 Эллиптическая криптография 105, 110, 112 эллиптической криптографии Библиотеки 116 эфир 26, 28, 29, 30, 36, 37, 47, 51, 60, 61, 62, 63, 64, 65, 66, 67, 69, 73, 74, 76, 79, 80, 85, 108, 128, 152, 158, 160, 165, 180, 184, 194, 197, 201, 219, 220, 223, 238, 246, 250, 251, 254, 255, 258, 263, 265, 274, 276, 280, 281, 286, 288, 291, 293, 318, 328, 331, 333, 334, 388, 390, 414, 423, 425, 430, 435, 436, 437, 438, 444, 448, 474, 478 Деноминации 52 Неожиданный 250 Номиналы валюты 51

цифровой подписи Создание 169 Цифровые ПОДПИСИ 168 цифровых подписей я на основе эллиптической кривой 168 языки программирования Принцип работы 169 декларативные 185 императивные 185 ч Чтение и запись данных 234

ш

Шестнадцатеричная кодировка с кон­ трольной суммой в верхнем регистре 123

Все права защищены. Книга или любая ее часть не может быть скопирована, воспроизведена в элек­ тронной или механической форме, в виде фотокопии, записи в память ЭВМ, репродукции или каким-ли­ бо иным способом, а также использована в любой информационной системе без получения разрешения от издателя. Копирование, воспроизведение и иное использование книги или ее части без согласия издателя является незаконным и влечет уголовную, административную и гражданскую ответственность. Научно- популярное издание М И РОВОЙ КОМПЬЮТЕРН Ы Й БЕСТСЕЛЛ ЕР

Андреас Антонопупос, Гэвин Вуд ОСВАИ ВАЕМ ETHEREUM СОЗДАНИЕ СМАРТ- КОНТРАКТОВ И ДЕЦЕНТРАЛИЗОВАННЫХ П РИЛОЖЕН ИЙ Главный редактор Р. Фасхутдинов Руководитель направления 8. Обручев Ответственный редактор Е. Истомина Литературный редактор С. Ульянов Научный редактор А. Власов Младший редактор А. Захарова Художественный редактор А. Гусев Компьютерная верстка Э. Брегис Корректоры Р. Болдинова, А. Баскакова Страна происхождения: Российская Федерация Шыf'арылf'ан елi: Ресей Федерациясы ООО •� •Эксмо123308, РоссиА, rupoдМocua, улица Зорrв, дом 1 , строение 1, ЗТU( 2Q, каб. 2013 Тел.: 8 (495) 411·68•86. Ноте page: ._.eksmo.ru E• mail: inloi>eksmo.ru 01,щipywi: •ЭКСМО• /1,J(p Басnвсы, 123308, Ресей, IQlll8. МВС1Сеу, Зорге кешесi, 1 уй, 1 mмарвт, 20 м,абвт, офис 2013 *· Тел. : 8 (495) 411-68·86 Horne page: www.eksmo.ru E-ma�: info81eksmo.ru Тауар белriс:1: •3':СМО• ИК'8рt18Т•М8183МН : ww.v.ЬOok24.ru Инт8рмет•М8rUМН : WWW.ЬООk24.kz Икnрмет-дУdН : WNN.ЬOok24.kz Импортёр в Ресnублику Казахстан ТОО • РДЦ-Алмаrы• 1(,азам,стан Республикасындаn,1 импорттаушы • РДЦ•АлМ111'Ы• ЖШС. �тор и nредстз8/11те.nь по np.teмy претензий на nPQAyм:woo, в Ресnублике Казахстан : ТОО •РДЦ-Аnматы• 1(,азаr,сrан Рес�нда дистрибыотор •ене ен1м бойынwа арыэ-таnаптарды r,абыпдв,уwы11t.1н.екini •РДЦ-Ал маты• ЖШС, Аnматы м;., ДомброесК14Й кеw., З•а•,литерб, офис 1 . Ten.: 8 (727)251 -59-90/91/92: E-ma�: RDC-Alma�.kz Еммнit1 .арамдылы� мерзiмi wектеnмеnж. Ceprnфмiraциf! Т)'pal'lы a,.napaт caйrra: _,,,,,eksmo.ru/certllication Сеедения о подтмр,кдении сооп,етСПIИА издания соmасно законодnальсnrу РФ о техничес1СОМ реrулировании мо•но по.nучить на сайте Издатвльсrва •3":смо• 'NWW.eksmo.ru/�rtilication 8tщ,рrен fМ!МJ)еКет: РесеА. Сертмфика1JИt1 �.;арасТЬ1рьи1маrаt1

Дата изготовления / Подписано в печать 05.07 . 2021 . Формат 70х 100'/, 6 • Печать офсетная. Усл . печ. л. 41 ,48. Тираж 1 000 экз. Заказ № 1 041 . Отпечатано в типографии АО « Рида», 603074, г. Нижний Новгород, ул . Шаляпина, д. 2а

П Р И С О ЕД И Н Я Й Т Е С Ь К Н А М '

БОМБОРА М3дАТЕЛЬСТIО

БОМБОРА - л идер на рынке полезных и вдохновляющих книг. Мы любим книги и создаем их, чтобы вы могл и творить, откры вать мир, пробовать новое, расти. Быть счастл и в ы м и . Быть на волне.

МЫ В СОЦСЕТЯХ:

OCEJ ЬomЬorabooks D Ьombora bombora.ru

• l�llJlll l�JlllJl!l,1111 , ISBN 978-5-04-1 0678 1 -6

lj J1 • r � 1 puннm,1 ш1,J( W\\/,/11 II\Г( с, (tJ

ЛитРес: Москва. ООО « Торговый Дом «Эксмо» Адрес: 123308, г. Москва, ул. Зорге, д. 1 , строение 1 . Телефон: + 7 (495) 41 1 -50-74. E-mall: reception@eksmo-sale . ru По вопросам приобретения книг «Эксмо• зарубежными оптовыми покупателями обращаться в отдел зарубежных продаж ТД •Эксмо• E-mail : [email protected] lnternational Sales: lnternational wholesale customers should contact Foreign Sales Department of Tradiлg House •Eksmo• for their orders. lnternatlonal@eksmo-sale. ru По вопросам заказа книг корпоративным клиентам, в том числе в специальном оформлении, обращаться по тел . : + 7 (495) 41 1 -68-59, доб. 2261 . E-mail: [email protected] Оптовая торговля бумажно-беловыми и канцелярскими товарами для школы и офиса •Канц-Эксмо»: Компания «Канц-Эксмо•: 142702, Московская обл . , Ленинский р-н, г. Видное-2, Белокаменное ш., д. 1, а/я 5. Тел./факс: +7 (495) 745-28-87 (многоканальный). e-mail: [email protected], сайт: www.kanc-eksmo.ru Филиал «Торгового Дома «Эксмо• в Ни•нем Новгороде Адрес: 603094, г. Нижний Новгород, улица Карпинского, д. 29, бизнес-парк •Грин Плаэа• Телефон: + 7 (831 ) 216- 1 5-91 (92, 93, 94). E-mall: [email protected] Филиал ООО •Иэдательство •Зксмо• в г. Санкт-Петербурге Адрес: 192029, г. Санкт-Петербург, пр. Обуховской обороны, д. 84, лит. •Е• Телефон: + 7 (812) 365-46-03 / 04. E-mall: [email protected] Филиал ООО «Издательство «Эксмо• в г. Екатеринбурге Адрес: 620024, г. Екатеринбург, ул. Новинская, д. 2щ Телефон: + 7 (343) 272-72-01 (02/03/04/05/06/08) Филиал ООО •Издательство «Эксмо• в г. Самаре Адрес: 443052, г. Самара, пр-т Кирова, д. 75/ 1 , лит. •Е• Телефон: +7 (846) 207-55-50. E-mall: RDC-samara@mail . ru Филиал ООО «Издательство с,�Эксмо• в г. Ростове-на-Дону Адрес: 344023, г. Ростов-на-Дону, ул . Страны Советов, 44А Телефон: + 7(863) 303-62-10. E-mall: [email protected] .ru Филиал ООО 11Иэдательство «Эксмо• в r. Новосибирске Адрес: 630015, г. Новосибирск, Комбинатский пер . , д. 3 Телефон: + 7(383) 289-91 -42. E-mail: [email protected] Обособленное подразделение в r. Хабаровске Фактический адрес: 680000, г. Хабаровск, ул . Фрунзе, 22, оф. 703 Почтовый адрес: 680020, г. Хабаровск, А/Я 1006 Телефон: (4212) 910- 120, 910-21 1 . E-mвll: [email protected] Филиал ООО 11Издательство 11Эксмо11 в г. Тюмени Центр оптово-розничных продаж Cash&Carry в г. Тюмени Адрес: 625022, г. Тюмень, ул. Пермякова, 1 а , 2 этаж. ТЦ •Перестрой-ка• Ежедневно с 9.00 до 20.00. Телефон: 8 (3452) 2 1 - 53-96 Республика Беларусь: ООО •ЭКСМО АСТ Си энд Си• Центр оптово-розничных продаж Cash&Carry в г. Минске Адрес: 220014, Республика Беларусь, г. Минск, проспект Жукова, 44, пом. 1 - 17, ТЦ •Outleto• Телефон: +375 17 251 -40-23; +375 44 581 -81 -92 Режим работы: с 10.00 до 22.00. E-mall: [email protected] Казахстан: «РДЦ Алматы» Адрес: 050039, г. Алматы, ул . Домбровского, ЗА Телефон: + 7 (727) 251 -58- 12, 251 -59-90 (91 ,92,99). E-mail: RDC-Almaty@eksmo .kz Украина : ООО •Форс Украина• Адрес: 04073, г. Киев, ул . Вербовая, 17а Телефон: +38 (044) 290-99-44, (067) 536-33-22. E-mall: [email protected] Полный ассортимент продукции ООО «Издательство «Эксмо• мо•но приобрести в кни•ных маrазинах «Читай-город• и заказать в интернет-магазине: www.chitai-gorod .ru. Телефон единой справочной службы: 8 (800) 444-8-444. Звонок по России бесплатный. Интернет-магазин ООО «Издательство «Эксмо» www.book24.ru Розничная продажа книг с доставкой по всему м и ру. Тел . : +7 (495) 745-89-14. E-mail: lmarket@eksmo-sale . ru

. 1 1\

Й Ч И ТА ГОРОД

book 24. ru 1

О фицмал"н ы И интернет-fllаГ.IЭМН иэдате.nьской груп п ы "ЗКСМО -Аст·