Send a proof, not your key.
Your secret never leaves the device.
At sign-in, what leaves the device is a proof — not a key. The server never receives or stores your key. It doesn't even learn which key you used.
Works with open standards
import * as seal from "@lemmaoracle/seal"; const { proof, publicSignals, nullifier } = await seal.prove({ secret: process.env.LEMMA_API_KEY, nonce: challengeNonce, }); // The key stays on the device. Only the proof is sent.
Seal outputs neither your key nor its hash — only a per-session value, the nullifier. Because the value changes at every sign-in, proofs can't be matched to the same person by lining them up.
Three sessions generated from the same key. There is no traceable relation between the three values.
(The service you sign in to matches the received proof against its own registered key to confirm who you are.)
Most leaks start from data that never needed to be held. Seal removes that data from the equation — from servers, from logs, from AI-agent context.
Without Seal
- API keys sit in the AI agent's context, exposed through logs and prompt injection.
- The server stores users' secrets — every breach leaks everyone's.
- Sign-in records can be linked back to the same user.
With Seal
- All the context holds is a proof — nothing worth leaking.
- The server stores no keys. At worst, a single session's proof leaks.
- Each proof is a different value per session. Lining them up reveals no relation.
Challenge
The server issues a single-use identifier (nonce).
Prove
The proof is generated on the device.
Note: Proof generation completes inside the browser. The secret key never leaves the device.
Verify
The server verifies the proof and confirms who you are.
Note: No key is ever received or stored.
Verification runs off-chain (groth16-bn254-snarkjs). No chain dependency.
Don't hand your keys to AI.
Where agents run, place proofs — not keys. Talk to us about the rollout that fits.