Trusted Signer
Vela расшифровывает каждую транзакцию до того, как вы её одобрите, и это честная работа — но делает её то же приложение, которое эту транзакцию и собрало. Если приложение или путь, которым оно до вас доходит, подменили, оно может показать одно, а подписать другое. Именно это произошло с Bybit.
Trusted Signer существует, чтобы разделить это надвое: приложение только передаёт запрос, а проверка и подпись происходят на отдельной странице — её можно прочитать от начала до конца, пересобрать байт в байт или запустить у себя.
Где он работает
Официальная страница раздаётся с sign.getvela.app. Настольное приложение (macOS,
Windows, Linux) и приложения для iPhone и Android могут отправить ей запрос: приложение
открывает страницу во вкладке браузера с запросом в ссылке, вы проверяете его и
подписываете там своим passkey, а страница возвращает подпись приложению через ссылку velawallet://. Веб-кошелёк использовать его не может.
Он включается только по вашему выбору. Вы выбираете его как способ подписи при создании
кошелька или входе, и с этого момента каждая подпись для этого кошелька на этом
устройстве проходит через него. Он может и создать ключи кошелька. На sign.getvela.app он использует те же passkey getvela.app, что и приложения.
Что он делает перед подписью
- Сам расшифровывает запрос. Что делает вызов, кому и на какую сумму — из calldata, включая вызовы, вложенные в пакет.
- Подписывает только тот дайджест, который вычислил сам. Дайджесты EIP-191,
EIP-712, SafeOp и SafeMessage вычисляются на странице и никогда не берутся у
запрашивающей стороны; тесты сверяют дайджесты SafeOp и SafeMessage с
vela-core— кодом, который использует кошелёк, — а приложение отклоняет подпись над любым дайджестом, кроме того, который вычислило само. - Проверяет, что транзакция та самая, которую запросили. Вызов, о котором просил сайт, должен действительно находиться внутри подписываемой операции, иначе страница отказывает.
- Говорит, когда разрешение неограниченное. Менять сумму он не может — он подписывает пришедшие байты или ничего, — поэтому неограниченное разрешение или permit (2^200 и больше, для Permit2 — 2^152: та же граница, что и в приложениях) показывается красным с этим объяснением, и его можно подписать как есть; лимит в блокчейне выбирается на собственном экране разрешения кошелька, ещё до того, как запрос попадёт сюда. Разрешение на всю коллекцию NFT отклоняется.
- Отказывает в том, за что не может ручаться: в
eth_sign, в незнакомом ему методе, в отправке токена на контракт самого этого токена, в операции, которую он не может прочитать, и во входе, challenge для которого передала запрашивающая сторона. - Отказывает в том, что отдало бы ваш аккаунт в чужие руки, по тому же
правилу, что и приложения: в вызове вашим аккаунтом одной из его собственных
функций владельцев, модулей, guard или fallback, в том числе внутри пакета; в
delegatecall, кроме вызова контракта MultiSend от Safe, который объединяет вызовы одной операции; и в подписиSafeTx. Он проверяет каждый вызов в операции, которую собрало приложение, а не только те, о которых попросил сайт. - Показывает адрес аккаунта и идентикон, вычисленный на странице. Получатели и контракты никогда не называются по данным запроса — назвать контракт может только собственная проверенная таблица страницы. Имя самого аккаунта, которое приложение присылает, чтобы вы выбрали нужный passkey, показывается рядом с его адресом.
- Запрашивает проверку пользователя (отпечаток пальца, лицо или PIN-код) при каждой подписи.
Чего у него намеренно нет
- Никаких редакторов. Запрос фиксируется в момент поступления: вы либо подписываете его, либо нет. Выбор комиссии или редактор лимита переписали бы calldata, а это ровно та болезнь, от которой эта страница должна защищать.
- Никакого доступа к сети. Страница — это один файл, и её политика безопасности
контента (
default-src 'none') записана в её собственных байтах, поэтому она не может ничего запросить, открыть соединение или загрузить изображение. Наружу уходит только её ответ, когда она переходит по ссылке обратного вызова из запроса (velawallet://, если запрос пришёл от приложения Vela). Логотипы токенов рисуются буквами.
Что приложение проверяет в ответ
Приложение тоже не доверяет странице. Оно принимает подпись, только если подписанный challenge — это дайджест, вычисленный самим приложением, проверка пользователя была выполнена, ключ — один из ключей вашего кошелька, а подпись P-256 проходит проверку этим ключом.
Каждая опубликованная версия — проверяемая
Каждая версия воспроизводимо собирается из app-web/trusted-signer/src/ в один файл —
Bun и Node дают одни и те же байты — и публикуется по собственному адресу, sign.getvela.app/b/<sha256>/sign.html, рядом со всеми предыдущими версиями. Список
лежит по адресу sign.getvela.app/index.json.
cd app-web/trusted-signer
node samples/build-single.mjs --check # rebuilds a version listed in dist/
curl -sL https://sign.getvela.app/b/<sha256>/sign.html | shasum -a 256 При запуске настольное приложение загружает опубликованную версию, которую собирается открыть, вычисляет её хеш и сравнивает с версиями, встроенными в само приложение. Результат только записывается в журнал, а несовпадающая страница всё равно открывается. Мобильные приложения эту проверку пока не выполняют.
Своя копия
В настройках хранится адрес страницы, которую открывают ваши приложения, так что его
можно направить на своё развёртывание: любой адрес HTTPS или localhost для
тестирования. Соберите страницу командой bun samples/build-single.mjs (или node)
и скопируйте dist/ на свой хост.
Копия на вашем собственном домене подписывает passkey, созданными для этого домена, а не passkey getvela.app, — то есть это способ создать и использовать
кошелёк, ключи которого живут под вашим доменом, а не способ подписывать за
существующий кошелёк getvela.app. Все ключи одного кошелька относятся к одному
домену.
Код и скрипты, которые его собирают и проверяют, лежат в app-web/trusted-signer/.