상세 컨텐츠

본문 제목

go-ethereum/p2p/discover/v4wire

Blockchain/Ethereum

by Yongari 2023. 3. 1. 22:27

본문

 

 

필요한 사전 지식

 

UDP:

https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9A%A9%EC%9E%90_%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B7%B8%EB%9E%A8_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C

 

사용자 데이터그램 프로토콜 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. UDP은 여기로 연결됩니다. 다른 뜻에 대해서는 UDP (동음이의) 문서를 참고하십시오. 사용자 데이터그램 프로토콜(User Datagram Protocol, UDP)은 인터넷 프로토콜 스위

ko.wikipedia.org

 

TCP:

https://ko.wikipedia.org/wiki/%EC%A0%84%EC%86%A1_%EC%A0%9C%EC%96%B4_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C

 

전송 제어 프로토콜 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 전송 제어 프로토콜(Transmission Control Protocol, TCP, 문화어: 전송조종규약)은 인터넷 프로토콜 스위트(IP)의 핵심 프로토콜 중 하나로, IP와 함께 TCP/IP라는 명칭으로

ko.wikipedia.org

 

RLP:

https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/

 

Home | ethereum.org

Ethereum is a global, decentralized platform for money and new kinds of applications. On Ethereum, you can write code that controls money, and build applications accessible anywhere in the world.

ethereum.org

 

https://docs.google.com/presentation/d/1nSoRv4hCmona_N1VENZdZ_POVV-LZpxOSNBntareZuA/edit#slide=id.g3597ea7b61_0_18

 

RLP 씹어먹기 by 철학자(Onther Inc.)

RLP(Recursive Length Prefix) 씹어먹기 Kevin J(철학자, 정순형) info@onther.io 2018.3.18 작성 <외부자료로 활용 시 출처만 밝혀주세요> Created by Onther Inc.

docs.google.com

https://medium.com/ethereum-core-research/rlp-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1c05a8150a04

 

RLP 이해하기

이더리움 네트워크에서 노드에 데이터 구조를 저장하거나, 혹은 노드끼리 데이터 구조를 주고 받으려면 통일된 형식이 필요하다. 그리고 이 통일된 형식을 만드는 것을 직렬화(serialization)라고

medium.com

 

ENR, Network address:

https://ethereum.org/ko/developers/docs/networking-layer/network-addresses/

 

Home | ethereum.org

Ethereum is a global, decentralized platform for money and new kinds of applications. On Ethereum, you can write code that controls money, and build applications accessible anywhere in the world.

ethereum.org


EIP868에 대한 자료

https://eips.ethereum.org/EIPS/eip-868

 

EIP-868: Node Discovery v4 ENR Extension

 

eips.ethereum.org

RLPX란?: 

RLPx 프로토콜은 Ethereum의 peer-to-peer 네트워크에서 사용되는 기본 프로토콜입니다.
RLPx는 RLP serialization을 기반으로 하며, 암호화 및 데이터 압축 기능을 갖춘 신뢰성 있는 데이터 전송을 제공합니다.
RLPx는 Ethereum 클라이언트들이 블록체인 데이터와 다른 Ethereum 노드와 통신하기 위한 기본 프로토콜로 사용됩니다.

RLPx는 Ethereum 클라이언트의 통신에서 가장 중요한 부분 중 하나이며, 피어간 데이터 통신에 대한 표준화된 인터페이스를 제공합니다.
RLPx는 블록체인 데이터를 빠르게 전송하기 위해 최적화되어 있습니다.
또한, 보안 기능을 포함하여 다른 프로토콜과 결합하여 Ethereum 클라이언트 간 데이터를 보호하고 신뢰성 있는 데이터 교환을 지원합니다.

 

geth/p2p/discover/v4wire/V4wire.go

 

v4wire 코드는 geth의 p2p/discover 폴더 안에 있다.
여러 패킷을 이용해서 p2p 통신을 구현하고 네트워크를 탐색하는 것이 핵심 기능인것으로 보인다.

 

 

Ping 구조체 : 버전, 송신자, 수신자, ENRSeq, Rest []rlp.RawValue를 담은 구조체 

