Перейти к основному содержимому

Обновление TVM 2023.07

warning

Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.

подсказка

Это обновление запущено в основной сети с декабря 2023 года.

c7

c7 — это регистр, в котором хранится локальная контекстная информация, необходимая для выполнения контракта (например, время, lt, конфигурации сети и т. д.).

c7 кортеж расширен с 10 до 14 элементов:

  • 10: cell с кодом самого смарт-контракта.
  • 11: [integer, maybe_dict]: значение ТON входящего сообщения, дополнительные валюты.
  • 12: integer, сборы, собранные на этапе хранения.
  • 13: tuple с информацией о предыдущих блоках.

10 В настоящее время код смарт-контракта представлен на уровне TVM только как исполняемое продолжение и не может быть преобразован в ячейку. Этот код часто используется для авторизации соседнего контракта того же типа, например jetton-wallet авторизует jetton-wallet. На данный момент нам нужно явно хранить ячейку кода в хранилище, что делает хранилище и init_wrapper более громоздкими, чем они могли бы быть. Использование 10 для кода совместимо с обновлением Everscale tvm.

11 В настоящее время значение входящего сообщения представлено в стеке после инициализации TVM, поэтому при необходимости во время выполнения нужно либо сохранить его в глобальной переменной, либо передать через локальные переменные (на уровне funC это выглядит как дополнительный аргумент msg_value во всех функциях). Поместив его в элемент 11, мы повторим поведение баланса контракта: он представлен как в стеке, так и в c7.

12 В настоящее время единственный способ рассчитать плату за хранение — это сохранить баланс в предыдущей транзакции, как-то рассчитать потребление газа в предыдущей транзакции, а затем сравнить с текущим балансом за вычетом значения сообщения. Между тем, часто требуется учитывать плату за хранение.

13 В настоящее время нет способа извлечь данные о предыдущих блоках. Одной из ключевых особенностей TON является то, что каждый структура представляет собой дружественный к доказательству Меркла пакет (дерево) ячеек, более того, TVM также дружественна к ячейкам и доказательсву Меркла. Таким образом, если мы включим информацию о блоках в контекст TVM, станет возможным реализовать множество не требующих доверия сценариев: контракт A может проверять транзакции по контракту B (без сотрудничества с B), можно восстановить разорванные цепочки сообщений (когда recovery-contract получает и проверяет доказательства того, что некоторая транзакция произошла, но была отменена), также требуется знание хэшей блоков мастерчейна для выполнения некоторых функций проверки onchain.

Идентификаторы блоков представлены в следующем формате:

[ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer ] = BlockId;
[ last_mc_blocks:[BlockId0, BlockId1, ..., BlockId15]
prev_key_block:BlockId ] : PrevBlocksInfo

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

Новые коды операций

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

Коды операций для работы с новыми значениями c7

26 газа для каждого, за исключением PREVMCBLOCKS и PREVKEYBLOCK (34 газа).


Синтаксис Fift

Стек

Описание
MYCODE- cИзвлекает код смарт-контракта из c7
INCOMINGVALUE- tИзвлекает значение входящего сообщения из c7
STORAGEFEES- iИзвлекает значение платы за фазу хранения из c7
PREVBLOCKSINFOTUPLE- tИзвлекает PrevBlocksInfo: [last_mc_blocks, prev_key_block] из c7
PREVMCBLOCKS- tИзвлекает только last_mc_blocks
PREVKEYBLOCK- tИзвлекает только prev_key_block
GLOBALID- iИзвлекает global_id из 19 конфигурации сети

Газ


Синтаксис Fift

Стек

Описание
GASCONSUMED- g_cВозвращает газ, потребленный виртуальной машиной на данный момент (включая эту инструкцию).
26 газа

Арифметика

Добавлены новые варианты кода операции деления (A9mscdf): d=0 берет одно дополнительное целое число из стека и добавляет его к промежуточному значению перед делением/rshift. Эти операции возвращают как частное, так и остаток (как d=3).

