EIP-8 devp2p 상위 호환성 요구사항 (devp2p Forward Compatibility Requirements for Homestead)

최종 확정 · 네트워크·노드 · 원문 상태 Final · 2015-12-18 작성

이더리움 노드끼리 연결할 때 쓰는 네트워크 프로토콜(devp2p)에 상위 호환 규칙을 넣어, 상대의 버전이 달라도 연결을 끊지 않고 모르는 데이터는 무시하게 만듭니다.

왜 필요한가

이더리움 노드는 devp2p라는 네트워크 프로토콜로 서로 연결합니다. 기존 클라이언트는 상대가 보낸 인사(hello) 패킷의 버전 번호나 구조가 자기 기대와 다르면 연결을 거부했습니다. 이 때문에 프로토콜에 새 버전이나 새 항목을 더하는 일이 사실상 불가능했습니다. 옛 버전을 쓰는 노드가 새 패킷을 전부 거절하기 때문입니다. 이 EIP는 홈스테드(Homestead) 합의 업그레이드에 맞춰 "보낼 때는 보수적으로, 받을 때는 너그럽게"라는 포스텔의 법칙(Postel's Law)을 프로토콜 전반에 적용합니다. 이렇게 하면 하위 호환이 유지되는 한, 네트워크에서 쓰이는 모든 클라이언트가 앞으로의 네트워크 프로토콜 업그레이드를 감당할 수 있게 됩니다.

무엇이 바뀌나

지금

EIP-8 이후

누구에게 영향이 있나

심층 설명

devp2p 와이어 프로토콜: hello 버전 검사 제거

구현은 hello 패킷의 버전 번호를 무시해야 하고, 보낼 때는 지원하는 가장 높은 devp2p 버전을 version 요소에 넣습니다. hello 패킷 끝의 추가 리스트 요소도 무시합니다. 기존 클라이언트들은 pydevp2p의 예시처럼 버전이 다르면 incompatible_p2p_version 사유로 연결을 끊는 검사를 두고 있었는데, 이 검사를 없애면 새 버전 클라이언트가 더 높은 버전과 추가 요소를 담은 패킷을 그냥 보낼 수 있습니다. 낮은 버전 노드는 상대가 하위 호환이라고 보고 구형 핸드셰이크로 응답하고, 같은 버전 노드는 새 기능을 쓰고, 높은 버전 노드는 하위 호환 로직을 켜거나 연결을 끊을 수 있습니다.

RLPx 디스커버리 프로토콜: 디코딩 규칙 완화

ping 패킷의 버전 번호를 검증하지 않고 모든 패킷에서 추가 리스트 요소를 무시하며 첫 RLP 값 뒤에 오는 데이터도 무시합니다. 알 수 없는 패킷 타입의 디스커버리 패킷은 조용히 버립니다. 디스커버리 패킷의 최대 크기는 그대로 1280바이트입니다. 이 완화는 대체로 당시 관행을 명문화한 것으로, go-ethereum을 예외로 하면 대부분의 구현이 이미 리스트 요소 개수를 따지지 않고 버전 불일치 노드를 거부하지 않았습니다. 다만 그 동작이 명세로 보장되지 않았던 것을 보장하도록 바꿨습니다.

RLPx TCP 핸드셰이크의 새 인코딩

auth-vsn = 4이고, auth-size는 enc-auth-body의 크기를 빅엔디안 16비트 정수로 인코딩한 값입니다. auth-body = rlp.list(sig, initiator-pubk, initiator-nonce, auth-vsn)이고, enc-auth-body = ecies.encrypt(recipient-pubk, auth-body, auth-size)이며, auth-packet = auth-size || enc-auth-body입니다. ack 쪽도 같은 구조로 ack-vsn = 4, ack-body = rlp.list(recipient-ephemeral-pubk, recipient-nonce, ack-vsn)입니다. 여기서 auth-size와 ack-size는 AUTHDATA로서 암호문에는 들어가지 않지만 HMAC-256 메시지 태그 생성에 들어갑니다. auth-body와 ack-body의 RLP를 디코딩할 때는 auth-vsn과 ack-vsn의 불일치, 추가 리스트 요소, 리스트 뒤 꼬리 데이터를 모두 무시합니다.

전환기 패딩과 신구 형식 판별

구 형식이 은퇴하기 전까지의 전환기 동안 구현은 auth-body에 최소 100바이트의 의미 없는(junk) 데이터를 패딩해야 합니다. 패킷 크기를 다양하게 만들기 위해 [100, 300] 범위에서 무작위 양을 더하는 것이 권장됩니다. RLP 리스트 뒤의 추가 데이터가 허용되고 사실상 필요한 이유는, 새 형식 핸드셰이크 패킷이 구 형식과 구분되려면 더 커져야 하기 때문입니다. 수신 측은 먼저 307바이트를 읽어 구 형식으로 복호화를 시도하고 실패하면 앞 2바이트를 크기로 해석해 나머지를 읽은 뒤 새 형식으로 복호화하는 의사코드 방식으로 두 형식을 동시에 처리할 수 있습니다.

핸드셰이크에서 더하고 뺀 필드

변경 내용은 네 가지입니다. 암호문 길이를 평문 머리말로 추가하고, 핸드셰이크 본문을 RLP로 인코딩하고, 쓰이지 않던 토큰 플래그 자리에 버전 번호를 넣고, 중복이라서 임시(ephemeral) 공개키의 해시를 제거합니다. 이렇게 바꾸는 배경에는 RLPx v5 논의(청크 패킷, 키 유도 변경)가 있었습니다. v4 핸드셰이크 인코딩은 논스의 무작위 부분을 줄이는 것 말고는 버전 번호를 넣을 인밴드 방법이 없고 고정 크기에 배치가 알려진 ECIES 암호문이라 이후 업그레이드가 어려웠기 때문입니다. 핸드셰이크 패킷의 첫 변경인 만큼 안 쓰이는 필드를 모두 정리했습니다.

평문 크기 머리말 논쟁

이 문서에서 가장 논쟁적인 부분은 평문 크기 머리말입니다. 네트워크 수준에서 RLPx 연결을 걸러내고 식별하려는 공격자를 돕는다는 반론이 있었습니다. 원문은 이를 공격자가 들일 노력의 문제로 봅니다. 길이 무작위화 권장을 따르면 순수한 패턴 기반 패킷 인식은 성공하기 어렵습니다. 일반적인 방화벽 운영자에게 첫 2바이트가 [300, 600] 범위 정수인 연결을 전부 막는 것은 지나치게 침습적이고 포트 기반 차단이 더 효과적입니다. 여러 기준을 상관시킬 수 있는 공격자라면 크기 머리말이 식별 지표를 하나 더 주긴 하지만 그런 공격자는 RLPx 디스커버리 트래픽을 읽거나 참여할 수 있어 형식과 무관하게 RLPx TCP 연결을 차단할 수 있습니다.

하위 호환성, 구현, 테스트 벡터

이 EIP는 하위 호환적입니다. 유효한 버전 4 패킷은 모두 계속 받아들여집니다. 구현은 go-ethereum, libweb3core, pydevp2p에 있습니다. 테스트 벡터로는 버전 22와 추가 리스트 요소를 담은 devp2p hello 패킷, 버전 4와 버전 555의 ping을 포함한 디스커버리 패킷들(pong, findnode, neighbours 포함), 그리고 RLPx 핸드셰이크의 구형(Auth₁, Ack₁)과 EIP-8 형식(버전 4의 Auth₂·Ack₂, 추가 요소 3개를 담은 버전 56 Auth₃과 버전 57 Ack₃) 패킷, Auth₂·Ack₂에서 유도되는 aes-secret과 mac-secret 값이 제공됩니다. 상태가 Final이므로 명세는 확정입니다.

EIP 원문 · ← EIPs