Одноразовое значение создаёт и расходует сервер
Перед открытием подключения сервер генерирует непредсказуемую строку `payload`, связывает её с одной попыткой входа и задаёт короткий срок. Клиентская часть лишь передаёт строку в запросе `tonProof`. Нельзя использовать адрес кошелька, метку времени, последовательный номер сессии или общее одноразовое значение: такие данные угадываются или повторяются.
В хранилище нужны состояние попытки, ожидаемый домен, разрешённая сеть и срок. После всех проверок `nonce` переводится из `unused` в `consumed` одной атомарной операцией. Два параллельных запроса с одним доказательством не должны оба увидеть `unused`. При любом несовпадении сессия не создаётся. Для нового входа сервер выпускает другое значение, а не просит кошелёк снова подписать прежнее.
Сессия появляется только после всех проверок
| Проверка | Положительный результат | Если пропустить |
|---|---|---|
| Ожидаемый домен и его длина в UTF-8 | Доказательство создано для домена этого приложения | Его можно принять в неверном контексте |
| Одноразовое значение сервера | Доказательство относится к открытой попытке входа | Старое доказательство можно воспроизвести |
| walletStateInit → contractAddress | StateInit действительно порождает заявленный адрес | Клиент подменит данные для открытого ключа |
| Подпись Ed25519 | Доверенный ключ подписал точный дайджест | Нет криптографического контроля аккаунта |
| Разрешённая сеть | Аккаунт подключён в разрешённой приложением сети | Подпись сама сеть не связывает |
- Проверка
- Ожидаемый домен и его длина в UTF-8
- Положительный результат
- Доказательство создано для домена этого приложения
- Если пропустить
- Его можно принять в неверном контексте
- Проверка
- Одноразовое значение сервера
- Положительный результат
- Доказательство относится к открытой попытке входа
- Если пропустить
- Старое доказательство можно воспроизвести
- Проверка
- walletStateInit → contractAddress
- Положительный результат
- StateInit действительно порождает заявленный адрес
- Если пропустить
- Клиент подменит данные для открытого ключа
- Проверка
- Подпись Ed25519
- Положительный результат
- Доверенный ключ подписал точный дайджест
- Если пропустить
- Нет криптографического контроля аккаунта
- Проверка
- Разрешённая сеть
- Положительный результат
- Аккаунт подключён в разрешённой приложением сети
- Если пропустить
- Подпись сама сеть не связывает
Одна перестановка байтов меняет дайджест
Кошелёк подписывает не JSON. Сначала идёт префикс `ton-proof-item-v2/` в UTF-8. Затем записываются `workchain` как знаковое 32-битное число старшим байтом вперёд (`big-endian`) и 256-битный хеш адреса. Следом идут длина домена как беззнаковое 32-битное число младшим байтом вперёд (`little-endian`) и сам домен в UTF-8. После них записываются метка времени как беззнаковое 64-битное число `little-endian` и точные байты `payload` в UTF-8. Сравнивайте `domain.lengthBytes` с фактической длиной UTF-8. Метку времени нормализуйте: SDK представляет её числом, а спецификация передачи — десятичной строкой.
| Фрагмент | Кодирование | Проверка до хеширования |
|---|---|---|
| Префикс | UTF-8 ton-proof-item-v2/ | Точное значение и порядок |
| Адрес | workchain int32 BE + хеш 256-bit BE | Полный исходный адрес из доверенно связанного StateInit |
| Домен приложения | длина uint32 LE + домен UTF-8 | Ожидаемый хост и фактическая длина в байтах |
| Метка времени | uint64 LE, секунды Unix | Допустимое прошлое окно и будущая погрешность часов |
| Payload | Точная строка UTF-8 | Неистёкший неиспользованный nonce этой попытки |
- Фрагмент
- Префикс
- Кодирование
- UTF-8 ton-proof-item-v2/
- Проверка до хеширования
- Точное значение и порядок
- Фрагмент
- Адрес
- Кодирование
- workchain int32 BE + хеш 256-bit BE
- Проверка до хеширования
- Полный исходный адрес из доверенно связанного StateInit
- Фрагмент
- Домен приложения
- Кодирование
- длина uint32 LE + домен UTF-8
- Проверка до хеширования
- Ожидаемый хост и фактическая длина в байтах
- Фрагмент
- Метка времени
- Кодирование
- uint64 LE, секунды Unix
- Проверка до хеширования
- Допустимое прошлое окно и будущая погрешность часов
- Фрагмент
- Payload
- Кодирование
- Точная строка UTF-8
- Проверка до хеширования
- Неистёкший неиспользованный nonce этой попытки
Сервер вычисляет внутренний SHA-256 сообщения, добавляет два байта `0xff` и строку `ton-connect` в UTF-8, затем снова считает SHA-256. Ed25519 проверяется по полученному дайджесту. Не сериализуйте поля в собственном порядке JSON и не используйте пользовательскую строку адреса как готовые байты. Не исправляйте `domain.toLowerCase()` после подписи.
Открытый ключ должен принадлежать заявленному контракту кошелька
Порядок установления аккаунта
- 1Разобрать walletStateInit
`BOC` (`Bag of Cells`) — бинарный контейнер сериализованных ячеек TON. Декодируйте из него `walletStateInit`, определите версию кошелька по хешу кода и извлеките открытый ключ из данных по структуре этой версии.
- 2Пересчитать адрес
`contractAddress` для того же `workchain` и StateInit обязан совпасть с `account.address`. Иначе доказательство относится к несогласованному набору данных.
- 3Сверить открытый ключ
Извлечённый ключ сравните с полем ответа. Для неизвестного контракта кошелька используйте документированный вызов `get_public_key` из цепочки как запасной путь, а не доверяйте клиентскому полю.
- 4Проверить подпись
Проверьте Ed25519-подпись точного дайджеста. Ошибка не исправляется сменой формата адреса или повторным хешированием готового дайджеста.
- 5Проверить правила и израсходовать nonce
После криптографии сравните домен, метку времени, `payload` и разрешённую сеть. Затем одним условным обновлением отметьте попытку как `consumed` и только после этого выдайте сессию.
Пользовательская, `bounceable` и `raw` формы TON могут обозначать один идентификатор аккаунта, но в байтовую сборку нельзя подставлять строку на глаз. Сначала нормализуйте адрес документированной библиотекой и отдельно сохраните исходную сеть.
Валидная подпись тоже может быть отклонена
| Наблюдение | Вероятная граница | Безопасное действие |
|---|---|---|
| Подпись неверна | Байты, ключ или сама подпись не совпали | Сохранить поля и сравнить сериализацию по шагам; не выдавать сессию |
| Подпись верна, nonce неизвестен | Payload выпущен другим сервером или потерян | Отклонить и начать новую попытку |
| Подпись верна, nonce использован | Повтор того же доказательства | Отклонить без повторного расходования и зафиксировать событие |
| Домен не совпал | Доказательство подписано для другого хоста или иной длины UTF-8 | Отклонить; не нормализовать несовпадение постфактум |
| Метка времени вне окна | Старое доказательство или недопустимое опережение часов | Закрыть попытку и выпустить новый nonce |
| Сеть не разрешена | Account.chain не проходит правила приложения | Отклонить отдельно от результата подписи |
- Наблюдение
- Подпись неверна
- Вероятная граница
- Байты, ключ или сама подпись не совпали
- Безопасное действие
- Сохранить поля и сравнить сериализацию по шагам; не выдавать сессию
- Наблюдение
- Подпись верна, nonce неизвестен
- Вероятная граница
- Payload выпущен другим сервером или потерян
- Безопасное действие
- Отклонить и начать новую попытку
- Наблюдение
- Подпись верна, nonce использован
- Вероятная граница
- Повтор того же доказательства
- Безопасное действие
- Отклонить без повторного расходования и зафиксировать событие
- Наблюдение
- Домен не совпал
- Вероятная граница
- Доказательство подписано для другого хоста или иной длины UTF-8
- Безопасное действие
- Отклонить; не нормализовать несовпадение постфактум
- Наблюдение
- Метка времени вне окна
- Вероятная граница
- Старое доказательство или недопустимое опережение часов
- Безопасное действие
- Закрыть попытку и выпустить новый nonce
- Наблюдение
- Сеть не разрешена
- Вероятная граница
- Account.chain не проходит правила приложения
- Безопасное действие
- Отклонить отдельно от результата подписи
`ton_proof` доказывает контроль ключа для точного аккаунта и попытки входа, а не личность человека, право на средства или безопасность приложения. Пользовательский экран подписи всё равно нужно читать как отдельное разрешение. Открытый и закрытый ключи выполняют разные роли, а закрытый никогда не передают серверу.
Это также не транзакция: `ton_proof` ничего не отправляет в блокчейн и не использует `seqno` или `valid_until` сообщения кошелька. Если приложение смешивает вход и перевод, разделите журналы проверки доказательства и следа в блокчейне. Тогда повтор авторизации не создаст новый платёж.
Сервер завершает вход единственной аудиторской записью: идентификатор попытки, хеш `nonce` без исходного секрета, ожидаемый домен, адрес, версию и хеш кода. Запись также хранит источник открытого ключа, проверку времени, разрешённую сеть, результат подписи и единственный переход в `consumed`. Доказательство, StateInit и токен сессии не должны попадать в публичные URL или аналитические события.
Источники
Мы используем прямые ссылки и фиксируем дату проверки. Полный текст чужих материалов не перепечатывается.
- TON Docs — How to authenticate users with ton_proof TON Foundation · Проверено 28 августа 2026 г. в 00:04 GMT+5
- TON Connect SDK — pinned implementation surface TON Connect · Проверено 28 августа 2026 г. в 00:04 GMT+5
