The core problem with a browser-based encrypted wallet is mundane: you have two laptops, or a phone and a desktop, and each holds a separate copy of your keys, vaults, and deals. Export, transfer, import — every time you make a change on one device, the other drifts. Traditional cloud sync would fix this, but it would also mean uploading your seed phrase to someone else's server.
Kaspa Forge Profile Mirror solves this with a constraint that shapes every design decision: the sync server must never hold anything decryptable. It stores ciphertext. Period. Profile Mirror is live in Desk as an explicit opt-in Beta — here is how the encrypted Kaspa wallet sync mechanism works under the hood.
The Problem: Multi-Device Self-Custody
Every Kaspa Forge user has a single encrypted profile: a blob that holds HD wallet keys, vault records, escrow deals, deposit positions, marketplace listings, board posting keys, and UI state. Desk locks this profile behind a password using WebCrypto. The keys never leave the device.
This works well on one machine. It breaks the moment you want the same profile on a second device. The naive approach — upload the plaintext profile to a server — defeats the entire self-custody model. The opposite extreme — manual .age file exports — is secure but tedious, and most people forget to do it after every transaction.
Profile Mirror sits between these poles: it keeps devices in sync while ensuring the server stores only data that is useless without the user's password.
Step 1 — Deriving a Sync Identity
Before you can sync, the server needs to know *which* profile to associate with your request. But sending your wallet address would be a privacy leak, and sending your private key is out of the question.
Profile Mirror derives a Sync identity deterministically from your original wallet key. The WASM function forge_sync_identity() applies domain-separated HMAC-SHA512 to produce a single stable anchor. Two properties fall out of this:
- Deterministic. The same wallet key always produces the same identity, regardless of device. If you created a Desk profile on your laptop and later open Desk on your phone with the same seed, both devices independently compute the same Sync identity and find the same server-side slot.
- Address-independent. Rotating your receive address — which Desk does after every Boards post and on demand — does not change the anchor. The identity is bound to the root key derivation, not to any particular
wallet/<index>address.
The raw wallet key never touches the network. It does not sign API requests. The identity derivation happens entirely inside the browser WASM runtime.
Step 2 — Projection: Stripping What Must Stay Local
Not everything in a Desk profile should travel — even encrypted — to a shared server. Some keys are device-specific by design.
Before encryption, mirrorProjection() builds a projection of the local profile. It is a read-only transform that does not mutate the local data on disk. The projection explicitly removes:
alarm_sk— the alarm key that can cancel a Kaspa Safe withdrawal. This key must stay on the device that created the vault; syncing it would let a second device cancel withdrawals intended to be device-locked.heir_sk— the inheritance key for "check-in or inherit" vaults.arbiter_sk— the dispute-resolution key for Escrow deals.- All
arenaCanaryKeys— the per-room keys used by Arena game signing.
A fail-closed snapshot gate verifies the projection before encryption: if the gate cannot confirm these keys are absent, the sync upload aborts. There is no code path that accidentally includes them.
When a second device receives and merges a synced profile, vault records arrive without the alarm key. The vault itself is on-chain and fully functional, but the withdrawal-cancellation capability requires importing the full .age export from the creating device. This is intentional friction — a second device should not inherit the ability to undo a security decision made on the first.
Projection — a read-only snapshot of the encrypted profile with sensitive device-bound keys removed. The server receives only the projected version; the local profile is never modified by the sync process.
Step 3 — Two-Layer Encryption
The projected profile goes through two encryption layers before leaving the browser:
1. age encryption — the same format used for .age export files. This is the inner envelope. 2. AES-256-GCM (via WebCrypto) — the outer envelope, keyed from the user's password through the same KDF used for local profile encryption.
The result is a ciphertext blob. The server receives this blob plus a signed manifest containing a version counter and a previous_hash — a hash of the ciphertext the client last saw. The signature is Ed25519, derived from the same key material.
At no point does the server hold a decryption key, a plaintext field, or a seed phrase. If someone compromises the mirror server, they get opaque blobs and signatures — useless without a password that was never transmitted.
Step 4 — Compare-and-Swap Updates
Two devices syncing to the same server slot need a way to avoid overwriting each other's changes. Profile Mirror uses compare-and-swap (CAS) semantics: every write must present the previous_hash of the version the client last read. If the server has moved on since that read, the write is rejected.
Think of it like a wiki edit conflict. You pull version 7, make changes, and try to push version 8 — but someone else already pushed their version 8 while you were editing. Your push fails, you re-pull (now at version 8), merge locally, and push version 9.
The server retains 512 versions of each profile. If a device goes offline for an extended period and falls behind the version window, the signed chain breaks — the client cannot construct a valid CAS proof. Recovery then requires restoring from the server-side backup, not from the client. This is a deliberate trade-off: the retention window is long enough for normal multi-device use, and unlimited history would grow both storage costs and attack surface.
Each upload also carries a request nonce to prevent replay attacks. Replaying a captured blob would fail the CAS check (the server's version has advanced) *and* fail the nonce gate.
Step 5 — Deterministic Merge
When a device pulls a newer version from the mirror, it runs mergeProfile — a deterministic function that combines local and remote state. The merge logic operates on stable identifiers:
- Vaults merge by their on-chain covenant address.
- Escrow deals merge by deal ID.
- Board claim keys merge by
ephemeral_pk. - Tombstones (deletion markers) are preserved: if you deleted a vault on device A, the tombstone syncs to device B, which then also drops the vault during merge rather than resurrecting it from the older snapshot.
To prevent spurious conflicts, the profile hash — which determines whether local and remote state actually differ — compares canonical logical state, not raw JSON byte sequences:
walletandwalletOldare treated as a single sorted key/address pool. Device-local active vs. retired role is irrelevant to the hash.prefs(UI preferences like theme, display name) are excluded. Two devices can have different UI preferences without triggering unnecessary sync uploads.- Identity collections are sorted before hashing.
Devices converge on shared cryptographic state while keeping their own UI quirks local.
Step 6 — Auto-Sync vs. Manual Sync
After opt-in, a user can separately enable Auto-sync on unlock. This sets a device-local flag (localStorage) that triggers a sync after opening an unlocked Desk — but not more than once per 15 minutes per profile, across tabs and full page reloads. Auto-sync does not prompt for confirmation; if the merge succeeds, it is silent. Errors stay in the sync status panel.
Manual Sync now is always available, bypasses the cooldown, prompts for the password, and shows an explicit merge confirmation if incoming changes exist. This is the path for users who want visibility into what changed.
Auto-sync is a local preference. It does not sync itself into the profile, does not enable Mirror on other devices, and does not start any background polling.
How Kaspa Forge Uses Profile Mirror
Profile Mirror sits behind Desk and touches every product that stores state in the encrypted profile:
| Surface | What syncs |
|---|---|
| Wallet | HD key pool (wallet + walletOld), address book, received-tx history |
| Kaspa Safe | Vault records, parameters (alarm key excluded as described above) |
| Kaspa Escrow | Deal records, counterparties, status |
| Deposit | Deposit positions and their covenant references |
| Marketplace | Listing metadata linked to escrow deals |
| Boards | boardClaimKeys[] for tip ownership, post tombstones |
| Arena | Room membership (canary keys excluded from mirror) |
The encrypted Kaspa wallet sync mechanism means every Kaspa Safe vault you create, every Escrow deal you open, and every Boards post you sign propagates to your other devices — as ciphertext the server cannot read.
Desk keeps your keys on your device and syncs everything else as ciphertext your server cannot read. If you run Kaspa Forge on more than one browser, open Desk, go to Settings, and opt in to Profile Mirror — the first sync pulls both devices onto one encrypted timeline.
Trade-offs and Limitations
No design is free. Profile Mirror makes explicit trade-offs:
The mirror is not a backup service. It syncs working state between devices you already control. The .age export remains the only true backup — it contains the alarm, heir, and arbiter keys that the mirror deliberately strips. If you lose every device *and* every .age file, the mirror's ciphertext is irretrievable. This is by design: a server that could decrypt your backup is a server that can steal your funds.
512-version retention is finite. A device that stays offline for months may fall off the edge of the version window. The gap cannot be bridged client-side — recovery requires restoring from the server's daily backup. The window is tuned for normal multi-device cadence, not archival.
Auto-sync is fire-and-forget by design. It does not show merge confirmations, which means a user who enables it trusts the deterministic merge to do the right thing. For most users this is correct; for power users who want to audit every merge, manual Sync now is the appropriate tool.
UI preferences are intentionally not synced. Two devices will not fight over theme settings or receive-address display names, but they also will not share them. This is a pragmatic split: sync the cryptographic state that matters, leave cosmetics local.
Alarm key asymmetry is intentional friction. A second device receives the vault record but cannot cancel withdrawals. This is a security feature, not a bug — but it does mean that cross-device vault management requires importing the full .age export at least once per vault-creating device.
---
The underlying principle is simple: a sync server that cannot decrypt your data is a sync server that cannot betray you. Profile Mirror achieves this by combining deterministic identity derivation, projection-based key exclusion, dual-layer encryption, and compare-and-swap versioning — all implemented in browser-side WASM and WebCrypto, with the server acting as a dumb encrypted ledger. The public Kaspa wiki covers the protocol primitives (transaction structure, UTXO model, fee calculation) that make on-chain operations work; Profile Mirror handles the off-chain coordination layer that keeps your encrypted Desk state consistent across devices without ever exposing it.
FAQ
Can the Profile Mirror server decrypt my wallet?
No. The server stores only AES-256-GCM ciphertext and an Ed25519-signed manifest. Your encryption keys never leave your device.
What if I lose access to all my devices?
Profile Mirror holds ciphertext only — it cannot help you recover without a decryption key. Your recovery path is the offline .age export file. Without that or at least one unlocked device, the mirror data is irretrievable.
Does auto-sync send my seed phrase to the cloud?
No. Auto-sync is a device-local localStorage flag. The sync process derives a deterministic identity from your wallet key via HMAC-SHA512, encrypts a projection of your profile, and uploads only the resulting ciphertext.
What happens when two devices make different changes?
mergeProfile runs deterministically on each client, combining vaults, escrow records, and board claim keys by their unique identifiers. UI preferences are local and excluded from sync, so they never cause conflicts.
Why does the alarm key not sync to other devices?
The mirror projection deliberately excludes alarm_sk, heir_sk, arbiter_sk, and Arena canary keys. This keeps the alarm key — which can cancel a withdrawal — on the device that created the vault. To use it elsewhere, import a full .age export.
Is Profile Mirror a replacement for backing up my .age file?
No. The mirror syncs working state between devices you already control. The .age export remains the only true backup because it contains keys the mirror intentionally strips.
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
