How KPOP backups work
What a backup carries, what it leaves out, and why. Written up from the design pass of 18 September 2026.
That cuts both ways. A printed backup is far safer than it feels, because there is no key material on it. It is also a document naming your custodians and the places your envelopes are kept — which is a targeting list. It wants a drawer, not a wall.
Three depths
They nest: recovery ⊂ working ⊂ everything. A field belongs to the smallest one that carries it. Depth is chosen under Storage → Manual export.
Every key, account, token, fragment, receipt and setting on this device — bar the Activity log, which is a memory jog rather than a record worth restoring. This is what Back up always produced before profiles existed, and it is the only depth that may be imported with replacing everything.
The same, minus everything the mirror node answers on demand: key trees, balances, token names and supplies, which accounts a key sits on, and when each was last looked at. All of it is fetched again on import. In a ledger weighted towards accounts this is the single biggest saving available — around 43% of a full backup in the modelling below.
Not a backup and not restorable — importing one is refused, with the reason. It is a directory: what you have, where it is and who holds it, in as few characters as will still lead you back. Each key is the last eight characters of its public key, under tail so nothing can mistake it for an id; each fragment is its name and the last four of its fingerprint; each account with a custom salt is its id and that salt. Copies are custodians, nothing more. Dates are the month.
Eight is 32 bits — ample to find the key again on chain, where you search with context and would tell two or three candidates apart at a glance. Four would be 16 bits, about a one-in-fifty chance of a clash inside a fifty-key ledger: a needless annoyance for four saved characters. And the sheet's job is to lead you to the secret; once you hold that, the public key follows from it, so printing the key in full only hands a reader more than they need.
Nothing empty is written down
At every depth, a field that is empty — null, "", [], {} — is left out entirely, recursively, so an object left hollow by the pruning goes too. false and 0 stay: they are answers, not absences (a fragment copy that is not sealed, a count of none).
This is lossless because import puts every default back, and that is the part worth stating, because it is where it can go wrong: a parent object can survive pruning with some of its keys missing. progress keeping its points and losing levelAt is enough to make level() read undefined[4]. So the normalisers fill sub-fields individually rather than replacing a missing parent — normaliseState, normalise, normaliseAccount, normaliseToken, normaliseFragment and revState between them — and the smoke test round-trips a pruned backup and checks the ledger comes back identical.
Scoped by network
A backup covers whichever network the toggle is on, unless you ask for both. A record declaring the other network is left out; a record declaring none is kept, because undeclared means "could be either" and dropping it would lose it from both.
One consequence worth knowing: replacing a device needs a backup covering both networks. A one-network backup used that way would take the other network's keys down with it — the same silent-deletion trap as a partial depth, and guarded the same way.
What is recoverable, and what is not
The useful question is not "is this important?" but "if I lost this, could anything give it back?". Three classes:
| class | fields | if lost |
|---|---|---|
| A — unrecoverable, load-bearing | key ids, fragment fingerprints, which fragments built which key, custom account salts | the map is broken and nothing rebuilds it |
| B — unrecoverable, not load-bearing | your labels, notes, owners, custodians, copy places, storage findings, receipts | painful, not fatal — the keys still work |
| C — recoverable | key trees, balances, token details, which accounts a key is on, lookup timestamps | nothing: ask the mirror node again |
Class C should never be exported at all, at any depth — which is what Working set does. Class A is the hint sheet. Class B is the part worth keeping somewhere online, and note the inversion: class A is public keys and hashes, so it is the safer half to sync; class B is the targeting data and the half that actually wants encryption. That is backwards from the instinct that the keys are the sensitive bit.
The account salt is the exception worth knowing
KSUB salts an account's child keys with the account number, which is derivable from the id and so never needs recording. A custom salt is the exception — and it is the only field in the whole ledger that is both unrecoverable and load-bearing. Without it, a salted child sitting on that account can never be matched back to the master it came from; the account simply looks like someone else's.
So every depth carries custom salts, the hint sheet included — as { id, net, salt } in a backup, and { id, salt } on the sheet.
It costs about a hundred bytes. It costs about a hundred bytes. Most accounts have no custom salt and cost nothing.
What it costs
Modelled on a synthetic ledger — 95 accounts, 11 keys, 9 fragments, three receipts each — built to the shape of a real one where accounts outweigh keys roughly two to one. Your own numbers are on the Back up panel, which sizes each depth live, and in Inspect…, which breaks any export down field by field.
| depth | JSON | base64 line | gzipped for QR |
|---|---|---|---|
| Everything | 82,396 | 110,116 | 19,631 |
| Working set | ~39,000 | ~52,000 | ~11,000 |
| Hint sheet | much smaller again — it holds eight characters per key, not sixty-four |
Three formats
| format | what it is | imports back? |
|---|---|---|
| JSON | the default — reads as it stands, and what a compressor wants | yes |
| base64 line | the tagged line KPOP made until September 2026; more compact as text | yes |
| printable sheet | a self-contained A4 page, for a drawer | no — keep the JSON |
The printable sheet has two layouts, because the depths want different things from paper. Everything and Working set print as bordered cards, two up, with their field names — a readable dump of a backup. The hint sheet prints as a directory: four newspaper columns, no boxes, no field names, no headings, no banner and no footer, entries divided by a hairline and never split across a column. Keys and fragments read A–Z; accounts sort by entity number and fill left to right. Fourteen keys, ten fragments and seven salted accounts come to one A4 page.
Whoever reads a hint sheet knows what a KPOP hint is. Anyone else should learn as little from it as possible, which is why it explains nothing about itself.
A QR code beside every card was built and then removed, on 2026-09-18: too bitty on the page for what it bought. For codes, paste the JSON into KBQR, which is built for exactly that.
Why JSON, and why not to base64 it
Exports are JSON now. It reads as it stands in any text editor, Inspect… shows it without decoding anything, and — the deciding reason — it is what a compressor wants.
Base64 destroys byte alignment, so gzip cannot see the JSON's repeated structure underneath it; all it recovers is base64's own expansion. Measured on a small export:
| what you hand KBQR | after its gzip |
|---|---|
| the base64 line | 3,013 |
| the JSON | 1,713 |
| a line KPOP had already gzipped | 1,756 |
So for QR codes: paste the JSON into KBQR, not the line. Note the third row — compressing inside KPOP first would be marginally worse than simply handing over the JSON, as well as making the export unreadable. That is why KPOP does not compress.
The base64 line stays as a format option and every line KPOP ever made still imports. It is more compact as text, which matters where something counts characters, and it is the wrong thing to hand a compressor.
QR capacity, for printed cards
For QR codes, hand the JSON to KBQR. One code at version 40 holds 2,953 bytes at error correction L but only 1,273 at H, and a sheet that will be folded and left in a drawer for years wants H — which is why a real export is several codes, and why KBQR's chunking and manifest are the right tool rather than something grown inside KPOP.
Scope, and the Cluster tab
Depth is one dimension; scope — which records, rather than what each carries — is the other. Today scope is either this whole device or one cluster.
A cluster starts from accounts you name. It walks forward to the keys on them (directly or as salted children), the fragments those keys were built from, the tokens they are treasury of and those tokens' keys; then backward to anything else those keys and fragments reach; and repeats until nothing new appears. The result is the smallest closed set containing your seeds, with a verdict of closed, closed by keys · fragments shared, or not closed.
The two dimensions are independent, so a cluster exports at whichever depth is chosen under Storage → Manual export — including a hint sheet of one cluster, which is the most useful printed sheet there is. The Cluster tab's job is the analysis; exporting from it is a shortcut from a result you are already looking at.
Importing
Import accepts the JSON envelope, any KPOPBACKUP1 line, any KPOPCLUSTER1 line, and bare state from before either existed. Nothing KPOP has ever exported stops being readable.
Import merges. Keys and accounts not already here are added; ones already here keep what this device holds, and receipts are unioned by signature so none is lost either way. A partial export imported into a full ledger therefore cannot degrade it — which is exactly what you want from the backup you would reach for in a panic. A hint sheet is the exception: it is refused outright, because a tail of eight characters cannot rebuild a key and should never be mistaken for one.
Import, replacing everything takes an Everything backup of a whole device and refuses anything else. A partial export would silently delete everything it does not carry, since the collections it omits default to empty.
Still to come
Open: whether receipts belong in Working set at all — they are the largest single field in a key, and they are evidence rather than recovery. Whether class B should sync somewhere encrypted while class A stays on paper. And whether a public-key directory, once one exists, lets a hint sheet's eight characters be resolved back to whole keys — which would make the sheet restorable without ever having printed a key.