Skip to content
Loess

@loess/keys

Where a device's own keys and a passkey's signatures come from, and the recovery phrase. The sdk turns these into a key member of an identity.

import { createDeviceKeys, indexedDbDeviceKeyStore } from '@loess/keys/device';
 
const store = indexedDbDeviceKeyStore('loess');
const kept = yield * createDeviceKeys(store); // the first time; later, `yield* store.read`

Interface

  • ./device: createDeviceKeys makes an Ed25519 key pair that can't be exported and a sealer secret, and keeps them (DeviceKeys) in a DeviceKeyStore, which indexedDbDeviceKeyStore gives in a browser. The sealer comes from a secret kept beside the key pair, because Safari's IndexedDB loses a stored X25519 key. It fails with exists when the store holds keys, so two tabs never both keep keys. A device's private keys never leave the device.
  • ./passkey: webAuthnAuthenticator (Authenticator, PasskeyAssertion) creates a passkey over WebAuthn that signs with ES256 or RS256 and asks it for assertions, with the PRF output when the passkey has PRF. A passkey's signature is a WebAuthn assertion whose challenge is the hash of the signed bytes.
  • ./phrase: createRecoveryPhrase makes 12 words of the BIP-39 English list, which bring everything back after every device is lost.
  • ./memory: memoryDeviceKeyStore is a device key store, and memoryAuthenticator is a software passkey authenticator whose assertions have the WebAuthn layout.
  • Every failure is a KeyError with a reason a screen can act on, such as refused for a cancelled prompt or exists for kept keys.

Not built: a crypto wallet, which needs a small library to check secp256k1 since WebCrypto doesn't have it, and email recovery, which is only safe with a delay that every other owner can stop. The passkey authenticator is tested with the simulated one, not a real browser.

Place in the stack

It imports @loess/protocol only, and runs on devices.