npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mentaproject/react-native-passkey-signer

v0.0.4

Published

WebAuthn/Passkey signer for React Native compatible with viem account abstraction

Readme

@mentaproject/react-native-passkey-signer

A WebAuthn/Passkey signer for React Native, compatible with viem account abstraction. This package enables the use of native passkeys (Face ID, Touch ID, fingerprint) as cryptographic authentication methods for blockchain applications.

Table of Contents

Features

  • Native biometric authentication: Uses Face ID, Touch ID (iOS) or fingerprint (Android)
  • viem compatible: Integrates directly with viem account abstraction for smart contract wallets
  • P-256 cryptography: Uses secp256r1 elliptic curve (ECDSA with SHA-256)
  • Cross-platform: Supports iOS 15+ and Android API 28+
  • WebAuthn standard: Implements FIDO2/WebAuthn standard

Prerequisites

| Dependency | Minimum Version | |------------|-----------------| | react-native-passkey | >= 3.0.0 | | viem | >= 2.0.0 | | iOS | 15.0+ | | Android | API 28+ |

Installation

npm install @mentaproject/react-native-passkey-signer
# or
yarn add @mentaproject/react-native-passkey-signer

Also install the peer dependencies:

npm install react-native-passkey viem

For iOS, install the pods:

cd ios && pod install

Platform Configuration

iOS

  1. Enable Associated Domains in Xcode:

    • Open your project in Xcode
    • Select your target > Signing & Capabilities
    • Add "Associated Domains"
    • Add webcredentials:your-domain.com
  2. Configure the apple-app-site-association file on your server:

{
  "webcredentials": {
    "apps": ["TEAM_ID.com.your.bundle.id"]
  }
}

This file must be accessible at https://your-domain.com/.well-known/apple-app-site-association.

Android

  1. Configure Digital Asset Links by creating the assetlinks.json file:
[{
  "relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.your.package",
    "sha256_cert_fingerprints": ["SHA256:XX:XX:XX:..."]
  }
}]

This file must be accessible at https://your-domain.com/.well-known/assetlinks.json.

  1. To get your SHA256 fingerprint:
keytool -list -v -keystore your-keystore.jks -alias your-alias

API Reference

PasskeySigner

The main class for creating and managing passkeys.

PasskeySigner.register(params)

Creates a new passkey and returns a PasskeySigner instance.

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | params.username | string | User identifier displayed during authentication | | params.config | IPasskeyConfig | Passkey configuration |

Returns: Promise<PasskeySigner>

How it works:

  1. Generates random userId and challenge
  2. Calls native Passkey.create() API with WebAuthn options
  3. Receives CBOR-encoded attestation
  4. Decodes CBOR and extracts P-256 public key
  5. Returns new instance with credentials
const signer = await PasskeySigner.register({
  username: "[email protected]",
  config: {
    rpId: "example.com",
    rpName: "My Application",
    userVerification: "required",
    timeout: 60000
  }
});

signer.getCredential()

Returns the stored credential information.

Returns: IPasskeyCredential

const credential = signer.getCredential();
// {
//   id: "abc123...",
//   publicKey: { x: 123456n, y: 789012n },
//   rpId: "example.com"
// }

signer.getPublicKeyHex()

Returns the public key in uncompressed hexadecimal format.

Returns: Hex (format 0x04 + X coordinate (32 bytes) + Y coordinate (32 bytes))

How it works:

  • Converts BigInt coordinates to hexadecimal strings
  • Pads coordinates to 64 characters (32 bytes each)
  • Prefixes with 0x04 (uncompressed key marker)
const publicKey = signer.getPublicKeyHex();
// "0x04a1b2c3d4...6789abcd" (65 bytes = 130 hex chars + 0x prefix)

signer.sign(challenge)

Signs a challenge using the passkey. Triggers biometric authentication.

Parameters:

| Parameter | Type | Description | |-----------|------|-------------| | challenge | Hex | Challenge to sign (hexadecimal format with 0x prefix) |

Returns:

{
  authenticatorData: Uint8Array;  // Authenticator data (includes rpIdHash, flags, counter)
  clientDataJSON: string;         // JSON containing challenge, origin, type
  signature: Uint8Array;          // ECDSA signature (DER format)
}

