GitHunt
LO

locke-inc/open-passkey

Post-quantum passkey library for both server-side and client-side

open-passkey

An open-source library for adding passkey authentication to any app. Built on WebAuthn with hybrid post-quantum signature verification (ML-DSA-65-ES256) that works today in Go and TypeScript.

Status: Production-ready for ES256 passkeys. Post-quantum algorithms verified but awaiting browser support.

Hybrid Post-Quantum Support

open-passkey implements ML-DSA-65-ES256 hybrid composite signatures (draft-ietf-jose-pq-composite-sigs), combining a NIST-standardized post-quantum algorithm with classical ECDSA in a single credential. Both signature components must verify independently. If either is broken, the other still protects you.

Algorithm COSE alg Status Go TS
ML-DSA-65-ES256 (composite) -52 IETF Draft Yes Yes
ML-DSA-65 (PQ only) -49 NIST FIPS 204 Yes Yes
ES256 (ECDSA P-256) -7 Generally Available Yes Yes

During registration, the server advertises preferred algorithms in pubKeyCredParams. During authentication, the core libraries read the COSE alg field from the stored credential and dispatch to the correct verifier automatically. No application code changes needed when PQ support arrives in browsers.

Architecture

open-passkey/
├── spec/vectors/        # Shared JSON test vectors (31 vectors, 3 ceremonies)
├── packages/
│   ├── core-go/         # Go core protocol
│   ├── server-go/       # Go HTTP bindings
│   ├── core-ts/         # TypeScript core protocol
│   └── angular/         # Angular components + service
└── tools/vecgen/        # Test vector generation

The core protocol is pure WebAuthn/FIDO2 verification logic with no framework dependencies. Framework bindings are thin adapters. Adding passkey support to a new framework only requires writing an adapter, not reimplementing cryptography.

Packages

core-go

Go core protocol. Registration and authentication verification with ES256, ML-DSA-65, and ML-DSA-65-ES256 composite.

Dependencies: Go stdlib crypto, fxamacker/cbor, cloudflare/circl.

import "github.com/locke-inc/open-passkey/packages/core-go/webauthn"

result, err := webauthn.VerifyRegistration(webauthn.RegistrationInput{
    RPID:              "example.com",
    ExpectedChallenge: challengeB64URL,
    ExpectedOrigin:    "https://example.com",
    ClientDataJSON:    credential.Response.ClientDataJSON,
    AttestationObject: credential.Response.AttestationObject,
})
// result.CredentialID, result.PublicKeyCOSE, result.BackupEligible, result.AttestationFormat

result, err := webauthn.VerifyAuthentication(webauthn.AuthenticationInput{
    RPID:                "example.com",
    ExpectedChallenge:   challengeB64URL,
    ExpectedOrigin:      "https://example.com",
    StoredPublicKeyCOSE: storedKeyBytes,
    StoredSignCount:     storedCount,
    ClientDataJSON:      credential.Response.ClientDataJSON,
    AuthenticatorData:   credential.Response.AuthenticatorData,
    Signature:           credential.Response.Signature,
})
// result.SignCount, result.BackupEligible, result.BackupState

server-go

Go HTTP bindings. Challenge management, 4 ceremony handlers, pluggable store interfaces. Works with any Go router.

import "github.com/locke-inc/open-passkey/packages/server-go"

p, _ := passkey.New(passkey.Config{
    RPID:            "example.com",
    RPDisplayName:   "My App",
    Origin:          "https://example.com",
    ChallengeStore:  passkey.NewMemoryChallengeStore(),
    CredentialStore: myDBCredentialStore,
})

mux := http.NewServeMux()
mux.HandleFunc("POST /passkey/register/begin",  p.BeginRegistration)
mux.HandleFunc("POST /passkey/register/finish", p.FinishRegistration)
mux.HandleFunc("POST /passkey/login/begin",     p.BeginAuthentication)
mux.HandleFunc("POST /passkey/login/finish",    p.FinishAuthentication)

Pluggable ChallengeStore and CredentialStore interfaces. In-memory defaults included for development. Discoverable credentials supported with userHandle verification.

core-ts

TypeScript core. Full parity with Go — same 31 spec vectors. ES256, ML-DSA-65, and composite verification.

Dependencies: Node crypto, @noble/post-quantum, cbor-x.

import { verifyRegistration, verifyAuthentication } from "@open-passkey/core";

Same API shape as Go. Automatic algorithm dispatch based on the stored COSE key.

angular

Headless Angular components and injectable service. Content projection for custom UI.

import { providePasskey } from "@open-passkey/angular";

// In app.config.ts
providers: [provideHttpClient(), providePasskey({ baseUrl: "/passkey" })]
<passkey-register [userId]="userId" [username]="username"
                  (registered)="onRegistered($event)" #reg>
  <button (click)="reg.register()" [disabled]="reg.loading()">Register Passkey</button>
</passkey-register>

<passkey-login [userId]="userId"
               (authenticated)="onAuthenticated($event)" #login>
  <button (click)="login.login()" [disabled]="login.loading()">Sign In</button>
</passkey-login>

Features

  • Attestation: none and packed (self-attestation + full x5c certificate chain)
  • Backup flags: BE/BS exposed in results, spec conformance enforced (§6.3.3)
  • PRF extension: Salt generation, per-credential evaluation, output passthrough
  • userHandle: Cross-checked against credential owner in discoverable flow
  • Sign count: Rollback detection per §7.2
  • Token binding: "present" rejected, "supported" allowed

Testing

31 shared test vectors across 3 ceremony files, verified in both Go and TypeScript:

./scripts/test-all.sh
Package Tests Description
core-go 31 vectors Spec vector verification
core-ts 31 vectors Same vectors, TypeScript
server-go 31 tests HTTP handlers, stores, userHandle
angular 37 tests Components, service, PRF, userHandle

Development

Prerequisites: Go 1.21+, Node.js 18+

# Generate test vectors
cd tools/vecgen && go run main.go -out ../../spec/vectors

# Run all tests
./scripts/test-all.sh

Roadmap

  • ES256 + ML-DSA-65 + ML-DSA-65-ES256 composite verification (Go + TypeScript)
  • Go HTTP server bindings with pluggable stores
  • Angular headless components + service
  • Packed attestation (self + x5c)
  • Backup flags, userHandle verification, PRF extension
  • 31 shared cross-language test vectors
  • React hooks and components
  • Additional attestation formats (TPM, Android)

Contributing

Strict TDD. To add a test case:

  1. Update tools/vecgen/main.go and regenerate vectors
  2. Run ./scripts/test-all.sh — new vector should fail
  3. Implement in each language until all pass

New language: Create packages/core-{lang}/, load spec/vectors/*.json, implement until all 31 vectors pass.

License

MIT. Copyright 2025 Locke Identity Networks Inc.

Languages

Go62.5%TypeScript36.9%JavaScript0.3%Shell0.3%

Contributors

MIT License
Created March 10, 2026
Updated March 13, 2026
locke-inc/open-passkey | GitHunt