A Kaspa covenant vault solves a specific self-custody problem: how do you hold your own keys *and* protect against the most common failure mode — a compromised hot key draining everything in seconds? Traditional cold storage answers this by going fully offline. A covenant vault answers it differently: rules enforced by the blockchain itself.
This article walks through the anatomy of vault.sil, the Silverscript contract behind Kaspa Safe, and how Kaspa Forge runs it in production — from browser-side key generation to the watcher that monitors your funds on-chain.
The problem: all-or-nothing self-custody
A standard Kaspa P2PK address has one spending condition: present a valid signature from the corresponding private key. Lose the key — lose the funds. Get the key stolen — lose the funds instantly. There is no middle ground.
For miners and long-term holders, this creates a tension. Keeping keys in a hot wallet is convenient but risky. Keeping them in cold storage is safe but slow and inflexible. Neither option handles the scenario where an attacker gets hold of your key at 3 AM and moves everything before you wake up.
A covenant vault lets you encode a middle path directly on the blockchain: *this UTXO can only be spent if certain conditions are met over time* — not just "prove you have the key."
How vault.sil works: the contract anatomy
The vault contract is written in Silverscript (^0.1.0) using Toccata covenant opcodes — live on Kaspa mainnet. It is compiled into the node's script engine and, in the case of Kaspa Forge, also embedded directly into the browser-side WASM core via include_str!. The same contract source file is used everywhere — the browser, the CLI tool, and the server-side validation.
State and parameters
Each vault UTXO carries two pieces of on-chain state:
state { mode, dest }
mode 0= VAULT (normal locked state)mode 1= UNVAULTING (withdrawal initiated, delay counting down)dest— a 36-byte version-prefixed script pubkey of the intended recipient (set during initiation, zeroed on cancel)
In Kaspa's blockDAG, the DAA score equals the blue score plus the count of red blocks that have been successfully merged and rewarded. The DAA window spans 2641 blocks. The vault uses this score — not wall-clock time — for its delay and inheritance timers.
The constructor takes nine parameters:
hot, alarm, delay, heir, inheritDelay, autoInherit, feeBudget, initMode, initDest
hot— the owner's day-to-day public keyalarm— a separate "emergency" public key (ideally on a different device)delay— how many DAA-score units must elapse before a withdrawal can completeheir— 32-byte public key of the inheritance beneficiary (32 zeros disables inheritance)inheritDelay— DAA-score units of owner inactivity before inheritance activatesautoInherit—1for automatic transfer to heir,0for heir-signed claimfeeBudget— maximum network fee the contract will tolerate (capped at 0.1 KAS)
The seven spending paths
The vault defines exactly seven ways to spend the UTXO. Each path enforces require(tx.inputs.length == 1) — a single-input invariant that prevents multi-UTXO siphoning attacks where two vault UTXOs in one transaction could cause the smaller one's excess to leak as miner fee.
1. initiate(hotSig, destPk) — start a withdrawal. Signed by the hot key. Transitions VAULT → UNVAULTING, locks the destination address into dest. This is the legitimate owner saying "I want to move funds to address X."
2. cancel(alarmSig) — stop a withdrawal in progress. Signed by the alarm key. Transitions UNVAULTING → VAULT, resets dest to zeros. Think of the alarm key like a secondary ignition that can remotely shut off the engine — if an attacker compromises the hot key and initiates a withdrawal, the owner has the entire delay window to notice and cancel it from a separate device.
3. complete() — finalize after delay. No signature required. Valid only when mode == UNVAULTING and age >= delay. Anyone can broadcast this — the watcher does it automatically. Funds go to the address locked in dest.
4. checkin(hotSig) — heartbeat. Signed by the hot key. Transitions VAULT → VAULT, resets the UTXO's age to zero. This is the "I'm alive" signal.
5. inheritAuto() — automatic inheritance. No signature. Valid when autoInherit == 1, mode == VAULT, and age >= inheritDelay. Funds go directly to the heir public key.
6. inheritSigned(heirSig) — claimed inheritance. Signed by the heir. Valid when autoInherit == 0, mode == VAULT, and age >= inheritDelay. The heir must actively claim.
7. migrate(hotSig, alarmSig) — emergency exit. Requires both hot and alarm signatures. Works from any mode, sends funds to any destination. This is the nuclear option: contract upgrade, key rotation, or emergency rescue.
The delay mechanism
The withdrawal flow creates a time-locked safety window:
Owner initiates Delay window (DAA score) Anyone broadcasts
─────┬────────────────────┬──────────────────────────┬─────
│ VAULT→UNVAULTING │ dest is locked │ complete()
│ │ alarm can cancel anytime │ → funds to dest
│ │ (UNVAULTING→VAULT) │
The delay is measured in DAA-score units, not wall-clock time. This ties the security window to Kaspa's consensus clock rather than any node's local time — the window is predictable regardless of network conditions.
Once initiate fires, the inheritance timer stops and the withdrawal timer takes over. If the owner never initiates and never checks in, the inheritance timer runs instead. The two are mutually exclusive by construction.
The dead-man switch: inheritance
The checkin path is the key to inheritance. Think of it as a dead-man switch: the owner periodically proves liveness by signing a checkin transaction. Each checkin resets the UTXO age to zero. If the owner becomes unable to check in — incapacitation, death, lost access to the hot key — the age climbs past inheritDelay and the inheritance paths unlock.
With autoInherit == 1, funds transfer to the heir's key automatically. With autoInherit == 0, the heir must actively sign a claim. Setting heir to 32 zero bytes disables the entire mechanism.
Fee griefing protection
Every keyless path (complete, inheritAuto) and every single-signer path enforces:
require(feeBudget > 0 && feeBudget <= MAX_FEE_BUDGET)
where MAX_FEE_BUDGET = 10_000_000 sompi (0.1 KAS). This prevents anyone from broadcasting a keyless transaction that burns the vault's contents as an inflated miner fee. The only path exempt is migrate — it requires both signatures, so the owner controls the outputs directly.
How Kaspa Forge implements it in production
The contract itself is consensus logic. Turning it into a usable product requires several layers.
Browser-side key management (WASM core)
Kaspa Safe's WASM crate embeds the same vault.sil source and compiles it client-side. All seven transaction builders — build_initiate_tx, build_cancel_tx, build_complete_tx, and so on — execute entirely in the browser. The server receives only signed transactions and public parameters.
The Kaspa Forge desk (encrypted HD profile) derives vault keys from a master seed using:
HMAC-SHA512(key=seed, msg="kaspaforge/v1/vault/<index>")
The first 32 bytes of the HMAC output become the secret key. A single .age key-file backup (scrypt-encrypted, ASCII-armored, compatible with the upstream age -d CLI) covers all current and future vaults.
The watcher and alerting layer
A background loop on the server (watcher.rs::run) polls every 10 seconds:
1. UTXO snapshot diff — detects incoming deposits, initiated withdrawals, cancellations, and completions. 2. Inheritance pre-alerts — at 80% of inheritDelay, sends a checkin reminder via Telegram and email. 3. Auto-complete / auto-inherit — broadcasts keyless transactions when conditions are met. Because these paths are keyless, the watcher cannot redirect funds; it can only trigger what the contract already allows. 4. Heir notification — sends email to the heir when inheritance activates.
Alerts require an optional subscription (100 KAS/year, 30-day free trial). The subscription is funded through a non-custodial payment flow using an xpub-derived address.
Offline recovery guarantee
If Kaspa Forge disappears, the vault is still a standard on-chain contract. Recovery requires:
1. The .age key-file backup (decryptable with the upstream age CLI or keyfile-decrypt.html) 2. The open-source CLI tool vaultctl or the offline recover.html guide 3. Any Kaspa v2+ node — vaultctl defaults to node.kaspaforge.org:16110 but accepts any --node flag
The vault contract lives on the blockchain independent of any server.
Curious how a covenant vault feels in practice? You can create a vault on mainnet in a few minutes — the alarm card, delay timer, and inheritance settings are all configurable during setup. The full how the vault works page has additional walkthrough details.
Trade-offs and honest limitations
No design is without costs. Here is what you should know before committing funds:
Both keys must stay separate. The migrate path gives full control to anyone holding both the hot and alarm keys. This is by design — it enables contract upgrades and emergency exits — but it means storing both keys together defeats the purpose. Compromise both simultaneously and funds move instantly.
Delay is DAA-score-based, not human-readable. You choose a delay parameter, but its wall-clock equivalent depends on block production rate. At approximately 10 blocks per second, a delay of 50,000 DAA-score units is roughly 83 minutes. Different network conditions change the real-time mapping.
No timelock on migrate. This is an accepted trade-off — the alternative (also delaying migrate) would trap the owner during a legitimate emergency. The mitigation is operational: never store both keys in the same place.
Contract version. The current vault.sil v3 has undergone internal security review — single-input invariant across all 7 paths, fee-budget cap analysis, griefing vectors. The selftest suite runs 18 checks in the node's VM.
Single-vault-per-UTXO model. Each deposit creates a separate UTXO managed by the same contract. The withdrawAll and withdrawOne helpers handle this in the UI, but the on-chain reality is multiple independent covenant UTXOs — each withdrawal spends exactly one.
Non-custodial is a spectrum of trust. The vault contract is trustless — the blockchain enforces the rules. But the current web frontend involves trusting that the served JavaScript matches the open-source code. The Tauri Android APK and the offline recover.html path reduce this surface. A fully deterministic build pipeline is a future goal.
---
The covenant vault model on Kaspa offers something most self-custody setups don't: a way to hold your own keys *and* have the network enforce a second line of defense. It is not a replacement for good key hygiene — it is a layer on top of it, built on Toccata's covenant opcodes that make time-based spending rules first-class citizens in the UTXO model.
The mechanics are straightforward once you see them: initiate, wait, complete — or cancel if something looks wrong. The interesting part is that the blockchain itself doesn't care who broadcasts the final transaction. It only cares that the delay has passed and the destination was locked at initiation time.
For the full technical specification and source code, see the Kaspa Safe repository and the Kaspa developers knowledge base.
FAQ
What is a Kaspa covenant vault?
An on-chain script on the Kaspa blockchain that locks KAS under rules — withdrawal delay, alarm key, optional inheritance — enforced by the network, not by a custodian.
Can the Kaspa Forge team access my vault funds?
No. Private keys are generated and stored only in your browser. The server never sees your seed, passphrase, or private keys.
What happens if Kaspa Forge goes offline permanently?
Your funds remain on-chain. You can recover using the open-source CLI tool vaultctl against any Kaspa v2+ node, or use the offline recover.html guide.
What is the alarm key for?
A separate key that can cancel a withdrawal during the delay window — designed to stop theft in progress if the hot key is compromised.
How does inheritance work in the vault?
If the owner stops checking in for inheritDelay blocks, funds transfer to a designated heir — either automatically or when the heir signs a claim.
Is the vault contract audited?
The contract has undergone internal security review (single-input invariant, fee-budget cap).
Put your KAS where theft can be cancelled
A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.
Create a vault