Pong 구조체 : Ping에 대한 응답으로  수신자 엔드포인트와 Ping패킷의 해시, 유효기간, ENRSeq, Rest []rlp.RawValue 정보등이 있다. 

Findnode 구조체 : 타겟과 만료, Rest []rlp.RawValue 변수등이 설정되어 있다. 

Neighbors 구조체 : Findnode에 대한 응답이고 노드정보와 만료정보, Rest []rlp.RawValue 등이 설정되어있다. 

ENRRequest 구조체 : 만료와 Rest []rlp.RawValue 변수등이 설정되어 있다. 

ENRResponse 구조체 : ENR요청에 대한 응답으로 ENRRequest 패킷의 해시, enr의 기록, Rest []rlp.RawValue등이 설정되어 있습니다. 

MaxNeighbors 변수 설정 : 12로 설정되어있음  MaxNeighbors는 Neighbors 패킷의 최대 인접 노드 수입니다.

Node 구조체 : IP, UDP(탐색을 위한 프로토콜), TCP(RLPx 프로토콜을 위함), ID Pubkey등이 설정된 구조체 

Endpoint 구조체 : 네트워크 엔드포인트 구조체를 나타냄 

 

// 새 엔드포인트함수는 엔드포인트를 만드는 함수다. 
func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint 


// 로깅을 위한 패키지 이름과 유형을 인터페이스에서 설정했다. 
type Packet interface {} : 


// ping 함수
func (req *Ping) Name() string { return "PING/v4" }
func (req *Ping) Kind() byte { return PingPacket }


// pong 함수

func (req *Pong) Name() string { return "PONG/v4" }

func (req *Pong) Kind() byte { return PongPacket }



// Findnode 함수 지정된 대상에 가까운 노드에 대한 쿼리입니다

func (req *Findnode) Name() string { return "FINDNODE/v4" }

func (req *Findnode) Kind() byte { return FindnodePacket }



// Findnode 함수에 대한 응답

func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }

func (req *Neighbors) Kind() byte { return NeighborsPacket }



// Ethereum Name Service Record 요청은 노드가 다른 노드의 ENR 레코드를 검색하기 위해 수행한 요청을 의미합니다.

func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }

func (req *ENRRequest) Kind() byte { return ENRRequestPacket }



// Ethereum Name Service Record 응답은 자체 ENR 레코드로 응답합니다.
// (노드가 이더리움 네트워크에 가입하면 네트워크 주소, 공개 키 및 기타 메타데이터에 대한 정보를 포함하는 자체 ENR 레코드를 생성한다.)

func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }

func (req *ENRResponse) Kind() byte { return ENRResponsePacket }



// Expired는 주어진 UNIX 시간 스탬프가 과거인지 확인합니다.
func Expired(ts uint64) bool 


// Decode은 discovery v4 패킷을 읽습니다.

func Decode(input []byte) (Packet, Pubkey, []byte, error)

 

// Encode는 discovery 패킷을 인코딩합니다.
func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error)


//recoverNodeKey는 서명에서 지정된 해시를 서명하는 데 사용되는 공용 키를 계산합니다.
func recoverNodeKey(hash, sig []byte) (key Pubkey, err error)

 

// EncodePubkey는 secp256k1 공개 키를 인코딩합니다.
func EncodePubkey(key *ecdsa.PublicKey) Pubkey

 

// DecodePubkey는 인코딩된 secp256k1 공개 키를 읽습니다
func DecodePubkey(curve elliptic.Curve, e Pubkey)

 

 

v4wire.go

개인적으로 공부하면서 정리해봤습니다. 

// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

// Package v4wire implements the Discovery v4 Wire Protocol.
// Copyright 2020 The Go-Ethereum 권위자들
// 이 파일은 Go-etherum 라이브러리의 일부입니다.
//
// Go-etherum 라이브러리는 무료 소프트웨어입니다. 재배포 및/또는 수정할 수 있습니다
// 그것은 GNU 소규모 일반 공중 사용 허가서의 조건에 의해 출판되었다
// Free Software Foundation, License 버전 3 또는
// (선택사항에 따라) 이후 버전을 선택합니다.
//
// Go-etherum 라이브러리는 유용하기를 바라며 배포된다,
// 하지만 어떠한 보증도 없이, 심지어 묵시적인 보증도 없다
// 특정 목적에 대한 상품성 또는 적합성. 을 참조
// 자세한 내용은 GNU Lesser General Public License를 참조하십시오.
//
// 당신은 GNU 소규모 일반 공중 사용 허가서 사본을 받았어야 했다
// 라이브러리와 함께. 그렇지 않은 경우 <http://www.gnu.org/licenses/>을 참조하십시오.

