개요

signTypedData는 EIP-712 표준을 구현한 서명 메서드로 구조화된 데이터에 서명하기 위한 표준 인터페이스를 제공함 지갑은 사람이 읽을 수 있는 형태로 서명 내용을 표시하고, 서명은 특정 도메인과 체인에 귀속되어 재사용 공격을 줄임

signTypedData와 EIP-712의 관계

  • 정의
    • signTypedData는 EIP-712 규격을 따르는 구조화 데이터 서명 메서드
    • 이더리움 지갑 및 제공자에서 eth_signTypedData, eth_signTypedData_v3, eth_signTypedData_v4 형태로 노출
  • 버전
    • signTypedData 최초 버전
    • signTypedData_v3
    • signTypedData_v4 가장 널리 사용되는 최신 버전
  • 라이브러리 사용
    • ethers에서는 _signTypedData로 제공
const signature = await signer._signTypedData(domain, types, value)
  • 지갑 연동은 일반적으로 provider에 직접 요청하는 방식 사용
const signature = await ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [account, JSON.stringify({ domain, types, message: value })],
})

동작 원리 요약

  • 타입화된 구조체 정의
    • 예시 구조체와 필드 타입을 정형화해 명세
  • 도메인 분리자 사용
    • 이름, 버전, 체인 ID, 검증 컨트랙트 주소를 포함해 서명 범위 고정
  • 타입 해시와 데이터 해시 생성
    • 타입 정의를 keccak256으로 해싱 후 데이터 인코딩 해시 생성
  • 최종 해시에 서명 및 검증
    • 지갑에서 서명 생성, 컨트랙트에서 도메인과 타입을 동일하게 재현해 검증

사용 예시

  • 간단한 도메인, 타입, 메시지 구성 예시
const domain = { name: 'MyApp', version: '1', chainId, verifyingContract }
const types = { Action: [ { name: 'user', type: 'address' }, { name: 'amount', type: 'uint256' } ] }
const value = { user: userAddress, amount }

// ethers 서명
const sig = await signer._signTypedData(domain, types, value)

// 지갑 요청 v4 서명
const sig2 = await ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [account, JSON.stringify({ domain, types, message: value })],
})
  • 컨트랙트 검증 예시 요약
// OpenZeppelin EIP712, ECDSA 사용 가정
bytes32 digest = _hashTypedDataV4(
  keccak256(abi.encode(
    keccak256("Action(address user,uint256 amount)"),
    user,
    amount
  ))
)
require(ECDSA.recover(digest, signature) == signer, "Invalid signature")

EIP-712 핵심 개념 정리

  • 목적
    • 사람이 읽을 수 있는 서명 메시지 제공
    • 도메인에 귀속된 서명으로 리플레이 공격 저감
  • 구조
    • 타입화된 데이터 스키마와 도메인 분리자
    • 타입 해시와 데이터 해시를 조합한 최종 해시
  • 장점
    • 지갑 UI에서 의미 있는 정보 노출로 UX 개선
    • 다른 dApp이나 체인에서 재사용 어려움으로 보안 강화

주의사항과 팁

  • v4 사용 권장
    • v4는 가장 널리 지원되고 구조체 및 배열 표현이 안정적임
  • provider 요청 방식
    • web3 라이브러리 함수보다 ethereum.request의 eth_signTypedData_v4 사용이 호환성 측면에서 안전함
  • 타입 정의 일치
    • 컨트랙트와 클라이언트의 타입 이름, 필드 순서, 정수 크기 등 완전 일치 필요
  • 도메인 정합성
    • chainId와 verifyingContract가 실제 네트워크와 배포 주소와 일치해야 검증 성공
  • 데이터 인코딩
    • JS 측 정수값은 문자열 또는 BigNumber 형태 사용 권장, 오버플로와 반올림 이슈 회피
  • ethers 사용 시
    • types에 EIP712Domain을 포함하지 않음, _signTypedData가 도메인을 별도로 처리함
  • v4 메시지 포맷
    • params에 JSON.stringify로 { domain, types, message } 형태 전달 필요

마무리

signTypedData는 곧 EIP-712 사용을 의미하며 구조화된 데이터에 대한 안전한 서명을 가능하게 함 ethers의 _signTypedData 또는 지갑의 eth_signTypedData_v4를 사용해 서명하고, 컨트랙트에서는 동일한 도메인과 타입으로 해시를 재현해 검증하면 됨 명세 일치와 도메인 정합성만 확보하면 안전하고 예측 가능한 서명 흐름을 구현 가능

참고자료