Также доступны тихие варианты (например, QMULADDDIVMOD или QUIET MULADDDIVMOD).

Если возвращаемые значения не вписываются в 257-разрядные целые числа или делитель равен нулю, при выполнении операции без прерывания возникает исключение переполнения целого числа. Операции без прерывания возвращают значение NaN вместо значения, которое не помещается (два NaN, если делитель равен нулю).

Стоимость газа равна 10 плюс длина опкода: 26 для большинства кодов операции, +8 для LSHIFT#/RSHIFT#, +8 для тихого режима.


Синтаксис Fift

Стек
MULADDDIVMODx y w z - q=floor((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODRx y w z - q=round((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODCx y w z - q=ceil((xy+w)/z) r=(xy+w)-zq
ADDDIVMODx w z - q=floor((x+w)/z) r=(x+w)-zq
ADDDIVMODRx w z - q=round((x+w)/z) r=(x+w)-zq
ADDDIVMODCx w y - q=ceil((x+w)/z) r=(x+w)-zq
ADDRSHIFTMODx w z - q=floor((x+w)/2^z) r=(x+w)-q*2^z
ADDRSHIFTMODRx w z - q=round((x+w)/2^z) r=(x+w)-q*2^z
ADDRSHIFTMODCx w z - q=ceil((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFT#MODx w - q=floor((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFTR#MODx w - q=round((x+w)/2^z) r=(x+w)-q*2^z
z ADDRSHIFTC#MODx w - q=ceil((x+w)/2^z) r=(x+w)-q*2^z
MULADDRSHIFTMODx y w z - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z
MULADDRSHIFTRMODx y w z - q=round((xy+w)/2^z) r=(xy+w)-q*2^z
MULADDRSHIFTCMODx y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFT#MODx y w - q=floor((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFTR#MODx y w - q=round((xy+w)/2^z) r=(xy+w)-q*2^z
z MULADDRSHIFTC#MODx y w - q=ceil((xy+w)/2^z) r=(xy+w)-q*2^z
LSHIFTADDDIVMODx w z y - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq
LSHIFTADDDIVMODRx w z y - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq
LSHIFTADDDIVMODCx w z y - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODx w z - q=floor((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODRx w z - q=round((x*2^y+w)/z) r=(x*2^y+w)-zq
y LSHIFT#ADDDIVMODCx w z - q=ceil((x*2^y+w)/z) r=(x*2^y+w)-zq

Операции со стеком

В настоящее время аргументы всех операций со стеком ограничены 256. Это означает, что если стек становится глубже 256, становится трудно управлять глубокими элементами стека. В большинстве случаев нет никаких причин для безопасности для этого ограничения, т. е. аргументы не ограничиваются, чтобы предотвратить слишком дорогие операции. Для некоторых массовых операций со стеком, таких как ROLLREV (где время вычисления линейно зависит от значения аргумента), стоимость газа также линейно зависит от значения аргумента.

  • Аргументы PICK, ROLL, ROLLREV, BLKSWX, REVX, DROPX, XCHGX, CHKDEPTH, ONLYTOPX, ONLYX теперь не ограничены.
  • ROLL, ROLLREV, REVX, ONLYTOPX потребляют больше газа, когда аргументы большие: дополнительная стоимость газа составляет max(arg-255,0) (для аргумента меньше 256 потребление газа постоянно и соответствует текущему режиму)
  • Для BLKSWX дополнительная стоимость составляет max(arg1+arg2-255,0) (это не соответствует текущему режиму, так как в настоящее время и arg1, и arg2 ограничены 255).

Хэши

В настоящее время в TVM доступны только две операции хэширования: вычисление хэша представления ячейки/среза и sha256 данных, но только до 127 байт (только столько данных помещается в одну ячейку).

Добавлено семейство операций HASHEXT[A][R]_(HASH):


Синтаксис Fift

Стек

Описание
HASHEXT_(HASH)s_1 ... s_n n - hВычисляет и возвращает хэш объединения срезов (или сборщиков) s_1...s_n.
HASHEXTR_(HASH)s_n ... s_1 n - hТо же самое, но аргументы приводятся в обратном порядке.
HASHEXTA_(HASH)b s_1 ... s_n n - b'Добавляет полученный хэш к строителю b вместо того, чтобы помещать его в стек.
HASHEXTAR_(HASH)b s_n ... s_1 n - b'Аргументы задаются в обратном порядке, добавляя хэш к строителю.

Используются только биты из корневых ячеек s_i.

Каждый фрагмент s_i может содержать нецелое число байтов. Однако сумма битов всех фрагментов должна делиться на 8. Обратите внимание, что TON использует порядок старших битов, поэтому при объединении двух фрагментов с нецелым числом байтов биты из первого фрагмента становятся старшими битами.

Расход газа зависит от количества хэшированных байтов и выбранного алгоритма. На каждый фрагмент потребляется дополнительно 1 единица газа.

Если параметр [A] не включен, результат хэширования будет возвращен в виде целого числа без знака, если оно соответствует 256 битам, или целого ряда целых чисел в противном случае.

Доступны следующие алгоритмы:

  • SHA256 - реализация openssl, 1/33 газа на байт, хеш составляет 256 бит.
  • SHA512 - реализация openssl, 1/16 газа на байт, хеш составляет 512 бит.
  • BLAKE2B - реализация openssl, 1/19 газа на байт, хеш составляет 512 бит.
  • KECCAK256 - реализация, совместимая с ethereum, 1/11 газа на байт, хеш составляет 256 бит.
  • KECCAK512 - совместимая с ethereum реализация, 1/6 газа на байт, хэш составляет 512 бит.

Использование газа округляется в меньшую сторону.

Крипто

В настоящее время доступен только один криптографический алгоритм - CHKSIGN: проверьте подпись Ed25519 хеша h для открытого ключа k.

  • Для совместимости с блокчейнами предыдущего поколения, такими как Bitcoin и Ethereum, нам также необходимо проверить подписи secp256k1.
  • Для современных криптографических алгоритмов абсолютный минимум - это сложение и умножение кривых.
  • Для совместимости с Ethereum 2.0 PoS и некоторыми другими современными криптографическими алгоритмами нам нужна схема BLS-подписи на кривой bls12-381.
  • Для некоторого защищенного оборудования требуется secp256r1 == P256 == prime256v1.

secp256k1

Подписи Bitcoin/Ethereum. Использует реализацию libsecp256k1.


Синтаксис Fift

Стек

Описание
ECRECOVERhash v r s - 0 или h x1 x2 -1Восстанавливает открытый ключ из подписи, идентично операциям Bitcoin/Ethereum.
Принимает 32-байтовый хэш как uint256 hash; 65-байтовая подпись как uint8 v и uint256 r, s.
Возвращает 0 в случае неудачи, открытый ключ и -1 в случае успеха.
65-байтовый открытый ключ возвращается как uint8 h, uint256 x1, x2.
1526 газа

secp256r1

Использует реализацию OpenSSL. Интерфейс похож на CHKSIGNS/CHKSIGNU. Совместимо с Apple Secure Enclave.


Синтаксис Fift

Стек

Описание
P256_CHKSIGNSd sig k - ?Проверяет seck256r1-подпись sig части данных среза d и открытый ключ k. Возвращает -1 в случае успеха, 0 в случае неудачи.
Открытый ключ представляет собой 33-байтовый срез (закодированный в соответствии с разделом 2.3.4, пункт 2 SECG SEC 1).
Подпись sig представляет собой 64-байтовый срез (два 256-битных беззнаковых целых числа r и s).
3526 газа
P256_CHKSIGNUh sig k - ?То же самое, но подписанные данные представляют собой 32-байтовую кодировку 256-битного беззнакового целого числа h.
3526 газа

Ристретто

Расширенная документация доступна здесь. Проще говоря, Curve25519 был разработан с учетом производительности, однако из-за симметрии некоторые элементы группы имеют несколько представлений. Более простые протоколы, такие как подписи Шнорра или Диффи-Хеллмана, применяют приемы на уровне протокола для смягчения некоторых проблем, но нарушают схемы вывода ключей и маскировки ключей. И эти приемы не масштабируются до более сложных протоколов, таких как Bulletproofs. Ристретто представляет собой арифметическое абстракцию поверх Curve25519 таким образом, что каждый элемент группы соответствует уникальной точке, что является требованием для большинства криптографических протоколов. Ристретто по сути является протоколом сжатия/распаковки для Curve25519, который предлагает необходимую арифметическую абстракцию. В результате криптопротоколы легко писать правильно, при этом извлекая выгоду из высокой производительности Curve25519.

Операции Ристретто позволяют вычислять операции кривой на Curve25519 (в обратном порядке это не так), таким образом мы можем считать, что добавляем как Ристретто, так и операции по кривой Curve25519 за один шаг.

Используется реализация libsodium.

Все точки ristretto-255 представлены в TVM как 256-битные целые числа без знака. При некорректных операциях генерируется range_chk, если аргументы не являются допустимыми закодированными точками. Нулевая точка представлена в виде целого числа 0.


Синтаксис Fift

Стек

Описание
RIST255_FROMHASHh1 h2 - xДетерминированно генерирует допустимую точку x из 512-битного хеша (заданного как два 256-битных целых числа).
626 газа
RIST255_VALIDATEx -Проверяет, что целое число x является допустимым представлением некоторой точки кривой. Выдает range_chk при ошибке.
226 газа
RIST255_ADDx y - x+yСложение двух точек на кривой.
626 газа
RIST255_SUBx y - x-yВычитание двух точек на кривой.
626 газа
RIST255_MULx n - x*nУмножает точку x на скаляр n.
Допустимо любое n, включая отрицательное.
2026 газа
RIST255_MULBASEn - g*nУмножает точку генератора g на скаляр n.
Допустимо любое n, включая отрицательное.
776 газа
RIST255_PUSHL- lВыводит целое число l=2^252+27742317777372353535851937790883648493, которое соответствует порядку в группе.
26 газа
RIST255_QVALIDATEx - 0 или -1Тихая версия RIST255_VALIDATE.
234 газа
RIST255_QADDx y - 0 или x+y -1Тихая версия RIST255_ADD.
634 газа
RIST255_QSUBx y - 0 или x-y -1Тихая версия RIST255_SUB.
634 газа
RIST255_QMULx n - 0 или x*n -1Тихая версия RIST255_MUL.
2034 газа
RIST255_QMULBASEn - 0 или g*n -1Тихая версия RIST255_MULBASE.
784 газа

BLS12-381

Операции на кривой BLS12-381, удобной для сопряжения. Используется реализация BLST. Также, операции для схемы подписи BLS, которая основана на этой кривой.

Значения BLS представлены в TVM следующим образом:

  • G1-точки и открытые ключи: 48-байтовый срез.
  • G2-точки и подписи: 96-байтовый срез.
  • Элементы поля FP: 48-байтовый срез.
  • Элементы поля FP2: 96-байтовый срез.
  • Сообщения: срез. Количество бит должно делиться на 8.

Когда входное значение является точкой или элементом поля, срез может иметь более 48/96 байт. В этом случае берутся только первые 48/96 байт. Если в срезе меньше байтов (или если размер сообщения не делится на 8), выдается исключение переполнения ячейки.

Высокоуровневые операции

Это высокоуровневые операции для проверки подписей BLS.


Синтаксис Fift

Стек

Описание
BLS_VERIFYpk msg sgn - boolПроверяет подпись BLS, возвращает true в случае успеха, в противном случае false.
61034 газа
BLS_AGGREGATEsig_1 ... sig_n n - sigАгрегирует подписи. n>0. Выдает исключение, если n=0 или если какой-либо sig_i не является допустимой подписью.
gas=n*4350-2616
BLS_FASTAGGREGATEVERIFY-pk_1 ... pk_n n msg sig - boolПроверяет агрегированную подпись BLS для ключей pk_1...pk_n и сообщения msg. Возвращает true в случае успеха, в противном случае false. Возвращает false, если n=0.
gas=58034+n*3000
BLS_AGGREGATEVERIFYpk_1 msg_1 ... pk_n msg_n n sgn - boolПроверяет агрегированную подпись BLS для пар ключ-сообщение pk_1 msg_1...pk_n msg_n. Возвращает true в случае успеха, в противном случае false. Верните false, если n=0.
gas=38534+n*22500

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

Низкоуровневые операции

Это арифметические операции над элементами группы.


Синтаксис Fift

Стек

Описание
BLS_G1_ADDx y - x+yСложение на G1.
3934 газа
BLS_G1_SUBx y - x-yВычитание на G1.
3934 газа
BLS_G1_NEGx - -xОтрицание на G1.
784 газа
BLS_G1_MULx s - x*sУмножает точку G1 x на скаляр s.
Допустим любой s, включая отрицательный.
5234 газа
BLS_G1_MULTIEXPx_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_nВычисляет x_1*s_1+...+x_n*s_n для точек G1 x_i и скаляров s_i. Возвращает нулевую точку, если n=0.
Любое s_i допустимо, включая отрицательное.
gas=11409+n*630+n/floor(max(log2(n),4))*8820
BLS_G1_ZERO- zeroПомещает нулевую точку в G1.
34 газа
BLS_MAP_TO_G1f - xПреобразует элемент FP f в точку G1.
2384 газа
BLS_G1_INGROUPx - boolПроверяет, что срез x представляет допустимый элемент G1.
2984 газа
BLS_G1_ISZEROx - boolПроверяет, что точка G1 x равна нулю.
34 газа
BLS_G2_ADDx y - x+yСложение на G2.
6134 газа
BLS_G2_SUBx y - x-yВычитание на G2.
6134 газа
BLS_G2_NEGx - -xОтрицание на G2.
1584 газа
BLS_G2_MULx s - x*sУмножает точку G2 x на скаляр s.
Любое s допустимо, включая отрицательное.
10584 газа
BLS_G2_MULTIEXPx_1 s_1 ... x_n s_n n - x_1*s_1+...+x_n*s_nВычисляет x_1*s_1+...+x_n*s_n для точек G2 x_i и скаляров s_i. Возвращает нулевую точку, если n=0.
Любое s_i допустимо, включая отрицательное.
gas=30422+n*1280+n/floor(max(log2(n),4))*22840
BLS_G2_ZERO- zeroПомещает нулевую точку в G2.
34 газа
BLS_MAP_TO_G2f - xПреобразует элемент FP2 f в точку G2.
7984 газа
BLS_G2_INGROUPx - boolПроверяет, что срез x представляет допустимый элемент G2.
4284 газа
BLS_G2_ISZEROx - boolПроверяет, что точка G2 x равна нулю.
34 газа
BLS_PAIRINGx_1 y_1 ... x_n y_n n - boolУчитывая точки G1 x_i и точки G2 y_i, вычисляет и умножает пары x_i,y_i. Возвращает true, если результат является мультипликативным тождеством в FP12, в противном случае возвращает false. Возвращает false, если n=0.
gas=20034+n*11800
BLS_PUSHR- rИзменяет порядок G1 и G2 (приблизительно 2^255).
34 газа

INGROUP, ISZERO не выбрасывают исключение на недопустимых точках (кроме исключений переполнения ячеек), вместо этого они возвращают false.

Другие арифметические операции выбрасывают исключение на недопустимых точках кривой. Обратите внимание, что они не проверяют, принадлежат ли заданные точки кривой группе G1/G2. Используйте инструкцию INGROUP, чтобы проверить это.

RUNVM

В настоящее время код в TVM не может вызвать внешний ненадежный код "в изолированной среде". Другими словами, внешний код всегда может необратимо обновить код, данные контракта или задать действия (например, отправку всех денег). Инструкция RUNVM позволяет создать независимый экземпляр виртуальной машины, запустить нужный код и получить необходимые данные (стек, регистры, потребление газа и т. д.) без риска загрязнения состояния вызывающей стороны. Безопасный запуск произвольного кода может быть полезен для плагинов в стиле v4, вычислений субконтрактов в стиле init Tact's и т. д.


Синтаксис Fift

Стек

Описание
flags RUNVMx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]Запускает дочернюю виртуальную машину с кодом code и стеком x_1...x_n. Возвращает результирующий стек x'_1...x'_m и код выхода.
Другие аргументы и возвращаемые значения включаются флагами, см. ниже.
RUNVMXx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] flags - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]То же самое, но извлекает флаги из стека.

Флаги похожи на runvmx в fift:

  • +1: присвоить c3 значение кода
  • +2: вставить неявный 0 перед запуском кода
  • +4: взять c4 из стека (постоянные данные), вернуть его конечное значение
  • +8: взять лимит газа g_l из стека, вернуть потребленный газ g_c
  • +16: взять c7 из стека (контекст смарт-контракта)
  • +32: вернуть конечное значение c5 (действия)
  • +64: вытолкнуть жесткий лимит газа (включено ACCEPT) g_m из стека
  • +128: "изолированное потребление газа". Дочерняя VM будет иметь отдельный набор посещенных ячеек и отдельный счетчик chksgn.
  • +256: вытолкнуть целое число r, вернуть ровно r значений сверху:* Если вызов RUNVM успешен и установлено r, он возвращает r элементов. Если r не задано - возвращает все;

Стоимость газа:

  • 66 газа
  • 1 газ за каждый элемент стека, переданный дочерней виртуальной машине (первые 32 бесплатны)
  • 1 газ за каждый элемент стека, возвращенный дочерней виртуальной машиной (первые 32 бесплатны)

Отправка сообщений

В настоящее время сложно рассчитать стоимость отправки сообщения в контракте (что приводит к некоторым приближениям, как в жетонах) и невозможно вернуть запрос, если фаза действия неверна. Также невозможно точно вычесть из входящего сообщения сумму "постоянной платы за логику контракта" и "расходов на газ".

  • SENDMSG принимает ячейку и режим в качестве входных данных. Создает выходное действие и возвращает плату за создание сообщения. Режим имеет тот же эффект, что и в случае SENDRAWMSG. Кроме того, +1024 означает - не создавать действие, только оценить плату. Другие режимы влияют на расчет платы следующим образом: +64 заменяет весь баланс входящего сообщения в качестве исходящего значения (немного неточно, расходы на газ, которые невозможно оценить до завершения расчета, не учитываются), +128 заменяет значение всего баланса контракта до начала фазы расчета (немного неточно, поскольку расходы на газ, которые невозможно оценить до завершения фазы расчета, не учитываются).
  • SENDRAWMSG, RAWRESERVE, SETLIBCODE, CHANGELIB - добавлен флаг +16, что означает в случае сбоя действия - возврат транзакции. Никакого эффекта, если используется +2.

Аудиты безопасности

Обновление виртуальной машины TON (TVM) было проанализировано на предмет безопасности и потенциальных уязвимостей.

Аудиторская фирма: Trail of Bits Аудиторский отчет: