library TypedSignature {

    // Solidity does not offer guarantees about enum values, so we define them explicitly
    uint8 private constant SIGTYPE_INVALID = 0;
    uint8 private constant SIGTYPE_ECRECOVER_DEC = 1;
    uint8 private constant SIGTYPE_ECRECOVER_HEX = 2;
    uint8 private constant SIGTYPE_UNSUPPORTED = 3;

    // prepended message with the length of the signed hash in hexadecimal
    bytes constant private PREPEND_HEX = "\x19Ethereum Signed Message:\n\x20";

    // prepended message with the length of the signed hash in decimal
    bytes constant private PREPEND_DEC = "\x19Ethereum Signed Message:\n32";

    /**
     * Gives the address of the signer of a hash. Allows for three common prepended strings.
     *
     * @param  hash               Hash that was signed (does not include prepended message)
     * @param  signatureWithType  Type and ECDSA signature with structure: {1:type}{1:v}{32:r}{32:s}
     * @return                    address of the signer of the hash
     */
    function recover(
        bytes32 hash,
        bytes signatureWithType
    )
        internal
        pure
        returns (address)
    {
        require(
            signatureWithType.length == 66,
            "SignatureValidator#validateSignature: invalid signature length"
        );

        uint8 sigType = uint8(signatureWithType[0]);

        require(
            sigType > uint8(SIGTYPE_INVALID),
            "SignatureValidator#validateSignature: invalid signature type"
        );
        require(
            sigType < uint8(SIGTYPE_UNSUPPORTED),
            "SignatureValidator#validateSignature: unsupported signature type"
        );

        uint8 v = uint8(signatureWithType[1]);
        bytes32 r;
        bytes32 s;

        /* solium-disable-next-line security/no-inline-assembly */
        assembly {
            r := mload(add(signatureWithType, 34))
            s := mload(add(signatureWithType, 66))
        }

        bytes32 signedHash;
        if (sigType == SIGTYPE_ECRECOVER_DEC) {
            signedHash = keccak256(abi.encodePacked(PREPEND_DEC, hash));
        } else {
            assert(sigType == SIGTYPE_ECRECOVER_HEX);
            signedHash = keccak256(abi.encodePacked(PREPEND_HEX, hash));
        }

        return ecrecover(
            signedHash,
            v,
            r,
            s
        );
    }
}
/**
 * @title TypedSignature
 * @author dYdX
 *
 * Allows for ecrecovery of signed hashes with three different prepended messages:
 * 1) ""
 * 2) "\x19Ethereum Signed Message:\n32"
 * 3) "\x19Ethereum Signed Message:\n\x20"
 */
function recover(
    bytes32 hash,
    bytes signatureWithType
)
    internal
    pure
    returns (address)
{
    require(
        signatureWithType.length == 66,
        "SignatureValidator#validateSignature: invalid signature length"
    );

    uint8 sigType = uint8(signatureWithType[0]);

    require(
        sigType > uint8(SIGTYPE_INVALID),
        "SignatureValidator#validateSignature: invalid signature type"
    );
    require(
        sigType < uint8(SIGTYPE_UNSUPPORTED),
        "SignatureValidator#validateSignature: unsupported signature type"
    );

    uint8 v = uint8(signatureWithType[1]);
    bytes32 r;
    bytes32 s;

    /* solium-disable-next-line security/no-inline-assembly */
    assembly {
        r := mload(add(signatureWithType, 34))
        s := mload(add(signatureWithType, 66))
    }

    bytes32 signedHash;
    if (sigType == SIGTYPE_ECRECOVER_DEC) {
        signedHash = keccak256(abi.encodePacked(PREPEND_DEC, hash));
    } else {
        assert(sigType == SIGTYPE_ECRECOVER_HEX);
        signedHash = keccak256(abi.encodePacked(PREPEND_HEX, hash));
    }

    return ecrecover(
        signedHash,
        v,
        r,
        s
    );
}

/**
 * Gives the address of the signer of a hash. Allows for three common prepended strings.
 *
 * @param  hash               Hash that was signed (does not include prepended message)
 * @param  signatureWithType  Type and ECDSA signature with structure: {1:type}{1:v}{32:r}{32:s}
 * @return                    address of the signer of the hash
 */