Проверка подписи в TON и Tycho
TON и Tycho совместимы на уровне TVM и поэтому используют похожие примитивы проверки подписи. Отличия появляются из-за разных требований к безопасности при существовании нескольких сетей, где пользователи могут переиспользовать один и тот же ключ и один и тот же контракт. В Tycho добавлен контекст сети в данные, которые проверяются подписью, чтобы убрать риск replay-атак между сетями.
Краткое сравнение
| Что сравниваем | TON | Tycho |
|---|---|---|
| Что фактически проверяется подписью | Обычно подпись над 256-битным хэшем сообщения | Подпись над теми же данными, но с добавлением домена подписи, который зависит от global_id сети |
| Базовые примитивы в VM | CHKSIGNU для проверки подписи хэша, CHKSIGNS для проверки подписи данных | Те же опкоды, но поведение меняется при включённой поддержке домена подписи |
| Контекст сети в подписи | Отсутствует | Добавляется на уровне VM как префикс к данным проверки, по умолчанию для L2 сетей |
Проверка подписи в TON
В TON стандартная схема для контрактов и кошельков выглядит так:
- офчейн код сериализует подписываемые поля в ячейку
- вычисляет 256-битный хэш ячейки
- подписывает этот хэш алгоритмом Ed25519
- контракт на чейне пересчитывает хэш и проверяет подпись
TVM предоставляет два варианта проверки подписи:
CHKSIGNUпроверяет подпись хэшаCHKSIGNSпроверяет подпись данных
На практике чаще используют проверку по хэшу через CHKSIGNU, потому что CHKSIGNS проверяет только данные из одной ячейки и игнорирует ссылки. Если подписываемая структура содержит несколько ячеек или ссылки, сначала считают хэш всей структуры, а затем проверяют подпись хэша.
Проверка подписи в Tycho
Tycho добавляет понятие домена подписи, чтобы одна и та же полезная нагрузка приводила к разным данным для подписи в разных сетях. Домен подписи связан с идентификатором сети global_id.
TL схема домена подписи
// Non-empty variant. Hash of its TL representation
// is used as a prefix for the verified data.
signature_domain.l2#71b34ee1 global_id:int = SignatureDomain;
// Special variant to NOT add any prefix for the verified data.
// Can be used to verify mainnet signatures from L2 networks.
signature_domain.empty#e1d571b = SignatureDomain;
Как домен подписи влияет на проверку
В VM появляется стек доменов подписи. Пустой стек эквивалентен пустому домену подписи. Текущий домен подписи влияет на CHKSIGNU и CHKSIGNS так:
- если домен пустой, проверка совпадает с исходным поведением и префикс не добавляется
- если домен непустой и включена capability
SignatureDomain, к данным проверки добавляется 32-байтный префикс, который является хэшем TL-представления домена подписи
Для L2 сетей Tycho домен подписи добавляется в стек неявно перед исполнением контракта. Это означает, что контракт, который в TON успешно проверял подпись, в Tycho может начать получать false, если подпись создавалась без учёта домена подписи.
Управление доменом подписи в коде контракта
Для работы с доменом подписи в Tycho добавлены опкоды, которых нет в TVM в сети TON.
Новые опкоды и их семантика из sources/SignatureDomain.md:
f91800SIGNDOMAIN(- x или ⊥) кладёт на стек текущий домен подписи, гдеxэтоglobal_id, либо ⊥ если домена подписи не былоf91801SIGNDOMAIN_POP(- x или ⊥) делает то же самое, но дополнительно удаляет считанное значение из стека доменов подписиf91802SIGNDOMAIN_PUSH(x или ⊥ -) добавляет опциональныйglobal_idв стек доменов подписи и делает его текущим доменом, диапазон-2^31 ≤ x < 2^31
Пустой домен можно использовать как режим совместимости, когда нужно проверить подпись, созданную без домена подписи.
Рекомендации для разработчиков
- При переносе контракта из TON в Tycho не нужно менять код контракта и использовать новые опкоды
SIGNDOMAIN*. Новое поведение реализовано на уровне VM вCHKSIGNUиCHKSIGNSи включается через capabilitySignatureDomain. Этот режим обычно включён по умолчанию через параметры сети. - При переносе TON контракта, который проверяет подписи, проверьте, что подпись формируется в том же контексте
global_id, который использует сеть Tycho. Если подпись создаётся старой библиотекой без домена подписи, проверка в контракте может начать падать. - Если контракту нужно принимать подписи из разных доменов, закладывайте это в протокол сообщения и верификацию. Например, перед проверкой подписи переключайте домен подписи через
SIGNDOMAIN_PUSH, либо используйте пустой домен для проверок без префикса. - Тестируйте негативные сценарии. Одна и та же подпись должна быть невалидной при другом
global_id, если вы рассчитываете на защиту от replay между сетями. - Изменения домена подписи относятся к базовой проверке Ed25519 через
CHKSIGNUиCHKSIGNS. Проверка подписей для алгоритмов отличных от Ed25519, включая BLS, не имплементирована в Tycho VM. Поэтому описываемая межсетевая replay-атака через BLS подписи для Tycho не актуальна.
Пример формирования подписи с учётом включения домена подписи в Tycho. Пример использует global_id и capability бит SignatureDomain, которые берутся из конфигурации сети.
const params = Dictionary.loadDirect(
Dictionary.Keys.Uint(32),
Dictionary.Values.Cell(),
// Root cell of a non-empty config params dict
configRootFromTheCurrentNetwork,
);
// 19 param is required in tycho networks, and might be null in others
const globalId = params.get(19)?.asSlice().loadInt(32);
// 8 param is always set
const capabilities = params.get(8)!.asSlice().skip(8 + 32).loadUintBig(64);
const SIG_DOMAIN_CAPABILITY_BIT = 0x800000000n;
const domain: SignatureDomain =
globalId != null && (capabilities & SIG_DOMAIN_CAPABILITY_BIT) != 0n
? { type: "l2", globalId }
: { type: "empty" };
const signature = domainSign({ data, secretKey, domain });
Пример расчёта данных для проверки подписи в CHKSIGNU с доменом подписи. Важно, что в данных для проверки участвует префикс домена подписи и 32-байтное представление проверяемого числа.
global_id = 1000 # 0x000003e8
prefix = sha256(
0x71b34ee1.to_bytes(4, byteorder="little")
+ global_id.to_bytes(4, byteorder="little")
)
data = 0xdeadbeef
signature = ...
public_key = ...
data_to_check = prefix + data.to_bytes(32, byteorder="big")
ed25519_verify(data_to_check, signature, public_key)