Перейти к содержанию

tx.origin в Solidity: риск ошибочной авторизации

В карте авторизации мы проверяли непосредственный caller. Теперь рассмотрим соблазнительную замену: разрешить запись, если tx.origin равен адресу преподавателя. Кажется, будто мы убедились, кто начал транзакцию. Но начальный отправитель не определяет, кому разрешено выбирать параметры промежуточного вызова.

Для разбора создадим собственную локальную модель без средств и без обращения к чужим адресам. OriginNote хранит одну заметку bytes32. Лабораторный посредник ExerciseRelay способен вызвать запись с заранее выбранным им значением. Код предназначен показать ошибочное условие, а не стать рабочей авторизацией библиотеки.

Контрпример с одной заметкой

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
// Учебная ошибочная модель. Не использовать для авторизации.
contract OriginNote {
    error Unauthorized();
    address public immutable teacher;
    bytes32 public note;
    constructor(address teacher_) { teacher = teacher_; }
    function set(bytes32 value) external {
        if (tx.origin != teacher) revert Unauthorized();
        note = value;
    }
}
contract ExerciseRelay {
    OriginNote public immutable target;
    constructor(OriginNote target_) { target = target_; }
    function exercise(bytes32 relayChosenValue) external {
        target.set(relayChosenValue);
    }
}

Это полный минимальный контрпример. У него нет проверки нулевого teacher, потому что предметом является именно ошибочная авторизация через origin; такой дополнительный недостаток также не переносится в исправленный продукт. Примеры не развернуты и не выполняются, а посредник является нашим собственным учебным контрактом.

Цепочка контекста

Пусть T отправляет прямую транзакцию к ExerciseRelay.exercise(H2). Внутри target.set непосредственным msg.sender является ExerciseRelay, тогда как tx.origin остаётся T для данной обычной цепочки. Условие в OriginNote проходит, хотя цель не ограничивала полномочие самого посредника.

T → ExerciseRelay → OriginNote
в OriginNote: msg.sender = ExerciseRelay
в OriginNote: tx.origin = T
OriginNote разрешает запись по начальному отправителю

Если T допустил взаимодействие с посредником, это не обязательно означает согласие на все действия, которые тот может выполнить дальше. Реальный интерфейс может показывать другое название операции или формировать параметры по своей логике. Ошибочная target-проверка превращает происхождение общей транзакции в слишком широкое доверие всем промежуточным маршрутам.

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

Исправление непосредственного условия

Если требуется прямое право только T, target должен сравнивать msg.sender с teacher. Тогда обычный вызов T→OriginNote разрешён, а T→ExerciseRelay→OriginNote отклоняется: непосредственный caller другой. Это осмысленное ограничение, а не универсальное требование, что все действия всегда должны приходить только от человека.

Для библиотеки publisher может быть контрактным кошельком, комитетом или Timelock. Тогда target сравнивает msg.sender именно с выбранным управляющим контрактом. Этот контракт отдельно ограничивает, какие действия может инициировать пользователь. Право композиции устанавливается явно вместо доверия любому промежуточному адресу.

Если посредник должен быть разрешён, нужно определить его предметный интерфейс, роли и точные параметры. Простая проверка, что origin является одним из участников, не заменяет его согласование. Разрешённое управление от имени контракта и передача глобального происхождения транзакции — разные модели.

Sender и подпись намерения

В relay-системе можно проверять специально подписанное распоряжение с доменом, параметрами, nonce и сроком. Но такая модель требует собственного полного договора. Она не появляется из tx.origin и не сводится к восстановлению адреса из любой когда-то полученной подписи.

Подпись должна описывать конкретную цель и действие. Иначе подтверждение входа на сайт можно ошибочно использовать как административную команду. Поздние главы курса разберут домен и повторное применение на отдельных собственных моделях. Базовый CourseCatalog пока использует простой проверяемый caller.

При необычных маршрутах аккаунтов начальный отправитель транзакции также может отличаться от предметного владельца операции. Поэтому интерфейс не должен обещать определить пользователя универсально по origin. Авторизация строится по выбранному протоколу аккаунта и действия, с его явно проверяемым субъектом.

Что не следует исправлять случайной эвристикой

Условие msg.sender==tx.origin иногда применяют как попытку запретить контракты. Оно не является доказательством полномочия нужного редактора. Посторонний прямой отправитель тоже может удовлетворять равенству. Кроме того, такая эвристика конфликтует с допустимыми контрактными маршрутами и не описывает современную политику аккаунтов.

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

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

Ожидаемые сценарии

Составьте четыре случая для OriginNote: T вызывает напрямую, X вызывает напрямую, T вызывает через ExerciseRelay, X вызывает через ExerciseRelay. Для каждого выпишите оба адреса контекста. Затем замените условие на msg.sender==teacher и определите новые результаты.

После этого назначьте teacher адресом специально разрешённого relay и разберите его внутреннее полномочие. Если relay открыт для любого X, target-проверка может быть корректной, а система всё равно позволять чужую запись. Это возвращает нас к карте всех путей предыдущей главы.

Итоговое свойство формулируется точнее: выбранный субъект может выполнить только разрешённый им маршрут с согласованными параметрами. tx.origin не обеспечивает такой договор через произвольную цепочку вызовов. Далее разберём внутренний вызов и self-call, где похожие имена тоже скрывают различный контекст.

Поля подтверждения

Когда пользователь согласует действие, полезно показать не только начальный адрес, но и полный целевой маршрут: контракт, selector и параметры. Даже такой экран не заменяет on-chain ограничения, поскольку другой интерфейс может сформировать собственные байты. Но он помогает выбранному управляющему субъекту осмысленно пользоваться своей разрешённой политикой.

Для заметки H2 это небольшое значение, для публикации курса оно связывается с проверенным файлом. Если посредник способен заменить параметр после согласования отдельной подписи, проверка должна включать именно digest в signed intent. Одно подтверждение адреса T не фиксирует содержание всех возможных его будущих вызовов.

Сохраните в модели угроз различие: caller target, первоначальный отправитель и автор подписанного распоряжения могут быть разными субъектами. Каждый определяется своим механизмом, а не удобным общим названием «пользователь».

Для самостоятельного уточнения: security-considerations.html, units-and-global-variables.html.

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

Оглавление серии