permissionless_passkeys

WebAuthn/Passkeys support for permissionless.dart ERC-4337 smart accounts.

Enable biometric authentication (Face ID, Touch ID, Windows Hello) for your smart accounts using P256 signatures.

Features

  • WebAuthn Smart Accounts - Create passkey-authenticated smart accounts
  • Kernel v0.3.x Support - WebAuthn validator with P256 precompile (RIP-7212)
  • Safe v1.4.1/v1.5.0 Support - Shared WebAuthn signer module
  • Cross-Platform - Works on iOS, Android, Web, macOS
  • Biometric Authentication - Face ID, Touch ID, Windows Hello, Security Keys

Supported Account Types

Account Version EntryPoint WebAuthn Signer
Kernel v0.3.0, v0.3.1 v0.7 WebAuthn validator module
Safe v1.4.1, v1.5.0 v0.7 Shared WebAuthn signer

Installation

Add to your pubspec.yaml:

dependencies:
  permissionless_passkeys: ^0.1.0
  permissionless: ^0.2.0  # Required peer dependency

Then run:

flutter pub get

Quick Start

1. Register a Passkey

import 'package:permissionless_passkeys/permissionless_passkeys.dart';

// Register a new passkey (triggers biometric prompt)
final credential = await createPasskeyCredential(
  rpId: 'myapp.com',
  rpName: 'My Application',
  userName: 'user@example.com',
);

// Credential can be serialized for storage
final json = credential.toJson();
// Later: final restored = WebAuthnCredential.fromJson(json);

2. Create a WebAuthn Account

import 'package:permissionless_passkeys/permissionless_passkeys.dart';

// Create a WebAuthn account from the credential
final webAuthnAccount = createWebAuthnAccount(
  credential: credential,
  rpId: 'myapp.com',
);

// Or use the viem-compatible API
final webAuthnAccount = toWebAuthnAccount(
  ToWebAuthnAccountParameters(
    credential: credential,
    rpId: 'myapp.com',
  ),
);

print('Public Key: ${webAuthnAccount.publicKey}');

3. Use with Kernel or Safe Smart Account

import 'package:permissionless/permissionless.dart';
import 'package:permissionless_passkeys/permissionless_passkeys.dart';

// Use with Kernel v0.3.x (supports WebAuthn validators)
final kernelAccount = createKernelSmartAccount(
  owner: webAuthnAccount,  // WebAuthnAccount IS an AccountOwner
  chainId: BigInt.from(11155111),
  version: KernelVersion.v0_3_1,
);

// Or use with Safe v1.4.1/v1.5.0 (supports WebAuthn shared signer)
final safeAccount = createSafeSmartAccount(
  owners: [webAuthnAccount],  // WebAuthnAccount IS an AccountOwner
  chainId: BigInt.from(11155111),
  version: SafeVersion.v1_4_1,
);

print('Account Address: ${kernelAccount.address.hex}');

4. Sign and Send Transactions

import 'package:permissionless/permissionless.dart';

// Create clients
final bundler = createPimlicoClient(
  url: 'https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY',
  entryPoint: EntryPointAddresses.v07,
);

final smartAccountClient = SmartAccountClient(
  account: account,
  bundler: bundler,
  publicClient: publicClient,
);

// Send a user operation (triggers biometric prompt for signing)
final hash = await smartAccountClient.sendUserOperation(
  calls: [
    Call(
      to: recipientAddress,
      value: BigInt.from(1000000000000000), // 0.001 ETH
    ),
  ],
  maxFeePerGas: gasPrices.fast.maxFeePerGas,
  maxPriorityFeePerGas: gasPrices.fast.maxPriorityFeePerGas,
);

print('UserOperation hash: $hash');

5. Use the WebAuthn Account Interface (viem-compatible)

For direct signing without a smart account, use the toWebAuthnAccount API:

import 'package:permissionless_passkeys/permissionless_passkeys.dart';

// Create a WebAuthn account from a credential
final account = createWebAuthnAccount(
  credential: credential,
  rpId: 'myapp.com',
);

// Account properties
print('ID: ${account.id}');           // Base64 credential ID
print('Public Key: ${account.publicKey}'); // Hex: 0x + x(64) + y(64)
print('Type: ${account.type}');       // 'webAuthn'

// Sign a hash (triggers biometric prompt)
final result = await account.sign(hash: userOpHash);

// Result contains:
// - result.signature: Hex signature (r + s, 64 bytes)
// - result.webauthn.authenticatorData: WebAuthn authenticator data
// - result.webauthn.clientDataJSON: Client data JSON string
// - result.webauthn.challengeIndex: Index of challenge in JSON
// - result.webauthn.typeIndex: Index of type in JSON
// - result.raw: Raw Signature object

