Обучение

Как проверить владельцев multisig и порог подписей

Практическая проверка multisig на примере Safe: текущие owners и threshold, типы владельцев, singleton, модули, guard, fallback handler и история изменений.

Как проверить владельцев multisig и порог подписей

Надпись «multisig 3 из 5» в интерфейсе не доказывает, что контракт действительно контролируют пять ожидаемых адресов и что для любого вывода нужны три подписи. Состав владельцев мог измениться, один owner может оказаться другим контрактом, а включённый module иногда исполняет операции по собственным правилам без обычного порога.

Надёжная проверка начинается с текущего состояния блокчейна. Ни сайт проекта, ни список в документации, ни неподписанная очередь Safe Transaction Service не заменяют чтение самого контракта. Ниже используется Safe как самый распространённый пример, но логика подходит и для других multisig: сначала определить реализацию, затем прочитать актуальных владельцев, порог и альтернативные пути исполнения.

Что именно нужно установить

Минимальный результат проверки – точный адрес multisig, сеть, список текущих owners и threshold. Порог показывает, сколько подтверждений требуется для обычной owner-транзакции. Если owners пять, а threshold три, базовая схема называется 3 из 5.

Для оценки реального контроля этого мало. Нужно выяснить, являются ли owners обычными аккаунтами, аппаратными кошельками, другими Safe, DAO-контрактами или программируемыми подписантами. Затем проверить modules, guard, fallback handler и singleton. Эти компоненты могут расширять или ограничивать стандартный путь исполнения.

Важно различать текущую конфигурацию и предложение об изменении. Подготовленная транзакция `addOwnerWithThreshold`, `removeOwner`, `swapOwner` или `changeThreshold` не меняет контракт до onchain-исполнения. После исполнения старый скриншот становится историческим.

Шаг 1. Зафиксируйте сеть и адрес

Один и тот же шестнадцатеричный адрес может существовать в нескольких EVM-сетях с разным кодом и состоянием. Запишите chain, полный адрес и источник, откуда он получен. Для treasury проекта полезно сопоставить несколько независимых мест: governance proposal, официальный реестр контрактов и предыдущие onchain-операции.

Откройте адрес в обозревателе нужной сети. Убедитесь, что у него есть bytecode. Если адрес помечен как EOA и кода нет, это не deployed multisig в данной сети. Исключение составляют новые механизмы делегирования аккаунта, но они тоже требуют отдельной проверки, описанной в материале об EIP-7702 delegation.

Не ищите кошелёк только по красивому ENS-имени или подписи в обозревателе. Имя может указывать не на ту сеть, а публичный label не является частью состояния контракта.

Шаг 2. Определите тип multisig

Safe обычно развёрнут как небольшой proxy, который делегирует вызовы общему singleton. Обозреватель может распознать proxy и показать ABI реализации. У стандартного Safe доступны функции `getOwners()`, `getThreshold()`, `nonce()` и `VERSION()`.

Если ABI не верифицирован, сравните bytecode proxy с официальными развёртываниями для этой сети или используйте проверенную ABI Safe через локальный RPC-клиент. Не подставляйте случайную ABI только потому, что вызов не меняет состояние: неверная сигнатура функции может вернуть ошибку или быть обработана fallback-логикой иначе.

Другой multisig может хранить подписантов в иной структуре, использовать роли AccessControl или вообще не быть кошельком. Тогда имена функций Safe неприменимы. Сначала найдите verified source, factory, события создания и документацию конкретной реализации.

Шаг 3. Прочитайте текущих owners

В разделе чтения контракта через proxy вызовите `getOwners()`. Полученный массив адресов – текущий набор владельцев в состоянии Safe на выбранном блоке. Скопируйте адреса полностью, не ограничиваясь первыми и последними символами.

Тот же вызов можно выполнить через RPC как `eth_call`, используя ABI интерфейса Safe. Укажите адрес multisig как `to` и вызов `getOwners()` как data. Важно читать именно proxy address: вызов singleton покажет состояние singleton, а не конкретного кошелька.

