Skip to content

Create passkey accounts

Prerequisites:

The recipe's two flows. On the first visit, createPasskeyWithPrfOutput creates the passkey and the app stores the credential metadata, credentialId and transports, which holds no key material. On a returning visit, getPasskeyPrfOutput signs in using the stored metadata. The PRF output becomes the seed through BIP-39, held in memory for the session, and HD derivation by index produces the EVM and Solana signing sessions, ended by end(). first visit Create the passkey createPasskeyWithPrfOutput Credential metadata returning visit Sign in getPasskeyPrfOutput PRF output BIP-39 Seed derivation by index Signing sessions end()
import {
function createPasskeyWithPrfOutput({ rp, user, timeout, prfSalt, webAuthnClient, }: createPasskeyWithPrfOutput.Options): Promise<createPasskeyWithPrfOutput.Result>

Creates a discoverable, user-verified passkey that requires WebAuthn PRF support and returns the first PRF output.

@param ― options - Passkey creation inputs.

@returns ― Credential metadata and the first PRF output.

@remarks ―

Runs one creation ceremony and shows one user-verification prompt. On authenticators that do not evaluate PRF during creation, a fallback assertion evaluates the same salt and shows a second prompt.

WebAuthn challenges and the credential's user handle (user.id) are generated internally, 32 random bytes each. An authenticator overwrites a discoverable credential that has the same rp.id and user.id, so a fresh handle per call adds a passkey instead of replacing one.

The credential is requested with fixed parameters: ES256 or RS256 key types, attestation "none", a required resident key, and required user verification (getPasskeyPrfOutput explains the requirement).

Any failure after the creation ceremony completes leaves the passkey on the authenticator, but the thrown error does not carry its metadata.

@throws ― MeraError with code PRF_UNAVAILABLE when the authenticator reports no PRF support and returns no create-time output, returns an output that is not 32 bytes, or returns none on the fallback ceremony.

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses a prompt, it times out, or no passkey answers.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

createPasskeyWithPrfOutput
} 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 created: createPasskeyWithPrfOutput.Result
created
= await
function createPasskeyWithPrfOutput({ rp, user, timeout, prfSalt, webAuthnClient, }: createPasskeyWithPrfOutput.Options): Promise<createPasskeyWithPrfOutput.Result>

Creates a discoverable, user-verified passkey that requires WebAuthn PRF support and returns the first PRF output.

@param ― options - Passkey creation inputs.

@returns ― Credential metadata and the first PRF output.

@remarks ―

Runs one creation ceremony and shows one user-verification prompt. On authenticators that do not evaluate PRF during creation, a fallback assertion evaluates the same salt and shows a second prompt.

WebAuthn challenges and the credential's user handle (user.id) are generated internally, 32 random bytes each. An authenticator overwrites a discoverable credential that has the same rp.id and user.id, so a fresh handle per call adds a passkey instead of replacing one.

The credential is requested with fixed parameters: ES256 or RS256 key types, attestation "none", a required resident key, and required user verification (getPasskeyPrfOutput explains the requirement).

Any failure after the creation ceremony completes leaves the passkey on the authenticator, but the thrown error does not carry its metadata.

@throws ― MeraError with code PRF_UNAVAILABLE when the authenticator reports no PRF support and returns no create-time output, returns an output that is not 32 bytes, or returns none on the fallback ceremony.

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses a prompt, it times out, or no passkey answers.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

createPasskeyWithPrfOutput
({
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" },
});

Every call creates a new passkey.

The app can store the credential metadata and pass it back on the next sign-in, so the browser reuses the same passkey instead of offering a choice.

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.derivedCredential",
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.

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

@param ― replacer A function that transforms the results.

@param ― space 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
({
credentialId: string
credentialId
:
const created: createPasskeyWithPrfOutput.Result
created
.
credentialId: string

Credential ID encoded as canonical unpadded base64url.

credentialId
,
transports: readonly PasskeyCredentialTransport[] | undefined
transports
:
const created: createPasskeyWithPrfOutput.Result
created
.
transports?: readonly PasskeyCredentialTransport[] | undefined

Authenticator transports reported by the platform, when available.

transports
,
}),
);

transports is optional and only a hint: the browser uses it to reach the authenticator directly (a platform prompt for a platform passkey, a QR flow for a phone) instead of offering every option.

