RSS Amplifier

PrivKey’s Blog · Aug 13, 2026

One Key, One Catastrophe

0
Sign in to vote or save

William K Santiago · PrivKey’s Blog

One private key is your entire Nostr identity. Lose it or have it copied and everything is gone.
The single-key catastrophe. One private key is your entire Nostr identity. Lose it or have it copied and everything is gone.

Your Nostr identity is a single private key. That key is your name, your reputation, your follower graph, and your DM history all at once. There is no support desk. There is no password reset. If someone copies that key, they are you, permanently.

And right now, most people’s keys live in the worst possible place: pasted into a browser extension, typed into a mobile app, backed up in a screenshot, or sitting in a notes file “just for a second.” Every app you paste it into is another copy. Every copy is another chance to lose everything.

The trap is that the two failure modes pull in opposite directions. Back your key up harder and it gets easier to steal. Hide it better and it gets easier to lose. There is no setting on a single key that fixes both.

Bitcoin custody escaped that trap years ago by refusing to depend on one key: any two of three, and losing one is a chore instead of a catastrophe.

Primer on multisig architectures.

Nostr cannot copy that directly. Bitcoin’s blockchain enforces the rule, while a Nostr event is valid if it carries one signature from one pubkey. Full stop. No referee.

So we needed the splitting to happen in the math instead.

A private key split into three shares, any two of which cooperate to produce a single Schnorr signature.

FROST: the key that is never whole. The private key is split into shares. Any threshold (2-of-3, 3-of-5, and so on) can cooperate to produce a normal Schnorr signature, and when the group is generated with DKG, no single device ever holds the complete key.*

FROST (Flexible Round-Optimized Schnorr Threshold Signatures) does exactly that.

Instead of splitting permission, FROST splits the key itself into shares. Say 2-of-3: three shares exist, any two can cooperate to produce a signature, and when the group is generated with distributed key generation (DKG), the entire key is never assembled anywhere. Not during setup, not during signing, not ever. (The caveat matters: if you instead take an existing nsec and split it, that key did exist in one place, on whatever machine split it. DKG is the version with no such moment.)

The part that makes this work for Nostr: the signature that comes out is an ordinary Schnorr signature for your ordinary pubkey. Relays cannot tell the difference. Clients cannot tell the difference. Your npub does not change. Your followers do not have to do anything. You get multisig-grade security with zero protocol changes and zero migration.

That is the entire trick, and it is why FROST matters more for Nostr than almost anywhere else.

Keep is not the only project building this. The FROSTR ecosystem covers browser, desktop, server, and Android. Keep is an independent implementation rather than a FROSTR node: it is built on a different FROST library and uses its own `kshare` share format, so shares from the two systems are not interchangeable.

The Keep Android app sitting between Nostr clients and a FROST share, passing signatures out but never key material.

Keep, the mobile FROST signer. An Android app that holds only a share, signs via NIP-55 and NIP-46, and never lets other apps touch key material.

Keep is a mobile signer: an Android app that holds a FROST share and signs Nostr events on behalf of your other apps, so no app on your phone ever sees a private key.

Concretely:

  • NIP-55 signer. Other Android Nostr clients hand Keep an event to sign and get a signature back. They never receive key material. Ever.

  • NIP-46 remote signing. Same guarantee for clients on other devices. Your desktop browser can ask your phone to sign.

  • FROST shares. Hold a share of a 2-of-3, a 3-of-5, or whatever fits your threat model. Import and export shares by QR code or text. Note that the phone holds a share, it does not create the group: today you generate the group with the Keep CLI or desktop app and import a share into the phone. On-phone key generation is not shipped yet.

  • Hardware-backed storage. Your share sits in the Android Keystore, gated behind biometrics or a PIN.

  • Per-app permissions. Every client that asks for signing authority gets an explicit grant you can revoke, plus rate limits, so a compromised app cannot quietly sign a thousand events.

  • Encrypted backup and restore, and multiple accounts, because real people have more than one identity.

MIT licensed. Distributed through Zapstore, F-Droid, and GitHub Releases.

Three things had to be true at once, and each one rules out an easier design.

  • The key must never be whole. Encrypted-key-on-device is still one key, just behind a lock. A DKG-generated FROST group means there is no complete key anywhere to steal. An attacker with your phone has a fragment that signs nothing on its own.

  • Your identity must not change. Any scheme that gives you a new pubkey costs you your entire social graph. FROST’s output is indistinguishable from a normal signature, so migration cost is zero.

  • Apps must never touch keys. NIP-55 and NIP-46 draw a hard boundary: apps request signatures, they do not get secrets. This makes the number of copies of your key exactly zero, no matter how many clients you use.

Losing a device stops being a catastrophe and becomes a chore: revoke, re-share, move on.

A signer is not a normal app. It is a small amount of code sitting on the most dangerous secret you own, so we picked the implementation for that job rather than for how fast we could ship to three platforms.

Keep’s core is Rust. Unsafe code is banned outright, secrets live in types that zero themselves when dropped, and key material is locked into RAM with `mlock(2)` so it cannot be paged out to disk. The Android app is native Kotlin over that core, and the secret-bearing screens hand key material across the boundary as buffers that get wiped, never as strings.