Сравните результат с заявленным списком. Лишний owner может быть забытым deployer или recovery-аккаунтом. Отсутствующий адрес может означать, что команда уже провела ротацию. Совпадение количества без совпадения самих адресов недостаточно.

Шаг 4. Прочитайте threshold

Вызовите `getThreshold()`. Значение должно быть больше нуля и не превышать число owners. Интерпретируйте его вместе со списком: threshold 2 при двух owners – схема 2 из 2, а threshold 2 при семи owners – 2 из 7.

Порог относится к стандартной Safe transaction, которая проходит проверку подписей owners. Он не означает, что каждый возможный вызов из Safe всегда требует столько подписей. Enabled module может получить право вызывать `execTransactionFromModule` после собственной проверки. Поэтому формулировка «ни одна операция невозможна без трёх владельцев» допустима только после проверки альтернативных путей.

Оцените не только число, но и устойчивость. Схема 1 из N почти не защищает от компрометации одного owner. Схема N из N исключает кражу одним участником, но один потерянный ключ блокирует обычные операции. Порог должен соответствовать распределению ключей, процедуре замены и допустимому времени реакции.

Шаг 5. Определите тип каждого owner

Для каждого адреса запросите code в той же сети. Пустой code обычно означает EOA, а непустой – контракт. Но это только начало анализа. EOA может контролироваться аппаратным кошельком, seed-фразой в облаке или одной централизованной системой, и блокчейн не раскрывает этот операционный контекст.

Если owner – контракт, откройте его отдельно. Это может быть другой Safe с собственными owners и threshold. Тогда внешний multisig 2 из 3 способен фактически зависеть от большего или меньшего числа людей. Пройдите проверку рекурсивно до понятных корневых контролёров.

Контрактный owner может подтверждать подписи через EIP-1271 или собственную логику. Passkey и account-abstraction решения также не обязаны выглядеть как обычная ECDSA-подпись. Нельзя автоматически считать контрактный owner безопаснее или опаснее EOA – нужно понять его код, upgradeability и recovery.

Проверьте независимость. Пять адресов не дают организационной схеме 3 из 5, если все ключи находятся в одной учётной записи кастодиана или у одного сотрудника. Onchain-анализ показывает адреса, но не доказывает разделение устройств и полномочий.

Шаг 6. Проверьте singleton и версию Safe

Safe Proxy передаёт основную логику singleton-контракту. Обозреватель может показать implementation или master copy, а стандартный proxy поддерживает специальное чтение адреса singleton. Сопоставьте его с официально поддерживаемым развёртыванием для данной сети и прочитайте `VERSION()` через сам Safe.

Не переносите правила OpenZeppelin Transparent или UUPS proxy на Safe автоматически. Стандартный Safe Proxy создан прежде всего для дешёвого повторного использования singleton и не является обычным индивидуально обновляемым proxy. Пользовательская модификация или нестандартная factory может вести себя иначе.

Если обозреватель показывает неизвестный singleton, отсутствие verified source или отличающийся bytecode, остановитесь и исследуйте код. Знакомый интерфейс может скрывать изменённую проверку подписей или дополнительный путь исполнения. Подробный порядок чтения implementation разобран в инструкции по проверке proxy-контрактов.

Шаг 7. Найдите enabled modules

В Safe список modules хранится отдельно от owners. Получите его через пагинированный метод modules или надёжный SDK, пока не пройдёте все страницы. Не ограничивайтесь первой записью и не доверяйте пустому списку стороннего интерфейса без onchain-вызова.

Module способен исполнять транзакции из Safe по собственным условиям. Это используется для allowance, automation, recovery и ERC-4337, но одновременно создаёт обход обычного owner threshold. Для каждого адреса module проверьте код, владельца его upgrade-механизма, лимиты, разрешённые targets и возможность отключения.

Наличие module не означает уязвимость. Ошибка – оценивать Safe только как 3 из 5, не учитывая module, который может переводить активы после одной внешней подписи или по расписанию. В модели угроз важен самый слабый авторизованный путь.

Шаг 8. Проверьте guard и fallback handler