import {
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, immediate, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@param ― options - Passkey PRF request inputs.

@returns ― The selected credential ID and first WebAuthn PRF output.

@remarks ―

Runs one assertion ceremony and shows one user-verification prompt.

With immediate, Chrome 148 and later (as of 2026-10) and the React Native client show nothing when the device holds no passkey for rpId. Browsers without immediate mode ignore the option and show their usual prompt. Without a user gesture, or in Incognito, Chrome rejects an immediate request with the same error even when a passkey exists.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@see ― https://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@see ― https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

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

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, credential.credentialId is empty or not canonical base64url, or immediate is combined with credential.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses the prompt, it times out, or no passkey answers, including an immediate request that finds none.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

getPasskeyPrfOutput
} from "@category-labs/mera";
const
const stored: string | null
stored
=
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.derivedCredential");
const
const known: any
known
=
const stored: string | null
stored
?
var JSON: JSON

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

JSON
.
JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): any

Converts a JavaScript Object Notation (JSON) string into an object.

@param ― text A valid JSON string.

@param ― reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is.

@throws ― {SyntaxError} If text is not valid JSON.

parse
(
const stored: string
stored
) :
var undefined
undefined
;
const {
const prfOutput: Uint8Array<ArrayBuffer>

First PRF output from WebAuthn. Always 32 bytes.

prfOutput
,
const credentialId: string

Credential ID selected by the platform, as canonical unpadded base64url.

credentialId
} = await
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, immediate, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@param ― options - Passkey PRF request inputs.

@returns ― The selected credential ID and first WebAuthn PRF output.

@remarks ―

Runs one assertion ceremony and shows one user-verification prompt.

With immediate, Chrome 148 and later (as of 2026-10) and the React Native client show nothing when the device holds no passkey for rpId. Browsers without immediate mode ignore the option and show their usual prompt. Without a user gesture, or in Incognito, Chrome rejects an immediate request with the same error even when a passkey exists.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@see ― https://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@see ― https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

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

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, credential.credentialId is empty or not canonical base64url, or immediate is combined with credential.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses the prompt, it times out, or no passkey answers, including an immediate request that finds none.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

getPasskeyPrfOutput
({
rpId: string

Relying party ID for the WebAuthn assertion.

rpId
,
credential?: PasskeyCredentialMetadata | undefined

Credential metadata to restrict the assertion to one passkey. When omitted, WebAuthn may choose any discoverable credential for the relying party.

credential
:
const known: any
known
,
});
// With no stored record, the browser may have used any discoverable passkey.
const
const record: any
record
=
const known: any
known
?.
any
credentialId
===
const credentialId: string

Credential ID selected by the platform, as canonical unpadded base64url.

credentialId
?
const known: any
known
: {
credentialId: string
credentialId
};
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.derivedCredential",
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.

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

@param ― replacer A function that transforms the results.

@param ― space 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 record: any
record
));

On a visit with no stored record, a single “Continue with passkey” button can sign in when the device holds a passkey and offer the other paths when it does not. immediate asks only for passkeys already on this device and rejects at once, with no prompt, when there is none:

