Rescue, not export

The passkey that outlives the company

TL;DR: Most wallets promise self-custody. Few survive the question that actually matters: what happens to your money when the company that built the wallet is gone? This article walks through the full Nuri design: a passkey whose PRF extension carries the wallet secret itself, Bitcoin outputs whose 2-of-2 co-signing decays to 1-of-1 through a CSV timelock, and an Arkade Lightning layer whose three-key ladder decays from co-signers to the user key alone. Recovery works offline, without nuri.com, without servers, without an export button. Co-signers are passkey-gated, phishing-resistant, and mortal by design. The only immortal key is yours.

Author Emin Mahrt

12 minute read

This post is also available in English , Deutsch and Español .

Why a normal passkey is not enough

A passkey is a beautiful authentication primitive. The private signing key never leaves the authenticator. Websites only ever see signatures. Phishing dies; password databases stop mattering.

But as a wallet primitive, a normal passkey is a dead end. It can prove you are you, again and again, forever. It cannot hand you a secret. And a wallet needs a secret — a number from which your Bitcoin and Ethereum keys are derived.

So most passkey wallets end up in one of two camps. The server holds the wallet key and the passkey is just a fancy login: custody with better UX. Or MPC and enclave models split the key across infrastructure — genuinely better, but reconstruction typically requires that infrastructure to be alive, and the escape hatch is export: if you remember to use it before the provider disappears.

Both camps share a quiet assumption: the domain you log in to outlives your need for the key.

There is also a second, less discussed trap. Passkeys are domain-bound. Your credential works for the RP ID it was created for — that is exactly what stops a random website from phishing your wallet login. But domain binding is protection for the company's lifetime, with no story for after. If your wallet key is entangled with a provider's ceremony and that provider's domain goes dark, your passkey still exists, still works — it just has nobody left to talk to.

The industry treats that as an edge case. For money, it is the case.

The passkey that carries its own secret

Nuri uses the WebAuthn PRF extension. PRF lets the wallet ask the authenticator a fixed question and receive a stable, pseudorandom answer — tied to that credential, reproducible forever, and never exposed to any server.

The RP ID is nuri.com, the PRF input is the fixed string "nuri-prf-salt-v1", and the output is 32 secret bytes, produced locally after user verification. Those 32 bytes are the seed. From there, everything is public, deterministic math: HKDF-SHA256 with domain separation into BIP32 paths m/86'/0'/0'/0/0 for Bitcoin Taproot and m/44'/60'/0'/0/0 for Ethereum. The derivation code and constants are public — the local-nuri-prf-passkey-recovery-tool on GitHub is exactly that code, packaged for the worst day.

One distinction carries most of the security model. A passkey ceremony produces two things: the WebAuthn assertion — a public proof of user presence, origin, RP ID and intent — and the PRF output — the private, deterministic secret behind your wallet keys. The Nuri co-signer receives assertions to verify that you approved a co-signing action. It never receives the PRF output.

The secret that creates your signature never travels; only the proof that you approved the server's signature does. Co-signing and recovery are separate ceremonies on purpose. The server can help you spend on a Tuesday and be irrelevant to your recovery a decade later.

But the passkey is bound to nuri.com

Yes. The passkey is cryptographically bound to the RP ID nuri.com. We consider that a feature — it is what stops any random website from asking your passkey for money. But here is the part that took real design work: the RP ID is a string, not a subscription.

WebAuthn checks that the page requesting the ceremony is a secure context whose hostname matches the RP ID. It does not check whether the company still exists, whether DNS resolves, or whether the certificate came from a public authority. Those are browser security properties, not corporate records.

So the recovery tool recreates the relying-party context on your own machine. A hosts entry points nuri.com at your loopback address. A locally generated, locally trusted certificate makes the page a secure origin. The local server on https://nuri.com:8443 serves reviewed, committed files — no remote JavaScript, no analytics, no fonts. Your passkey asks for Face ID, Touch ID, or your PIN — the real ceremony, your real credential — and hands the PRF output to a page running entirely on your computer, ideally with the network cable pulled.

