체인의정석

이더리움에서 데이터 서명관련 EIP 정리 (EIP1271 & EIP2098) 본문

블록체인/Solidity

이더리움에서 데이터 서명관련 EIP 정리 (EIP1271 & EIP2098)

체인의정석 2022. 6. 27. 16:12
728x90
반응형

호출 부분

   /**
     * @dev Internal view function to verify the signature of an order. An
     *      ERC-1271 fallback will be attempted if either the signature length
     *      is not 32 or 33 bytes or if the recovered signer does not match the
     *      supplied offerer. Note that in cases where a 32 or 33 byte signature
     *      is supplied, only standard ECDSA signatures that recover to a
     *      non-zero address are supported.
     *
     * @param offerer   The offerer for the order.
     * @param orderHash The order hash.
     * @param signature A signature from the offerer indicating that the order
     *                  has been approved.
     */
    function _verifySignature(
        address offerer,
        bytes32 orderHash,
        bytes memory signature
    ) internal view {
        // Skip signature verification if the offerer is the caller.
        if (offerer == msg.sender) {
            return;
        }

        // Derive EIP-712 digest using the domain separator and the order hash.
        bytes32 digest = _deriveEIP712Digest(_domainSeparator(), orderHash);

        // Ensure that the signature for the digest is valid for the offerer.
        _assertValidSignature(offerer, digest, signature);
    }

 

Derive EIP712 => domainSeparator() 를 통해 구분 지으며, orderHash와 같이 서명하여 서명에 대한 구분 진행

   /**
     * @dev Internal pure function to efficiently derive an digest to sign for
     *      an order in accordance with EIP-712.
     *
     * @param domainSeparator The domain separator.
     * @param orderHash       The order hash.
     *
     * @return value The hash.
     */
    function _deriveEIP712Digest(bytes32 domainSeparator, bytes32 orderHash)
        internal
        pure
        returns (bytes32 value)
    {
        // Leverage scratch space to perform an efficient hash.
        assembly {
            // Place the EIP-712 prefix at the start of scratch space.
            mstore(0, EIP_712_PREFIX)

            // Place the domain separator in the next region of scratch space.
            mstore(EIP712_DomainSeparator_offset, domainSeparator)

            // Place the order hash in scratch space, spilling into the first
            // two bytes of the free memory pointer — this should never be set
            // as memory cannot be expanded to that size, and will be zeroed out
            // after the hash is performed.
            mstore(EIP712_OrderHash_offset, orderHash)

            // Hash the relevant region (65 bytes).
            value := keccak256(0, EIP712_DigestPayload_size)

            // Clear out the dirtied bits in the memory pointer.
            mstore(EIP712_OrderHash_offset, 0)
        }
    }

 

 

 

1. 컨트렉트 대상인 경우 

signer.code.length > 0

스마트 컨트렉트 지갑인 경우 EIP1271

EIP1271은 스마트컨트렉트 형태의 지갑 주소에 대한 서명이 유효한지 검증해 주는 컨트렉트이다.

 

    /**
     * @dev Internal view function to verify the signature of an order using
     *      ERC-1271 (i.e. contract signatures via `isValidSignature`).
     *
     * @param signer    The signer for the order.
     * @param digest    The signature digest, derived from the domain separator
     *                  and the order hash.
     * @param signature A signature (or other data) used to validate the digest.
     */
    function _assertValidEIP1271Signature(
        address signer,
        bytes32 digest,
        bytes memory signature
    ) internal view {
        if (
            EIP1271Interface(signer).isValidSignature(digest, signature) !=
            EIP1271Interface.isValidSignature.selector
        ) {
            revert InvalidSigner();
        }
    }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

interface ERC20ApprovalInterface {
    function approve(address, uint256) external returns (bool);
}

interface NFTApprovalInterface {
    function setApprovalForAll(address, bool) external;
}

contract EIP1271Wallet {
    bytes4 private constant _EIP_1271_MAGIC_VALUE = 0x1626ba7e;

    address public immutable owner;

    bool public showRevertMessage;

    mapping(bytes32 => bool) public digestApproved;

    bool public isValid;

    constructor(address _owner) {
        owner = _owner;
        showRevertMessage = true;
        isValid = true;
    }

    function setValid(bool valid) external {
        isValid = valid;
    }

    function revertWithMessage(bool showMessage) external {
        showRevertMessage = showMessage;
    }

    function registerDigest(bytes32 digest, bool approved) external {
        digestApproved[digest] = approved;
    }

    function approveERC20(
        ERC20ApprovalInterface token,
        address operator,
        uint256 amount
    ) external {
        if (msg.sender != owner) {
            revert("Only owner");
        }

        token.approve(operator, amount);
    }

    function approveNFT(NFTApprovalInterface token, address operator) external {
        if (msg.sender != owner) {
            revert("Only owner");
        }

        token.setApprovalForAll(operator, true);
    }

    function isValidSignature(bytes32 digest, bytes memory signature)
        external
        view
        returns (bytes4)
    {
        if (digestApproved[digest]) {
            return _EIP_1271_MAGIC_VALUE;
        }

        // NOTE: this is obviously not secure, do not use outside of testing.
        if (signature.length == 64) {
            // All signatures of length 64 are OK as long as valid is true
            return isValid ? _EIP_1271_MAGIC_VALUE : bytes4(0xffffffff);
        }

        if (signature.length != 65) {
            revert();
        }

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }

        if (
            uint256(s) >
            0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
        ) {
            revert();
        }

        if (v != 27 && v != 28) {
            revert();
        }

        address signer = ecrecover(digest, v, r, s);

        if (signer == address(0)) {
            revert();
        }

        if (signer != owner) {
            if (showRevertMessage) {
                revert("BAD SIGNER");
            }

            revert();
        }

        return isValid ? _EIP_1271_MAGIC_VALUE : bytes4(0xffffffff);
    }
}

 

 

2. EIP 2098 서명

signature.length == 64

서명 코드

            // If signature contains 64 bytes, parse as EIP-2098 signature. (r+s&v)
            // Declare temporary vs that will be decomposed into s and v.
            bytes32 vs;

            (r, vs) = abi.decode(signature, (bytes32, bytes32));

            s = vs & EIP2098_allButHighestBitMask;

            v = uint8(uint256(vs >> 255)) + 27;

 

// Signature-related
bytes32 constant EIP2098_allButHighestBitMask = (
    0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);

EIP 2098  설명

secp256k1 곡선은 서명과 결합될 때 서명된 다이제스트의 공개 키 계산을 허용하며, 이는 메타 트랜잭션 및 다중 서명 계약과 같은 EVM 계약의 온체인뿐만 아니라 외부 소유 계정에서 트랜잭션의 원점을 설정하는 데 암묵적으로 사용된다.

현재 시그니처는 표현하기 위해 65바이트가 필요하며, 256비트 워드에 정렬될 경우 96바이트가 필요하다. 또한 RLP 인코딩 트랜잭션의 yParity에는 (평균) 1.5바이트가 필요합니다. 콤팩트 서명의 경우 64바이트로 줄일 수 있으며, 워드 정렬 시 64바이트로 유지되며, RLP 인코딩 트랜잭션의 경우 yParity에 필요한 1.5바이트를 절약할 수 있다.

 

=> 65 바이트 인 경우 일반적인 서명이며.

=> 64 바이트 인 경우 콤팩트 서명이다. 워드 정렬을 한 서명이다. 

 

일반 EOA 주소로 이루어진 서명이 여기에 해당된다.

 

728x90
반응형
Comments