Skip to content

Latest commit

 

History

History
183 lines (129 loc) · 11 KB

File metadata and controls

183 lines (129 loc) · 11 KB

SplitSig: Split-Knowledge Key Derivation from LNURL-auth

Abstract

We present a non-custodial key derivation scheme for web applications that derives signing keys from Lightning wallet authentication without the server ever having access to the private key. The scheme splits the key derivation material between two independent channels: an ECDSA signature (visible to the server via LNURL-auth) and a random nonce (generated client-side, never transmitted to the server). Neither piece alone can derive the signing key.

To our knowledge, this is the first implementation of split-knowledge key derivation from LNURL-auth signatures.

Problem

Non-custodial Bitcoin applications on the web face a fundamental tension:

  1. Key generation: The user needs a signing key for on-chain transactions
  2. Key recovery: The user must be able to recover the key on a new device
  3. Server trust: The server delivers the application code and participates in authentication

Existing approaches each sacrifice one property:

Approach Generation Recovery Non-custodial
Server generates key trivial server stores it no
Browser generates key trivial user must backup yes, but fragile
Mnemonic-derived key trivial wallet has seed only non-custodial wallets
Signature-derived key from wallet re-sign same msg no — server sees signature

The last approach (used by zkSync, Umbra, StarkWare) derives keys from wallet signatures: privkey = SHA256(sign(message)). This works well when the wallet signs locally (browser extension, hardware wallet). But in LNURL-auth, the wallet sends the signature to the server's callback URL — the server sees it and could derive the same key.

Solution

Add a second factor that the server never sees:

privkey = SHA256( ecdsa_signature || nonce )

Where:

  • ecdsa_signature: The wallet's RFC 6979 deterministic ECDSA signature on the LNURL-auth challenge, delivered to the server via the standard LNURL-auth callback
  • nonce: A 256-bit random value generated by crypto.getRandomValues() in the browser, never transmitted to the server
  • ||: Byte concatenation

The server receives the signature (inherent to LNURL-auth) but never the nonce. The browser has both pieces and derives the key locally. The nonce is stored in a recovery kit that the user downloads.

LNURL-auth Linking Key Stability

LNURL-auth linking keys are derived deterministically from the wallet's BIP-32 seed via the LUD-04 specification:

hashingKey = key at m/138'/0
derivationMaterial = HMAC-SHA256(hashingKey, domain)
linkingKey = key at m/138'/<4 path components from derivationMaterial>

The linking key is a function of the wallet seed and the service domain. It is stable across:

  • App updates: same seed, same key
  • OS upgrades: same seed, same key
  • Device changes: restore the seed, same key
  • Multiple sessions: always the same key for the same domain

The only event that changes the linking key is creating a new wallet with a different seed. As long as the user retains their wallet (or restores it from backup), the LNURL-auth signature — and therefore the SplitSig derived key — is reproducible.

Protocol Flow

Key Derivation

Browser                          Server                    LN Wallet
   │                               │                          │
   │  1. Generate nonce            │                          │
   │     (crypto.getRandomValues)  │                          │
   │     Store in localStorage     │                          │
   │                               │                          │
   │  2. GET /auth/challenge ─────>│                          │
   │  <── k1, lnurl ─────────────│                          │
   │                               │                          │
   │  3. Display LNURL QR          │                          │
   │                               │    4. Wallet signs k1    │
   │                               │<────── sig, pubkey ─────│
   │                               │    {"status":"OK"} ────>│
   │                               │                          │
   │  5. GET /auth/status/{k1} ──>│                          │
   │  <── sig, session_token ────│                          │
   │                               │                          │
   │  6. Derive key (client-side): │                          │
   │     privkey = SHA256(sig+nonce)│                         │
   │     pubkey = privkey * G      │                          │
   │                               │                          │
   │  7. POST /register-pubkey ──>│                          │
   │     { pubkey only }           │  Store pubkey            │
   │                               │                          │
   │  8. Download recovery kit     │                          │
   │     (contains nonce)          │                          │

Key Recovery