How it works:

  1. Converts hex challenge to bytes
  2. Encodes to base64url for native API
  3. Calls Passkey.get() which triggers biometric prompt
  4. Receives and decodes authenticator response
  5. Returns raw data for further processing
const challenge = "0x1234567890abcdef...";
const { authenticatorData, clientDataJSON, signature } = await signer.sign(challenge);

signer.toWebAuthnAccount()

Creates a WebAuthn account compatible with viem account abstraction.

Returns: WebAuthnAccount

How it works:

  1. Creates a custom getFn function that intercepts WebAuthn requests
  2. This function converts challenges and calls the native authenticator
  3. Formats response according to WebAuthn standard (ArrayBuffer)
  4. Uses viem's toWebAuthnAccount to create the account
const webAuthnAccount = signer.toWebAuthnAccount();

// Usable with permissionless.js, ZeroDev, etc.
import { createSmartAccountClient } from "permissionless";

const smartAccountClient = createSmartAccountClient({
  account: webAuthnAccount,
  // ... other options
});

Types

IPasskeyConfig

Configuration for WebAuthn operations.

interface IPasskeyConfig {
  /** Relying Party ID (usually the domain) */
  rpId: string;
  
  /** Relying Party name displayed to user */
  rpName: string;
  
  /** Timeout in milliseconds (default: 60000) */
  timeout?: number;
  
  /** 
   * User verification requirement
   * - "required": Always require biometrics
   * - "preferred": Prefer biometrics if available
   * - "discouraged": Don't ask if possible
   */
  userVerification?: "required" | "preferred" | "discouraged";
}

IPasskeySignerParams

Parameters for creating a PasskeySigner instance.

interface IPasskeySignerParams {
  /** Username for the Passkey account */
  username: string;
  
  /** Passkey configuration */
  config: IPasskeyConfig;
}

IPasskeyCredential

Represents a stored passkey credential.

interface IPasskeyCredential {
  /** Unique credential ID (base64url) */
  id: string;
  
  /** P-256 public key */
  publicKey: {
    x: bigint;  // X coordinate
    y: bigint;  // Y coordinate
  };
  
  /** Associated Relying Party ID */
  rpId: string;
}

Utilities

The package also exports utility functions:

b64ToBytes(str)

Converts a base64url string to Uint8Array.

const bytes = b64ToBytes("SGVsbG8");
// Uint8Array([72, 101, 108, 108, 111])

bytesToB64(bytes)

Converts a Uint8Array to base64url string (without padding).

const b64 = bytesToB64(new Uint8Array([72, 101, 108, 108, 111]));
// "SGVsbG8"

extractPublicKey(authData)

Extracts X and Y coordinates of the public key from authenticator data.

const { x, y } = extractPublicKey(authData);
// x: Uint8Array (32 bytes)
// y: Uint8Array (32 bytes)

bytesToBigInt(bytes)

Converts a Uint8Array to BigInt.

const num = bytesToBigInt(new Uint8Array([0x01, 0x02]));
// 258n

Internal Workings

Cryptographic Algorithm

  • Algorithm: ECDSA with SHA-256 (ES256, COSE identifier: -7)
  • Curve: P-256 (secp256r1/prime256v1)
  • Key format: Uncompressed point (65 bytes: 0x04 + X + Y)

Registration Flow

┌─────────────┐     ┌──────────────┐     ┌─────────────────┐
│ Application │────▶│ PasskeySigner │────▶│ Native Passkey  │
│             │     │  .register()  │     │     Module      │
└─────────────┘     └──────────────┘     └─────────────────┘
                            │                      │
                            │                      ▼
                            │              ┌─────────────────┐
                            │              │   Biometrics    │
                            │              │ (Face ID, etc)  │
                            │              └─────────────────┘
                            │                      │
                            ▼                      ▼
                    ┌──────────────┐     ┌─────────────────┐
                    │ CBOR Decode  │◀────│  Attestation    │
                    │  Extract     │     │  Object (CBOR)  │
                    │ public key   │     └─────────────────┘
                    └──────────────┘

Signing Flow

