개요
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를 사용해 서명하고, 컨트랙트에서는 동일한 도메인과 타입으로 해시를 재현해 검증하면 됨 명세 일치와 도메인 정합성만 확보하면 안전하고 예측 가능한 서명 흐름을 구현 가능