> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> Every function and React hook exported from @chipi-stack/chipi-passkey. Grouped by which wallet type they apply to.

The package ships two parallel surfaces. Pick the one that matches your wallet type. Mixing them is fine — both can live on the same device under different localStorage keys.

| Wallet type | Surface | Use when |
| - | - | - |
| SHHH with a `WEBAUTHN_P256` signer | `createShhhPasskey`, `signShhhMessage`, `…ShhhCredential` | The passkey is the signer. No SDK session keys (they need a STARK or ED25519 signer) |
| PRF-encrypted (SHHH STARK from `useCreateWallet({ usePasskey: true })`, CHIPI v29) | `createWalletPasskey`, `getWalletEncryptKey`, `…Credential` | Wallets that use session keys (agents), and existing CHIPI v29 wallets |

The React hooks (`usePasskeySetup`, `usePasskeyAuth`, `usePasskeyStatus`) wrap the **PRF** surface (the default STARK-signer wallets) today. SHHH equivalents ship in a follow-on release; until then, call `createShhhPasskey` / `signShhhMessage` directly inside your own effects.

## SHHH passkey (modern path)

### createShhhPasskey

Registers a new P-256 platform passkey. No PRF extension, no PIN derivation. The private key stays inside the platform authenticator.

```ts theme={null}
import { createShhhPasskey } from "@chipi-stack/chipi-passkey";

const result = await createShhhPasskey(
  userId: string,
  userName: string,
  options?: { rpName?: string; rpId?: string; displayName?: string },
);
// result: {
//   credentialId: string,
//   publicKey: { x: Uint8Array(32), y: Uint8Array(32) },
//   publicKeyHex: { x: string, y: string },
//   rpId: string,
// }
```

Defaults: `rpId = window.location.hostname`, `rpName = "Chipi Wallet"`. Persists `{ credentialId, publicKey, userId, rpId, transports }` to localStorage under the key `chipi_wallet_shhh_passkey_credential`. Throws on `NotAllowedError` (user cancelled) or `InvalidStateError` (passkey already exists for this account on this device).

### signShhhMessage

Runs `navigator.credentials.get()` with `messageHash` encoded as the 32-byte big-endian WebAuthn challenge. Returns the raw assertion bytes ready to feed into `buildWebAuthnEnvelopeFromAssertion` from `@chipi-stack/backend`.

```ts theme={null}
import { signShhhMessage } from "@chipi-stack/chipi-passkey";

const result = await signShhhMessage({
  messageHash: bigint,           // SHHH SNIP-12 hash
  credentialId?: string,         // defaults to the stored credential
  rpId?: string,                 // defaults to the registered rpId
});
// result: {
//   authenticatorData: Uint8Array,
//   clientDataJSON: Uint8Array,
//   signatureDer: Uint8Array,
//   credentialId: string,
// }
```

### hasShhhPasskey

Synchronous check for a stored SHHH credential. Use it to branch between "Sign up" and "Sign in" UI without prompting.

```ts theme={null}
import { hasShhhPasskey } from "@chipi-stack/chipi-passkey";

const ready = hasShhhPasskey(); // boolean
```

### getStoredShhhCredential

Reads the persisted credential metadata.

```ts theme={null}
import { getStoredShhhCredential } from "@chipi-stack/chipi-passkey";

const stored = getStoredShhhCredential();
// stored | null:
// {
//   credentialId: string,
//   publicKey: { x: string, y: string },   // hex
//   createdAt: string,                       // ISO timestamp
//   userId: string,
//   rpId: string,
//   transports?: AuthenticatorTransport[],
// }
```

### removeStoredShhhCredential

Clears the stored credential id from localStorage. Use at logout, account reset, or when the user opts to re-register. The platform authenticator's stored credential is NOT deleted — only your app's reference to it.

```ts theme={null}
import { removeStoredShhhCredential } from "@chipi-stack/chipi-passkey";

removeStoredShhhCredential();
```

### Extraction utilities

Exported for tests and for advanced callers who want to verify a passkey's pubkey against a separately-stored copy.

```ts theme={null}
import {
  bigintToBe32,
  extractP256PubkeyFromRegistration,
  extractP256PubkeyFromSpki,
  extractP256PubkeyFromAttestationObject,
} from "@chipi-stack/chipi-passkey";
```

`extractP256PubkeyFromRegistration` is the dispatch helper — it tries the SPKI fast path first (Chrome, Safari, modern Edge), then falls back to decoding the CBOR `attestationObject` for older browsers. You only need these utilities if you're writing your own registration-response handler.

## PRF passkey \[#legacy-prf-passkey]

The PRF passkey path encrypts a STARK private key with a key derived from the WebAuthn PRF extension. It backs `useCreateWallet({ usePasskey: true })`, which creates an SHHH wallet with a STARK signer by default (the kind that supports session keys), and **CHIPI v29 wallets**. A PIN backup (dual-key, passkey primary + PIN backup) is optional. SHHH wallets with a `WEBAUTHN_P256` signer use `createShhhPasskey` above instead.

To unlock on a device where this browser did not create the passkey, pass the `credentialId` and `prfSupported` stored on the wallet row: `getWalletEncryptKey(wallet.credentialId ?? undefined, { prfSupported: wallet.prfSupported ?? undefined })`. With no argument it reads `localStorage` and throws `"No wallet passkey found"` on a new device.

### createWalletPasskey

Registers a passkey with the PRF extension enabled, derives an encryption key from the PRF output, and stores the credential metadata.

