: ERC-191
은 이더리움에서 signed data를 어떻게 다룰 것인지에 대한 표준 정의이다.
(2016년 기준) Presigned transaction을 accept하는 Multisignature wallet의 implementation들이 나오게 되었다.
이러한 signed_data에 대한 interpretation에 대한 표준이 나오지 않아 다음의 문제들이 생긴다.
이더리움의 표준 transcation은 signed_data
로 제출되게 된다.
사실상 Off-chain 서명과 동일한 과정으로 이루어지며, '무엇'에 서명을 하는지의 차이일 뿐이다.
이 때 이더리움 transcation의 경우 RLP data와 그것에 서명을 한 r,s,v 값으로 이루어진다.
여기서 이 때, Off-chain 서명한 message값이 이더리움 transaction으로서의 RLP data에 valid하게 되면 내 서명이 ethereum transaction
으로 사용될 수 있다.
해당 ERC 문서에는 다음의 Multisig Wallte의 예시를 들고 있다.
지갑 X: A, B, C 가 사용
지갑 Y: A, B, D 가 사용
이 경우, A와 B가 지갑 X를 위해 서명한 내용이, 지갑 Y에 replay되어 사용될 수 있다.
0x19 <1 byte version> <version specific data> <data to sign>.
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).
EIP-191
(0x00
)
: validator를 명시하고 싶을 경우에 사용한다.
EIP-712
(0x01
)
: Structured data를 사용하고 싶을 경우에 사용한다.
(가장 흔하게 사용되는 구조이다. ERC20 Permit, ERC721 Permit 등에서 많이 사용된다.)
EIP-191
(0x45
)
: personal_sign
message
0x00
- validator0x19 <0x00> <intended validator address> <data to sign>
The data to sign could be any arbitrary data.
validator가 누군지만 명시하고 싶고, 그 뒤에는 arbitrary data를 붙여서 서명할 수 있다.
0x01
- EIP712이 부분은 다음 글인 EIP-712
에서 자세히 다룰 예정이다.
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);
}
Off-chain 서명은 복잡해보이지만 사실상 다음과 같은 과정으로 비교하게 된다.
signer
message
가 있다.keccak256(message)
로 messageHash
를 만든다signature
가 생긴다 (v, r, s값)message
와 signature
(v, r, s)값을 전달한다.verifier
ecrecover
를 돌린다.ecrecover
의 결과로 나온 address
가 signer(msg.sender)와 동일한지를 확인한다.참고로 verifier의 3번째 단계에서
msg.sender
가 반드시signer
라는 보장이 없으므로 (누군가 정당한 절차로 전달해주었을 수도 있으므로),이를 고려해야 하는 상황이면 msg.sender가 아닌 Openzeppelin의
Context.sol
의_msgSender
함수를 직접 implement해서 사용해야 한다.
항상 Off-chain 서명에 대해서 아리송 했는데 싹 정리를 해보니 앞에 붙는 Prefix들에 대해서 이해가 되기 시작한다.
이후 글에서는 더 많이 사용하는 EIP-712에 대해서 사용할 계획이다.