Skip to content
Get startedGetting started →

Encrypt an existing secret with a passkey

A secret vault encrypts a recovery phrase, private key, or other byte string behind a passkey. This recipe encrypts a phrase and unlocks it later.

Prerequisites:

  • @category-labs/mera installed.
  • A place to keep vault JSON (localStorage here; a backend or sync service works the same).

createSecretVaultWithNewPasskey generates a fresh 32-byte salt per vault, creates the passkey, and encrypts the secret. The salt and credential metadata are stored in the returned vault (vault format).

import {
function createSecretVaultWithNewPasskey({ secret, ...passkeyOptions }: CreateSecretVaultWithNewPasskeyOptions): Promise<PasskeySecretVault>

Creates a passkey and encrypts one secret into a vault.

@paramoptions - Passkey creation inputs and secret bytes.

@returnsA JSON-safe secret vault containing the new credential metadata.

@remarks

Runs one creation ceremony and shows one user-verification prompt. On authenticators that do not evaluate PRF during creation, also runs an assertion, which shows a second.

A fresh random PRF salt is generated internally and stored in the returned vault.

A fresh random user handle (user.id) is generated for the new credential, so each call adds a passkey instead of replacing one.

If the fallback ceremony or vault encryption fails, the passkey from the completed creation ceremony still exists on the authenticator, but the thrown error does not carry its metadata.

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not enable PRF or return a usable 32-byte PRF output.

@throwsMeraError with code INPUT_INVALID when secret is empty.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues or crypto.subtle is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

createSecretVaultWithNewPasskey
} from "@category-labs/mera";
const
const rpId: string
rpId
=
var location: Location

The Window.location read-only property returns a Location object with information about the current location of the document.

MDN Reference

location
.
Location.hostname: string

The hostname property of the Location interface is a string containing either the domain name or IP address of the location URL.

MDN Reference

hostname
;
const
const phrase: "legal winner thank year wave sausage worth useful legal winner thank yellow"
phrase
=
"legal winner thank year wave sausage worth useful legal winner thank yellow";
const
const secret: Uint8Array<ArrayBuffer>
secret
= new
var TextEncoder: new () => TextEncoder

The TextEncoder interface takes a stream of code points as input and emits a stream of UTF-8 bytes.

MDN Reference

TextEncoder class is a global reference for import { TextEncoder } from 'node:util' https://nodejs.org/api/globals.html#textencoder

@sincev11.0.0

TextEncoder
().
TextEncoder.encode(input?: string): Uint8Array<ArrayBuffer>

The TextEncoder.encode() method takes a string as input, and returns a Global_Objects/Uint8Array containing the text given in parameters encoded with the specific method for that TextEncoder object.

MDN Reference

encode
(
const phrase: "legal winner thank year wave sausage worth useful legal winner thank yellow"
phrase
);
const
const vault: PasskeySecretVault
vault
= await
function createSecretVaultWithNewPasskey({ secret, ...passkeyOptions }: CreateSecretVaultWithNewPasskeyOptions): Promise<PasskeySecretVault>

Creates a passkey and encrypts one secret into a vault.

@paramoptions - Passkey creation inputs and secret bytes.

@returnsA JSON-safe secret vault containing the new credential metadata.

@remarks

Runs one creation ceremony and shows one user-verification prompt. On authenticators that do not evaluate PRF during creation, also runs an assertion, which shows a second.

A fresh random PRF salt is generated internally and stored in the returned vault.

A fresh random user handle (user.id) is generated for the new credential, so each call adds a passkey instead of replacing one.

If the fallback ceremony or vault encryption fails, the passkey from the completed creation ceremony still exists on the authenticator, but the thrown error does not carry its metadata.

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not enable PRF or return a usable 32-byte PRF output.

@throwsMeraError with code INPUT_INVALID when secret is empty.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues or crypto.subtle is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

createSecretVaultWithNewPasskey
({
rp: PasskeyRelyingParty

Relying party identity passed to WebAuthn. id is required so the fallback assertion can target the same relying party.

rp
: {
id: string

Relying party ID: the host the passkey is scoped to.

id
:
const rpId: string
rpId
,
name: string

Relying party name the authenticator may show.

name
: "Example" },
user: {
name: string;
displayName: string;
}

User identity passed to WebAuthn.

user
: {
name: string

User name displayed or stored by the authenticator.

name
: "account@example.com",
displayName: string

Human-readable display name for the authenticator UI.

displayName
: "Example account" },
secret: Uint8Array<ArrayBufferLike>

Secret bytes to encrypt. Any non-empty length.

secret
,
});
var localStorage: Storage
localStorage
.
Storage.setItem(key: string, value: string): void

The setItem() method of the Storage interface, when passed a key name and value, will add that key to the given Storage object, or update that key's value if it already exists.