It is worth being precise about what that buys, because the lazy version of this argument is wrong. TypeScript is memory safe. Garbage collection is not the gap. The gap is what happens to a secret *after* you are done with it: in JavaScript, strings are immutable, and the collector is free to copy them around the heap, so there is no reliable way to erase a key once it has existed. In Rust, you can zero it, and you can keep it out of swap. For a process whose entire job is holding key material, that is not a detail.

The second difference is surface area. Igloo Mobile is a React app running inside an Android WebView, which puts a browser engine between your share and your signature. That is a reasonable trade if you want one codebase on browser, desktop, and phone, and it stores its share in the Android Keystore the same way Keep does. It is just more moving parts, and moving parts are where signers get compromised.

None of this makes FROSTR’s cryptography suspect. Bifrost builds on the noble libraries, which are careful, widely reviewed work. We wanted the smallest possible gap between “this key exists” and “this key is gone,” and that is a question about the language and the runtime, not about the math.

Status check first, because the honest version of this is more useful than the impressive one. The always-on co-signer that exists today is keep-startos, which packages Keep’s `keep-web` daemon as StartOSservice. It is installable now, x86_64 only, and it imports an existing share rather than creating the group itself. Practice on a throwaway npub before you move your real identity.

Picture your key split into three pieces (shares), where any two of them together can sign:

  • Your phone (Keep) | Everyday signing, approved on the device in your pocket

  • An always-on node on your network (keep-startos) | The counterparty that co-signs with the phone

  • Cold backup, kept somewhere else | Never touched day to day; exists, so a lost phone is not fatal

Three shares: a phone, an always-on node, and a cold backup, with any two able to sign.

The everyday 2-of-3. Phone (Keep) plus always-on co-signer plus cold backup. Lose the phone and recovery is still possible; steal the phone and the attacker gets a useless fragment.*

Setting it up runs in one direction: generate the 2-of-3 group with the Keep CLI or desktop app, export the three shares, then import one into the phone by QR, one into keep-startos through its Web Admin, and put the third somewhere cold. Co-signing on the node ships switched off (fail-closed) until you deliberately flip the kill switch, so nothing signs until you say so.

Day to day you sign with phone plus node, and it feels like nothing: you tap approve, the note posts, the relay sees an ordinary event from your ordinary npub. Nothing about the experience announces that two devices were involved.

The interesting part is what happens when something goes wrong. Lose the phone, and the node plus the cold backup still add up to two. You sign, you issue a fresh share to a new phone, and you retire the old one. Someone steals the phone, and they hold one share out of three, which signs nothing. With DKG, the full key never existed in one place to be stolen in the first place.

Two things matter more than any of the setup details. First, keep the shares genuinely apart. Three pieces on the same network, patched by the same person, is one failure dressed up as three. Second, rehearse the recovery before you need it. A backup you have never restored is a guess, not a backup.

Threshold signing removes one specific failure, a single stolen or lost key. It does not make you unhackable, and pretending otherwise is how people get hurt.

  • Signing the wrong thing. If a malicious or compromised client asks you to sign a bad event, and you approve it, FROST signs it. The math verifies *that* your quorum authorized the event, never *whether* it was a good idea. Read prompts. Grant per-app permissions narrowly and revoke the ones you stopped using.

  • Coercion. A threshold scheme cannot tell an approval you wanted from one someone stood over you to obtain. If you hold enough shares to sign, so does anyone who can compel you. Geographic and custodial separation of shares is the real mitigation. A share you cannot reach in the next hour is one you cannot be forced to use.

  • Enough compromised shares. 2-of-3 means two compromised devices is total compromise. Shares on three machines with the same OS, patched by the same admin, on the same network are correlated, not independent. Spread them.

  • Endpoint compromise on an approving device. Malware with control of your unlocked phone can ask for signatures at the moment you would approve them. Keystore gating and rate limits shrink the blast radius; they do not eliminate it.

  • Losing too many shares. The flip side of the threshold. In a 2-of-3, losing two shares locks you out permanently. No vendor can recover it, which is the point. Rehearse recovery.

  • Metadata. Nostr events are public by design. Protecting the key does nothing about who can see what you posted and when.

The honest summary: FROST converts “one secret, one catastrophe” into “several secrets, several smaller problems.” That is a large, real improvement. It is not immunity.

Left: one key copied into a browser extension, a screenshot, and a notes file. Right: three FROST shares held on separate devices.

Before and after.

Left: keys scattered across browser extensions, screenshots, and notes.

Right: distributed FROST shares that never exist as a complete secret in one place.*

Keep is one piece of what PrivKey builds: open-source Bitcoin, Lightning, and Nostr infrastructure in Rust, Zig, and C, aimed at self-custody that actually holds up. Same principle every time: no single point of failure, no trusted third party, no proprietary black box.

Bitcoin’s custody culture took years to arrive at “do not use one key.” Nostr should not need to relearn that lesson the expensive way.

Read the original on privkey.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.