```ts theme={null}
import { createWalletPasskey } from "@chipi-stack/chipi-passkey";

const result = await createWalletPasskey(
  userId: string,
  userName: string,
  options?: { requirePrf?: boolean }, // defaults to true
);
// result: {
//   encryptKey: string,        // hex — use to encrypt the wallet's STARK key
//   credentialId: string,
//   prfSupported: boolean,
// }
```

Persists `{ credentialId, createdAt, userId, transports, prfSupported }` to localStorage under `chipi_wallet_passkey_credential`. **Separate** storage key from the SHHH path — both surfaces coexist on the same device.

**`requirePrf` (default `true`) makes creation refuse a non-self-custodial wallet.** On an authenticator that cannot produce a WebAuthn PRF secret, the only fallback is a key derived from `PBKDF2(credentialId, userId)`; both are values your backend stores, so the derived wrap key is server-recomputable and the wallet is effectively custodial. With `requirePrf` at its default, `createWalletPasskey` throws `PrfUnsupportedError` (`.code === "PRF_UNSUPPORTED"`) instead of creating that wallet. Catch it and tell the user their device can't secure a wallet. Pass `{ requirePrf: false }` only to knowingly opt into the custodial fallback (never for self-custodial consumer wallets). Also throws on `NotAllowedError` (cancelled) and `InvalidStateError` (passkey already exists). Unlocking existing non-PRF wallets via `getWalletEncryptKey` is unaffected.

### getWalletEncryptKey

Authenticates with an existing PRF passkey and returns the same `encryptKey` derived at registration time. Use it before every wallet operation that needs the STARK key (transfer, session-key registration, encryption rotation).

```ts theme={null}
import { getWalletEncryptKey } from "@chipi-stack/chipi-passkey";

const encryptKey = await getWalletEncryptKey(
  credentialId?: string,
  options?: { prfSupported?: boolean },
);
// encryptKey: string | null   // null if user cancels the biometric prompt
```

The PRF output is deterministic — same passkey, same PRF salt, same encryption key. If PRF is unavailable (Firefox before 122, or a browser switch after registering on Chrome), the function falls back to PBKDF2 derivation **only if** the credential was originally registered without PRF. Otherwise it throws — see [When passkeys fail](/sdk/passkeys/when-passkeys-fail).

### Status checks

```ts theme={null}
import {
  isWebAuthnSupported,
  isWebAuthnPRFSupported,
  hasWalletPasskey,
  verifyWalletPasskey,
  verifyWalletPasskeyDetailed,
} from "@chipi-stack/chipi-passkey";

isWebAuthnSupported();      // boolean
isWebAuthnPRFSupported();   // alias for isWebAuthnSupported (kept for backcompat)
hasWalletPasskey();         // boolean — is a credential stored locally?

await verifyWalletPasskey();        // boolean
await verifyWalletPasskeyDetailed(); // { valid, reason?, message? }
```

### Credential management

```ts theme={null}
import {
  getStoredCredential,
  removeStoredCredential,
} from "@chipi-stack/chipi-passkey";

const stored = getStoredCredential();
// stored | null:
// {
//   credentialId: string,
//   createdAt: string,
//   userId: string,
//   transports?: AuthenticatorTransport[],
//   prfSupported?: boolean,
// }

removeStoredCredential();
```

## React hooks (PRF passkey path) \[#react-hooks-legacy-passkey-path]

The hooks today wrap the **PRF** surface — `usePasskeySetup` calls `createWalletPasskey`, `usePasskeyAuth` calls `getWalletEncryptKey`. Import from the `/hooks` subpath so Next.js bundles get the right `"use client"` directives.

```ts theme={null}
import {
  usePasskeySetup,
  usePasskeyAuth,
  usePasskeyStatus,
} from "@chipi-stack/chipi-passkey/hooks";
```

### usePasskeySetup

```tsx theme={null}
function CreateWalletButton({ userId, userName }: { userId: string; userName: string }) {
  const { setupPasskey, isLoading, error, data } = usePasskeySetup();

  async function onClick() {
    const result = await setupPasskey(userId, userName);
    // result: { encryptKey, credentialId, prfSupported }
  }

  return (
    <button onClick={onClick} disabled={isLoading}>
      {isLoading ? "Creating…" : "Create wallet"}
    </button>
  );
}
```

### usePasskeyAuth

```tsx theme={null}
function SignButton() {
  const { authenticate, isLoading, error, data } = usePasskeyAuth();

  async function onClick() {
    const encryptKey = await authenticate(); // null if user cancelled
    if (encryptKey) {
      // use encryptKey for the next wallet operation
    }
  }
}
```

### usePasskeyStatus

```tsx theme={null}
function PasskeyStatusBanner() {
  const { status, hasPasskey, refresh } = usePasskeyStatus();
  // status: "loading" | "ready" | "not_supported" | "no_passkey" | "error"

  if (status === "no_passkey") return <SetupPrompt />;
  if (status === "not_supported") return <PinFallback />;
  return null;
}
```

## TypeScript types

```ts theme={null}
import type {
  ShhhPasskeyCredential,
  CreateShhhPasskeyResult,
  ShhhAssertion,
  CreateWalletPasskeyResult,
  WalletCredentialMetadata,
  PasskeyVerifyResult,
  PrfUnsupportedError,
  UsePasskeySetupResult,
  UsePasskeyAuthResult,
  UsePasskeyStatusResult,
  PasskeyStatus,
} from "@chipi-stack/chipi-passkey";
```

## Related

* [Quickstart](/sdk/passkeys/quickstart) — end-to-end SHHH passkey flow
* [When passkeys fail](/sdk/passkeys/when-passkeys-fail) — recovery, PIN fallback, browser switching


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.