Skip to content

Commit

Permalink
Refactor Contract Layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nlordell committed Mar 11, 2024
1 parent 76c2076 commit ebdc3c0
Show file tree
Hide file tree
Showing 19 changed files with 362 additions and 293 deletions.
11 changes: 6 additions & 5 deletions modules/passkey/contracts/4337/Safe256BitECSignerLaunchpad.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import {IAccount} from "@account-abstraction/contracts/interfaces/IAccount.sol";
import {PackedUserOperation} from "@account-abstraction/contracts/interfaces/PackedUserOperation.sol";
import {_packValidationData} from "@account-abstraction/contracts/core/Helpers.sol";
import {SafeStorage} from "@safe-global/safe-contracts/contracts/libraries/SafeStorage.sol";
import {SignatureValidatorConstants} from "../SignatureValidatorConstants.sol";

import {ICustom256BitECSignerFactory} from "../interfaces/ICustomSignerFactory.sol";
import {ISafeSetup} from "../interfaces/ISafe.sol";
import {ISafe} from "../interfaces/ISafe.sol";
import {ERC1271} from "../libraries/ERC1271.sol";

/**
* @title SafeOpLaunchpad - A contract for Safe initialization with custom unique signers that would violate ERC-4337 factory rules.
* @dev The is intended to be set as a Safe proxy's implementation for ERC-4337 user operation that deploys the account.
*/
contract Safe256BitECSignerLaunchpad is IAccount, SafeStorage, SignatureValidatorConstants {
contract Safe256BitECSignerLaunchpad is IAccount, SafeStorage {
bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");

// keccak256("SafeSignerLaunchpad.initHash") - 1
Expand Down Expand Up @@ -197,7 +198,7 @@ contract Safe256BitECSignerLaunchpad is IAccount, SafeStorage, SignatureValidato
)
returns (bytes4 magicValue) {
// The timestamps are validated by the entry point, therefore we will not check them again
validationData = _packValidationData(magicValue != EIP1271_MAGIC_VALUE, validUntil, validAfter);
validationData = _packValidationData(magicValue != ERC1271.MAGIC_VALUE, validUntil, validAfter);
} catch {
validationData = _packValidationData(true, validUntil, validAfter);
}
Expand All @@ -219,7 +220,7 @@ contract Safe256BitECSignerLaunchpad is IAccount, SafeStorage, SignatureValidato
address[] memory owners = new address[](1);
owners[0] = ICustom256BitECSignerFactory(signerFactory).createSigner(signerX, signerY, signerVerifier);

ISafeSetup(address(this)).setup(owners, 1, setupTo, setupData, fallbackHandler, address(0), 0, payable(address(0)));
ISafe(address(this)).setup(owners, 1, setupTo, setupData, fallbackHandler, address(0), 0, payable(address(0)));
}

(bool success, bytes memory returnData) = address(this).delegatecall(callData);
Expand Down
15 changes: 0 additions & 15 deletions modules/passkey/contracts/SignatureValidatorConstants.sol

This file was deleted.

126 changes: 12 additions & 114 deletions modules/passkey/contracts/WebAuthnSigner.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable one-contract-per-file */
pragma solidity >=0.8.0;

import {SignatureValidatorConstants} from "./SignatureValidatorConstants.sol";
import {ICustom256BitECSignerFactory} from "./interfaces/ICustomSignerFactory.sol";
import {SignatureValidator} from "./SignatureValidator.sol";
import {IWebAuthnVerifier, WebAuthnConstants} from "./verifiers/WebAuthnVerifier.sol";

struct SignatureData {
bytes authenticatorData;
bytes clientDataFields;
uint256[2] rs;
}
import {SignatureValidator} from "./base/SignatureValidator.sol";
import {IWebAuthnVerifier} from "./interfaces/IWebAuthnVerifier.sol";
import {WebAuthnFlags} from "./libraries/WebAuthnFlags.sol";
import {WebAuthnSignature} from "./libraries/WebAuthnSignature.sol";

/**
* @title WebAuthnSigner
* @title WebAuthn Safe Signature Validator
* @dev A contract that represents a WebAuthn signer.
* @custom:security-contact [email protected]
*/
contract WebAuthnSigner is SignatureValidator {
uint256 public immutable X;
Expand All @@ -38,114 +32,18 @@ contract WebAuthnSigner is SignatureValidator {
* @inheritdoc SignatureValidator
*/
function _verifySignature(bytes32 message, bytes calldata signature) internal view virtual override returns (bool isValid) {
SignatureData calldata signaturePointer;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
signaturePointer := signature.offset
}
WebAuthnSignature.Data calldata data = WebAuthnSignature.cast(signature);

return
WEBAUTHN_SIG_VERIFIER.verifyWebAuthnSignatureAllowMalleability(
signaturePointer.authenticatorData,
WebAuthnConstants.AUTH_DATA_FLAGS_UV,
data.authenticatorData,
WebAuthnFlags.USER_VERIFICATION,
message,
signaturePointer.clientDataFields,
signaturePointer.rs,
data.clientDataFields,
data.r,
data.s,
X,
Y
);
}
}