// 패키지 v4wire는 Discovery v4 Wire 프로토콜을 구현합니다.
package v4wire

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"errors"
	"fmt"
	"math/big"
	"net"
	"time"

	"github.com/ethereum/go-ethereum/common/math"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/p2p/enode"
	"github.com/ethereum/go-ethereum/p2p/enr"
	"github.com/ethereum/go-ethereum/rlp"
)

// RPC packet types
// RPC 패킷 타입
const (
	PingPacket = iota + 1 // zero is 'reserved'
	PongPacket
	FindnodePacket
	NeighborsPacket
	ENRRequestPacket
	ENRResponsePacket
)

// RPC request structures
// RPC 요청 구조
/*
RLP stands for Recursive Length Prefix, it is a serialization/deserialization encoding scheme used in the go-ethereum codebase.
It is used to serialize data structures for storage on the blockchain and for transmitting data between nodes.
RLP is a binary encoding that recursively encodes data structures with variable-length elements.
It is particularly useful for encoding lists, as it can represent the length of the list in a compact and efficient manner.
In Ethereum, RLP is used to encode transactions, blocks, and account state data, among other things.

RLP는 Recursive Length Prefix의 약자로, Geth 코드베이스에 사용되는 직렬화/비직렬화 인코딩 방식이다.
블록체인에 저장하기 위한 데이터 구조를 직렬화하고 노드 간 데이터를 전송하는 데 사용된다.
RLP는 가변 길이 요소로 데이터 구조를 재귀적으로 인코딩하는 이진 인코딩이다.
목록을 인코딩하는 데 특히 유용한데, 목록의 길이를 간결하고 효율적으로 나타낼 수 있기 때문이다.
이더리움에서 RLP는 트랜잭션, 블록, 계정 상태 데이터 등을 인코딩하는 데 사용된다.



Recursive Length Prefix (RLP) serialization은 Ethereum의 실행 클라이언트에서 광범위하게 사용됩니다.
RLP는 노드 간 데이터 전송을 공간 효율적인 형식으로 표준화합니다.
RLP의 목적은 이진 데이터의 임의로 중첩된 배열을 인코딩하는 것이며,
RLP는 Ethereum의 실행 레이어에서 객체를 직렬화하는 데 사용되는 기본 인코딩 방법입니다.
RLP의 목적은 구조를 인코딩하는 것 뿐이며, 특정 데이터 유형 (예: 문자열, 부동 소수점)의 인코딩은 상위 프로토콜에서 결정됩니다.
하지만 양의 RLP 정수는 선행하는 0이 없는 빅 엔디언 이진 형식으로 표시되어야 합니다 (따라서 정수 값 0은 빈 바이트 배열과 같습니다).
선행하는 0이 있는 역직렬화된 양의 정수는 잘못 처리됩니다. 문자열 길이의 정수 표현도 이와 같이 인코딩되어야 하며, 페이로드의 정수도 동일합니다.

RLP를 사용하여 사전을 인코딩하려면, 두 가지 권장되는 표준형이 있습니다:

lexicographic 순서의 키를 사용하여 [[k1, v1], [k2, v2]...]를 사용합니다.
Ethereum과 같은 상위 수준 Patricia Tree 인코딩을 사용합니다.


*/
type (
	Ping struct {
		Version    uint
		From, To   Endpoint
		Expiration uint64
		ENRSeq     uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. EIP-868에 의해 추가된 로컬 레코드의 시퀀스 번호.

		// Ignore additional fields (for forward compatibility).
		// 앞에있는 호환성을 위해 추가 필드를 무시합니다.
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// Pong is the reply to ping.
	// 퐁은 핑에 대한 답이다.
	Pong struct {
		// This field should mirror the UDP envelope address
		// of the ping packet, which provides a way to discover the
		// external address (after NAT).
		// 이 필드는 UDP 엔벨로프 주소를 미러링해야 합니다
		// ping 패킷을 검색하는 방법을 제공합니다
		// 외부 주소(NAT 이후).
		To         Endpoint
		ReplyTok   []byte // This contains the hash of the ping packet. 이것은 ping 패킷의 해시를 포함합니다.
		Expiration uint64 // Absolute timestamp at which the packet becomes invalid. 패킷이 유효하지 않게 되는 절대 타임스탬프입니다.
		ENRSeq     uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868. EIP-868에 의해 추가된 로컬 레코드의 시퀀스 번호.

		// Ignore additional fields (for forward compatibility).
		// 앞에있는 호환성을 위해 추가 필드를 무시합니다.
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// Findnode is a query for nodes close to the given target.
	// 노드 찾기는 지정된 대상에 가까운 노드에 대한 쿼리입니다
	Findnode struct {
		Target     Pubkey
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		// 앞에있는 호환성을 위해 추가 필드를 무시합니다.
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// Neighbors is the reply to findnode.
	// Neighbors는 노드 찾기에 대한 응답입니다.
	Neighbors struct {
		Nodes      []Node
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		// 앞에있는 호환성을 위해 추가 필드를 무시합니다.
		Rest []rlp.RawValue `rlp:"tail"`
	}
	/*
	   ENR stands for Ethereum Name Service Record, which is a type of record used for identifying and discovering nodes in the Ethereum network.
	   ENR requests in geth refer to requests made by nodes to retrieve the ENR records of other nodes.

	   When a node joins the Ethereum network, it creates its own ENR record, which contains information about its network address,
	   public key, and other metadata. Other nodes can then use this record to discover and connect to the node.

	   ENR requests are sent when a node needs to connect to another node for various reasons, such as syncing its blockchain or sending a transaction. T
	   he node sends an ENR request to the target node, which responds with its own ENR record.
	   This allows the requesting node to obtain the necessary information to establish a connection with the target node.

	   ENR은 이더리움 네임 서비스 레코드(Ethereum Name Service Record)의 약자로, 이더리움 네트워크에서 노드를 식별하고 발견하는 데 사용되는 레코드의 일종이다.
	   ENR 요청은 노드가 다른 노드의 ENR 레코드를 검색하기 위해 수행한 요청을 의미합니다.

	   노드가 이더리움 네트워크에 가입하면 네트워크 주소, 공개 키 및 기타 메타데이터에 대한 정보를 포함하는 자체 ENR 레코드를 생성한다.
	   그러면 다른 노드가 이 레코드를 사용하여 노드를 검색하고 연결할 수 있습니다.

	   ENR 요청은 블록체인을 동기화하거나 트랜잭션을 전송하는 등 다양한 이유로 노드가 다른 노드에 연결해야 할 때 전송됩니다.
	   노드는 대상 노드에 ENR 요청을 전송하고, 대상 노드는 자체 ENR 레코드로 응답합니다. 이를 통해 요청 노드는 대상 노드와의 연결을 설정하는 데 필요한 정보를 얻을 수 있습니다
	*/

	// ENRRequest queries for the remote node's record.
	// 원격 노드의 레코드에 대한 ENR 요청 쿼리입니다.
	ENRRequest struct {
		Expiration uint64
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}

	// ENRResponse is the reply to ENRRequest.
	// ENR 응답은 ENR 요청에 대한 응답입니다.
	ENRResponse struct {
		ReplyTok []byte // Hash of the ENRRequest packet. ENRRequest 패킷의 해시입니다.
		Record   enr.Record
		// Ignore additional fields (for forward compatibility).
		Rest []rlp.RawValue `rlp:"tail"`
	}
)

// MaxNeighbors is the maximum number of neighbor nodes in a Neighbors packet.
// MaxNeighbors는 Neighbors 패킷의 최대 인접 노드 수입니다.
const MaxNeighbors = 12

// This code computes the MaxNeighbors constant value.

// func init() {
// 	var maxNeighbors int
// 	p := Neighbors{Expiration: ^uint64(0)}
// 	maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
// 	for n := 0; ; n++ {
// 		p.Nodes = append(p.Nodes, maxSizeNode)
// 		size, _, err := rlp.EncodeToReader(p)
// 		if err != nil {
// 			// If this ever happens, it will be caught by the unit tests.
// 			panic("cannot encode: " + err.Error())
// 		}
// 		if headSize+size+1 >= 1280 {
// 			maxNeighbors = n
// 			break
// 		}
// 	}
// 	fmt.Println("maxNeighbors", maxNeighbors)
// }

// Pubkey represents an encoded 64-byte secp256k1 public key.
// Pubkey는 인코딩된 64바이트 secp256k1 공개 키를 나타냅니다.
type Pubkey [64]byte

// ID returns the node ID corresponding to the public key.
func (e Pubkey) ID() enode.ID {
	return enode.ID(crypto.Keccak256Hash(e[:]))
}

// Node represents information about a node.
// 노드는 노드에 대한 정보를 나타냅니다.
type Node struct {
	IP  net.IP // len 4 for IPv4 or 16 for IPv6 IPv4의 경우 len 4 또는 IPv6의 경우 16
	UDP uint16 // for discovery protocol
	// 사용자 데이터그램 프로토콜(User Datagram Protocol, UDP)
	// UDP의 전송 방식은 너무 단순해서 서비스의 신뢰성이 낮고, 데이터그램 도착 순서가 바뀌거나, 중복되거나, 심지어는 통보 없이 누락시키기도 한다.
	// UDP는 일반적으로 오류의 검사와 수정이 필요 없는 애플리케이션에서 수행할 것으로 가정한다.
	// TCP는 데이터를 주고 받을 양단 간에 먼저 연결을 설정하고 설정된 연결을 통해 양방향으로 데이터를 전송하지만, UDP는 연결을 설정하지 않고 수신자가 데이터를 받을 준비를 확인하는 단계를 거치지 않고 단방향으로 정보를 전송한다.
	// 신뢰성 - TCP는 메시지 수신을 확인하지만 UDP는 수신자가 메시지를 수신했는지 확인할 수 없다.
	// 순서 정렬 - TCP에서는 메시지가 보내진 순서를 보장하기 위해 재조립하지만 UDP는 메시지 도착 순서를 예측할 수 없다.
	// 부하 - TCP보다 속도가 일반적으로 빠르고 오버헤드가 적다.

	TCP uint16 // for RLPx protocol
	// 전송 제어 프로토콜(Transmission Control Protocol, TCP, 문화어: 전송조종규약)은 인터넷 프로토콜 스위트(IP)의 핵심 프로토콜 중 하나로,
	// IP와 함께 TCP/IP라는 명칭으로도 널리 불린다. TCP는 근거리 통신망이나 인트라넷, 인터넷에 연결된 컴퓨터에서 실행되는 프로그램 간에 일련의 옥텟을 안정적으로,
	// 순서대로, 에러없이 교환할 수 있게 한다. TCP는 전송 계층에 위치한다.
	// 네트워크의 정보 전달을 통제하는 프로토콜이자 인터넷을 이루는 핵심 프로토콜의 하나로서 국제 인터넷 표준화 기구(IETF)의 RFC 793에 기술되어 있다.
	ID Pubkey
}

/*


*/

// Endpoint represents a network endpoint.
// 엔드포인트는 네트워크 엔드포인트를 나타냅니다.
type Endpoint struct {
	IP  net.IP // len 4 for IPv4 or 16 for IPv6
	UDP uint16 // for discovery protocol
	TCP uint16 // for RLPx protocol
}

// NewEndpoint creates an endpoint.
// 새 엔드포인트는 엔드포인트를 만듭니다.
func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
	ip := net.IP{}
	if ip4 := addr.IP.To4(); ip4 != nil {
		ip = ip4
	} else if ip6 := addr.IP.To16(); ip6 != nil {
		ip = ip6
	}
	fmt.Println("NewEndpoint")
	return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
}

type Packet interface {
	// Name is the name of the package, for logging purposes.
	// Name은 로깅을 위한 패키지의 이름입니다.
	Name() string
	// Kind is the packet type, for logging purposes.
	// Kind는 로깅을 위한 패킷 유형입니다.
	Kind() byte
}

// ping 함수
func (req *Ping) Name() string { return "PING/v4" }
func (req *Ping) Kind() byte   { return PingPacket }

// pong 함수
func (req *Pong) Name() string { return "PONG/v4" }
func (req *Pong) Kind() byte   { return PongPacket }

// Findnode 함수 지정된 대상에 가까운 노드에 대한 쿼리입니다
func (req *Findnode) Name() string { return "FINDNODE/v4" }
func (req *Findnode) Kind() byte   { return FindnodePacket }

// Findnode 함수에 대한 응답
func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }
func (req *Neighbors) Kind() byte   { return NeighborsPacket }

// Ethereum Name Service Record 요청은 노드가 다른 노드의 ENR 레코드를 검색하기 위해 수행한 요청을 의미합니다.
func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }
func (req *ENRRequest) Kind() byte   { return ENRRequestPacket }