Browser (any device)             Server                    Same Wallet
   │                               │                          │
   │  1. Load recovery kit         │                          │
   │     (has nonce)               │                          │
   │                               │                          │
   │  2. GET /auth/challenge ─────>│                          │
   │  <── k1, lnurl ─────────────│                          │
   │                               │                          │
   │  3. Display LNURL QR          │    4. Same wallet signs  │
   │                               │<────── same sig ────────│
   │                               │                          │
   │  5. GET /auth/status/{k1} ──>│                          │
   │  <── same sig ──────────────│                          │
   │                               │                          │
   │  6. SHA256(same sig + nonce)  │                          │
   │     = same privkey            │                          │

The deterministic k1 challenge ensures the wallet always signs the same message for the same context, producing the same signature via RFC 6979.

Security Properties

Knowledge Distribution

Piece Server Browser Recovery Kit
ECDSA signature yes yes no
Nonce no yes yes
Private key no yes no (derived)
Public key yes yes no

Threat Analysis

Compromised server: The server has the ECDSA signature but not the nonce. It cannot derive the private key. Even if the server stores all signatures indefinitely, the nonce (256 bits of entropy) makes brute force computationally infeasible (2^256 operations).

Compromised browser: If an attacker controls the browser (malicious extension, XSS, compromised JS delivery), they have access to both the signature and the nonce. They can derive the key. This is the "trusted code delivery problem" — an inherent limitation of all web applications, acknowledged by Blockstream ("a browser must always be considered compromised") and the broader security community. Native applications with reproducible builds are the only mitigation.

Lost recovery kit: The nonce is only in the recovery kit. If lost, the key cannot be re-derived. The application must provide a fallback path (e.g. timelock-based recovery).

Stolen recovery kit: The kit contains the nonce but not the signature. An attacker with only the kit cannot derive the key — they would also need to authenticate as the user's wallet (possess the wallet's BIP-32 seed).

Wallet seed lost: The LNURL-auth linking key is derived from the wallet seed. A new wallet produces a different linking key and therefore a different signature. The derived key cannot be reproduced. This is the same fundamental risk as any seed-based system.

Both kit + server compromised: The attacker has the nonce (from kit) and the signature (from server). They can derive the key. This requires compromising two independent systems.

Comparison to Prior Art

Signature-derived keys (zkSync, Umbra, EIP-2645)

These systems use privkey = hash(signature) where the signature is produced locally (MetaMask, hardware wallet). The application server never sees the signature.

In LNURL-auth, the signature is sent to the server's callback URL — this is inherent to the protocol. SplitSig compensates by ensuring the signature alone is insufficient.

Threshold key management (Web3Auth/tKey)

Web3Auth splits an already-generated key into shares using Shamir's Secret Sharing (2-of-3). SplitSig never generates the key on the server at all — the key only exists in the browser at the moment of derivation.

Two-party key generation (Bellare et al.)

Academic protocols for jointly generating keys without either party learning the full key. SplitSig is simpler: the server doesn't participate in generation at all. It passively holds one factor (the signature) without knowing it's being used for key derivation.

Cryptographic Justification

SHA256 of ECDSA signatures: Alex Gluchowski (Matter Labs/zkSync) notes that "SHA256 is important from the cryptanalytic perspective: signatures are elliptic curve points, so we need to break any potential relation between the two keys." SHA256 acts as a randomness extractor, ensuring the derived key is uniformly distributed in the scalar field.

Nonce entropy: 256 bits from crypto.getRandomValues() (CSPRNG). Even 128 bits would make brute force infeasible. The nonce adds entropy independent of the signature structure.

RFC 6979 determinism: LNURL-auth wallets use deterministic ECDSA (RFC 6979), ensuring the same wallet + same challenge = same signature. This is required for key recovery — the user must be able to reproduce the same signature on a new device with the same wallet seed.

Applications

SplitSig is a general-purpose key derivation scheme. Any web application that needs non-custodial signing keys and uses LNURL-auth can apply it:

  • Nostr clients: derive a Nostr signing key from a Lightning wallet without exposing the nsec to the server
  • Ecash wallets (Cashu, Fedimint): protect spending keys in browser-based wallets
  • Swap services: derive claim keys for submarine swaps and atomic swaps
  • Escrow services: derive buyer/seller signing keys for multisig or tapscript contracts
  • Any web wallet: non-custodial key management using existing Lightning wallet infrastructure

Implementation

License

GPL-3.0