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.
devrels.xyz/a/129short linkSeed 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:
program id Secp256r1SigVerify1111111111111111111111111
max sigs 8 per instruction
pubkey SEC1 compressed (33 bytes)
malleability lowS enforced — any highS signature fails immediatelyIt 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):
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 u16If 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
Most wallets optimize for onboarding; Unruggable optimizes for not getting drained. The project spans a pure-bash ATM-style CLI wallet, a Rust/Dioxus app for all five platforms with MPC instead of seed phrases, and an open-firmware ESP32 signer that only signs when you physically press the button. The first hardware run already sold out.
Your wallet is your identity. Sign In With Solana turns that into a real auth protocol: a structured message, a wallet signature, and a server that verifies the Ed25519 sig and issues a session. No email required. Here's how to implement the full stack — frontend signIn call, backend nonce management, signature check, and JWT — plus the security edge cases that trip people up.
Give an AI agent a wallet and you've given it your private key. OWS — MoonPay's open standard — keeps keys local in ~/.ows, gates every signature through a policy engine, and exposes a CLI, SDKs, and an MCP server. One seed, nine chains including Solana. The model.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