MDN Reference

setItem
("app.vault",
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@paramvalue A JavaScript value, usually an object or array, to be converted.

@paramreplacer A function that transforms the results.

@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws{TypeError} If a circular reference or a BigInt value is found.

stringify
(
const vault: PasskeySecretVault
vault
));

A private key is encrypted the same way: pass its raw bytes as secret instead of encoded text.

The unlock runs one ceremony, pinned automatically to the credential stored in the vault:

import {
function decryptSecretVaultWithPasskey({ vault, ...passkeyOptions }: DecryptSecretVaultWithPasskeyOptions): Promise<Uint8Array<ArrayBuffer>>

Performs the passkey assertion for a vault and decrypts its secret.

@paramoptions - Relying party ID and vault.

@returnsThe decrypted secret bytes in a fresh allocation.

@remarksRuns one assertion ceremony and shows one user-verification prompt. The assertion is restricted to the credential stored in the vault.

@throwsMeraError with code VAULT_FORMAT_INVALID when the vault's required structure, version, or encoded data is invalid.

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not return a usable 32-byte PRF output.

@throwsMeraError with code DECRYPT_FAILED when AES-GCM authentication fails.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues or crypto.subtle is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

decryptSecretVaultWithPasskey
,
function parseSecretVault(value: unknown): PasskeySecretVault

Parses and validates untrusted secret-vault JSON or objects.

Only version 1 vaults are accepted. The credential ID, PRF salt, nonce, and ciphertext are validated as canonical base64url and length-checked. Unknown fields are dropped from the returned vault.

@paramvalue - Secret vault as JSON text or an untrusted object.

@returnsA validated secret vault.

@throwsMeraError with code VAULT_FORMAT_INVALID when required structure, version, or encoded data is invalid.

parseSecretVault
,
} from "@category-labs/mera";
async function
function unlockPhrase(): Promise<string>
unlockPhrase
():
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<string> {
const
const raw: string | null
raw
=
var localStorage: Storage
localStorage
.
Storage.getItem(key: string): string | null

The getItem() method of the Storage interface, when passed a key name, will return that key's value, or null if the key does not exist, in the given Storage object.

MDN Reference

getItem
("app.vault");
if (
const raw: string | null
raw
=== null) throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("No vault on this device yet.");
const
const vault: PasskeySecretVault
vault
=
function parseSecretVault(value: unknown): PasskeySecretVault

Parses and validates untrusted secret-vault JSON or objects.

Only version 1 vaults are accepted. The credential ID, PRF salt, nonce, and ciphertext are validated as canonical base64url and length-checked. Unknown fields are dropped from the returned vault.

@paramvalue - Secret vault as JSON text or an untrusted object.

@returnsA validated secret vault.

@throwsMeraError with code VAULT_FORMAT_INVALID when required structure, version, or encoded data is invalid.

parseSecretVault
(
const raw: string
raw
);
const
const secret: Uint8Array<ArrayBuffer>
secret
= await
function decryptSecretVaultWithPasskey({ vault, ...passkeyOptions }: DecryptSecretVaultWithPasskeyOptions): Promise<Uint8Array<ArrayBuffer>>

Performs the passkey assertion for a vault and decrypts its secret.

@paramoptions - Relying party ID and vault.

@returnsThe decrypted secret bytes in a fresh allocation.

@remarksRuns one assertion ceremony and shows one user-verification prompt. The assertion is restricted to the credential stored in the vault.

@throwsMeraError with code VAULT_FORMAT_INVALID when the vault's required structure, version, or encoded data is invalid.

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not return a usable 32-byte PRF output.

@throwsMeraError with code DECRYPT_FAILED when AES-GCM authentication fails.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues or crypto.subtle is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

decryptSecretVaultWithPasskey
({
rpId: string

Relying party ID for the WebAuthn assertion.

rpId
,
vault: PasskeySecretVault

Secret vault to decrypt.

vault
});
return new
var TextDecoder: new (label?: string, options?: TextDecoderOptions) => TextDecoder

The TextDecoder interface represents a decoder for a specific text encoding, such as UTF-8, ISO-8859-2, KOI8-R, GBK, etc.

MDN Reference

TextDecoder class is a global reference for import { TextDecoder } from 'node:util' https://nodejs.org/api/globals.html#textdecoder

@sincev11.0.0

TextDecoder
().
TextDecoder.decode(input?: AllowSharedBufferSource, options?: TextDecodeOptions): string

The TextDecoder.decode() method returns a string containing text decoded from the buffer passed as a parameter.

MDN Reference

decode
(
const secret: Uint8Array<ArrayBuffer>
secret
);
}

parseSecretVault is the boundary for the untrusted stored JSON.