Create a portable identity with Keet identity keys
Derive a portable identity from a mnemonic, attest a per-device key, then sign and verify data with it — the Pear/Bare logic, no UI required.
This guide focuses on the Pear/Bare logic. It shows keet-identity-key on its own — no Electron, no UI. For the same identity wired into a full desktop chat app, see the worked example pear-chat-identity and its walkthrough, Add Keet identity to a chat app.
A Hypercore key identifies a log, not a person.
keet-identity-key gives you a portable identity: a key derived from a 24-word mnemonic that stays the same across devices and reinstalls.
Each device generates its own throwaway key pair, which the identity attests — so you can sign data on any device and anyone can verify it was authored by the same person, without ever copying the identity's secret onto that device.
Without the identity, you would need to share a secret between devices to sign data — and that secret would need to be copied onto each device. With the identity, you can sign data on any device and anyone can verify it was authored by the same person, without ever copying the identity's secret onto that device.
This is purely Pear-end logic: it runs in a Bare worker (or any Bare/Node process) and never touches a UI.
The model
| Concept | What it is |
|---|---|
| Mnemonic | 24 words. The root secret — back it up like a wallet seed. |
| Identity | Derived from the mnemonic. Its identityPublicKey is stable everywhere. |
| Device key pair | A fresh, per-device key. Never leaves the device. |
| Device proof | The identity's signature attesting that the device key speaks for it. |
| Data proof | A signature over a payload, made with the device key, anchored to the identity. |
Add the dependencies
npm install keet-identity-key hypercore-cryptoDerive the identity and attest a device
Generate (or load) a mnemonic (L5), derive the identity from it (L7), then mint a fresh per-device key pair (L11) and bootstrap it — the identity attests the device key and returns a deviceProof (L12):
import Identity from 'keet-identity-key'
import crypto from 'hypercore-crypto'
// Generate once and persist it somewhere safe (treat it like a wallet seed).
const mnemonic = Identity.generateMnemonic()
const identity = await Identity.from({ mnemonic })
// identity.identityPublicKey is the same on every device that loads this mnemonic.
// Each device gets its own ephemeral key pair, attested by the identity.
const deviceKeyPair = crypto.keyPair()
const deviceProof = await identity.bootstrap(deviceKeyPair.publicKey)Persist the mnemonic, not the device key — a new device re-derives the identity from the same mnemonic and bootstraps a fresh device key of its own.
Sign data
Attest a payload (L15) with the device key. The resulting proof (L16) carries the chain back to the identity:
const payload = Buffer.from('hello from this device')
const proof = Identity.attestData(payload, deviceKeyPair, deviceProof)Attach proof to whatever you append to your Hypercore/Autobase alongside the payload.
Verify data
Any peer replicating the data can verify it was authored by the expected identity — pass the proof, the payload, and the expected identityPublicKey, and verify returns truthy only for a valid proof (L19–L21). No shared secret needed:
const ok = Identity.verify(proof, payload, {
expectedIdentity: identity.identityPublicKey
})
// ok is truthy when the proof is valid for that identity.Because verification only needs the public identityPublicKey, you can stamp every message with a proof and let every reader confirm authorship across reinstalls and across machines.
See also
- Add Keet identity to a chat app — this primitive wired into a full desktop chat, stamping every message with a verifiable identity.
- Connect two peers by key with HyperDHT — once you have a stable identity key, you can dial it directly.
- Secretstream — the noise-based encrypted transport that authenticates connections by public key.
- Workers — why identity belongs in the worker, not the renderer.