pragma solidity 0.8.20;

contract SigUtils {
    bytes32 internal immutable DOMAIN_SEPARATOR;

    constructor(bytes32 domainSeparator) {
        DOMAIN_SEPARATOR = domainSeparator;
    }

    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH =
        0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    struct Permit {
        address owner;
        address spender;
        uint256 value;
        uint256 nonce;
        uint256 deadline;
    }

    // computes the hash of a permit
    function getStructHash(Permit memory _permit)
        internal
        pure
        returns (bytes32 structHash)
    {
        assembly {
            let ptr := mload(0x40)

            mstore(ptr, PERMIT_TYPEHASH)                    // PERMIT_TYPEHASH
            mstore(add(ptr, 32),  mload(_permit))           // owner
            mstore(add(ptr, 64),  mload(add(_permit, 32)))  // spender
            mstore(add(ptr, 96),  mload(add(_permit, 64)))  // value
            mstore(add(ptr, 128), mload(add(_permit, 96)))  // nonce
            mstore(add(ptr, 160), mload(add(_permit, 128))) // deadline

            structHash := keccak256(ptr, 192)
        }
    }

    // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer
    function getTypedDataHash(Permit memory _permit)
        public
        view
        returns (bytes32 digest)
    {
        bytes32 structHash = getStructHash(_permit);
        bytes32 domainSeparator = DOMAIN_SEPARATOR;
        assembly {
            let ptr := mload(0x40)

            mstore(ptr, 0x1901000000000000000000000000000000000000000000000000000000000000) // \x19\x01
            mstore(add(ptr, 2), domainSeparator)
            mstore(add(ptr, 34), structHash)

            digest := keccak256(ptr, 66)
        }
    }
}