Guard проверяет Safe transaction до исполнения и может выполнить проверку после неё. Корректный guard ограничивает опасные операции, но ошибочный или злонамеренный способен блокировать все стандартные транзакции. Адрес guard хранится в выделенном storage slot и может быть прочитан специализированным Safe-инструментом или прямым чтением storage.

Fallback handler добавляет функции, которых нет в основном Safe, например проверку EIP-1271 сообщений или поддержку account abstraction. Он получает forwarded call и исходного caller в calldata. Проверьте адрес, исходный код и совместимость с версией Safe.

Изменение module, guard или fallback handler само требует подтверждения текущим threshold, но после включения компонент меняет будущую поверхность доступа. Поэтому транзакцию настройки нужно проверять так же тщательно, как перевод средств.

Шаг 9. Сопоставьте состояние с историей

Найдите создание Safe и последующие owner-management операции. Полезные сигналы – добавление, удаление и замена owner, изменение threshold, включение modules и смена guard. Для SafeL2 многие изменения видны в событиях, а некоторые версии и варианты Safe требуют tracing или специализированного indexer.

История не является единственным источником истины. Событие могло не индексироваться, chain могла пережить реорганизацию, а кастомная реализация – менять storage иначе. Текущие `getOwners()` и `getThreshold()` важнее реконструкции по логам.

Проверьте блок, в котором возникло изменение, транзакцию и вызывающий адрес. Owner-management должен исполняться самим Safe после достаточных подтверждений, а не произвольным EOA напрямую. Внутренний batch может одновременно заменить owner, изменить threshold и включить module, поэтому читайте весь calldata.

Шаг 10. Проверьте подготовленную транзакцию

Если аудит проводится перед подписью, сравните текущий onchain nonce Safe с nonce предложения. Затем декодируйте `to`, `value`, `data` и `operation`. Для обычного управления owners target часто совпадает с адресом Safe, потому что Safe вызывает собственную функцию.

Проверьте новый owner, предыдущий owner в связанном списке, итоговый threshold и все операции внутри batch. Неверный параметр `prevOwner` приведёт к revert, а неожиданный `delegatecall` может выполнить код в storage-контексте Safe. Подпись EIP-712 должна относиться к правильной сети и Safe address – подробный чек-лист есть в статье о проверке EIP-712.

Симуляция полезна для обнаружения revert и ожидаемых storage changes, но не заменяет чтение прав. До включения транзакции состояние может измениться, а симулятор может не раскрыть последствия нового module или будущего вызова.

Шаг 11. Перепроверьте после исполнения

Дождитесь включения транзакции и достаточной для вашей задачи финальности. Снова вызовите `getOwners()` и `getThreshold()`, проверьте modules, guard, fallback handler и nonce. Не делайте вывод по статусу «executed» в интерфейсе без receipt нужной сети.

Зафиксируйте блок и результаты в отчёте. Если multisig контролирует treasury или upgrade admin протокола, настройте мониторинг изменений owners, threshold, modules и singleton. Разовая проверка отвечает только на вопрос о выбранном состоянии, а не гарантирует неизменность.

Краткий чек-лист

  • совпадают сеть и полный адрес multisig;
  • на адресе есть ожидаемый proxy bytecode;
  • `getOwners()` возвращает заявленные адреса;
  • `getThreshold()` соответствует объявленной схеме;
  • тип и реальный контроль каждого owner понятны;
  • контрактные owners проверены рекурсивно;
  • singleton и версия относятся к доверенному развёртыванию;
  • полный список modules прочитан onchain;
  • guard и fallback handler известны и проверены;
  • история изменений не противоречит текущему состоянию;
  • после исполнения конфигурация прочитана повторно.

Итог

Проверка multisig – это не поиск подписи «3/5» рядом с адресом. Сначала нужно прочитать фактические owners и threshold у контракта, затем раскрыть тип и контроль каждого owner. Для Safe дополнительно проверяются singleton, modules, guard и fallback handler.

Текущий threshold защищает только стандартный путь owner-транзакции. Реальная безопасность определяется всеми авторизованными путями, качеством ключевого управления и возможностью восстановить работу после потери участника. Подробный обзор компонентов Safe доступен в материале о Safe Smart Account.