// Ethereum Name Service Record 응답은  자체 ENR 레코드로 응답합니다.  (노드가 이더리움 네트워크에 가입하면 네트워크 주소, 공개 키 및 기타 메타데이터에 대한 정보를 포함하는 자체 ENR 레코드를 생성한다.)
func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
func (req *ENRResponse) Kind() byte   { return ENRResponsePacket }

// Expired checks whether the given UNIX time stamp is in the past.
// Expired는 주어진 UNIX 시간 스탬프가 과거인지 확인합니다.
func Expired(ts uint64) bool {
	return time.Unix(int64(ts), 0).Before(time.Now())
}

// Encoder/decoder.
// 엔코드/디코드
const (
	macSize  = 32
	sigSize  = crypto.SignatureLength
	headSize = macSize + sigSize // space of packet frame data
)

var (
	ErrPacketTooSmall = errors.New("too small")
	ErrBadHash        = errors.New("bad hash")
	ErrBadPoint       = errors.New("invalid curve point")
)

var headSpace = make([]byte, headSize)

// Decode reads a discovery v4 packet.
// Decode은 discovery v4 패킷을 읽습니다.
func Decode(input []byte) (Packet, Pubkey, []byte, error) {
	if len(input) < headSize+1 {
		return nil, Pubkey{}, nil, ErrPacketTooSmall
	}
	hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:]
	shouldhash := crypto.Keccak256(input[macSize:])
	if !bytes.Equal(hash, shouldhash) {
		return nil, Pubkey{}, nil, ErrBadHash
	}
	fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig)
	if err != nil {
		return nil, fromKey, hash, err
	}

	var req Packet
	switch ptype := sigdata[0]; ptype {
	case PingPacket:
		req = new(Ping)
	case PongPacket:
		req = new(Pong)
	case FindnodePacket:
		req = new(Findnode)
	case NeighborsPacket:
		req = new(Neighbors)
	case ENRRequestPacket:
		req = new(ENRRequest)
	case ENRResponsePacket:
		req = new(ENRResponse)
	default:
		return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
	}
	s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
	err = s.Decode(req)
	fmt.Println("Decode", req, fromKey, hash, err)
	return req, fromKey, hash, err
}

