SplitSig: Non-Custodial Key Derivation from Lightning Wallets
Every web application that handles Bitcoin faces the same dilemma: the user needs a signing key, but where does that key live? If the server generates it, the service is custodial. If the browser generates it, the user loses it when they clear their cache. If you derive it from the wallet, the server sees the derivation material.
I built a scheme that solves this. I call it SplitSig.
The Formula
privkey = SHA256( ecdsa_signature + nonce )
─────────────── ─────
server sees this browser-only
Two pieces. The server has one. The browser has the other. Neither alone can derive the private key.
How It Works
When a user authenticates with their Lightning wallet via LNURL-auth, the wallet signs a challenge with ECDSA. That signature goes to the server — that's how LNURL-auth works, it's inherent to the protocol.
Most signature-derived key systems (zkSync, Umbra, StarkWare) would simply hash that signature to produce a private key. But the server saw the signature, so the server could do the same hash and have the key. That's custodial.
SplitSig adds a second ingredient: a random 256-bit nonce generated in the browser via crypto.getRandomValues(). The nonce never leaves the browser. The server never sees it.
The split:
Server has: signature (from LNURL-auth callback)
Browser has: signature + nonce
Only the browser can compute SHA256(sig + nonce) = private key
Recovery
The nonce is saved in a recovery kit that the user downloads. To re-derive the key on a new device:
- Load the recovery kit (contains the nonce)
- Scan the LNURL-auth QR with the same wallet
- Same wallet = same seed = same signature (RFC 6979, deterministic)
- SHA256(same signature + same nonce) = same private key
The LNURL-auth linking key is derived from the wallet's BIP-32 seed at path m/138'/... (LUD-04). It's stable across app updates, OS upgrades, and device changes. As long as the wallet seed is the same, the key is recoverable.
What's New
Deriving keys from signatures isn't new. These systems do it in production:
| System | Key from signature? | Split knowledge? |
|---|---|---|
| zkSync | yes | no |
| Umbra Protocol | yes | no |
| EIP-2645 / StarkWare | yes | no |
| Web3Auth / tKey | no (Shamir) | yes |
| SplitSig | yes | yes |
To my knowledge, SplitSig is the first to combine signature-derived keys with a client-side nonce split. The reason it hasn't appeared before is likely that LNURL-auth is a Bitcoin/Lightning niche, while most signature-derived key work happened in the Ethereum ecosystem where the wallet signs locally and the server never sees the signature.
Threat Model
Compromised server: Has the signature but not the nonce. Cannot derive the key. Even storing all signatures forever, brute-forcing a 256-bit nonce is infeasible.
Compromised browser: Has both pieces. Can derive the key. This is the "trusted code delivery problem" — an inherent limitation of all web applications. Blockstream has stated that "a browser must always be considered compromised." Native apps with reproducible builds are the only full mitigation.
Stolen recovery kit: Has the nonce but not the signature. Cannot derive the key without the wallet's BIP-32 seed.
Lost wallet seed: Different seed = different signature = different key. Same risk as any seed-based system.
Applications
SplitSig works for any web application that uses LNURL-auth and needs non-custodial signing keys:
- Nostr clients — derive a signing key from a Lightning wallet login, no nsec pasted into a web form
- Ecash wallets (Cashu, Fedimint) — protect spending keys in browser-based wallets
- Swap services — derive claim keys for submarine swaps
- Escrow services — derive buyer/seller keys for multisig contracts
- Any web wallet — non-custodial key management using existing Lightning infrastructure
Try It
The interactive demo walks through each step of the protocol, showing exactly what the server sees and what stays in the browser.
Protocol spec · Test vectors · Demo video · GPL-3.0