/**
* @title WebAuthnSignerFactory
* @dev A factory contract for creating and managing WebAuthn signers.
*/
contract WebAuthnSignerFactory is ICustom256BitECSignerFactory, SignatureValidatorConstants {
// @inheritdoc ICustom256BitECSignerFactory
function getSigner(uint256 qx, uint256 qy, address verifier) public view override returns (address signer) {
bytes32 codeHash = keccak256(abi.encodePacked(type(WebAuthnSigner).creationCode, qx, qy, uint256(uint160(verifier))));
signer = address(uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), bytes32(0), codeHash)))));
}

// @inheritdoc ICustom256BitECSignerFactory
function createSigner(uint256 qx, uint256 qy, address verifier) external returns (address signer) {
signer = getSigner(qx, qy, verifier);

if (_hasNoCode(signer) && _validVerifier(verifier)) {
WebAuthnSigner created = new WebAuthnSigner{salt: bytes32(0)}(qx, qy, verifier);
require(address(created) == signer);
}
}

/**
* @dev Checks if the given verifier address contains code.
* @param verifier The address of the verifier to check.
* @return A boolean indicating whether the verifier contains code or not.
*/
function _validVerifier(address verifier) internal view returns (bool) {
// The verifier should contain code (The only way to implement a webauthn verifier is with a smart contract)
return !_hasNoCode(verifier);
}

// @inheritdoc ICustom256BitECSignerFactory
function isValidSignatureForSigner(
uint256 qx,
uint256 qy,
address verifier,
bytes32 message,
bytes calldata signature
) external view override returns (bytes4 magicValue) {
if (checkSignature(verifier, message, signature, qx, qy)) {
magicValue = EIP1271_MAGIC_VALUE;
}
}

/**
* @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.
*/
function _hasNoCode(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
size := extcodesize(account)
}
return size == 0;
}

/**
* @dev Checks the validity of a signature using WebAuthnVerifier.
* @param verifier The address of the WebAuthnVerifier contract.
* @param dataHash The hash of the data being signed.
* @param signature The signature to be verified.
* @param qx The x-coordinate of the public key.
* @param qy The y-coordinate of the public key.
* @return A boolean indicating whether the signature is valid or not.
*/
function checkSignature(
address verifier,
bytes32 dataHash,
bytes calldata signature,
uint256 qx,
uint256 qy
) internal view returns (bool) {
SignatureData calldata signaturePointer;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
signaturePointer := signature.offset
}

return
IWebAuthnVerifier(verifier).verifyWebAuthnSignatureAllowMalleability(
signaturePointer.authenticatorData,
WebAuthnConstants.AUTH_DATA_FLAGS_UV,
dataHash,
signaturePointer.clientDataFields,
signaturePointer.rs,
qx,
qy
);
}
}
86 changes: 86 additions & 0 deletions modules/passkey/contracts/WebAuthnSignerFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;

import {ICustom256BitECSignerFactory} from "./interfaces/ICustomSignerFactory.sol";
import {IWebAuthnVerifier} from "./interfaces/IWebAuthnVerifier.sol";
import {ERC1271} from "./libraries/ERC1271.sol";
import {WebAuthnFlags} from "./libraries/WebAuthnFlags.sol";
import {WebAuthnSignature} from "./libraries/WebAuthnSignature.sol";
import {WebAuthnSigner} from "./WebAuthnSigner.sol";

