Kaspa Safe needs a hot key and an alarm key per vault. Kaspa Escrow needs buyer, seller, and chat keys per deal. The desk wallet has its own key pair. Backing these up individually means a growing pile of exports — and one missed backup means locked funds.
Kaspa Forge collapses all of this into a single master seed. Every private key — for every vault, deal, and address you create today or years from now — derives deterministically from that one seed. Back up the .age key-file once, and restoration covers past, present, and future keys.
This deep-dive explains the derivation scheme, how the encrypted profile protects the seed, and how the gap-scan restore mechanism discovers keys created long after the backup was made.
The Problem: Keys Multiply, Backups Shouldn't
In conventional self-custody, each new key is a new backup obligation. Miss one, and funds locked behind that key become irrecoverable.
Kaspa Safe alone generates three key types per vault: a hot key for initiating withdrawals, an alarm key for emergency cancellation, and a funding key for receiving deposits. Create ten vaults, and that's thirty key pairs to track. Escrow deals add more: buyer-side, seller-side, and chat keys per deal. The desk wallet carries its own key pair for spending aggregated funds across derived addresses.
Naively, you'd export each key separately and store them somewhere safe. The failure modes are obvious: the USB drive left at a friend's house, the backup made before your third vault but not your fourth, the key file buried in an old laptop you no longer own.
A cryptographic method where every key derives from a single master seed through a one-way function. Given the same seed and the same path, the same key always emerges — no randomness, no ambiguity. Back up the seed once; every key it produces is implicitly backed up.
The alternative is to back up one thing — the seed — and derive everything else mathematically. This is the approach Kaspa Forge takes.
How Kaspa Forge Derives Keys
At the center of the desk profile sits a single 256-bit master seed. Every private key the application needs is computed from this seed using HMAC-SHA512 — a standard keyed hash function that produces a 64-byte output:
HMAC-SHA512(
key = master_seed,
msg = "kaspaforge/v1/vault/0"
)
The first 32 bytes of the output become a secp256k1 private key. The remaining 32 bytes are discarded. This computation lives in derive.rs, a module compiled both to WebAssembly (for the browser) and into the Rust server binary.
The message string encodes a path — a combination of domain and index:
kaspaforge/v1/<domain>/<index>
- Domain identifies the purpose:
vaultfor Kaspa Safe keys,escrowfor deal keys,walletfor the desk spending key. - Index is a sequential counter starting from zero: vault 0, vault 1, vault 2, and so on.
Domain separation ensures keys for one tool never collide with another. A vault key at index 3 and an escrow key at index 3 produce completely different private keys because their paths differ. The scheme also supports specialized subspaces:
<domain>~token— for token-specific operations within a domain (e.g.,kaspaforge/v1/vault~token/0).- Kasia chat keys — derived under a separate domain with an additional even-parity check. The Kasia encryption protocol requires a specific parity bit on the public key; the derivation examines the result and adjusts if needed.
The crucial property: derivation is deterministic. Given the same seed and the same path, the same key always appears. There is no randomness after the initial seed generation. This means you never need to export individual keys — the seed implicitly contains all of them, including keys for vaults and deals that don't yet exist.
To guard against silent scheme drift, the codebase includes pinned known-vector tests: hardcoded triples of (seed, path, expected_key) that must match on every build. If someone changes the derivation logic and forgets to update the vectors, the test suite fails immediately.
The Encrypted Profile and the .age Backup
The master seed lives in the desk profile — an encrypted blob stored in the browser's localStorage. Profile version 3 contains:
- The HD master seed (source of all derived keys)
- Per-vault cached keys: hot key, alarm key (or a flag indicating the alarm key lives on a physical card outside the profile), funding key, fee budget, and a user note
- The desk wallet's current and previous receive addresses
- Escrow deal keys
The cached keys are convenience copies — the seed can regenerate all of them. The entire profile is encrypted with the age encryption tool, specifically using passphrase-based encryption backed by the scrypt key-derivation function. The output is an ASCII-armored .age file compatible with the standard age CLI:
age -d -o profile.json profile.age
# Enter passphrase → plaintext JSON with seed and keys
Encryption and decryption happen entirely in the browser via WebAssembly — the module age_crypto.rs compiled to wasm32. The server never sees the passphrase, the seed, or the plaintext profile. When you unlock the desk, the WASM module decrypts the profile into memory, derives the keys it needs for signing, and discards everything after 15 minutes of inactivity.
The .age file is your sole portable backup. Store it on offline media — a USB drive in a safe, an encrypted external disk, a printed copy of the armored text. If Kaspa Forge disappears tomorrow, the open-source recover.html page and the vaultctl CLI let you reconstruct everything from the seed against any Kaspa v2+ node.
Restore: Gap-Scanning Without Trusting the Server
A backup is only useful if restoration actually works — including for keys created *after* the backup was made. Kaspa Forge implements a gap-scan for exactly this:
1. You load the recovery page and decrypt your .age profile with the passphrase. 2. The browser extracts the master seed and begins deriving addresses sequentially: kaspaforge/v1/vault/0, kaspaforge/v1/vault/1, kaspaforge/v1/vault/2, and so on. 3. For each derived public key, the browser asks the server: *"Does any on-chain UTXO exist for this public key?"* 4. The server responds yes or no. It matches only by exact public-key pair — it does not act as a general oracle of "which keys exist." The browser never transmits the private key or the seed.
If the scan finds matches at indices 0 through 2 but nothing at indices 3 through 9, it stops and presents the three discovered vaults. Vault parameters — delay, alarm key, inheritance settings — are read from the on-chain covenant state, since the vault.sil contract encodes its rules directly in the UTXO script. (Vault delays are measured in DAA scores — Kaspa's difficulty-adjusted block counter, specified in the Kaspa wiki.)
This is why a single backup made before creating your fifth vault still works months later: the gap-scan walks the derivation tree and discovers that fifth vault at kaspaforge/v1/vault/4, wherever it was created.
How Each Tool Uses Deterministic Keys
All three Kaspa Forge tools share one master seed through the desk profile:
- Kaspa Safe: vault keys derive under
kaspaforge/v1/vault/<N>. Each new vault increments the index. The hot key, alarm key, and funding key all come from the same derivation tree. The desk wallet address — used for spending aggregated UTXOs across all derived addresses in a single P2PK transaction — derives underkaspaforge/v1/wallet/0.
- Kaspa Escrow: deal keys derive under
kaspaforge/v1/escrow/<N>. Buyer-side, seller-side, and chat keys each use their own sub-path, keeping deal keys isolated from vault keys.
- Desk wallet: the spending key and receive addresses share the
walletdomain, withwalletOldholding the previous address during rotation.
Because everything flows from one seed, one .age backup covers all three tools — including vaults and deals that will be created in the future.
One backup, every future vault and deal. Kaspa Safe and Kaspa Escrow derive all their keys from the same desk seed — a single .age export covers every vault and deal you'll ever create. On-chain operations are free forever; your keys never leave your browser. Create your first vault →
Trade-offs and Honest Limitations
Reducing backup complexity to a single seed has clear advantages — and clear risks worth stating plainly.
Single point of compromise. If someone obtains your master seed, they can derive every key you own: vaults, escrow deals, wallet addresses. This is the fundamental trade-off of deterministic derivation. One seed means one thing to protect, but also one thing to lose. Physical security of the .age backup and the strength of your passphrase become critical.
Passphrase is the real lock. The .age file uses scrypt-based encryption, which is intentionally slow to resist brute-force. But a weak passphrase — a short dictionary word, a birthday — undermines the protection. There is no server-side recovery; forget the passphrase and the backup is inert.
Gap-scan needs a cooperating node. Restore queries a Kaspa node for UTXO existence. Kaspa Forge's public node front at node.kaspaforge.org works out of the box. Offline, you can point the open-source vaultctl CLI at your own node (--node grpc://...). But without any node access at all, the scan stalls — the seed is valid, but you cannot discover which indices are in use.
Beta limitations. The desk profile lives in a single browser's localStorage with no built-in multi-device sync. Moving to a new device means importing the .age file manually. The derivation paths are versioned (v1); a future scheme change would require explicit migration handling. Known-vector tests pin the current scheme against accidental drift, but they cannot guarantee the absence of subtle implementation bugs — the scheme's security rests on HMAC-SHA512 being a well-studied primitive and the domain separation being logically sound.
No isolated key rotation. You cannot swap a single hot key without creating a new vault or using the migrate path (which requires both hot and alarm signatures). This is a deliberate trade-off for backup simplicity — per-key flexibility is exchanged for one-backup coverage.
The Bottom Line
Self-custody is a backup problem. Kaspa Forge reduces it to one seed, one .age file, one passphrase — and a gap-scan that discovers the rest. The derivation code is auditable in the open-source repository, the encryption uses a standard tool (age), and recovery works without the service being alive (vaultctl + any Kaspa v2+ node).
Back up once. The math handles the future.
FAQ
What happens if I lose my .age backup?
Without the encrypted .age key-file and its passphrase, Kaspa Forge cannot recover your keys. The seed exists only in your encrypted profile and its backup — we never see it. Store the backup on offline media and remember the passphrase.
Does a backup made before creating a new vault still work?
Yes. All keys derive deterministically from one master seed. Restoring from your .age file and running a gap-scan will rediscover vaults created after the backup was made.
Where are my private keys stored?
In your browser's encrypted desk profile, protected by your passphrase. Only public keys and parameters are sent to the server — never the seed or private keys.
What algorithm does Kaspa Forge use for key derivation?
HMAC-SHA512 with the master seed as key and structured message paths like kaspaforge/v1/vault/0. The first 32 bytes of the HMAC output become the secp256k1 private key.
Can Kaspa Forge read my seed?
No. Encryption and decryption happen entirely in your browser via WebAssembly. The server never sees the passphrase, the seed, or the plaintext profile.
Do escrow deal keys use the same backup?
Yes. Escrow, vault, and wallet keys all derive from the same master seed under different domain paths. One .age backup covers every tool.
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