API Reference

Types

WebAuthnCredential

Wrapper for WebAuthn credential data:

// Create from a PassKeyPublicKey (after registration)
final credential = WebAuthnCredential.fromPublicKey(passKeyPublicKey);

// Or use the factory function
final credential = await createPasskeyCredential(
  rpId: 'myapp.com',
  rpName: 'My App',
  userName: 'user@example.com',
);

// Convert to/from JSON for storage
final json = credential.toJson();
final restored = WebAuthnCredential.fromJson(json);

// Get public key as hex (64 bytes: x || y)
final pubKeyHex = credential.publicKeyHex;

WebAuthnAccount

viem-compatible account interface for passkey signing:

// Create from credential
final account = createWebAuthnAccount(
  credential: credential,
  rpId: 'myapp.com',
);

// Or use the viem-style function
final account = toWebAuthnAccount(
  ToWebAuthnAccountParameters(
    credential: credential,
    rpId: 'myapp.com',
  ),
);

// Properties
account.id         // Base64 credential ID
account.publicKey  // Hex public key (0x + x + y)
account.type       // 'webAuthn'

// Sign a hash
final result = await account.sign(hash: '0x...');
// Returns WebAuthnSignReturnType with signature and metadata

Encoding Functions

Kernel Signatures

// Encode a WebAuthn signature for Kernel
final encoded = encodeKernelWebAuthnSignature(
  authenticatorData: '0x...',
  clientDataJSON: '{"type":"webauthn.get",...}',
  responseTypeLocation: BigInt.from(1),
  r: BigInt.parse('...'),
  s: BigInt.parse('...'),
  usePrecompile: true,
);

// Get dummy signature for gas estimation
final dummy = getDummyKernelWebAuthnSignature(usePrecompile: true);

Safe Signatures

// Encode a WebAuthn signature for Safe
final encoded = encodeSafeWebAuthnSignature(
  authenticatorData: '0x...',
  clientDataFields: '"challenge":"..."',
  r: BigInt.parse('...'),
  s: BigInt.parse('...'),
  validAfter: BigInt.zero,
  validUntil: BigInt.zero,
);

// Get dummy signature for gas estimation
final dummy = getDummySafeWebAuthnSignature();

Platform Setup

WebAuthn requires platform-specific configuration. See the example app README for detailed setup instructions for:

  • iOS (Associated Domains)
  • Android (App Links)
  • Web (HTTPS)
  • macOS (Entitlements)

P256 Precompile (RIP-7212)

For gas-efficient signature verification, use chains with the P256 precompile. The library supports two detection methods:

  • Dynamic detection (recommended): isRip7212Supported(publicClient) probes the precompile via eth_call with a known-valid P256 test vector. Works on any chain, results are cached per chain ID.
  • Static detection: shouldUseP256Precompile(chainId:) checks against a curated list of 67 known-supported chain IDs.

Kernel accounts automatically use dynamic detection when a publicClient is configured, falling back to the static list otherwise.

import 'package:permissionless/permissionless.dart';

// Dynamic: probe any chain via RPC
final supported = await isRip7212Supported(publicClient);

// Static: check against known chain IDs
final supported = shouldUseP256Precompile(chainId: chainId);

Without the precompile, signature verification falls back to Solidity-based P256, which uses ~800k gas vs ~3.5k with the precompile (~6.9k on mainnet).

Example

See the example/ directory for a complete Flutter app demonstrating:

  • Passkey registration with biometrics
  • Kernel and Safe account creation
  • Transaction encoding
  • Credential export/import

Run the example:

cd example
flutter pub get
flutter run

Testing

Run the unit tests:

dart test

The package includes comprehensive tests for:

  • Signature encoding (Kernel and Safe formats)
  • Credential serialization
  • Account configuration
  • Address computation

Architecture

lib/
├── permissionless_passkeys.dart   # Public exports
└── src/
    ├── accounts/
    │   ├── webauthn_account.dart         # WebAuthnAccount abstract class
    │   └── to_webauthn_account.dart      # toWebAuthnAccount factory
    ├── encoding/
    │   ├── kernel_encoding.dart          # Kernel signature ABI
    │   └── safe_encoding.dart            # Safe signature ABI
    ├── types/
    │   └── webauthn_credential.dart      # Credential wrapper
    └── factory.dart                      # Convenience factory functions

License

MIT License - see LICENSE for details.

Libraries

permissionless_passkeys
WebAuthn/Passkey support for permissionless.dart ERC-4337 smart accounts.