// Encode encodes a discovery packet.
// Encode는 discovery 패킷을 인코딩합니다.
func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) {
	b := new(bytes.Buffer)
	b.Write(headSpace)
	b.WriteByte(req.Kind())
	if err := rlp.Encode(b, req); err != nil {
		return nil, nil, err
	}
	packet = b.Bytes()
	sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
	if err != nil {
		return nil, nil, err
	}
	copy(packet[macSize:], sig)
	// Add the hash to the front. Note: this doesn't protect the packet in any way.
	// 해시를 앞에 추가합니다. 참고: 이렇게 해도 패킷은 보호되지 않습니다.
	hash = crypto.Keccak256(packet[macSize:])
	copy(packet, hash)
	fmt.Println("Encode", packet, hash)
	return packet, hash, nil
}

// recoverNodeKey computes the public key used to sign the given hash from the signature.
// recoverNodeKey는 서명에서 지정된 해시를 서명하는 데 사용되는 공용 키를 계산합니다.
func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) {
	pubkey, err := crypto.Ecrecover(hash, sig)
	if err != nil {
		return key, err
	}
	copy(key[:], pubkey[1:])
	fmt.Println("recoverNodeKey", key)
	return key, nil
}

// EncodePubkey encodes a secp256k1 public key.
// EncodePubkey는 secp256k1 공개 키를 인코딩합니다.
func EncodePubkey(key *ecdsa.PublicKey) Pubkey {
	var e Pubkey
	math.ReadBits(key.X, e[:len(e)/2])
	math.ReadBits(key.Y, e[len(e)/2:])
	fmt.Println("EncodePubkey", key)
	return e
}