/**
* @title WebAuthnSignerFactory
* @dev A factory contract for creating and managing WebAuthn signers.
*/
contract WebAuthnSignerFactory is ICustom256BitECSignerFactory {
// @inheritdoc ICustom256BitECSignerFactory
function getSigner(uint256 qx, uint256 qy, address verifier) public view override returns (address signer) {
bytes32 codeHash = keccak256(abi.encodePacked(type(WebAuthnSigner).creationCode, qx, qy, uint256(uint160(verifier))));
signer = address(uint160(uint256(keccak256(abi.encodePacked(hex"ff", address(this), bytes32(0), codeHash)))));
}

// @inheritdoc ICustom256BitECSignerFactory
function createSigner(uint256 qx, uint256 qy, address verifier) external returns (address signer) {
signer = getSigner(qx, qy, verifier);

if (_hasNoCode(signer) && _validVerifier(verifier)) {
WebAuthnSigner created = new WebAuthnSigner{salt: bytes32(0)}(qx, qy, verifier);
require(address(created) == signer);
}
}

// @inheritdoc ICustom256BitECSignerFactory
function isValidSignatureForSigner(
uint256 qx,
uint256 qy,
address verifier,
bytes32 message,
bytes calldata signature
) external view override returns (bytes4 magicValue) {
WebAuthnSignature.Data calldata data = WebAuthnSignature.cast(signature);

// Work around stack-too-deep issues by helping out the compiler figure out how to re-order
// the stack.
uint256 x = qx;
uint256 y = qy;

if (
IWebAuthnVerifier(verifier).verifyWebAuthnSignatureAllowMalleability(
data.authenticatorData,
WebAuthnFlags.USER_VERIFICATION,
message,
data.clientDataFields,
data.r,
data.s,
x,
y
)
) {
magicValue = ERC1271.MAGIC_VALUE;
}
}

/**
* @dev Checks if the given verifier address contains code.
* @param verifier The address of the verifier to check.
* @return A boolean indicating whether the verifier contains code or not.
*/
function _validVerifier(address verifier) internal view returns (bool) {
// The verifier should contain code (The only way to implement a webauthn verifier is with a smart contract)
return !_hasNoCode(verifier);
}

/**
* @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.
*/
function _hasNoCode(address account) internal view returns (bool) {
uint256 size;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
size := extcodesize(account)
}
return size == 0;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;

import {SignatureValidatorConstants} from "./SignatureValidatorConstants.sol";
import {ERC1271} from "../libraries/ERC1271.sol";

/**
* @title ISafeSigner
* @dev A interface for smart contract Safe owners that supports multiple `isValidSignature` versions.
* @title Signature Validator Base Contract
* @dev A interface for smart contract Safe owners that supports multiple ERC-1271 `isValidSignature` versions.
* @custom:security-contact [email protected]
*/
abstract contract SignatureValidator is SignatureValidatorConstants {
abstract contract SignatureValidator {
/**
* @dev Validates the signature for the given data.
* @param data The signed data bytes.
Expand All @@ -16,7 +17,7 @@ abstract contract SignatureValidator is SignatureValidatorConstants {
*/
function isValidSignature(bytes memory data, bytes calldata signature) external view returns (bytes4 magicValue) {
if (_verifySignature(keccak256(data), signature)) {
magicValue = LEGACY_EIP1271_MAGIC_VALUE;
magicValue = ERC1271.LEGACY_MAGIC_VALUE;
}
}

Expand All @@ -28,7 +29,7 @@ abstract contract SignatureValidator is SignatureValidatorConstants {
*/
function isValidSignature(bytes32 message, bytes calldata signature) external view returns (bytes4 magicValue) {
if (_verifySignature(message, signature)) {
magicValue = EIP1271_MAGIC_VALUE;
magicValue = ERC1271.MAGIC_VALUE;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ pragma solidity >=0.8.0 <0.9.0;
/**
* @title ICustomECSignerFactory
* @dev Interface for creating and verifying ECDSA signers. This is a generalized interface that should be
* compatible with curves of any order size. Currently not used in the project and exists here for reference.
* compatible with curves of any order size. Currently not used in the project and exists here for reference.
* @custom:security-contact [email protected]
*/
interface ICustomECSignerFactory {
/**
Expand Down Expand Up @@ -42,6 +43,7 @@ interface ICustomECSignerFactory {
/**
* @title ICustom256BitECSignerFactory
* @dev Interface for creating and verifying ECDSA signers using 256-bit elliptic curves.
* @custom:security-contact [email protected]
*/
interface ICustom256BitECSignerFactory {
/**
Expand Down
34 changes: 34 additions & 0 deletions modules/passkey/contracts/interfaces/IP256Verifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: LGPL-3.0-only
/* solhint-disable payable-fallback */
pragma solidity ^0.8.0;

/**
* @title P-256 Elliptic Curve Verifier.
* @dev P-256 verifier contract that follows the EIP-7212 EC verify precompile interface. For more
* details, refer to the EIP-7212 specification: <https://eips.ethereum.org/EIPS/eip-7212>
* @custom:security-contact [email protected]
*/
interface IP256Verifier {
/**
* @notice A fallback function that takes the following input format and returns a result
* indicating whether the signature is valid or not:
* - `input[ 0: 32]`: message
* - `input[ 32: 64]`: signature r
* - `input[ 64: 96]`: signature s
* - `input[ 96:128]`: public key x
* - `input[128:160]`: public key y
*
* The output is a Solidity ABI encoded boolean value indicating whether or not the signature is
* valid. Specifically, it returns 32 bytes with a value of `0x00..00` or `0x00..01` for an
* invalid or valid signature respectively.
*
* Note that this function does not follow the Solidity ABI format (in particular, it does not
* have a 4-byte selector), which is why it requires a fallback function and not regular
* Solidity function. Additionally, it has `view` function semantics, and is expected to be
* called with `STATICCALL` opcode.
*
* @param input The encoded input parameters.
* @return output The encoded signature verification result.
*/
fallback(bytes calldata input) external returns (bytes memory output);
}
2 changes: 1 addition & 1 deletion modules/passkey/contracts/interfaces/ISafe.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0 <0.9.0;

interface ISafeSetup {
interface ISafe {
function setup(
address[] calldata _owners,
uint256 _threshold,
Expand Down
Loading

0 comments on commit ebdc3c0

Please sign in to comment.