EIP-712는 이더리움에서 구조화된 데이터에 서명하는 표준을 정의합니다. 기존의 raw 해시 서명과 달리, MetaMask 같은 지갑이 서명 내용을 "이 주소에게 100 USDC를 승인합니다"처럼 사람이 읽을 수 있게 보여줄 수 있습니다. Permit(ERC-2612), Seaport(NFT 거래), Uniswap Permit2 등 현대 DeFi의 핵심 서명 메커니즘이 EIP-712를 기반으로 합니다.
도메인 구분자(Domain Separator)
도메인 구분자는 서명이 특정 애플리케이션/체인/버전에만 유효하도록 바인딩합니다. DOMAIN_SEPARATOR = hashStruct(EIP712Domain { name, version, chainId, verifyingContract, salt }). 예: name="Uniswap V2", version="1", chainId=1, verifyingContract=0x주소. 도메인이 다르면 서명을 재사용할 수 없으므로 크로스-도메인 리플레이 공격을 방지합니다.
typeHash와 hashStruct
typeHash는 타입 문자열의 keccak256 해시입니다. 예: TYPE_HASH = keccak256("Transfer(address to,uint256 amount)"). hashStruct(s) = keccak256(typeHash ++ encodeData(s)). encodeData는 구조체의 각 필드를 ABI 인코딩 규칙으로 직렬화합니다. bytes, string 같은 동적 타입은 keccak256 해시로 대체하고, 중첩 구조체는 재귀적으로 hashStruct합니다.
최종 서명 해시
서명할 최종 해시는 EIP-191의 버전 0x01 형식을 따릅니다: signingHash = keccak256("\x19\x01" ++ DOMAIN_SEPARATOR ++ hashStruct(message)). 이 해시를 ECDSA로 서명하면 (v, r, s)를 얻습니다. 스마트 컨트랙트는 이 서명을 검증할 때 ecrecover(signingHash, v, r, s)로 서명자 주소를 복원하고 예상 주소와 비교합니다.
MetaMask에서의 표현
MetaMask(및 대부분의 지갑)는 eth_signTypedData_v4 API를 통해 EIP-712 서명 요청을 받으면 구조화된 형태로 표시합니다. 사용자는 "Permit: 허가 대상: 0x주소, 금액: 1000 USDC, 만료: 2026-01-01"처럼 각 필드와 값을 명확히 볼 수 있습니다. 이는 사용자가 자신이 무엇에 서명하는지 이해할 수 있게 해 피싱 공격을 크게 줄입니다.
실용 예시: ERC-2612 Permit
ERC-2612 permit 함수는 EIP-712를 사용해 가스비 없는 토큰 승인을 구현합니다. 사용자는 {owner, spender, value, nonce, deadline} 구조체를 EIP-712로 서명해 오프체인에서 전달합니다. 프로토콜은 이 서명을 온체인에서 검증한 뒤 allowance를 업데이트합니다. 이로써 approve 트랜잭션 없이도 토큰을 위임할 수 있고, 사용자는 가스비를 절약할 수 있습니다.