// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.12; /* solhint-disable reason-string */ import "../core/BasePaymaster.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /** * A sample paymaster that uses external service to decide whether to pay for the UserOp. * The paymaster trusts an external signer to sign the transaction. * The calling user must pass the UserOp to that external signer first, which performs * whatever off-chain verification before signing the UserOp. * Note that this signature is NOT a replacement for wallet signature: * - the paymaster signs to agree to PAY for GAS. * - the wallet signs to prove identity and wallet ownership. */ contract VerifyingPaymasterSample is BasePaymaster { using ECDSA for bytes32; using UserOperationLib for UserOperation; address public verifyingSigner; constructor(IEntryPoint _entryPoint, address _verifyingSigner) BasePaymaster(_entryPoint) { verifyingSigner = _verifyingSigner; } function setNewSigner(address _newVerifyingSigner) public onlyOwner { verifyingSigner = _newVerifyingSigner; } /** * return the hash we're going to sign off-chain (and validate on-chain) * this method is called by the off-chain service, to sign the request. * it is called on-chain from the validatePaymasterUserOp, to validate the signature. * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", * which will carry the signature itself. */ function getHash(UserOperation calldata userOp) public pure returns (bytes32) { //can't use userOp.hash(), since it contains also the paymasterAndData itself. return keccak256(abi.encode( userOp.getSender(), userOp.nonce, keccak256(userOp.initCode), keccak256(userOp.callData), userOp.callGasLimit, userOp.verificationGasLimit, userOp.preVerificationGas, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas )); } /** * verify our external signer signed this request. * the "paymasterAndData" is expected to be the paymaster and a signature over the entire request params */ function validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*requestId*/, uint256 requiredPreFund) external view override returns (bytes memory context) { (requiredPreFund); bytes32 hash = getHash(userOp); bytes calldata paymasterAndData = userOp.paymasterAndData; uint256 sigLength = paymasterAndData.length - 20; //ECDSA library supports both 64 and 65-byte long signatures. // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA" require(sigLength == 64 || sigLength == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); require(verifyingSigner == hash.toEthSignedMessageHash().recover(paymasterAndData[20:]), "VerifyingPaymaster: wrong signature"); //no need for other on-chain validation: entire UserOp should have been checked // by the external service prior to signing it. return ""; } }