The key that never exists: a threshold signing ceremony in your browser
A cryptographic key is a single point of failure: whoever holds it, holds everything. Every custody disaster you have read about reduces to that sentence. Threshold cryptography attacks the premise itself: what if the key never exists in one place, not even for a millisecond, not even at birth?
On this page, three keyholders - alice, bob and carol - create one key together: any two of them can sign, no one at any point ever holds the whole key, and no trusted dealer makes it and splits it. The key is born already in pieces.
Everything below is real cryptography, compiled from Zig to WebAssembly and running locally in your tab; the library, zig-mpc, is on GitHub. Pick a curve, press Begin the ceremony, and the page walks you through the rest.
Choose a key
The cast is fixed: alice is party 1, bob is party 2, carol is party 3, and the threshold is 2 - any two can sign, any one alone can do nothing. In production each party is a separate machine; this page plays all three and shows you exactly which bytes would cross the network.
Two pieces of jargon before the rounds start. This is MPC - secure multi-party computation, protocols where several parties jointly compute a result while each party’s input stays secret. It is the machinery inside every “MPC wallet”, normally sealed behind a vendor’s API; here it runs in the open. And a key ceremony is custody’s name for the formal procedure in which key material is born or used: classically a scripted afternoon in a vault, with witnesses, dual controls and tamper-evident bags. The ceremony on this page is the real protocol, minus the vault.
The details behind the pickers: the ceremony is identical for all three schemes - only the curve and the final signature format differ. Ed25519 and BIP-340 sign with FROST (Flexible Round-Optimized Schnorr Threshold signatures, RFC 9591); the same distributed key generation (DKG) also feeds threshold ECDSA (CGGMP24) for chains that require it. The curve and hash primitives are the Zig standard library’s; the protocol layer on top is zig-mpc, and it reproduces the RFC 9591 test vectors byte for byte.
Every byte you will see is a genuine protocol message, in the same wire format the zmpc CLI exchanges between real machines. Nothing you type or click leaves this page: there is no server, no analytics call, no network request carrying your input anywhere. The page fetches one wasm file and then goes quiet.
Grow a key that never exists whole
Distributed key generation, three rounds plus a finalize. With the CLI this is zmpc dkg round1..round3, finalize, one command per machine per round. Each round reads the frames that arrived, produces new ones, and the page delivers them the way you would between machines: a broadcast frame (…-t0) is copied to both other parties, a p2p frame (…-tN) goes to party N only.
The shape of the trick is worth stating before you click. Each party invents its own secret polynomial and hands the others single points on it. Add three polynomials together and you get a group secret nobody chose and nobody knows; each party’s share is just its own stack of points. Commitments in round 1 stop anyone from picking their polynomial after seeing the others; verification in round 3 catches anyone who dealt inconsistent points. Cheating does not corrupt the key. It aborts the ceremony and names the cheater.
If you know Shamir’s secret sharing, the algebra is familiar - polynomials, points, interpolation - but the trust model is not. Shamir needs a dealer who holds the whole secret before splitting it, and using the secret means putting it back together: the key exists whole at birth and again at every use, which is exactly the exposure we set out to delete. Here each party deals only its own contribution, and reconstruction never happens - not at birth, and, as the signing section shows, not at signing either.
The rounds unlock once you begin the ceremony above.
All three parties computed this same key
independently, from each other's commitments. The matching private key
has never existed anywhere: alice, bob and carol each hold
keyshare.zmpc - one point on a secret polynomial nobody
knows - and that file never leaves their machine.
Sign with any two
Signing is FROST: two rounds plus an aggregation - zmpc sign commit / share / aggregate. Pick which two sit at the table; the third stays home and learns nothing, not even that a signature happened. Every signer must hold the same message bytes and a fresh signing-session id.
The output is the punchline: an ordinary signature. A Solana validator, a Bitcoin node, an Ethereum client verifies it with the standard algorithm against the group public key. No verifier can tell a ceremony happened. The threshold structure is invisible on-chain, which also means no chain needs to support any of this for it to work today.
Unlocks once the key above exists.
Try to break it
A protocol is only as interesting as its failure modes. Each experiment runs a fresh signing session with one thing wrong: a bit flipped in transit, a swapped message at verification, a party trying to sign below the threshold with its perfectly genuine share.
One byte of the first signer's commitment frame is flipped in transit - pick which one, or leave blank for a random position - and the other signer tries to use the frame.
Two signers run a fresh session over the message from the signing panel, then the signature is verified against whatever you type here. Try changing one character - then try typing the original message exactly.
Below the threshold: one party runs the entire signing protocol by itself, honestly, with its genuine share.
Unlocks once the key exists - the experiments need real shares to attack.
The third experiment is the one to sit with. Alice’s lone attempt fails not because some component checked a policy and said no. Every protocol step runs without complaint. It fails because one point does not determine a degree-1 polynomial: the math is missing a variable, and the resulting signature simply does not verify. The threshold is enforced by algebra, not by an if-statement anyone could patch out.
Each experiment’s message for the impossible outcome reads “that is a bug”. If you ever see it, the issue tracker wants to hear from you.
Under the hood
This page embeds zmpc.wasm (about 230 KB), the WebAssembly build of zig-mpc, a threshold-signing library written in Zig and compiled to wasm32-freestanding. The primitives are not rolled: Edwards25519 and secp256k1 group arithmetic come from std.crypto.ecc, field math from std.crypto.ff, SHA-2 and HMAC from the same place. What zig-mpc adds is the protocol layer, plus the habits that matter around it: every consumed secret is zeroed with secureZero, comparisons on secret data are constant-time. No emscripten, no JS crypto: the JavaScript on this page only moves bytes in and out of linear memory and draws tables.
The DKG and FROST rounds running here are the exact functions the zmpc CLI runs, and the frames shown are byte-compatible with it: a keyshare.zmpc generated on this page works in a CLI signing session between real machines, and vice versa. On real hardware the same ceremony looks like this, one process per party, one command per round:
zmpc init --dir p1 --suite ed25519 --party 1 --n 3 --threshold 2
zmpc dkg round1 --dir p1 # ... deliver frames to the other machines ...
zmpc dkg round2 --dir p1
zmpc dkg round3 --dir p1
zmpc dkg finalize --dir p1 # -> artifacts/keyshare.zmpc, prints the public key
The library and that CLI live at github.com/808bits/zig-mpc: the same Zig source this page is running, the vector suites below, and a multi-process end-to-end test that gives every party its own process. zig build produces the CLI, zig build wasm produces the module this page just loaded, and there are no dependencies beyond the Zig standard library.
Validation, since threshold code that “seems to work” is worthless: the FROST implementation reproduces the RFC 9591 test vectors byte for byte, and the resulting signatures verify under std.crypto’s ordinary Ed25519 verifier. The Taproot mode passes all 19 official BIP-340 vectors. The library also does threshold ECDSA (CGGMP24, end-to-end presign and sign; this wasm module can complete a signature from a CLI-generated presignature), proactive share refresh, and BIP-32/SLIP-10 HD derivation against the SLIP-10 vectors.
One honest caveat: the point of MPC is that each party is a different machine, and the shares never meet. This tab plays all three parties so you can watch the messages; the cryptography neither knows nor cares. And a second one, in bold: zig-mpc is unaudited, pre-production software. It exists so you can see how a signing ceremony actually works, byte by byte. Do not put keys you care about anywhere near it yet. What “pre-audit” means here, and what has to happen before it stops meaning that, is written out in the repository’s roadmap rather than left to your imagination.