"Not your keys, not your coins" is the oldest promise in crypto. But for most services, proving that promise means a seed phrase on paper and a vague hope that the wallet software will still be around when you need it. What happens when the service that created your vault — the website, the backend, the monitoring alerts — simply vanishes?
For Kaspa Forge, the answer is built into the architecture: three independent recovery paths, each usable without our servers, each operating against on-chain contracts that exist independently of any website. This article walks through how each path works mechanically, and what the honest trade-offs are.
The Test of Non-Custody
Non-custodial — a system where the operator never holds private keys or the ability to move user funds. Proving this means showing that every spending path is either signed by the user's key or triggered by an on-chain condition (time lock, contract logic) that no server controls.
A custodial service holds your funds in its own wallet. If it disappears, your money goes with it. A non-custodial service creates funds in *your* wallet — but if recovery depends on the service's infrastructure, the distinction is academic. The real test is: can you move your money with tools you control, after the service dies?
Kaspa's on-chain covenants, introduced with the Toccata hardfork, make this testable at the protocol level. A vault contract on Kaspa is a piece of script with fixed rules: who can sign, what time delays apply, where funds go. That script lives on the BlockDAG, not on any server. Recovery, then, is about three distinct capabilities: knowing what contract you own, finding it on-chain, and building a valid transaction against it.
Path 1 — Decrypt the Backup
When you create a vault through Kaspa Forge, the browser generates an HD master seed and derives every key from it. The entire profile — seed, vault keys, parameters, wallet addresses — is encrypted into a .age key-file that you download once during setup.
The encryption uses age, a modern tool based on scrypt key derivation and X25519/ChaCha20-Poly1305. Your passphrase is the only input; the output is an ASCII-armored text blob you can store on a USB drive, print on paper, or save in a password manager.
.age key-file — an age-encrypted archive of your Kaspa Forge Desk profile. Contains the HD master seed, per-vault keypairs, contract parameters, and wallet addresses. Decryptable with the standalone keyfile-decrypt.html tool or the upstream age -d CLI.
What's inside a decrypted profile (version 3):
{
"version": 3,
"seed": "<64-hex master seed>",
"wallet": "kaspa:q...",
"walletOld": "kaspa:q...",
"vaults": [
{
"hot_pk": "...",
"alarm_pk": "...",
"address": "kaspa:q...",
"delay": 86400,
"heir": "0000...0000",
"inheritDelay": 0,
"fee_budget": 5000000,
"note": "cold storage"
}
]
}
If the alarm key lives on a physical card (outside the digital profile), the record shows "alarm_card": true — the card itself is the backup for that key.
Decryption does not require our server. Kaspa Forge publishes a standalone keyfile-decrypt.html — a single HTML file with the WASM decryption module embedded as base64. It works from file:// with zero network requests. Open it in a browser, drop in your .age file, enter your passphrase, and you have the raw JSON. It is also compatible with the upstream age -d command-line tool.
Path 2 — Rediscover Your Vaults
Having the seed is necessary but not sufficient — you also need to know *which on-chain addresses* your vaults live at. If you created three vaults at derivation indexes 0, 1, and 2, the addresses are deterministic: the same seed produces the same keys at the same indexes, every time, on any machine.
The derivation in Kaspa Forge works like this:
secret_key = HMAC-SHA512(
key = master_seed,
msg = "kaspaforge/v1/vault/<index>"
)[0..32] // first 32 bytes → secp256k1 private key
Each vault index produces a unique keypair. The gap-scan mechanism (available at recover.html) walks indexes 0, 1, 2… and checks whether the derived address holds UTXOs on-chain. If the scanner finds a funded address, it reports the vault — even if that vault was created months after you made the backup.
The current production scan sends derived public keys to our endpoint, which matches them against the UTXO set. Critically, the server sees only public keys and token identifiers — your private key and seed never leave the browser. If our server is also unavailable, you can derive addresses locally (any Kaspa SDK or even manual HMAC computation) and check them against a block explorer or your own node.
This is the key property of HD derivation: a single backup covers all future vaults. You do not need to re-backup after creating a new one.
Path 3 — Spend Without the Server
Once you have the vault record (pubkeys, delay, fee budget, parameters) and the corresponding private key, you need to build a Kaspa transaction that satisfies the covenant script. This is where vaultctl comes in.
vaultctl is an open-source Rust CLI published alongside Kaspa Forge. It accepts a vault record from your decrypted profile, connects to a Kaspa node, and constructs the correct spending transaction for any of the vault's seven spending paths:
# Complete a previously initiated withdrawal
# (no signature needed — only the delay must have elapsed):
vaultctl complete \
--vault vault.json \
--dest kaspa:q... \
--node grpc://any-kaspa-node:16110
# Initiate a new withdrawal (requires hot key signature):
vaultctl initiate \
--vault vault.json \
--dest kaspa:q... \
--key hot.key
# Check in to reset the inheritance dead-man timer:
vaultctl checkin \
--vault vault.json \
--key hot.key
The critical detail: vaultctl talks to any Kaspa v2+ node, not just ours. The default endpoint points at a public front, but --node accepts any synced kaspad instance — your own, a community node, one you spin up for the occasion. The covenant script is identical everywhere because it lives on-chain. Transactions submitted via RPC (as vaultctl does) never expire in the Kaspa mempool, so your recovery transaction persists until it gets mined — however long that takes.
The tool ships with 18 self-tests that exercise every spending path against the contract's verification logic. You can run these locally to confirm the binary matches the published source.
How Kaspa Forge Ties It Together
The recovery guide on Kaspa Forge walks through all three paths in sequence: decrypt the backup → scan for vaults → spend with vaultctl. The page includes SHA-256 checksums for verifying the published tools.
The design principle is explicit: recovery does not require us. The vault contract on the BlockDAG does not know or care whether kaspaforge.org exists. The complete() path — the most common recovery scenario after a theft attempt — requires no signature at all; anyone can broadcast it once the delay expires. The inheritAuto() path works the same way for the inheritance case.
For Kaspa Safe vaults specifically, the watcher service sends alerts (Telegram, email, web push) when something changes with your UTXO — but alerts are a convenience layer, not a requirement. The contract's time locks and spending paths function with or without notifications.
If you hold KAS in self-custody, Kaspa Safe gives you a time-locked vault with a separate alarm key for theft prevention — and every recovery path described here works with it. The on-chain contract is free to use forever; paid notifications are optional.
Trade-offs and Honest Limitations
No recovery system is frictionless. Here is what this design costs:
- You must have the backup. If you lose both the
.agekey-file and the master seed, funds in the vault are gone. There is no "forgot password" flow — that is the point, and the risk. Kaspa Forge never sees your seed, so we cannot help you recover it.
- Alarm key on a physical card lives outside the digital backup. If your vault uses an alarm card (a physical key for the
cancelpath), that card is not inside the.agefile. Lose the card and you lose the ability to cancel a theft in progress. The vault remains recoverable viainitiate+completeor viamigrate(if you still have the hot key), but the alarm path is gone.
- Gap-scan currently uses our server endpoint for matching derived addresses against the UTXO set. The server sees only public keys, not private ones — but if you want zero dependency on our infrastructure, you must derive addresses locally and check them against an explorer or your own node manually. An offline gap-scan tool is not yet published.
- vaultctl is a command-line tool. Non-technical users may find the recovery flow intimidating. The recovery guide mitigates this with step-by-step instructions, but it is not a one-click process. This is an honest trade-off: an HTML-button recovery would require server infrastructure, which defeats the purpose.
- Time delays are real. Vault delays on Kaspa are measured in DAA score, which at the current 10 blocks per second advances roughly 10 units per second. A delay of 86,400 DAA translates to roughly 2.4 hours. This is the alarm window doing its job — but it means you wait before
complete()can broadcast.
- The
migratepath requires both keys. Migration (moving funds to a new vault version or rotating keys) needs both the hot and alarm signatures. If one key is permanently lost, migration is impossible — you would useinitiate→ wait →completeto withdraw instead.
The honest summary: Kaspa Forge's recovery design is strong for users who maintain their backup and are comfortable with a CLI tool in an emergency. It is not designed for users who want a custodial safety net — because a custodial safety net is exactly the thing that disappears when the service does.
FAQ
What happens if kaspaforge.org goes offline permanently?
Your funds remain in the on-chain vault contract. With your .age backup and the open-source vaultctl CLI, you can build and broadcast spending transactions against any public Kaspa node — ours is not required.
Can Kaspa Forge access or freeze my funds?
No. Private keys are generated and stored only in your browser. The server receives public keys, parameters, and signed transactions — never seeds or secret keys.
What is inside the .age key-file?
An age-encrypted JSON profile containing your HD master seed, vault keys (hot and alarm), vault parameters (delay, heir, fee budget), and wallet addresses — encrypted with a passphrase you choose.
How does gap-scan find vaults created after my backup?
HD derivation is deterministic: the same master seed always produces the same keys at the same index. By deriving addresses at index 0, 1, 2… and checking each on-chain, the scanner finds every vault — even those created months after the backup.
Do I need to run my own Kaspa node to recover funds?
No. vaultctl defaults to a public node endpoint, but you can point it at any synced Kaspa v2+ node using the --node flag.
What if I lose my .age backup?
If you memorized or separately stored the HD master seed, you can still derive all keys and recover via manual derivation plus vaultctl. Without the seed or backup, funds in the vault are unrecoverable — that is the trade-off of true non-custodial design.
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
