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
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.
Embedded smart wallets, fiat checkout, REST minting, and agent wallets — one TypeScript SDK across 40+ chains, Solana first-class. Where it wins and where it doesn't.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.