Skip to content
Get startedGetting started →

Getting started

What mera does:

The pipeline from a passkey to a signature. A sign-in ceremony returns 32 secret bytes, the same bytes on every sign-in. The app derives a private key from them. A signing session holds the key and produces signatures. mera owns every step except the derivation, which belongs to the app. Sign in mera 32 secret bytes mera Derive a key the app Signing session mera Signature

Prerequisites:

  • A secure context: HTTPS, or localhost during development.
  • An authenticator that supports the PRF extension. Authenticator support lists the combinations known to work. 1Password and iCloud Keychain are good first choices.
Terminal window
npm install @category-labs/mera

This walkthrough derives accounts with @scure/bip32 and @scure/bip39. Any derivation scheme works.

Terminal window
npm install @scure/bip32 @scure/bip39

createPasskeyWithPrfOutput creates a passkey and evaluates its PRF in one call.

import { createPasskeyWithPrfOutput } from "@category-labs/mera";
// The relying party ID, the domain the passkey is bound to.
const rpId = "account.example.com";
const { prfOutput } = await createPasskeyWithPrfOutput({
rp: { id: rpId, name: "Example" },
user: { name: "account@example.com", displayName: "Example account" },
});

mera uses a stable PRF salt for this call, so a later sign-in reproduces the same 32 bytes.

The PRF output is the root of every account. This walkthrough maps it through BIP-39 to a phrase and a seed, then derives keys with BIP-32.

import { HDKey } from "@scure/bip32";
import { entropyToMnemonic, mnemonicToSeedSync } from "@scure/bip39";
import { wordlist } from "@scure/bip39/wordlists/english.js";
const firstEthereumAccountPath = "m/44'/60'/0'/0/0";
const mnemonic = entropyToMnemonic(prfOutput, wordlist);
const seed = mnemonicToSeedSync(mnemonic);
const node = HDKey.fromMasterSeed(seed).derive(firstEthereumAccountPath);
if (node.privateKey === null) throw new Error("derivation produced no key");
import {
createSecp256k1SigningSession,
getEvmAddress,
} from "@category-labs/mera";
const session = createSecp256k1SigningSession({
privateKey: node.privateKey,
});
const address = getEvmAddress(session.publicKey);
const digest = new Uint8Array(32);
const signature = await session.signDigest(digest);
session.end();

A later visit reproduces the same account.

import { getPasskeyPrfOutput } from "@category-labs/mera";
const { prfOutput } = await getPasskeyPrfOutput({
rpId,
});

Without credential, the browser offers any discoverable passkey it holds for the relying party. The Create passkey accounts recipe shows the app storing the credential ID at create time and passing it back to pin later sign-ins.