diff --git a/modules/passkey/contracts/SafeWebAuthnSignerFactory.sol b/modules/passkey/contracts/SafeWebAuthnSignerFactory.sol index 46ec06f3e..26d9f54c0 100644 --- a/modules/passkey/contracts/SafeWebAuthnSignerFactory.sol +++ b/modules/passkey/contracts/SafeWebAuthnSignerFactory.sol @@ -7,8 +7,10 @@ import {SafeWebAuthnSignerSingleton} from "./SafeWebAuthnSignerSingleton.sol"; import {P256} from "./libraries/P256.sol"; /** - * @title SafeWebAuthnSignerFactory - * @dev A factory contract for creating and managing WebAuthn proxy signers. + * @title Safe WebAuthn Signer Factory + * @dev A factory contract for creating WebAuthn signers. Additionally, the factory supports + * signature verification without deploying a signer proxies. + * @custom:security-contact bounty@safe.global */ contract SafeWebAuthnSignerFactory is ISafeSignerFactory { /** @@ -76,11 +78,9 @@ contract SafeWebAuthnSignerFactory is ISafeSignerFactory { // solhint-disable-next-line no-inline-assembly assembly { - let dataSize := mload(data) - let dataLocation := add(data, 0x20) // staticcall to the singleton contract with return size given as 32 bytes. The // singleton contract is known and immutable so it is safe to specify return size. - if staticcall(gas(), singleton, dataLocation, dataSize, 0, 32) { + if staticcall(gas(), singleton, add(data, 0x20), mload(data), 0, 32) { magicValue := mload(0) } } @@ -89,14 +89,12 @@ contract SafeWebAuthnSignerFactory is ISafeSignerFactory { /** * @dev Checks if the provided account has no code. * @param account The address of the account to check. - * @return True if the account has no code, false otherwise. + * @return result True if the account has no code, false otherwise. */ - function _hasNoCode(address account) internal view returns (bool) { - uint256 size; + function _hasNoCode(address account) internal view returns (bool result) { // solhint-disable-next-line no-inline-assembly assembly ("memory-safe") { - size := extcodesize(account) + result := iszero(extcodesize(account)) } - return size == 0; } } diff --git a/modules/passkey/contracts/SafeWebAuthnSignerProxy.sol b/modules/passkey/contracts/SafeWebAuthnSignerProxy.sol index 310a457fb..82b7d05ad 100644 --- a/modules/passkey/contracts/SafeWebAuthnSignerProxy.sol +++ b/modules/passkey/contracts/SafeWebAuthnSignerProxy.sol @@ -6,8 +6,9 @@ import {P256} from "./libraries/WebAuthn.sol"; /** * @title Safe WebAuthn Signer Proxy - * @dev A proxy to a {SafeWebAuthnSignerSingleton} signature validator implementation for Safe - * accounts. Using a proxy pattern for the signature validator greatly reduces deployment gas costs. + * @dev A specialized proxy to a {SafeWebAuthnSignerSingleton} signature validator implementation + * for Safe accounts. Using a proxy pattern for the signature validator greatly reduces deployment + * gas costs. * @custom:security-contact bounty@safe.global */ contract SafeWebAuthnSignerProxy { @@ -49,15 +50,26 @@ contract SafeWebAuthnSignerProxy { * @dev Fallback function forwards all transactions and returns all received return data. */ fallback() external payable { - bytes memory data = abi.encodePacked(msg.data, _X, _Y, _VERIFIERS); address singleton = _SINGLETON; + uint256 x = _X; + uint256 y = _Y; + P256.Verifiers verifiers = _VERIFIERS; // solhint-disable-next-line no-inline-assembly assembly { - let dataSize := mload(data) - let dataLocation := add(data, 0x20) + // Forward the call to the singleton implementation. We append the configuration to the + // calldata instead of having the singleton implementation read it from storage. This is + // both more gas efficient and required for ERC-4337 compatibility. Note that we append + // the configuration fields in reverse order since the fields are packed, and this makes + // it so we don't need to mask any bits from the `verifiers` value. This computes `data` + // to be `abi.encodePacked(msg.data, x, y, verifiers)`. + let data := mload(0x40) + mstore(add(data, add(calldatasize(), 0x36)), verifiers) + mstore(add(data, add(calldatasize(), 0x20)), y) + mstore(add(data, calldatasize()), x) + calldatacopy(data, 0x00, calldatasize()) - let success := delegatecall(gas(), singleton, dataLocation, dataSize, 0, 0) + let success := delegatecall(gas(), singleton, data, add(calldatasize(), 0x56), 0, 0) returndatacopy(0, 0, returndatasize()) if iszero(success) { revert(0, returndatasize()) diff --git a/modules/passkey/contracts/SafeWebAuthnSignerSingleton.sol b/modules/passkey/contracts/SafeWebAuthnSignerSingleton.sol index b7caafc03..799f5450a 100644 --- a/modules/passkey/contracts/SafeWebAuthnSignerSingleton.sol +++ b/modules/passkey/contracts/SafeWebAuthnSignerSingleton.sol @@ -5,10 +5,11 @@ import {SignatureValidator} from "./base/SignatureValidator.sol"; import {P256, WebAuthn} from "./libraries/WebAuthn.sol"; /** - * @title WebAuthn Safe Signature Validator Singleton - * @dev A singleton contract that implements WebAuthn signature verification. This sigleton contract - * must be used with the {SafeWebAuthnSignerProxy}, as it encodes the credential configuration - * (public key coordinates and P-256 verifier to use) that is required by this implementation. + * @title Safe WebAuthn Signer Singleton + * @dev A singleton contract that implements WebAuthn signature verification. This singleton + * contract must be used with the specialized proxy {SafeWebAuthnSignerProxy}, as it encodes the + * credential configuration (public key coordinates and P-256 verifier to use) in calldata, which is + * required by this implementation. * @custom:security-contact bounty@safe.global */ contract SafeWebAuthnSignerSingleton is SignatureValidator { @@ -17,7 +18,6 @@ contract SafeWebAuthnSignerSingleton is SignatureValidator { */ function _verifySignature(bytes32 message, bytes calldata signature) internal view virtual override returns (bool success) { (uint256 x, uint256 y, P256.Verifiers verifiers) = getConfiguration(); - success = WebAuthn.verifySignature(message, signature, WebAuthn.USER_VERIFICATION, x, y, verifiers); } diff --git a/modules/passkey/contracts/interfaces/ISafe.sol b/modules/passkey/contracts/interfaces/ISafe.sol index 016a2ffca..724d339c1 100644 --- a/modules/passkey/contracts/interfaces/ISafe.sol +++ b/modules/passkey/contracts/interfaces/ISafe.sol @@ -1,7 +1,26 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.0 <0.9.0; +/** + * @title Safe Smart Account + * @dev Minimal interface of a Safe smart account. This only includes functions that are used by + * this project. + * @custom:security-contact bounty@safe.global + */ interface ISafe { + /** + * @notice Sets an initial storage of the Safe contract. + * @dev This method can only be called once. If a proxy was created without setting up, anyone + * can call setup and claim the proxy. + * @param _owners List of Safe owners. + * @param _threshold Number of required confirmations for a Safe transaction. + * @param to Contract address for optional delegate call. + * @param data Data payload for optional delegate call. + * @param fallbackHandler Handler for fallback calls to this contract + * @param paymentToken Token that should be used for the payment (0 is ETH) + * @param payment Value that should be paid + * @param paymentReceiver Address that should receive the payment (or 0 if tx.origin) + */ function setup( address[] calldata _owners, uint256 _threshold,