// DecodePubkey reads an encoded secp256k1 public key.
// DecodePubkey는 인코딩된 secp256k1 공개 키를 읽습니다.
func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) {
	p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
	half := len(e) / 2
	p.X.SetBytes(e[:half])
	p.Y.SetBytes(e[half:])
	if !p.Curve.IsOnCurve(p.X, p.Y) {
		return nil, ErrBadPoint
	}
	fmt.Println("DecodePubkey", p)
	return p, nil
}

 

v4wire_test.go

v4wire.go를 테스트하는 go 스크립트입니다. 파일명에 "_test"를 붙이며 테스트할 함수 앞에는 Test를 붙이는 것으로 알고 있습니다. (혹시 잘못된 정보나 글이 보기 불편하시면 댓글로 지적 부탁드립니다~~)

// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// 저작권 2020 go-ethereum 작성자
// 본 파일은 go-ethereum 라이브러리의 일부입니다.
//
// go-ethereum 라이브러리는 다음과 같은 조건 하에서 자유 소프트웨어로서 배포되며 수정될 수 있습니다:
// GNU Lesser General Public License 버전 3 또는
// (귀하의 선택에 따라) 이후의 버전.
//
// go-ethereum 라이브러리는 유용할 수 있지만 어떠한 보증도 제공하지 않습니다.
// 상업성 또는 특정 목적에 대한 적합성의 암시적 보증도 없습니다.
// 자세한 내용은 GNU Lesser General Public License을 참조하십시오.
//
// go-ethereum 라이브러리와 함께 GNU Lesser General Public License 사본이 제공되었는지 확인하십시오.
// 그렇지 않은 경우 http://www.gnu.org/licenses/를 방문하십시오.
package v4wire

