EIP-627 위스퍼 명세 (Whisper Specification)
최종 확정 · 네트워크·노드 · 원문 상태 Final · 2017-05-05 작성
이더리움의 P2P 메시징 프로토콜인 위스퍼(Whisper)의 메시지 형식을 표준으로 정합니다. 패킷 코드, 봉투 구조, 작업증명과 블룸 필터 교환 방식을 담아 클라이언트 간 호환성을 보장합니다.
왜 필요한가
위스퍼는 이더리움 노드들이 블록체인과 별도로 암호화 메시지를 주고받는 ÐΞVp2p 기반 통신 프로토콜입니다. 당시 메시지 형식은 위키 문서로만 설명되어 있었고 go-ethereum과 Parity처럼 서로 다른 팀이 만든 클라이언트가 각자 구현하고 있었습니다. 형식이 표준으로 고정되지 않으면 클라이언트마다 메시지를 다르게 해석해 서로 통신하지 못할 수 있고 프로토콜이 새 버전으로 발전할 때 기존 노드와의 호환이 깨질 수 있습니다. 이 EIP는 기존 위키 명세를 대체해 패킷 코드와 봉투 형식을 문서로 확정하고 모르는 코드는 오류 없이 무시한다는 규칙을 세워 서로 다른 위스퍼 클라이언트 사이의 전방 호환성을 보장하려 합니다.
무엇이 바뀌나
지금
- 위스퍼 메시지 형식이 위키 문서로만 존재해 공식 표준이 없습니다
- 클라이언트마다 메시지 해석이 달라질 수 있어 호환성을 보장하기 어렵습니다
- 작업증명 요구치나 관심 토픽을 피어와 주고받는 표준 방법이 없습니다
EIP-627 이후
- 패킷 코드 0부터 127까지가 위스퍼용으로 예약되고 필수 코드 4개가 정해집니다
- 모르는 코드는 오류 없이 무시하는 규칙으로 미래 버전과의 호환성이 지켜집니다
- PoW Requirement와 Bloom Filter 패킷으로 받을 메시지의 조건을 실시간으로 조절합니다
누구에게 영향이 있나
- 클라이언트 팀: go-ethereum, Parity 등 위스퍼 구현체가 같은 패킷 코드와 봉투 형식을 따르게 되어 서로 다른 구현 사이의 통신이 보장됩니다.
- 앱(Dapp) 개발자: 위스퍼로 메시징 기능을 만들 때 어떤 클라이언트와도 호환되는 표준 형식을 기준으로 삼을 수 있습니다.
- 메일 서버 운영자: 예약된 P2P Request(126)와 P2P Message(127) 코드로 만료된 과거 메시지를 요청하고 배달하는 위스퍼 메일 서버와 클라이언트를 구현할 수 있습니다.
심층 설명
패킷 코드와 전방 호환성
위스퍼 메시지는 ÐΞVp2p 와이어 프로토콜 패킷으로 전송되며 정수 패킷 코드와 코드에 따라 달라지는 객체 하나를 담은 RLP 인코딩 배열입니다. 위스퍼용으로 예약된 코드는 0부터 127까지입니다. 필수 코드는 Status(0), Messages(1), PoW Requirement(2), Bloom Filter(3) 네 가지이고 P2P Request(126)와 P2P Message(127)는 선택이지만 용도가 예약되어 있습니다. 노드가 지원하지 않는 코드의 패킷을 받으면 오류를 내지 말고 무시해야 합니다. 미래 버전과의 전방 호환성을 지키기 위한 규칙입니다.
Status와 Messages 패킷
Status(0x00)는 정수 버전, 부동소수점 PoW 요구치, 블룸 필터를 이 순서로 담은 리스트입니다. 블룸 필터는 생략할 수 있으며 없거나 nil이면 그 노드는 모든 메시지를 받는 풀 노드로 간주합니다. Status는 초기 핸드셰이크 뒤, 다른 어떤 메시지보다 먼저 보내야 합니다. Messages(0x01)는 위스퍼 봉투의 리스트(비어 있을 수 있음)를 담으며 표준 위스퍼 봉투를 보내는 데 씁니다.
PoW Requirement와 계산식
PoW Requirement(0x02)는 64비트 IEEE 754 부동소수점 값 하나를 담습니다. qNAN, sNAN, INF, -INF와 음수는 허용되지 않습니다. 이 메시지를 받은 노드는 보낸 노드에게 지정된 값보다 낮은 PoW의 메시지를 이후로는 전달하지 않아야 합니다. PoW는 현재 BestBit(해시 앞부분에 연속된 0 비트의 수)를 찾는 데 필요한 평균 반복 횟수를 메시지 크기와 TTL로 나눈 값으로, PoW = (2**BestBit) / (size * TTL)입니다. 계산은 env_nonce를 제외한 봉투의 RLP(short_rlp)에 env_nonce를 이어 붙여 sha3 해시를 구하고(pow_hash), 2**leading_zeros(pow_hash) / (size * ttl)로 정의합니다. size는 완전한 RLP 인코딩 봉투의 크기입니다. Rationale은 최소 PoW 값을 하드코딩하지 않고 네트워크가 스스로 요구치를 조절하게 하는 편이 낫다고 설명합니다.
블룸 필터의 구조
Bloom Filter(0x03)는 임의 길이의 바이트 배열로, 노드가 어떤 토픽의 메시지에 관심이 있는지 피어에게 알리는 데 씁니다. 관심 토픽을 정확히 드러내지 않으면서 범위를 전달하므로 프라이버시를 크게 해치지 않고 비트를 더해 정보량과 필터 효율을 조절할 수 있습니다. 블룸은 토픽별 블룸 결과를 비트 OR로 합쳐 만듭니다. 블룸 함수는 4바이트 토픽을 512비트 슬라이스로 투영하며 토픽 하나당 최대 3개의 비트를 표시합니다. 투영은 S[0], S[1], S[2] 각 바이트 값 n에 대해 S[3]의 i번째 비트가 켜져 있으면 n에 256을 더한 위치의 비트를 1로 놓는 방식입니다. Rationale은 트래픽이 과도할 때 노드가 관심 있는 메시지만 받게 하는 확장성 장치라고 설명합니다.
위스퍼 봉투 형식
봉투는 [ Expiry, TTL, Topic, Data, Nonce ] 형식의 RLP 인코딩 구조입니다. Expiry는 4바이트 UNIX 시간(초), TTL은 4바이트 생존 시간(초), Topic은 4바이트 임의 데이터, Data는 암호화된 메시지를 담는 임의 크기의 바이트 배열, Nonce는 PoW 계산에 쓰는 8바이트 임의 데이터입니다.
Data 필드 형식과 서명, 암호화 (선택)
Data 필드의 내부 형식은 선택 사항이며 수신 메시지를 복호화하려는 경우에만 의미가 있습니다. 다른 형식의 메시지도 유효하며 피어에게 그대로 전달해야 합니다. 평문은 flags(1바이트, 앞 두 비트는 보조 필드 크기, 셋째 비트는 서명 유무), 보조 필드(최대 4바이트, 페이로드 크기), payload, padding, signature(있으면 65바이트) 순서로 이어 붙이고 대칭 암호화면 12바이트 Salt(AES Nonce)가 붙습니다. 서명은 암호화 전 데이터의 Keccak-256 해시에 대한 ECDSA 서명으로 R, S, V 순서로 직렬화하며 V는 27 또는 28이어야 합니다. 복호화하지 못하는 쪽은 서명에도 접근할 수 없습니다. padding은 메시지 크기만으로 중요한 메타정보가 드러나는 것을 막기 위한 것으로, 암호화 전 Data 필드 크기(Salt 제외)를 256바이트의 배수로 맞추기를 권장합니다. 암호화는 비대칭이면 SECP-256k1 공개키의 표준 ECIES, 대칭이면 랜덤 96비트 논스의 AES GCM을 씁니다.
P2P Request와 P2P Message
P2P Request(0x7E)와 P2P Message(0x7F)는 각각 위스퍼 봉투 하나를 담습니다. P2P Request는 Dapp 수준의 피어 간 요청에 쓰이며 위스퍼 메일 클라이언트가 메일 서버에 과거 메시지를 요청하는 것이 예입니다. P2P Message는 더 이상 다른 노드로 전달되지 않아야 하는 피어 간 메시지용으로, 메일 서버가 만료된 과거 메시지를 배달할 때 씁니다. Rationale에 따르면 P2P 메시지가 없으면 만료된 메시지는 프로토콜 위반으로 인식되어 피어 연결이 끊기므로 과거 메시지를 배달할 수 없고 거래소의 실시간 체결 피드처럼 PoW에 시간을 쓸 수 없는 용도에도 유용할 수 있습니다.
호환성과 구현 현황
이 EIP는 위스퍼 버전 6과 호환됩니다. 특정 패킷 코드를 구현하지 않은 클라이언트는 그 코드의 패킷을 조용히 무시해야 하며 이것이 전방 호환성을 보장합니다. 원문 작성 시점 기준으로 golang 구현(v6)은 이미 0x00부터 0x03 코드를 쓰고 있고 Parity의 v6 구현도 같은 코드를 쓸 예정입니다. 0x7E와 0x7F는 예약만 되어 있고 아직 쓰이지 않으며 위스퍼 메일 서버의 커스텀 구현용으로 남겨져 있습니다. 문서 상태는 Final로 확정된 명세입니다.