How to Add Support for Passkeys Authentication
Passkeys can be used to secure onchain smart accounts using fingerprint, face recognition, or the device PIN code. Users no longer need to manage and remember complex private keys; instead, they can effortlessly access their digital wallets using passkey-enabled devices, synced across devices via Apple's iCloud Keychain, or other cross-platform password managers like Proton Pass and Bitwarden.
In the context of an Ethereum wallet, Passkeys serve as substitutes for traditional seedphrase backups. Unlike the default curve (secp256k1) used for Externally Owned Accounts (EOA), Passkeys generate unique digital keys using the secp256r1 curve. These keys benefit from device secure enclave cryptography, which enhances security by preventing password reuse. Passkeys are built on the WebAuthn standard, leveraging public-key cryptography. Developed collaboratively by the FIDO Alliance, which includes Apple, Google, Microsoft, and others, Passkeys strictly adhere to WebAuthn standards.
Safe Passkeys
The Safe Passkeys contracts are developed by the Safe Protocol Team.
The contracts and its audits are available on the Safe-Modules repo. The deployment addresses can be found in our Contracts Deployment
Demo
These example showcases a Safe Smart Account deployment utilizing 4337 and Passkeys, and minting an NFT.
Safe Passkeys WebApp:
Node script simulated Passkeys
Create Passkeys Account
- npm
- yarn
npm i abstractionkit
yarn add abstractionkit
We will often import WebAuthn API helper functions and classes from /webauthn. Make sure to copy it and using it during your developement.
Step 1: Create WebAuthn credentials
Create WebAuthn credentials using the navigator.credentials API. This includes defining parameters such as the relying party (RP) name and ID, user details, and challenge.
import ethers from "ethers";
import { WebAuthnCredentials } from './webauthn';
const navigator = {
credentials: new WebAuthnCredentials(),
}
const credential = navigator.credentials.create({
publicKey: {
rp: {
name: 'Candide',
id: 'candide.dev',
},
user: {
id: ethers.getBytes(ethers.id('chucknorris')),
name: 'chucknorris',
displayName: 'Chuck Norris',
},
challenge: ethers.toBeArray(Date.now()),
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
},
})
Step 2: Extract Public Key
Extract the public key from the generated WebAuthn credential response
import { extractPublicKey } from "./webauthn";
import { WebauthPublicKey } from "abstractionkit";
const publicKey = extractPublicKey(credential.response)
const webauthPublicKey: WebauthPublicKey = {
x: publicKey.x,
y: publicKey.y,
}
Step 3: Initialize Smart Account
Initialize the Safe Smart Account as usual. The SafeAccountV0_3_0
supports Entrypoint v0.7, while SafeAccountV0_2_0
supports Entrypoint v0.6.
import { SafeAccountV0_3_0 as SafeAccount } from "abstractionkit";
const smartAccount = SafeAccount.initializeNewAccount([webauthPublicKey])
You can combine an EOA public address and a webauthPublicKey in the initializing function to create a multisig secured by both types.
Create Passkey UserOp
This step follows the same flow as the normal Safe flow with createUserOperation, with the addition of the webauthn dummy signature.
import { WebauthDummySignerSignaturePair } from "abstractionkit";
let userOperation = await smartAccount.createUserOperation(
[transaction] // constructed your MetaTranasction
jsonRpcNodeProvider, //The Node rpc endpoint.
bundlerUrl, //The Bundler rpc endpoint.
{
dummySignatures: [WebauthDummySignerSignaturePair]
},
)
Sign with Passkeys
Step 1: Calculate the EIP712 hash
Calculate the Safe EIP712 hash for the UserOp
const safeInitOpHash = SafeAccount.getUserOperationEip712Hash(
userOperation,
chainId,
);
Step 2: Request a WebAuthn assertion
Using the navigator.credentials API
import { UserVerificationRequirement } from "./webauthn";
const assertion = navigator.credentials.get({
publicKey: {
challenge: ethers.getBytes(safeInitOpHash),
rpId: 'candide.dev',
allowCredentials: [{ type: 'public-key', id: new Uint8Array(credential.rawId) }],
userVerification: UserVerificationRequirement.required,
},
})
Step 3: Extract WebAuthn signature data
Extract WebAuthn signature data from the assertion response and create a signature from the extracted signature data
import { WebauthSignatureData } from "abstractionkit";
import { extractClientDataFields, extractSignature } from "./webauthn";
const webauthSignatureData: WebauthSignatureData = {
authenticatorData: assertion.response.authenticatorData,
clientDataFields: extractClientDataFields(assertion.response),
rs: extractSignature(assertion.response),
}
const webauthSignature: string = SafeAccount.createWebAuthnSignature(webauthSignatureData)
Step 4: Create a Signer Signature Pair and Format
Create a SignerSignaturePair
containing the webauthPublicKey
and webauthSignature
, and format the SignerSignaturePair
into the expected format for the userOperation signature
import { SignerSignaturePair } from "abstractionkit";
const signerSignaturePair: SignerSignaturePair = {
signer: webauthPublicKey,
signature: webauthSignature,
}
userOperation.signature = SafeAccount.formatSignaturesToUseroperationSignature(
[signerSignaturePair],
{ isInit: userOperation.nonce == 0n },
)
Submit the UserOp onchain
const sendUserOperationResponse = await smartAccount.sendUserOperation(
userOperation,
bundlerUrl,
);
const userOperationReceiptResult = await sendUserOperationResponse.included();
Additional Notes
Saving Public Credentials
It's crucial to store the Passkey's public credentials, specifically the x, y, and rawId, in a retrievable location. Losing this data would mean users can't recover their accounts with Passkeys. This information isn't sensitive, so you can set up a simple server to do so, or you can you use@simplewebauthn/server for this purpose.
Sync & Recovery
Apple
Passkey recovery on Apple devices involves iCloud Keychain escrow. In case of device loss, users authenticate through their iCloud account using standard procedure. After authentication, they enter their device passcode. Apple users also has the option to add an account recovery contact for additional support. Learn more on Apple Passkeys security
Google
Google Password Manager seamlessly syncs passkeys across devices, with plans to extend syncing support to a broader range of operating systems. Learn more on Google Passkeys security
Yubikey
YubiKey is compatible with passkeys through its support for the authentication protocol. Passkeys can be protected and managed using YubiKey's hardware-based security features. Learn more on Yubico
Password Managers
Passkey backups are not limited to hardware manufactures, they are supported across different password managers like Windows Hello, Bitwarden, ProtonPass, 1Password, LastPass and others.
Device Support
Passkeys are widely available across devices such as:
- Apple Devices: iPhones & iPads (iOS 16+), Mac (macOS 13+)
- Android Devices: Phones and tablets (Android 9+)
- Windows (10/11/+): Supported on Chrome, Brave, Edge, and Firefox browsers
- Linux: Supported on Chrome, Firefox, Edge, and Brave browsers
For a comprehensive list of supported systems, please visit passkeys.dev/device-support