import {
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, immediate, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@param ― options - Passkey PRF request inputs.

@returns ― The selected credential ID and first WebAuthn PRF output.

@remarks ―

Runs one assertion ceremony and shows one user-verification prompt.

With immediate, Chrome 148 and later (as of 2026-10) and the React Native client show nothing when the device holds no passkey for rpId. Browsers without immediate mode ignore the option and show their usual prompt. Without a user gesture, or in Incognito, Chrome rejects an immediate request with the same error even when a passkey exists.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@see ― https://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@see ― https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

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

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, credential.credentialId is empty or not canonical base64url, or immediate is combined with credential.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses the prompt, it times out, or no passkey answers, including an immediate request that finds none.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

getPasskeyPrfOutput
,
class MeraError

Error thrown by this package.

MeraError
} from "@category-labs/mera";
async function
function signInOnThisDevice(): Promise<Uint8Array | undefined>
signInOnThisDevice
():
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
| undefined> {
try {
const {
const prfOutput: Uint8Array<ArrayBuffer>

First PRF output from WebAuthn. Always 32 bytes.

prfOutput
,
const credentialId: string

Credential ID selected by the platform, as canonical unpadded base64url.

credentialId
} = await
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, immediate, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@param ― options - Passkey PRF request inputs.

@returns ― The selected credential ID and first WebAuthn PRF output.

@remarks ―

Runs one assertion ceremony and shows one user-verification prompt.

With immediate, Chrome 148 and later (as of 2026-10) and the React Native client show nothing when the device holds no passkey for rpId. Browsers without immediate mode ignore the option and show their usual prompt. Without a user gesture, or in Incognito, Chrome rejects an immediate request with the same error even when a passkey exists.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@see ― https://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@see ― https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

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

@throws ― MeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, credential.credentialId is empty or not canonical base64url, or immediate is combined with credential.

@throws ― MeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throws ― MeraError with code PASSKEY_CANCELLED when the person dismisses the prompt, it times out, or no passkey answers, including an immediate request that finds none.

@throws ― MeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, fails for another reason, or returns an unexpected credential.

getPasskeyPrfOutput
({
rpId: string

Relying party ID for the WebAuthn assertion.

rpId
,
immediate?: boolean | undefined

Asks only for passkeys already on this device, and rejects at once, without a prompt, when there is none. A passkey on a phone or a security key does not count. Cannot be combined with credential.

immediate
: true,
});
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.derivedCredential",
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.

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

@param ― replacer A function that transforms the results.

@param ― space 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
({
credentialId: string
credentialId
}),
);
return
const prfOutput: Uint8Array<ArrayBuffer>

First PRF output from WebAuthn. Always 32 bytes.

prfOutput
;
} catch (
function (local var) error: unknown
error
) {
if (
!(
function (local var) error: unknown
error
instanceof
class MeraError

Error thrown by this package.

MeraError
) ||
function (local var) error: MeraError
error
.
MeraError.code: MeraErrorCode

Stable machine-readable category for the failure.

code
!== "PASSKEY_CANCELLED"
) {
throw
function (local var) error: unknown
error
;
}
return
var undefined
undefined
;
}
}
const
const prfOutput: Uint8Array<ArrayBufferLike> | undefined
prfOutput
= await
function signInOnThisDevice(): Promise<Uint8Array | undefined>
signInOnThisDevice
();

The trade-off: immediate skips passkeys on other devices, so a person whose passkey is on their phone gets undefined too. When prfOutput is undefined, the app offers “Create a passkey” and “Use a passkey from another device”, a sign-in without immediate. Browsers without immediate mode ignore the option and show their usual prompt.

deriveEvmPrivateKey and deriveSolanaPrivateKey number accounts by accountIndex. These helpers open one account of each kind:

