ERC-191 서명 데이터 표준 (Signed Data Standard)

Final · 서명·인증 · 2016-01-20 작성

컨트랙트가 검증할 서명 데이터의 앞머리를 0x19와 버전 바이트로 통일합니다. 서명한 데이터가 이더리움 거래로 둔갑하거나 다른 지갑에서 재사용되는 일을 막습니다.

왜 필요한가

여러 다중서명 지갑(multisignature wallet)이 미리 서명된 거래(presigned transaction)를 받아 실행합니다. 미리 서명된 거래는 서명할 데이터 덩어리와 서명값 r, s, v로 이루어지는데, 이 데이터를 어떻게 해석할지가 정해져 있지 않았습니다. 그래서 두 가지 문제가 생겼습니다. 첫째, 보통의 이더리움 거래를 그대로 서명 데이터로 제출할 수 있습니다. 거래를 풀면 nonce, gasPrice, startGas, to, value, data를 묶은 RLP 데이터와 r, s, v가 나오는데, 서명 데이터에 형식 제약이 없으면 이 RLP 데이터가 형식상 유효한 미리 서명된 거래로 통합니다. 둘째, 미리 서명된 거래가 특정 검증자(validator), 곧 특정 지갑에 묶여 있지 않았습니다. A, B, C가 2/3 지갑 X를 갖고 A, B, D가 2/3 지갑 Y를 가진 상황에서, A와 B가 X에 보낸 서명을 공격자가 그대로 Y에 제출할 수 있습니다.

구현해야 하는 것

심층 설명

서명 데이터의 기본 형식

서명 데이터(signed_data)는 0x19 <1 byte version> <version specific data> <data to sign> 순서로 만듭니다. 맨 앞에 1바이트 0x19, 그다음 1바이트 버전, 버전마다 정해진 데이터, 마지막에 실제로 서명할 데이터가 옵니다. 버전별 데이터의 모양은 버전 바이트에 따라 달라지고 서명할 데이터는 어떤 내용이든 올 수 있습니다. 컨트랙트는 서명을 검증할 때 이 순서대로 바이트를 이어 붙여 해시한 뒤 서명과 대조합니다.

0x19를 고른 이유

0x19는 서명 데이터가 유효한 RLP가 되지 않게 하려고 골랐습니다. RLP에서 값이 0x00부터 0x7f 사이인 1바이트는 그 바이트 자체가 자기 RLP 인코딩입니다. 따라서 0x19로 시작하는 서명 데이터는 하나의 RLP 구조가 될 수 없고 1바이트짜리 RLP 페이로드 뒤에 다른 무언가가 붙은 모양이 됩니다. 그래서 이 표준을 따르는 서명 데이터는 결코 이더리움 거래가 될 수 없습니다. 또 go-ethereum의 변경(ethereum/go-ethereum#2940) 이후 personal_sign은 해시 전에 "\x19Ethereum Signed Message:\n" + len(message)를 앞에 붙입니다. 이 머리말도 0x19로 시작하므로, 버전 0x45(E)를 정의해 이런 서명까지 같은 틀로 다룰 수 있습니다.

버전 바이트 등록표

원문은 버전 바이트 세 개를 등록해 두었습니다. 0x00은 이 표준(EIP-191)의 것으로, 의도한 검증자를 담은 데이터에 씁니다. 0x01은 EIP-712의 것으로, 구조화 데이터에 씁니다. 0x45는 이 표준의 것으로, personal_sign 메시지에 씁니다. 버전 바이트 뒤에 오는 버전별 데이터의 모양은 버전마다 따로 정합니다. 0x19 뒤의 버전 바이트를 바꾸는 방식으로 새 용도를 더할 수 있게 설계되어 있습니다.

버전 0x00: 의도한 검증자를 담은 데이터

형식은 0x19 <0x00> <intended validator address> <data to sign>입니다. 버전별 데이터 자리에 의도한 검증자 주소(intended validator address)가 들어갑니다. 전달받은 서명을 근거로 실행하는 다중서명 지갑이라면 검증자 주소는 그 다중서명 지갑 자신의 주소입니다. 서명할 데이터는 어떤 내용이든 됩니다. 지갑 주소가 해시에 들어가므로 지갑 X를 위해 만든 서명은 지갑 Y에서 해시가 맞지 않아 쓸 수 없습니다.

버전 0x01과 0x45: EIP-712와 personal_sign

버전 0x01은 EIP-712에서 정의한 구조화 데이터(structured data)를 위한 것입니다. 이 문서는 버전 바이트만 등록하고 세부 형식은 EIP-712에 맡깁니다. 버전 0x45(E)의 형식은 0x19 <0x45 (E)> <thereum Signed Message:\n" + len(message)> <data to sign>입니다. personal_sign이 붙이는 머리말 "\x19Ethereum Signed Message:\n" + len(message)에서 첫 글자 E가 16진수로 0x45이므로, E가 버전 바이트 역할을 하고 나머지 thereum Signed Message:\n + len(message)가 버전별 데이터가 됩니다. 서명할 데이터는 어떤 내용이든 됩니다.

예시: 버전 0x00으로 서명 기반 실행

원문 예시는 Solidity 0.8.0으로 쓰였습니다. signatureBasedExecution 함수는 target, nonce, payload와 서명값 v, r, s를 받습니다. 검증용 해시는 keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), msg.value, nonce, payload))로 만듭니다. 차례대로 0x19, 버전 바이트 0, 검증자 주소인 address(this), 그리고 응용별 데이터인 msg.value, nonce, payload입니다. 이 해시와 v, r, s를 ecrecover에 넣어 서명자 주소를 복원합니다. 복원된 주소가 소유자이면 target에 payload를 실행하는 식으로 지갑 로직을 잇습니다.

ERC 원문 · ← ERCs