This is not a WebAuthn bypass. An attacker still needs your passkey and your user verification. It is not a forged public certificate — you trust your own local CA only on your own machine, for one recovery session. It is the recognition that the user who owns the passkey owns the RP context — even when the domain registrar no longer cares.

When the ceremony is done, you remove the hosts entry, delete the local CA, and disconnect. The string "nuri.com" in your passkey's memory outlived the company. That was the point.

The Bitcoin layer: 2-of-2 with a built-in exit

Normal spending on Nuri Bitcoin is a 2-of-2 MuSig2 collaboration. Your key — derived from the PRF — and the Nuri co-signer's key aggregate into a single Taproot key. On chain it looks like an ordinary Taproot output: fast, private, one signature.

But every output also carries a Miniscript escape hatch, committed into the chain itself. The key path is the MuSig2 aggregate; the script path is your x-only key wrapped in a CSV delay: and_v(v:pk(user), older(csv_blocks)).

While Nuri is alive, you and the co-signer spend together through the key path. The co-signer gives you instant, validated spending — spending limits, anomaly checks, a human-shaped safety net. If Nuri is gone, you wait out the CSV delay — CheckSequenceVerify, a relative timelock that starts counting when the output confirms — and then your key alone sweeps the funds through the script path. No permission needed, because the permission was baked into the output when it was created.

The math is public: unlock height is confirmation height plus CSV blocks. The recovery tool can build and sign the sweep transaction before unlock for inspection, but refuses to broadcast until the unlock condition is satisfied.

This is why we insist on the distinction: the private key is recoverable offline immediately; the onchain script may still require waiting. That is not provider custody — it is a public Bitcoin consensus rule committed into the output. Custody is when someone can refuse you. Nobody can refuse a timelock.

The decay architecture: co-signers that expire

Now the part that is easiest to miss and hardest to build: who is allowed to sign, and how that set shrinks over time. Most multisig wallets treat the signer set as permanent. Revocation means infrastructure: a revocation server, a key-rotation ceremony, an upgrade transaction. Every one of those is a dependency that can fail exactly when you need it most.

Nuri treats the signer set as a schedule. Co-signers are not revoked by infrastructure — they decay on chain, on a clock that Bitcoin itself enforces. The user key never rotates. The policy around it just gets simpler as time passes.

On Bitcoin L1, the output starts as a 2-of-2: your key and the Nuri co-signer's key aggregate into one MuSig2 Taproot key. Then, at a fixed relative timelock after the output confirms, the co-signer decays out of existence. At time zero the set is user plus co-signer, instant key-path spends. After the CSV delay the set is the user alone, script-path sweep. No transaction removes the co-signer. No revocation server, no key rotation, no migration. The same output simply has two spending conditions, and time selects which one is live.

That is what an honest non-custodial 2-of-2 has to mean: not 2-of-2 until we decide otherwise, but 2-of-2, decaying to 1-of-1, on a schedule nobody can pause.

The Arkade wallet: three keys, two decays

The Lightning layer has one more signer, so the decay ladder has one more rung. Every VTXO — the virtual transaction output that represents your off-chain balance — commits to a Taproot tree with three leaves, each a complete spending condition.

Leaf one is the arkade path: the MuSig2 aggregate of user and Nuri co-signer, together with the arkade server. Instant, off-chain, Lightning-fast. Leaf two is aggregate recovery: user and Nuri co-signer alone, after the arkade CSV delay. Leaf three is client recovery: the user key alone, after the sum of both delays.

In plain words, the signer set decays like this: at time zero it is user, Nuri co-signer and arkade server — normal Lightning life. After the first delay, the arkade server has decayed: user and co-signer remain. After the full sum of both delays, the Nuri co-signer has decayed too, and only the user key remains.