import {
function createEd25519SigningSession({ privateKey, }: CreateSigningSessionOptions): Ed25519SigningSession

Creates a signing session from an Ed25519 private key.

@param ― options - Signing session inputs.

@returns ― A live Ed25519 signing session.

@throws ― MeraError with code INPUT_INVALID when privateKey is not 32 bytes.

createEd25519SigningSession
,
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@param ― options - Signing session inputs.

@returns ― A live secp256k1 signing session.

@throws ― MeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
,
function deriveEvmPrivateKey({ prfOutput, accountIndex, }: DerivePrivateKeyOptions): Uint8Array<ArrayBuffer>

Derives the private key of an EVM account from a PRF output.

@param ― options - PRF output and account index.

@returns ― A 32-byte secp256k1 private key.

@remarks ―

The PRF output is the entropy of a 24-word BIP-39 phrase from the English wordlist, and the phrase's seed uses an empty passphrase. The key is the BIP-32 key at m/44'/60'/0'/0/{accountIndex} under that seed. Wallet apps such as MetaMask use the same path, so importing the phrase into one shows the same accounts.

The phrase exists as a JavaScript string, which cannot be zeroed, and @scure/bip32 leaves the intermediate keys and chain codes unzeroed.

@see ― https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki BIP-39

@see ― https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki BIP-32

@see ― https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP-44

@throws ― MeraError with code INPUT_INVALID when prfOutput is not 32 bytes, or accountIndex is not an integer from 0 to 2^31 - 1.

deriveEvmPrivateKey
,
function deriveSolanaPrivateKey({ prfOutput, accountIndex, }: DerivePrivateKeyOptions): Uint8Array<ArrayBuffer>

Derives the private key of a Solana account from a PRF output.

@param ― options - PRF output and account index.

@returns ― A 32-byte Ed25519 private key (the seed).

@remarks ―

The PRF output is the entropy of a 24-word BIP-39 phrase from the English wordlist, and the phrase's seed uses an empty passphrase. The key is the SLIP-0010 Ed25519 key at m/44'/501'/{accountIndex}'/0' under that seed. Wallet apps such as Phantom use the same path, so importing the phrase into one shows the same accounts.

The phrase exists as a JavaScript string, which cannot be zeroed.

@see ― https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki BIP-39

@see ― https://github.com/satoshilabs/slips/blob/master/slip-0010.md SLIP-0010

@throws ― MeraError with code INPUT_INVALID when prfOutput is not 32 bytes, or accountIndex is not an integer from 0 to 2^31 - 1.

deriveSolanaPrivateKey
,
function getEvmAddress(publicKey: Uint8Array): EvmAddress

Derives the EIP-55 checksummed EVM address for a secp256k1 public key.

@param ― publicKey - A compressed or uncompressed secp256k1 public key.

@returns ― The EIP-55 mixed-case checksummed EVM address.

@throws ― MeraError with code INPUT_INVALID when publicKey is not valid secp256k1.

getEvmAddress
,
function getSolanaAddress(publicKey: Uint8Array): string

Derives the base58-encoded Solana address for an Ed25519 public key.

@param ― publicKey - A 32-byte Ed25519 public key.

@returns ― The base58-encoded Solana address.

@throws ― MeraError with code INPUT_INVALID when publicKey is not 32 bytes.

getSolanaAddress
,
} from "@category-labs/mera";
function
function openEvmAccount(prfOutput: Uint8Array, accountIndex: number): {
session: Secp256k1SigningSession;
address: `0x${string}`;
}
openEvmAccount
(
prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
:
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
,
accountIndex: number
accountIndex
: number) {
const
const privateKey: Uint8Array<ArrayBuffer>
privateKey
=
function deriveEvmPrivateKey({ prfOutput, accountIndex, }: DerivePrivateKeyOptions): Uint8Array<ArrayBuffer>

Derives the private key of an EVM account from a PRF output.

@param ― options - PRF output and account index.

@returns ― A 32-byte secp256k1 private key.

@remarks ―

The PRF output is the entropy of a 24-word BIP-39 phrase from the English wordlist, and the phrase's seed uses an empty passphrase. The key is the BIP-32 key at m/44'/60'/0'/0/{accountIndex} under that seed. Wallet apps such as MetaMask use the same path, so importing the phrase into one shows the same accounts.

The phrase exists as a JavaScript string, which cannot be zeroed, and @scure/bip32 leaves the intermediate keys and chain codes unzeroed.

@see ― https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki BIP-39

@see ― https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki BIP-32

@see ― https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP-44

@throws ― MeraError with code INPUT_INVALID when prfOutput is not 32 bytes, or accountIndex is not an integer from 0 to 2^31 - 1.

deriveEvmPrivateKey
({
prfOutput: Uint8Array<ArrayBufferLike>

PRF output used as the BIP-39 entropy. Must be exactly 32 bytes.

prfOutput
,
accountIndex: number

Account number in the derivation path, an integer from 0 to 2^31 - 1.

accountIndex
});
const
const session: Secp256k1SigningSession
session
=
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@param ― options - Signing session inputs.

@returns ― A live secp256k1 signing session.

@throws ― MeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
({
privateKey: Uint8Array<ArrayBufferLike>

Curve private key. Must be exactly 32 bytes and, for secp256k1, a valid scalar.

privateKey
});
const privateKey: Uint8Array<ArrayBuffer>
privateKey
.
Uint8Array<ArrayBuffer>.fill(value: number, start?: number, end?: number): Uint8Array<ArrayBuffer>

Changes all array elements from start to end index to a static value and returns the modified array

@param ― value value to fill array section with

@param ― start index to start filling the array at. If start is negative, it is treated as length+start where length is the length of the array.

@param ― end index to stop filling the array at. If end is negative, it is treated as length+end.

fill
(0);
return {
session: Secp256k1SigningSession
session
,
address: `0x${string}`
address
:
function getEvmAddress(publicKey: Uint8Array): EvmAddress

Derives the EIP-55 checksummed EVM address for a secp256k1 public key.

@param ― publicKey - A compressed or uncompressed secp256k1 public key.

@returns ― The EIP-55 mixed-case checksummed EVM address.

@throws ― MeraError with code INPUT_INVALID when publicKey is not valid secp256k1.

getEvmAddress
(
const session: Secp256k1SigningSession
session
.
publicKey: Uint8Array<ArrayBuffer>

65-byte uncompressed secp256k1 public key for the session.

publicKey
) };
}
function
function openSolanaAccount(prfOutput: Uint8Array, accountIndex: number): {
session: Ed25519SigningSession;
address: string;
}
openSolanaAccount
(
prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
:
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
,
accountIndex: number
accountIndex
: number) {
const
const privateKey: Uint8Array<ArrayBuffer>
privateKey
=
function deriveSolanaPrivateKey({ prfOutput, accountIndex, }: DerivePrivateKeyOptions): Uint8Array<ArrayBuffer>

Derives the private key of a Solana account from a PRF output.

@param ― options - PRF output and account index.

@returns ― A 32-byte Ed25519 private key (the seed).

@remarks ―

The PRF output is the entropy of a 24-word BIP-39 phrase from the English wordlist, and the phrase's seed uses an empty passphrase. The key is the SLIP-0010 Ed25519 key at m/44'/501'/{accountIndex}'/0' under that seed. Wallet apps such as Phantom use the same path, so importing the phrase into one shows the same accounts.

The phrase exists as a JavaScript string, which cannot be zeroed.

@see ― https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki BIP-39

@see ― https://github.com/satoshilabs/slips/blob/master/slip-0010.md SLIP-0010

@throws ― MeraError with code INPUT_INVALID when prfOutput is not 32 bytes, or accountIndex is not an integer from 0 to 2^31 - 1.

deriveSolanaPrivateKey
({
prfOutput: Uint8Array<ArrayBufferLike>

PRF output used as the BIP-39 entropy. Must be exactly 32 bytes.

prfOutput
,
accountIndex: number

Account number in the derivation path, an integer from 0 to 2^31 - 1.

accountIndex
});
const
const session: Ed25519SigningSession
session
=
function createEd25519SigningSession({ privateKey, }: CreateSigningSessionOptions): Ed25519SigningSession

Creates a signing session from an Ed25519 private key.

@param ― options - Signing session inputs.

@returns ― A live Ed25519 signing session.

@throws ― MeraError with code INPUT_INVALID when privateKey is not 32 bytes.

createEd25519SigningSession
({
privateKey: Uint8Array<ArrayBufferLike>

Curve private key. Must be exactly 32 bytes and, for secp256k1, a valid scalar.

privateKey
});
const privateKey: Uint8Array<ArrayBuffer>
privateKey
.
Uint8Array<ArrayBuffer>.fill(value: number, start?: number, end?: number): Uint8Array<ArrayBuffer>

Changes all array elements from start to end index to a static value and returns the modified array

@param ― value value to fill array section with

@param ― start index to start filling the array at. If start is negative, it is treated as length+start where length is the length of the array.

@param ― end index to stop filling the array at. If end is negative, it is treated as length+end.

fill
(0);
return {
session: Ed25519SigningSession
session
,
address: string
address
:
function getSolanaAddress(publicKey: Uint8Array): string

Derives the base58-encoded Solana address for an Ed25519 public key.

@param ― publicKey - A 32-byte Ed25519 public key.

@returns ― The base58-encoded Solana address.

@throws ― MeraError with code INPUT_INVALID when publicKey is not 32 bytes.

getSolanaAddress
(
const session: Ed25519SigningSession
session
.
publicKey: Uint8Array<ArrayBuffer>

32-byte Ed25519 public key for the session.

publicKey
) };
}

