sigil
All patterns
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 call

Code

// 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);
    }
}