import (
	"encoding/hex"
	"net"
	"reflect"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/rlp"
)

// EIP-8 test vectors.
var testPackets = []struct {
	input      string
	wantPacket interface{}
}{
	{
		input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355",
		wantPacket: &Ping{
			Version:    4,
			From:       Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
			To:         Endpoint{net.ParseIP("::1"), 2222, 3333},
			Expiration: 1136239445,
		},
	},
	{
		input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102",
		wantPacket: &Ping{
			Version:    4,
			From:       Endpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544},
			To:         Endpoint{net.ParseIP("::1"), 2222, 3333},
			Expiration: 1136239445,
			ENRSeq:     1,
			Rest:       []rlp.RawValue{{0x02}},
		},
	},
	{
		input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396",
		wantPacket: &Findnode{
			Target:     hexPubkey("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"),
			Expiration: 1136239445,
			Rest:       []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}},
		},
	},
	{
		input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0",
		wantPacket: &Neighbors{
			Nodes: []Node{
				{
					ID:  hexPubkey("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"),
					IP:  net.ParseIP("99.33.22.55").To4(),
					UDP: 4444,
					TCP: 4445,
				},
				{
					ID:  hexPubkey("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"),
					IP:  net.ParseIP("1.2.3.4").To4(),
					UDP: 1,
					TCP: 1,
				},
				{
					ID:  hexPubkey("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"),
					IP:  net.ParseIP("2001:db8:3c4d:15::abcd:ef12"),
					UDP: 3333,
					TCP: 3333,
				},
				{
					ID:  hexPubkey("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"),
					IP:  net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"),
					UDP: 999,
					TCP: 1000,
				},
			},
			Expiration: 1136239445,
			Rest:       []rlp.RawValue{{0x01}, {0x02}, {0x03}},
		},
	},
}

