Key Generation
A recipient identity is a hybrid pair: a secp256k1 spending keypair (the same curve Ethereum and Sui use) and a post-quantum ML-KEM-768 viewing keypair. This page explains how they are produced, why the protocol keeps them separate, and how their public halves combine into the one meta-address a recipient shares.
Two keypairs, two jobs
The split exists so that a recipient can let software watch for payments without giving that software the power to spend.
| Keypair | Public half does | Secret half does | Who needs the secret |
|---|---|---|---|
| Spending | Lets a sender derive a stealth address | Recovers the private key that spends | The owner, at spend time |
| Viewing | Lets a sender encapsulate a shared secret | Detects which payments are yours | A scanner, continuously |
A scanning service or a hot device can hold the viewing secret key and still never move funds. The spending secret key can stay in colder storage and only come out to sign. See security boundaries for why this matters.
What each keypair produces
The two keypairs use different curves, so their sizes differ:
| Keypair | Public key | Secret key | Notes |
|---|---|---|---|
| Spending (secp256k1) | 33 B (compressed) | 32 B (scalar) | Standard Ethereum/Sui curve; safe to publish the public key |
| Viewing (ML-KEM-768) | 1,184 B (encapsulation key) | 2,400 B (decapsulation key) | Post-quantum; the viewing public key encapsulates shared secrets |
generateSpecterKeys produces both and labels them spending and viewing. The viewing sizes are fixed by FIPS 203 and exported as KYBER_PUBLIC_KEY_SIZE / KYBER_SECRET_KEY_SIZE; the spending sizes are exported as SPEND_PUBLIC_KEY_SIZE / SPEND_SECRET_KEY_SIZE. generateKeysLocal remains available if you want a single ML-KEM-768 viewing keypair on its own.
Where the randomness and the keys live
Key generation needs a good random source, and the secret keys must not leak. SPECTER handles both in the Rust core that the SDK, CLI, and backend all share:
- Keys are generated with the RustCrypto
ml-kemcrate, pure Rust with no C dependencies, which is what makes the WebAssembly build possible. - Every crate enforces
#![forbid(unsafe_code)]. - Secret key material is wiped from memory on drop with the
zeroizecrate, so it does not linger after use.
With the SDK, this runs on the user's device. The hosted API can also generate keys server side, but then the server sees the secrets. For wallets, generate locally.
import { generateSpecterKeys } from '@specterpq/sdk';
const recipient = generateSpecterKeys();
// recipient.spending.publicKey, recipient.spending.secretKey
// recipient.viewing.publicKey, recipient.viewing.secretKey
The secret keys exist on the returned object but are non-enumerable and redacted from logs. See the SDK security model.
From public keys to a meta-address
A recipient never publishes four separate keys. The two public keys are serialized into one meta-address, optionally with metadata such as a display name or avatar.
The serialized form is 1,218 bytes: a version byte, the 33-byte secp256k1 spending public key, and the 1,184-byte ML-KEM viewing public key (any display metadata is carried alongside). The meta-address is the only address a recipient hands out, and it is reusable. Every payment to it still lands at a different stealth address, which is the subject of the stealth derivation page.
import { metaAddressFromPublicKeys } from '@specterpq/sdk';
const meta = metaAddressFromPublicKeys(
recipient.spending.publicKey,
recipient.viewing.publicKey,
{ description: 'Receive profile' },
);
// meta.hex is publishable; meta.bytes.length === 1218
Recovery implications
These keypairs are long-lived. Treat both secret keys the way you treat a seed phrase:
- Lose the viewing secret key and the wallet can no longer detect incoming payments.
- Lose the spending secret key and the funds at every stealth address are unrecoverable.
There is no server-side reset. The protocol has no record of the secrets, which is the point.
Next
- Shared secret: what a sender does with the viewing public key.
- Security boundaries: the trust model behind the key split.