Хранилище на ковенантах Kaspa решает конкретную проблему самостоятельного хранения: как хранить собственные ключи *и* при этом защититься от самого частого сценария отказа — когда скомпрометированный «горячий» ключ за секунды выводит все средства? Традиционное холодное хранение отвечает на это полным уходом в офлайн. Ковенантное хранилище отвечает иначе: правила, обеспеченные самим блокчейном.
В этой статье разберем анатомию vault.sil, контракта на Silverscript, лежащего в основе Kaspa Safe, и то, как Kaspa Forge запускает его в продакшене — от генерации ключей на стороне браузера до вотчера, который отслеживает ваши средства в ончейне.
Проблема: самостоятельное хранение по принципу «всё или ничего»
Стандартный адрес Kaspa P2PK имеет одно условие расхода: предоставить действительную подпись от соответствующего приватного ключа. Потерял ключ — потерял средства. Ключ украли — мгновенно теряешь средства. Середины нет.
Для майнеров и долгосрочных холдеров это создает напряжение. Хранить ключи в «горячем» кошельке удобно, но рискованно. Хранить их в холодном хранилище безопасно, но медленно и неудобно. Ни один вариант не справляется со сценарием, когда злоумышленник получает ваш ключ в 3 часа ночи и переводит все, пока вы спите.
Ковенантное хранилище позволяет закодировать средний путь прямо в блокчейне: *этот UTXO можно потратить только при выполнении определенных условий во времени* — а не просто «докажи, что у тебя есть ключ».
Как работает vault.sil: анатомия контракта
Контракт хранилища написан на Silverscript (^0.1.0) с использованием кодов ковенантов Toccata — он работает в мейннете Kaspa. Он компилируется в скриптовый движок узла, а в случае Kaspa Forge также встраивается непосредственно в ядро на стороне браузера на WASM через include_str!. Один и тот же исходный файл контракта используется везде — в браузере, в инструменте командной строки и в серверной валидации.
Состояние и параметры
Каждый UTXO хранилища несет два элемента ончейн-состояния:
state { mode, dest }
mode 0= VAULT (нормальное заблокированное состояние)mode 1= UNVAULTING (вывод инициирован, идет обратный отсчет задержки)dest— 36-байтовый скрипт-паблик-ключ с версионным префиксом предполагаемого получателя (устанавливается при инициации, обнуляется при отмене)
В blockDAG Kaspa DAA score равен синему счету (blue score) плюс количество красных блоков (red blocks), которые были успешно объединены и получили награду. Окно DAA охватывает 2641 блок. Хранилище использует этот показатель, а не время на стене, для своих таймеров задержки и наследования.
Конструктор принимает девять параметров:
hot, alarm, delay, heir, inheritDelay, autoInherit, feeBudget, initMode, initDest
hot— ежедневный публичный ключ владельцаalarm— отдельный «аварийный» публичный ключ (в идеале на другом устройстве)delay— сколько единиц DAA score должно пройти, прежде чем вывод сможет завершитьсяheir— 32-байтовый публичный ключ наследника (32 нуля отключают наследование)inheritDelay— единиц DAA score бездействия владельца до активации наследованияautoInherit—1для автоматической передачи наследнику,0для заявки, подписываемой наследникомfeeBudget— максимальная комиссия сети, которую контракт готов принять (ограничена 0.1 KAS)
Семь путей расхода
Хранилище определяет ровно семь способов потратить UTXO. Каждый путь обеспечивает require(tx.inputs.length == 1) — инвариант одного входа, предотвращающий атаки на выкачивание из нескольких UTXO, когда два UTXO хранилища в одной транзакции могут привести к утечке излишков меньшего из них в виде комиссии майнеру.
1. initiate(hotSig, destPk) — начать вывод. Подписан «горячим» ключом. Переводит состояние VAULT → UNVAULTING, фиксирует адрес назначения в dest. Это легитимный владелец говорит: «Я хочу перевести средства на адрес X».
2. cancel(alarmSig) — остановить вывод в процессе. Подписан тревожным ключом. Переводит UNVAULTING → VAULT, сбрасывает dest в нули. Представьте тревожный ключ как вторичное зажигание, которое может дистанционно выключить двигатель — если злоумышленник скомпрометировал «горячий» ключ и инициировал вывод, у владельца есть всё окно задержки, чтобы заметить это и отменить с другого устройства.
3. complete() — завершить после задержки. Подпись не требуется. Действует только при mode == UNVAULTING и age >= delay. Любой может отправить эту транзакцию — вотчер делает это автоматически. Средства уходят на адрес, зафиксированный в dest.
4. checkin(hotSig) — сигнал активности. Подписан «горячим» ключом. Переводит VAULT → VAULT, сбрасывает возраст UTXO в ноль. Это сигнал «я жив».
5. inheritAuto() — автоматическое наследование. Без подписи. Действует при autoInherit == 1, mode == VAULT и age >= inheritDelay. Средства переходят непосредственно на публичный ключ heir.
6. inheritSigned(heirSig) — наследование по заявке. Подписан наследником. Действует при autoInherit == 0, mode == VAULT и age >= inheritDelay. Наследник должен активно подать заявку.
7. migrate(hotSig, alarmSig) — аварийный выход. Требует обеих подписей — «горячей» и тревожной. Работает из любого режима, отправляет средства на любой адрес. Это крайний вариант: обновление контракта, ротация ключей или аварийное спасение.
Механизм задержки
Процесс вывода создает тайм-лок окно безопасности:
Владелец инициирует Окно задержки (DAA score) Любой отправляет
─────┬────────────────────┬──────────────────────────┬─────
│ VAULT→UNVAULTING │ dest зафиксирован │ complete()
│ │ alarm может отменить │ → средства на dest
│ │ в любой момент │
│ │ (UNVAULTING→VAULT) │
Задержка измеряется в единицах DAA score, а не во времени на стене. Это привязывает окно безопасности к консенсусным часам Kaspa, а не к локальному времени какого-либо узла — окно предсказуемо независимо от состояния сети.
Как только срабатывает initiate, таймер наследования останавливается, и запускается таймер вывода. Если владелец никогда не инициирует вывод и не подтверждает активность, работает таймер наследования. По конструкции они взаимоисключающие.
Механизм мертвого переключателя: наследование
Путь checkin — ключ к наследованию. Представьте его как механизм мертвого переключателя: владелец периодически подтверждает жизнеспособность, подписывая транзакцию checkin. Каждое подтверждение сбрасывает возраст UTXO в ноль. Если владелец становится неспособен подтвердить активность — болезнь, смертя, потеря доступа к «горячему» ключу — возраст превышает inheritDelay, и пути наследования разблокируются.
При autoInherit == 1 средства автоматически передаются на ключ наследника. При autoInherit == 0 наследник должен активно подписать заявку. Установка heir в 32 нулевых байта отключает весь механизм.
Защита от злоупотреблений с комиссиями
Каждый бесключевой путь (complete, inheritAuto) и каждый путь с одним подписантом обеспечивает:
require(feeBudget > 0 && feeBudget <= MAX_FEE_BUDGET)
где MAX_FEE_BUDGET = 10_000_000 сомпи (0.1 KAS). Это предотвращает отправку кем-либо бесключевой транзакции, которая сжигает содержимое хранилища в виде завышенной комиссии майнеру. Единственный путь, освобожденный от этого — migrate: он требует обеих подписей, поэтому владелец напрямую контролирует выходы.
Как Kaspa Forge реализует это в продакшене
Сам контракт — это консенсусная логика. Превращение его в удобный продукт требует нескольких уровней.
Управление ключами на стороне браузера (ядро на WASM)
WASM-крейт Kaspa Safe встраивает тот же исходник vault.sil и компилирует его на стороне клиента. Все семь сборщиков транзакций — build_initiate_tx, build_cancel_tx, build_complete_tx и так далее — выполняются полностью в браузере. Сервер получает только подписанные транзакции и публичные параметры.
Рабочий стол Kaspa Forge (зашифрованный HD-профиль) выводит ключи хранилища из мастер-сида с помощью:
HMAC-SHA512(key=seed, msg="kaspaforge/v1/vault/<index>")
Первые 32 байта выхода HMAC становятся секретным ключом. Один файл-бэкап ключа в формате .age (зашифрованный scrypt, в ASCII-арморе, совместимый с upstream CLI age -d) покрывает все текущие и будущие хранилища.
Вотчер и слой оповещений
Фоновый цикл на сервере (watcher.rs::run) опрашивает каждые 10 секунд:
1. Дифф снимка UTXO — обнаруживает входящие депозиты, инициированные выводы, отмены и завершения. 2. Предупреждения о наследовании — на 80% от inheritDelay отправляет напоминание подтвердить активность через Telegram и электронную почту. 3. Авто-завершение / авто-наследование — отправляет бесключевые транзакции при выполнении условий. Поскольку эти пути бесключевые, вотчер не может перенаправить средства; он только запускает то, что контракт уже разрешает. 4. Уведомление наследника — отправляет письмо наследнику при активации наследования.
Оповещения требуют необязательной подписки (100 KAS/год, 30-дневный бесплатный пробный период). Подписка финансируется через некастодиальный платежный поток с использованием адреса, производного от xpub.
Гарантия офлайн-восстановления
Если Kaspa Forge исчезнет, хранилище по-прежнему остается стандартным ончейн-контрактом. Для восстановления потребуется:
1. Файл-бэкап ключа .age (расшифровывается с помощью upstream CLI age или keyfile-decrypt.html) 2. Инструмент командной строки с открытым исходным кодом vaultctl или офлайн-руководство recover.html 3. Любой узел Kaspa версии 2+ — vaultctl по умолчанию использует node.kaspaforge.org:16110, но принимает любой флаг --node
Контракт хранилища живет в блокчейне независимо от какого-либо сервера.
Интересно, каково на практике использовать ковенантное хранилище? Вы можете создать хранилище в мейннете за несколько минут — карта тревоги, таймер задержки и настройки наследования настраиваются во время установки. На странице полного описания как работает хранилище есть дополнительные подробности с пошаговым руководством.
Компромиссы и честные ограничения
Ни один дизайн не обходится без издержек. Вот что вам следует знать, прежде чем вкладывать средства:
Оба ключа должны храниться раздельно. Путь migrate дает полный контроль любому, кто владеет обоими ключами — «горячим» и тревожным. Это сделано намеренно — это позволяет обновлять контракт и аварийно выходить — но это означает, что хранение обоих ключей вместе лишает смысла. Скомпрометируйте оба одновременно — и средства переводятся мгновенно.
Задержка основана на DAA score, а не удобочитаема для человека. Вы выбираете параметр задержки, но его эквивалент во времени на стене зависит от скорости производства блоков. При примерно 10 блоках в секунду задержка в 50 000 единиц DAA score составляет около 83 минут. Различные условия сети меняют эту привязку ко времени.
Нет тайм-лока на migrate. Это принятый компромисс — альтернатива (также задерживать migrate) бы заставила владельца ждать во время легитимной аварийной ситуации. Смягчение — операционное: никогда не храните оба ключа в одном месте.
Версия контракта. Текущий vault.sil v3 прошел внутреннюю проверку безопасности — инвариант одного входа во всех 7 путях, анализ ограничения бюджета комиссий, векторы злоупотреблений. Набор самотестов запускает 18 проверок в VM узла.
Модель «одно хранилище — один UTXO». Каждый депозит создает отдельный UTXO, управляемый тем же контрактом. Помощники withdrawAll и withdrawOne обрабатывают это в интерфейсе, но ончейн-реальность — это множество независимых ковенантных UTXO. Каждый вывод тратит ровно один.
Некастодиальность — это спектр доверия. Контракт хранилища не требует доверия — правила обеспечивает блокчейн. Но текущий веб-интерфейс предполагает доверие к тому, что обслуживаемый JavaScript соответствует коду с открытым исходным кодом. Tauri Android APK и офлайн-путь recover.html уменьшают эту поверхность. Полностью детерминированный конвейер сборки — будущая цель.
---
Модель ковенантного хранилища в Kaspa предлагает то, чего нет у большинства конфигураций самостоятельного хранения: способ хранить собственные ключи *и* при этом иметь сеть, которая обеспечивает вторую линию обороны. Это не замена хорошей гигиене ключей — это слой поверх нее, построенный на кодах ковенантов Toccata, которые делают правила расходования по времени первоклассными гражданами в UTXO-модели.
Механика проста, когда вы ее видите: инициируй, жди, заверши — или отмени, если что-то не так. Интересно то, что блокчейну все равно, кто отправляет финальную транзакцию. Ему важно только то, что задержка прошла, а адрес назначения был зафиксирован во время инициации.
Полную техническую спецификацию и исходный код смотрите в репозитории Kaspa Safe и в базе знаний разработчиков Kaspa.
FAQ
Что такое хранилище на ковенантах в Kaspa?
Ончейн-скрипт в блокчейне Kaspa, который блокирует KAS по правилам — задержка вывода, тревожный ключ, опциональное наследование — обеспеченные сетью, а не кастодианом.
Может ли команда Kaspa Forge получить доступ к средствам моего хранилища?
Нет. Приватные ключи генерируются и хранятся только в вашем браузере. Сервер никогда не видит вашу сид-фразу, парольную фразу или приватные ключи.
Что произойдет, если Kaspa Forge навсегда выйдет из сети?
Ваши средства остаются на блокчейне. Вы можете восстановить доступ с помощью инструмента командной строки с открытым исходным кодом vaultctl на любом узле Kaspa версии 2+ или использовать офлайн-руководство recover.html.
Для чего нужен тревожный ключ?
Отдельный ключ, который может отменить вывод во время окна задержки — предназначен для остановки кражи в процессе, если «горячий» ключ скомпрометирован.
Как работает наследование в хранилище?
Если владелец прекращает подтверждение активности на протяжении inheritDelay блоков, средства передаются указанному наследнику — либо автоматически, либо когда наследник подписывает запрос.
Проводился ли аудит контракта хранилища?
Контракт прошел внутреннюю проверку безопасности (инвариант одного входа, ограничение бюджета комиссий).
Держите KAS там, где кражу можно отменить
Ковенант-сейф в мейннете Kaspa: ваши ключи, ваши правила, наш инструмент. Ончейн — бесплатно, навсегда.
Создать сейф