// This test checks that the decoder accepts packets according to EIP-8.
func TestForwardCompatibility(t *testing.T) {
	testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
	wantNodeKey := EncodePubkey(&testkey.PublicKey)

	for _, test := range testPackets {
		input, err := hex.DecodeString(test.input)
		if err != nil {
			t.Fatalf("invalid hex: %s", test.input)
		}
		packet, nodekey, _, err := Decode(input)
		if err != nil {
			t.Errorf("did not accept packet %s\n%v", test.input, err)
			continue
		}
		if !reflect.DeepEqual(packet, test.wantPacket) {
			t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket))
		}
		if nodekey != wantNodeKey {
			t.Errorf("got id %v\nwant id %v", nodekey, wantNodeKey)
		}
	}
}

func hexPubkey(h string) (ret Pubkey) {
	b, err := hex.DecodeString(h)
	if err != nil {
		panic(err)
	}
	if len(b) != len(ret) {
		panic("invalid length")
	}
	copy(ret[:], b)
	return ret
}

 

go test했을 때 테스트 데이터 출력값

go test
EncodePubkey &{0xc0000167e0 91542642498385314440228946746332025222205438489088139647774839856022021812536 53125609284960317672145139604811166881173812225835228968067023372906354834559}
recoverNodeKey [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127]
Decode &{4 {127.0.0.1 3322 5544} {::1 2222 3333} 1136239445 0 []} [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127] [113 219 218 58 121 85 71 40 212 249 68 17 228 46 225 248 176 213 97 193 14 30 95 88 147 54 121 72 198 167 215 11] <nil>
recoverNodeKey [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127]
Decode &{4 {127.0.0.1 3322 5544} {::1 2222 3333} 1136239445 1 [[2]]} [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127] [233 97 76 207 217 252 62 116 54 0 24 82 45 48 225 65 154 20 52 7 255 204 231 72 222 62 34 17 107 126 141 201] <nil>
recoverNodeKey [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127]
Decode &{[202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127] 1136239445 [[130 153 153] [131 153 153 153]]} [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127] [199 196 64 65 185 247 199 228 25 52 65 126 186 201 168 225 164 198 41 143 116 85 63 47 207 220 174 110 214 254 83 22] <nil>
recoverNodeKey [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127]
Decode &{[{99.33.22.55 4444 4445 [49 85 225 66 127 133 241 10 92 154 119 85 135 119 72 4 26 241 188 216 212 116 236 6 94 179 61 245 122 151 186 191 84 191 210 16 53 117 250 130 145 21 210 36 197 35 89 107 64 16 101 169 127 116 1 6 16 252 231 99 130 192 191 50]} {1.2.3.4 1 1 [49 44 85 81 36 34 207 155 138 64 151 233 166 173 121 64 46 135 161 90 233 9 164 191 239 162 35 152 240 61 32 149 25 51 190 234 30 77 250 111 150 130 18 56 94 130 159 4 194 211 20 252 45 78 37 94 13 59 192 135 146 176 105 219]} {2001:db8:3c4d:15::abcd:ef12 3333 3333 [56 100 50 0 177 114 220 254 248 87 73 33 86 151 31 14 106 162 197 56 216 183 64 16 248 225 64 129 29 83 185 140 118 93 210 217 97 38 5 25 19 244 69 130 232 193 153 173 124 109 104 25 233 165 100 131 246 55 254 170 201 68 138 172]} {2001:db8:85a3:8d3:1319:8a2e:370:7348 999 1000 [141 202 184 97 140 50 83 181 88 212 89 218 83 189 143 166 137 53 167 25 175 248 184 17 25 113 1 164 178 180 125 210 212 114 149 40 111 192 12 192 129 187 84 45 118 7 23 209 189 214 190 194 195 124 215 46 202 54 125 109 211 185 223 115]}] 1136239445 [[1] [2] [3]]} [202 99 76 174 13 73 172 180 1 216 164 198 182 254 140 85 183 13 17 91 244 0 118 156 193 64 15 50 88 205 49 56 117 116 7 127 48 27 66 27 200 77 247 38 108 68 233 230 213 105 252 86 190 0 129 41 4 118 123 245 204 209 252 127] [198 121 252 143 224 184 177 47 6 87 127 46 128 45 52 246 250 37 126 97 55 169 149 246 244 203 252 158 229 14 211 113] <nil>
PASS
ok      github.com/ethereum/go-ethereum/p2p/discover/v4wire     0.031s

관련글 더보기