Three properties make this a ladder and not a pile of scripts. First, thresholds only ever shrink toward you: three keys, to two, to one. Never grows, never moves away from the user. Second, each delay is the sum of all delays above it: the client-recovery leaf unlocks at exactly the moment both co-signers have fully decayed, and the app structurally rejects any tree where the user-only path would unlock before the last co-signer's decay completes. While a co-signer is still in the script, it is still protecting you — against fat-fingered amounts, against a compromised client, against you at 3 a.m. Third, the decay is consensus, not infrastructure: at the first delay the arkade server does not get revoked — it simply stops being mentioned by any live spending condition. Even if it is still running, healthy and online, it has no mathematical role in your output anymore.

This is what we mean when we say the Arkade wallet is a 2-of-2-of-3. Not three keys, threshold two, forever — but a multisig whose co-signers are mortal by design, and whose only immortal key is yours.

How the operators and co-signers work together

It helps to see the cast. Your key is derived from your passkey's PRF output — it lives on your devices, in your password manager, or on a hardware authenticator. The Nuri co-signer is an operator we run: it verifies WebAuthn assertions before it contributes its signature share. The arkade server is the Lightning operator that makes off-chain sends possible.

The critical detail is how the co-signer decides to sign. It does not trust an API key or a session cookie. It requires a fresh WebAuthn assertion — a real passkey ceremony with user verification — for every co-signing action. That is what makes the whole system phishing-proof in depth: even if an attacker extracts a valid PRF output, they hold a deterministic key seed, but spending still requires the co-signer, and the co-signer only acts on a fresh passkey assertion from the user. A leaked PRF alone cannot move money while the co-signer is in the script. And once the co-signer has decayed, the attacker's window is the same CSV delay that protects everyone — a delay the user can also use to reach their funds first.

This is also the no-single-point-of-failure core: the operator, the arkade server, and even the domain are all mortal, all replaceable, all outside the critical path after their decay. The design extends naturally: a hardware wallet can be added as a backup co-signer for a 2-of-3 recovery option, and PRF-capable hardware authenticators — YubiKeys with PRF support, or our biometric NFC passkey smartcard — can hold the user key in silicon. More keys, more decay rungs, same rule: thresholds only ever shrink toward the user.

What this means when the lights go out

Run the full scenario. It is 2040. Nuri GmbH was dissolved in 2031. Nobody renewed the domain. You still have your passkey in your password manager, your phone, or a hardware authenticator.

On an offline computer, you run the open-source recovery tool. It recreates the nuri.com RP context locally — hosts entry, local CA, local server. Your passkey — the same credential, domain-bound to a string that no longer resolves — evaluates the PRF. Deterministic derivation produces your Bitcoin and Ethereum keys. You pull the encrypted Nostr backups, reconstruct your VTXOs, calculate each decay rung's unlock height, and sweep: L1 outputs where the CSV has matured, Arkade outputs where the ladder has finished decaying, anything still timelocked as soon as it matures.

At no point in this story did anyone at a company have to press a button, approve a ticket, or keep a database alive. The rescue was designed in on day one.

Why we keep saying rescue, not export

An export button is a promise about your future behavior. A rescue path is a property of the system.

Privy-style export is a real escape hatch — but it is an escape hatch you must remember to take, while the door is still open. Nuri's answer inverts the order. The key material lives with the user's passkey. The decay schedule lives in the chain. The recovery state lives on Nostr. The company's job is to be useful while it exists — not to be load-bearing after it doesn't.

This is also why the Arkade architecture matters beyond Nuri. Agents that hold money need the same property: an operator that can make money fast while it exists, and mathematically cannot matter once its time is up. A wallet whose co-signers decay is a wallet an agent can inherit without inheriting a dependency.

Self-custody that requires the custodian's continued existence is just custody with extra steps. We built Nuri so that even our absence is survivable. That is not a marketing line; it is testable, today, with tools we published and a scenario you can rehearse in an afternoon.

Recovery still needs the original passkey, an authenticator that supports PRF, and a reviewed copy of the tool. And honesty about the trade-off: after the co-signers decay, their protections decay with them. Self-custody means owning that. The single most important sentence in this article is this one: check whether your wallet's passkey carries a secret, or just a signature.

The co-signers decay. The user key never changes. That is the whole design.

Emin Mahrt · Founder, Nuri