┌─────────────┐     ┌──────────────┐     ┌─────────────────┐
│  Challenge  │────▶│ PasskeySigner │────▶│ Native Passkey  │
│    (Hex)    │     │    .sign()    │     │     Module      │
└─────────────┘     └──────────────┘     └─────────────────┘
                            │                      │
                            │                      ▼
                            │              ┌─────────────────┐
                            │              │   Biometric     │
                            │              │  Verification   │
                            │              └─────────────────┘
                            │                      │
                            ▼                      ▼
                    ┌──────────────┐     ┌─────────────────┐
                    │   Formatted  │◀────│    Signature    │
                    │   Response   │     │   ECDSA (DER)   │
                    └──────────────┘     └─────────────────┘

WebAuthn Data Structure

authenticatorData (minimum 37 bytes):

  • Bytes 0-31: SHA-256 of rpId
  • Byte 32: Flags (UP, UV, AT, ED)
  • Bytes 33-36: Signature counter (big-endian)
  • Remaining: Attested credential data (if present)

clientDataJSON:

{
  "type": "webauthn.get",
  "challenge": "<base64url>",
  "origin": "https://example.com",
  "crossOrigin": false
}

Usage Examples

Registration and Storage

import { PasskeySigner } from "@mentaproject/react-native-passkey-signer";
import AsyncStorage from "@react-native-async-storage/async-storage";

async function registerUser(email: string) {
  const signer = await PasskeySigner.register({
    username: email,
    config: {
      rpId: "myapp.com",
      rpName: "My Application",
      userVerification: "required"
    }
  });

  // Store credential for later use
  const credential = signer.getCredential();
  await AsyncStorage.setItem("passkey_credential", JSON.stringify({
    id: credential.id,
    publicKey: {
      x: credential.publicKey.x.toString(),
      y: credential.publicKey.y.toString()
    },
    rpId: credential.rpId
  }));

  return signer;
}

Restoring an Existing Signer

import { PasskeySigner } from "@mentaproject/react-native-passkey-signer";
import AsyncStorage from "@react-native-async-storage/async-storage";

async function restoreSigner(): Promise<PasskeySigner | null> {
  const stored = await AsyncStorage.getItem("passkey_credential");
  if (!stored) return null;

  const data = JSON.parse(stored);
  
  // Recreate signer with stored credential
  // Note: This requires access to the constructor via a factory method
  // or storing the config as well
  
  return signer;
}

Signing a Message

async function signMessage(signer: PasskeySigner, message: string) {
  // Hash the message (example with viem)
  const { keccak256, toHex, toBytes } = await import("viem");
  const messageHash = keccak256(toBytes(message));
  
  const { signature, authenticatorData, clientDataJSON } = await signer.sign(messageHash);
  
  return {
    signature,
    authenticatorData,
    clientDataJSON,
    messageHash
  };
}

Integration with Smart Account (ERC-4337)

import { PasskeySigner } from "@mentaproject/react-native-passkey-signer";
import { createSmartAccountClient } from "permissionless";
import { http } from "viem";
import { sepolia } from "viem/chains";

async function createSmartWallet(signer: PasskeySigner) {
  const webAuthnAccount = signer.toWebAuthnAccount();
  
  // Use with a provider like ZeroDev, Pimlico, etc.
  const smartAccountClient = await createSmartAccountClient({
    account: webAuthnAccount,
    chain: sepolia,
    transport: http("https://sepolia.infura.io/v3/YOUR_KEY"),
    // ... bundler and paymaster configuration
  });

  return smartAccountClient;
}

Sending a Transaction

async function sendTransaction(smartAccountClient: any, to: string, value: bigint) {
  // Signature will be automatically requested via biometric prompt
  const txHash = await smartAccountClient.sendTransaction({
    to,
    value,
    data: "0x"
  });

  return txHash;
}

Error Handling

import { PasskeySigner } from "@mentaproject/react-native-passkey-signer";

async function safeRegister(username: string, config: IPasskeyConfig) {
  try {
    const signer = await PasskeySigner.register({ username, config });
    return { success: true, signer };
  } catch (error) {
    if (error.message.includes("UserCancelled")) {
      return { success: false, error: "Authentication cancelled by user" };
    }
    if (error.message.includes("NotSupported")) {
      return { success: false, error: "Passkeys not supported on this device" };
    }
    if (error.message.includes("SecurityError")) {
      return { success: false, error: "Security error - check domain configuration" };
    }
    return { success: false, error: error.message };
  }
}

License

ISC