intermediatecookbook
Passkey wallet authentication
No seed phrase. Sign with Face ID via WebAuthn directly to chain.
Native secp256r1 verification (`0x0100`) + the `0x77` TxPasskey type let users authenticate to your dApp with platform passkeys (Face ID, Touch ID, Windows Hello). The chain checks the WebAuthn signature directly — no off-chain relayer, no MetaMask, no seed phrase.
Architecture
┌──────────┐ WebAuthn ┌──────────────┐ 0x77 tx ┌────────────┐
│ Browser │ ─────────▶ │ Sigil dApp │ ─────────────▶ │ Ritual │
│ Face ID │ │ packs P-256 │ type=0x77 │ 0x0100 │
└──────────┘ └──────────────┘ └─────┬──────┘
│
▼ verify
chain accepts callCode
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract PasskeyAuth {
address constant P256 = address(0x100);
mapping(address => bytes) public passkeyPubkey; // user → 65-byte 0x04||X||Y
event Authenticated(address indexed user, bytes32 challenge);
function register(bytes calldata pubkey) external {
require(pubkey.length == 65 && pubkey[0] == 0x04, "uncompressed P-256");
passkeyPubkey[msg.sender] = pubkey;
}
/// @notice Authenticate using a WebAuthn signature. Each call must be
/// nonced via `challenge` to prevent replay.
function authenticate(bytes32 challenge, bytes calldata sig) external {
bytes memory pk = passkeyPubkey[msg.sender];
require(pk.length == 65, "not registered");
bytes memory input = abi.encodePacked(pk, challenge, sig);
(bool ok, bytes memory result) = P256.staticcall(input);
require(ok && abi.decode(result, (uint256)) == 1, "passkey invalid");
emit Authenticated(msg.sender, challenge);
}
}