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

@tinfoilsh/passkey-kit

v0.1.0

Published

WebAuthn PRF passkey SDK for protecting encryption keys with passkey-derived keys.

Readme

Tinfoil Passkey Kit

Cross-platform SDKs for protecting content-encryption keys with passkeys and the WebAuthn PRF extension. The JavaScript and Swift implementations share the same protocol constants and wire formats, so either client can recover a CEK wrapped by the other.

  • Passkey creation and authentication with PRF
  • HKDF-SHA-256 key-encryption-key derivation
  • AES-256-GCM CEK wrapping and unwrapping
  • Stable CEK key-ID derivation
  • Device-local PRF and credential persistence

JavaScript

Install the browser package:

npm install @tinfoilsh/passkey-kit
import { createPasskeyKit, generateCek } from "@tinfoilsh/passkey-kit";

const kit = createPasskeyKit({
  rpId: "example.com",
  rpName: "Example App",
});

const cek = generateCek();
const enrolled = await kit.enroll({
  user: { id: userId, name: email, displayName },
  cek,
});

if (enrolled) {
  await api.saveBundle(enrolled.wrappedCek);
}

const unlocked = await kit.unlock(bundlesFromServer);
if (unlocked) {
  useCek(unlocked.cek);
}

High-level ceremony methods return null when the user cancels. They throw PrfNotSupportedError when the authenticator lacks PRF support and PasskeyTimeoutError when the provider hangs. Classify these errors with instanceof, not message strings.

The kit also provides cached unlock and rewrap flows. Lower-level exports include detectPrfSupport, deriveKeyEncryptionKey, generateCek, isValidCek, wrapCek, unwrapCek, and deriveKeyId.

The default storage adapter uses localStorage on a best-effort basis. Pass storage: null to disable persistence or provide a custom StorageAdapter. Cached PRF output is raw secret key material and must be protected accordingly.

Swift

Add this repository as a Swift Package Manager dependency and link the TinfoilPasskeyKit product. The package requires iOS 18 or macOS 15.

import TinfoilPasskeyKit

@MainActor
func configurePasskeyKit() async throws {
    let store = KeychainPasskeyStateStore(
        service: "example.com",
        account: "com.example.passkey-prf",
        localCredentialIdKey: "com.example.local-passkey-id"
    )
    let kit = PasskeyKit(
        configuration: PasskeyKitConfiguration(
            rpId: "example.com",
            rpName: "Example App",
            stateStore: store
        )
    )

    let cek = try PasskeyCrypto.generateCEK()
    let enrollment = try await kit.enroll(
        user: PasskeyUser(id: userId, name: email, displayName: displayName),
        cek: cek
    )
    await saveToServer(enrollment.wrappedCEK)

    let unlocked = try await kit.unlock(wrappedCEKsFromServer)
    useCEK(unlocked.cek)
}

KeychainPasskeyStateStore stores cached PRF output with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. Pass stateStore: nil to disable local persistence. The host app must provide the webcredentials associated-domain entitlement for its relying-party domain.

Protocol

Both implementations default to the Tinfoil v1 PRF salt and HKDF info. These values must remain identical across clients that wrap the same CEK. Override both values together to establish a separate protocol domain.

The server-persisted wrapped bundle contains only:

  • The unpadded base64url credential ID
  • The 12-byte AES-GCM IV as lowercase hexadecimal
  • The wrapped CEK ciphertext and 16-byte authentication tag as lowercase hexadecimal

User identity, server persistence, associated-domain configuration, and recovery UI remain the host application's responsibility.

License

Apache License 2.0. See LICENSE.

Development

npm install
npm test
npm run typecheck
npm run build
swift test