Each session copies the key, so the helpers zero the derived key right away.

Build the account list from these helpers. The PRF output reproduces every account, so it is zeroed once they are open:

const
const accounts: ({
session: Secp256k1SigningSession;
address: EvmAddress;
} | {
session: Ed25519SigningSession;
address: string;
})[]
accounts
= [
function openEvmAccount(prfOutput: Uint8Array, accountIndex: number): {
session: Secp256k1SigningSession;
address: EvmAddress;
}
openEvmAccount
(
const prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
, 0),
function openSolanaAccount(prfOutput: Uint8Array, accountIndex: number): {
session: Ed25519SigningSession;
address: string;
}
openSolanaAccount
(
const prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
, 0),
];
const prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
.
Uint8Array<ArrayBufferLike>.fill(value: number, start?: number, end?: number): Uint8Array<ArrayBufferLike>

Changes all array elements from start to end index to a static value and returns the modified array

@param ― value value to fill array section with

@param ― start index to start filling the array at. If start is negative, it is treated as length+start where length is the length of the array.

@param ― end index to stop filling the array at. If end is negative, it is treated as length+end.

fill
(0);
for (const
const account: {
session: Ed25519SigningSession | Secp256k1SigningSession;
}
account
of
const accounts: {
session: Ed25519SigningSession | Secp256k1SigningSession;
}[]
accounts
) {
const account: {
session: Ed25519SigningSession | Secp256k1SigningSession;
}
account
.
session: Ed25519SigningSession | Secp256k1SigningSession
session
.
function end(): void

Zeroes the session-owned private-key copy; later signing throws SESSION_ENDED.

end
();
}

Ending a session zeroes the private keys it owns.