ERC-191: Signed Data Standard

frenchkebab·2023년 5월 9일
0

EIP / Open Source

목록 보기
2/9
post-thumbnail

ERC-191

: ERC-191은 이더리움에서 signed data를 어떻게 다룰 것인지에 대한 표준 정의이다.

Motivation

(2016년 기준) Presigned transaction을 accept하는 Multisignature wallet의 implementation들이 나오게 되었다.

이러한 signed_data에 대한 interpretation에 대한 표준이 나오지 않아 다음의 문제들이 생긴다.

문제1: 내 서명이 트랜잭션으로 !?

이더리움의 표준 transcation은 signed_data로 제출되게 된다.

사실상 Off-chain 서명과 동일한 과정으로 이루어지며, '무엇'에 서명을 하는지의 차이일 뿐이다.

이 때 이더리움 transcation의 경우 RLP data와 그것에 서명을 한 r,s,v 값으로 이루어진다.

여기서 이 때, Off-chain 서명한 message값이 이더리움 transaction으로서의 RLP data에 valid하게 되면 내 서명이 ethereum transaction으로 사용될 수 있다.

문제2: Replay

해당 ERC 문서에는 다음의 Multisig Wallte의 예시를 들고 있다.

지갑 X: A, B, C 가 사용
지갑 Y: A, B, D 가 사용

이 경우, A와 B가 지갑 X를 위해 서명한 내용이, 지갑 Y에 replay되어 사용될 수 있다.

Specification

0x19 <1 byte version> <version specific data> <data to sign>.

1. 0x19 prefix: "이거슨 RLP 구조가 아니다!"

For a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding.

이 말은 1 byte 데이터 + 그 값의 범위가 [0x00, 0x7f] 일 경우, RLP encoding이 되어도 그 자신임을 의미한다.

근데 우리의 경우 0x19 이후에 서명할 메세지 내용이 붙을 것이므로, single byte data가 아니게 되어 RLP encoding 규칙에 어긋나게 된다.

따라서 이것이 RLP encoding이 아님을 알리기 위한 Prefix로서 붙는 것이다.

추후에 personal_sign에서는 다음의 접두사가 또한 붙게 되었다.

"\x19Ethereum Signed Message:\n" + len(message).

2. <1 byte version>

  1. EIP-191 (0x00)
    : validator를 명시하고 싶을 경우에 사용한다.

  2. EIP-712 (0x01)
    : Structured data를 사용하고 싶을 경우에 사용한다.
    (가장 흔하게 사용되는 구조이다. ERC20 Permit, ERC721 Permit 등에서 많이 사용된다.)

  3. EIP-191 (0x45)
    : personal_sign message

1) Version 0x00 - validator

0x19 <0x00> <intended validator address> <data to sign>

The data to sign could be any arbitrary data.

validator가 누군지만 명시하고 싶고, 그 뒤에는 arbitrary data를 붙여서 서명할 수 있다.

2) Version 0x01 - EIP712

이 부분은 다음 글인 EIP-712에서 자세히 다룰 예정이다.

3) Version 0x45 - personal_sign (regular signed message)

0x19 <0x45 (E)> <thereum Signed Message:\n" + len(message)> <data to sign>

0x45를 string으로 변환하면 'E'라서

0x19 0x45

뒤에 그냥

<thereum Signed Message:\n" + len(message)> <data to sign>

만 붙여주는 형식이다.

이 글을 보면, 대부분 은 그냥 length를 32로 박아버리고 다음과 같이 부분에 message의 hash값을 사용하고 있다.

  "\x19Ethereum Signed Message:\n32" + Keccak256(message)

예시

function signatureBasedExecution(address target, uint256 nonce, bytes memory payload, uint8 v, bytes32 r, bytes32 s) public payable {
        
    // Arguments when calculating hash to validate
    // 1: byte(0x19) - the initial 0x19 byte
    // 2: byte(0) - the version byte
    // 3: address(this) - the validator address
    // 4-6 : Application specific data

    bytes32 hash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), msg.value, nonce, payload));

    // recovering the signer from the hash and the signature
    addressRecovered = ecrecover(hash, v, r, s);
   
    // logic of the wallet
    // if (addressRecovered == owner) executeOnTarget(target, payload);
}  

Verify의 과정

Off-chain 서명은 복잡해보이지만 사실상 다음과 같은 과정으로 비교하게 된다.

signer

  1. 서명할 message가 있다.
  2. keccak256(message)messageHash를 만든다
  3. user가 messageHash에 자신의 Private Key로 서명을 하고 signature가 생긴다 (v, r, s값)
  4. 원본 messagesignature (v, r, s)값을 전달한다.

verifier

  1. 받은 원본 message를 keccak256으로 hashing한다.
  2. 해당 hash값과 v, r, s 값으로 ecrecover를 돌린다.
    (v, r, s 값으로 signer의 Public Key를 계산해내서 decrypt함)
  3. ecrecover의 결과로 나온 address가 signer(msg.sender)와 동일한지를 확인한다.

참고로 verifier의 3번째 단계에서 msg.sender가 반드시 signer라는 보장이 없으므로 (누군가 정당한 절차로 전달해주었을 수도 있으므로),

이를 고려해야 하는 상황이면 msg.sender가 아닌 Openzeppelin의 Context.sol_msgSender 함수를 직접 implement해서 사용해야 한다.

결론

항상 Off-chain 서명에 대해서 아리송 했는데 싹 정리를 해보니 앞에 붙는 Prefix들에 대해서 이해가 되기 시작한다.

이후 글에서는 더 많이 사용하는 EIP-712에 대해서 사용할 계획이다.

profile
Solidity에 대해 공부하고 있습니다.

0개의 댓글