splitkey
Draft v1, for review, not implemented yet

The split-key protocol

Rotation (Stellar) and mint creation (Solana) make a seller's copy of a key worthless. Some keys, such as a wallet's, must stay powerful forever. For those, split-key lets a searcher find a vanity address for a key it never learns.

Idea

Ed25519 public keys are points s·B on a curve. Points can be added, and so can their scalars. The client picks a secret scalar s_client and publishes only A_client = s_client·B. The searcher tries offsets i until A_client + i·B encodes to a matching address, then proves it found one, without revealing i. After payment it sends i. Only the client can compute the final key s_client + i; the searcher knows i but not s_client.

Notation

B is the edwards25519 base point and ℓ = 2^252 + 27742317777372353535851937790883648493 its order. Points are 32-byte RFC 8032 encodings. Scalars are 32 bytes, little-endian, canonical (< ℓ).

1. Client share

2. Search

3. Proof of knowledge, before payment

Let P = (neg ? −A_total : A_total) − A_client = i·B. The searcher sends a standard Ed25519 signature by the scalar i over a context message, so the client can check it with any Ed25519 library:

M = "splitkey/pok/v1" ‖ network ‖ job_id ‖ A_client ‖ A_total ‖ neg
    ‖ SHA-256(canonical pattern) ‖ expiry
r = SHA-512(nonce_key ‖ i ‖ M) mod ℓ        deterministic, never reused
R = r·B;  c = SHA-512(R ‖ P ‖ M) mod ℓ;  z = (r + c·i) mod ℓ
proof = R ‖ z                              a 64-byte Ed25519 signature

The client computes P itself and checks: Ed25519.verify(pubkey = P, msg = M, sig = proof), that A_total matches the pattern, and that P is not the identity. This proves the searcher knows i and reveals nothing about it. The nonce must never repeat: two proofs with the same r would reveal i. That is why r is derived from a secret nonce key.

4. Delivery and assembly, after payment

The searcher sends i. The client checks (neg ? −1 : 1)·(A_client + i·B) == A_total, then computes s_total = neg ? −(s_client + i) : (s_client + i) mod ℓ, checks s_total·B == A_total, uses it, and wipes both scalars. JavaScript and Go cannot guarantee that memory is wiped, and the security notes say so.

5. Signing with the assembled scalar

The result is an expanded Ed25519 key (a scalar, not a seed), so it signs like this:

prefix = SHA-512("splitkey/prefix/v1" ‖ s_total)[32..64]
r = SHA-512(prefix ‖ msg) mod ℓ;  R = r·B
k = SHA-512(R ‖ A_total ‖ msg) mod ℓ;  S = (r + k·s_total) mod ℓ
signature = R ‖ S                          verifies anywhere

The export format splitkey-expanded-v1 (scalar, prefix, address) is deliberately unlike a Stellar S… seed or a Solana 64-byte keypair. Wallets that import only seeds cannot import it, which is the main practical limit of split-key.

6. Point validation

Checking 8·A ≠ O is not enough: A = s·B + T with a torsion point T passes it but has no usable scalar. The searcher requires all of:

7. Network notes

Status and review

This draft follows an internal design review. The planned library is Go (on filippo.io/edwards25519) and TypeScript (on @noble/curves), both MIT, with shared test vectors. Proofs and signatures will verify with the standard crypto/ed25519 and @noble/curves verifiers. Comments are welcome at hello@keyplates.com.