All articles
solanapasskeyswebauthnsecp256r1wallets

Passkeys on Solana: how Face ID signs transactions now

Since June 2025 Solana can verify WebAuthn passkey signatures natively via the secp256r1 precompile (SIMD-0075). Here's the curve mismatch, the precompile spec, the smart-wallet pattern, and who's building on it.

Share
devrels.xyz/a/129short link

Seed phrases are the single worst onboarding step in crypto. Passkeys — the WebAuthn credentials behind Face ID, Touch ID, Windows Hello, and YubiKeys — are how the rest of the internet already solved "prove it's you" without making users write twelve words on paper. Since June 2025, Solana can verify passkey signatures natively on-chain. Here's how the pieces fit.

The curve mismatch

Solana transactions are signed with Ed25519. Passkeys sign with secp256r1 (a.k.a. P-256 / NIST r1) — because that's the curve hardware secure enclaves support. P-256 is the only curve implemented by both Android and iOS/macOS enclaves, and browsers don't expose Ed25519 signing through WebAuthn in practice.

So a passkey cannot sign a Solana transaction. Full stop. Every "passkey wallet" on Solana is an answer to the question: how do you get from a P-256 signature to an executed transaction? There are two answers, and they have very different trust models.

The native answer: SIMD-0075, the secp256r1 precompile

SIMD-0075 added a native precompile that verifies P-256 signatures on-chain, activated on mainnet in June 2025:

text
program id   Secp256r1SigVerify1111111111111111111111111
max sigs     8 per instruction
pubkey       SEC1 compressed (33 bytes)
malleability lowS enforced — any highS signature fails immediately

It works like the older Ed25519 and secp256k1 precompiles: the instruction data is a count plus an array of offset structs pointing at where the signature, public key, and message live (either in the same instruction or another instruction in the transaction):

text
byte 0     num_signatures (u8)
byte 1     padding
bytes 2+   Secp256r1SignatureOffsets[num_signatures], each:
             signature_offset            u16
             signature_instruction_index u16   (0xFFFF = this instruction)
             public_key_offset           u16
             public_key_instruction_index u16
             message_offset              u16
             message_length              u16
             message_instruction_index   u16

If any signature in the batch fails — bad sig, wrong key, highS value — the whole transaction fails. Your program never sees an unverified signature; it just checks (via the Instructions sysvar) that the precompile instruction ran in the same transaction with the expected pubkey and message. That instruction-introspection pattern is the same one Solana programs have used with the Ed25519 precompile for years.

The smart-wallet pattern

The precompile verifies signatures, but someone still has to sign the transaction envelope with Ed25519 (the fee payer). The pattern that emerged:

  • Your wallet is a PDA owned by a smart-wallet program. The wallet account stores your passkey's P-256 public key.
  • To act, your device signs an intent (what the wallet should do) with the passkey via WebAuthn.
  • A relayer — anyone, typically the app's paymaster — wraps it in a transaction: instruction 1 is the secp256r1 precompile verifying your passkey signature; instruction 2 is the smart-wallet program, which introspects instruction 1, checks the pubkey matches the one stored on your wallet PDA, checks the signed message matches the intent (and isn't a replay), then executes via CPI.

Net effect: the user's only credential is a passkey in the secure enclave. No seed phrase, no browser extension, and gasless UX falls out for free since the fee payer was never the user.

One real implementation detail: WebAuthn doesn't sign your message directly. It signs authenticatorData || SHA-256(clientDataJSON), where your challenge (the intent hash) is embedded inside clientDataJSON. On-chain code has to reconstruct and validate that envelope, not just a raw hash — this is where naive implementations get burned, and why you want an audited smart-wallet program rather than rolling your own.

The other answer: passkey as login, MPC signs

Embedded-wallet providers (Para, Privy, Turnkey, Web3Auth) take a different route: the passkey never touches the chain. It authenticates you to the provider's infrastructure, which unlocks a session with an Ed25519 key held in MPC/TEE. That key signs normal Solana transactions.

  • Pro: works everywhere today, normal transactions, no relayer needed, easy recovery flows.
  • Con: the signing key lives with a provider. The passkey is authentication, not authorization — you're trusting the provider's infra to only sign what you asked.

The precompile route keeps verification on-chain: the P-256 key in your enclave is the authority, cryptographically checked by the runtime. The MPC route is smoother today but reintroduces a trusted party. Expect both to coexist; know which one you're integrating.

Who's building on it

  • LazorKit — passkey-native smart wallets: every wallet is a PDA controlled by a secp256r1 passkey, with session keys and role-based access control in the V2 program. SDKs for Next.js and React Native, so the same passkey flow works on web and mobile.
  • Swig — smart-wallet program adopting passkey-based flows for app-native onboarding.
  • Embedded-wallet stacks (Para, Privy, Turnkey) — the passkey-as-login model above, for apps that want invisible wallets more than self-custody purity.

Trade-offs to be honest about

  • Recovery is platform-shaped. Passkeys sync via iCloud Keychain / Google Password Manager. Lose the platform account, lose the passkey — so serious smart-wallet programs support multiple registered keys and recovery authorities. Ask about this before trusting real money to a single passkey.
  • No export. The private key physically can't leave the enclave. That's the security model — and it means "import to another wallet" doesn't exist. Key rotation on the smart wallet replaces it.
  • Relayer dependency. Pure passkey wallets need someone to pay fees and submit transactions. Good programs make the relayer trustless (it can censor, not steal); still, it's a liveness dependency.
  • P-256 signatures cost more than Ed25519 to verify, and the WebAuthn envelope adds bytes to every transaction. Fine for a wallet action; not what you'd use for high-frequency program auth.

TL;DR

  • Passkeys sign P-256; Solana transactions need Ed25519. The secp256r1 precompile (SIMD-0075, mainnet since June 2025, program id Secp256r1SigVerify1111…) bridges the gap by verifying passkey signatures on-chain — up to 8 per instruction, lowS enforced.
  • Pure passkey wallets = PDA smart wallet + precompile + instruction introspection + relayer. LazorKit is the most complete open implementation.
  • Embedded-wallet providers use passkeys as login to an MPC-held Ed25519 key — smoother, but a trusted party is back in the loop.
  • Either way: no more seed phrases in your onboarding flow. That alone is worth the integration.

Keep reading

Get new articles in your inbox

Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.

Passkeys on Solana: how Face ID signs transactions now | devrels.xyz