// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.12; /* solhint-disable reason-string */ /* solhint-disable no-inline-assembly */ 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 the account-specific signature: * - the paymaster checks a signature to agree to PAY for GAS. * - the account checks a signature to prove identity and account ownership. */ contract VerifyingPaymaster is BasePaymaster { using ECDSA for bytes32; using UserOperationLib for UserOperation; address public immutable verifyingSigner; uint256 private constant VALID_TIMESTAMP_OFFSET = 20; uint256 private constant SIGNATURE_OFFSET = 84; constructor(IEntryPoint _entryPoint, address _verifyingSigner) BasePaymaster(_entryPoint) { verifyingSigner = _verifyingSigner; } mapping(address => uint256) public senderNonce; function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) { // lighter signature scheme. must match UserOp.ts#packUserOp bytes calldata pnd = userOp.paymasterAndData; // copy directly the userOp from calldata up to (but not including) the paymasterAndData. // this encoding depends on the ABI encoding of calldata, but is much lighter to copy // than referencing each field separately. assembly { let ofs := userOp let len := sub(sub(pnd.offset, ofs), 32) ret := mload(0x40) mstore(0x40, add(ret, add(len, 32))) mstore(ret, len) calldatacopy(add(ret, 32), ofs, len) } } /** * 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, uint48 validUntil, uint48 validAfter) public view returns (bytes32) { //can't use userOp.hash(), since it contains also the paymasterAndData itself. return keccak256(abi.encode( pack(userOp), block.chainid, address(this), senderNonce[userOp.getSender()], validUntil, validAfter )); } /** * verify our external signer signed this request. * the "paymasterAndData" is expected to be the paymaster and a signature over the entire request params * paymasterAndData[:20] : address(this) * paymasterAndData[20:84] : abi.encode(validUntil, validAfter) * paymasterAndData[84:] : signature */ function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 requiredPreFund) internal override returns (bytes memory context, uint256 validationData) { (requiredPreFund); (uint48 validUntil, uint48 validAfter, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData); //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(signature.length == 64 || signature.length == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); bytes32 hash = ECDSA.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter)); senderNonce[userOp.getSender()]++; //don't revert on signature failure: return SIG_VALIDATION_FAILED if (verifyingSigner != ECDSA.recover(hash, signature)) { return ("",_packValidationData(true,validUntil,validAfter)); } //no need for other on-chain validation: entire UserOp should have been checked // by the external service prior to signing it. return ("",_packValidationData(false,validUntil,validAfter)); } function parsePaymasterAndData(bytes calldata paymasterAndData) public pure returns(uint48 validUntil, uint48 validAfter, bytes calldata signature) { (validUntil, validAfter) = abi.decode(paymasterAndData[VALID_TIMESTAMP_OFFSET:SIGNATURE_OFFSET],(uint48, uint48)); signature = paymasterAndData[SIGNATURE_OFFSET:]; } }