Class: Ciri::P2P::Discovery::Protocol::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/ciri/p2p/discovery/protocol.rb

Overview

implement the DiscV4 protocol github.com/ethereum/devp2p/blob/master/discv4.md

Constant Summary collapse

MAX_LEN =
1280

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(message_hash:, signature:, packet_type:, packet_data:) ⇒ Message

Returns a new instance of Message.



45
46
47
48
49
50
# File 'lib/ciri/p2p/discovery/protocol.rb', line 45

def initialize(message_hash:, signature:, packet_type:, packet_data:)
  @message_hash = message_hash
  @signature = signature
  @packet_type = packet_type
  @packet_data = packet_data
end

Instance Attribute Details

#message_hashObject (readonly)

Returns the value of attribute message_hash.



43
44
45
# File 'lib/ciri/p2p/discovery/protocol.rb', line 43

def message_hash
  @message_hash
end

#packet_dataObject (readonly)

Returns the value of attribute packet_data.



43
44
45
# File 'lib/ciri/p2p/discovery/protocol.rb', line 43

def packet_data
  @packet_data
end

#packet_typeObject (readonly)

Returns the value of attribute packet_type.



43
44
45
# File 'lib/ciri/p2p/discovery/protocol.rb', line 43

def packet_type
  @packet_type
end

Class Method Details

.decode_message(raw_bytes) ⇒ Object

return a Message



102
103
104
105
106
107
108
109
# File 'lib/ciri/p2p/discovery/protocol.rb', line 102

def decode_message(raw_bytes)
  hash = raw_bytes[0...32]
  # signature is 65 length r,s,v
  signature = raw_bytes[32...97]
  packet_type = Utils.big_endian_decode raw_bytes[97]
  packet_data = raw_bytes[98..-1]
  Message.new(message_hash: hash, signature: signature, packet_type: packet_type, packet_data: packet_data)
end

.pack(packet, private_key:) ⇒ Object

return a new message instance include packet



112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ciri/p2p/discovery/protocol.rb', line 112

def pack(packet, private_key:)
  packet_data = Ciri::RLP.encode(packet)
  packet_type = packet.class.code
  encoded_packet_type = Utils.big_endian_encode(packet_type)
  signature = private_key.ecdsa_signature(Utils.keccak(encoded_packet_type + packet_data)).to_s
  hash = Utils.keccak(signature + encoded_packet_type + packet_data)
  if (msg_size=hash.size + signature.size + encoded_packet_type.size + packet_data.size) > MAX_LEN
    raise InvalidMessageError.new("failed to pack, message size is too long, size: #{msg_size}, max_len: #{MAX_LEN}")
  end
  Message.new(message_hash: hash, signature: signature, packet_type: packet_type, packet_data: packet_data)
end

Instance Method Details

#encode_messageObject

encode message to string



91
92
93
94
95
96
97
98
# File 'lib/ciri/p2p/discovery/protocol.rb', line 91

def encode_message
  buf = String.new
  buf << message_hash
  buf << @signature
  buf << packet_type
  buf << packet_data
  buf
end

#packetObject



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/ciri/p2p/discovery/protocol.rb', line 61

def packet
  packet_class = case @packet_type
                 when Ping::CODE
                   Ping
                 when Pong::CODE
                   Pong
                 when FindNode::CODE
                   FindNode
                 when Neighbors::CODE
                   Neighbors
                 else
                   raise UnknownMessageCodeError.new("unkonwn discovery message code: #{@packet_type}")
                 end
  # TODO according discv4 protocol, rlp_decode should support ignore additional elements
  # we should support ignore_extra_data option in Ciri::RLP
  packet_class.rlp_decode @packet_data
end

#senderObject

compute key and return NodeID



53
54
55
56
57
58
59
# File 'lib/ciri/p2p/discovery/protocol.rb', line 53

def sender
  @sender ||= begin
                encoded_packet_type = Utils.big_endian_encode(packet_type)
                public_key = Key.ecdsa_recover(Utils.keccak(encoded_packet_type + packet_data), @signature)
                NodeID.new(public_key)
              end
end

#validateObject

validate message hash and signature



80
81
82
83
84
85
86
87
88
# File 'lib/ciri/p2p/discovery/protocol.rb', line 80

def validate
  encoded_packet_type = Utils.big_endian_encode(packet_type)
  raise InvalidMessageError.new("mismatch hash") if message_hash != Utils.keccak(@signature + encoded_packet_type + packet_data)
  begin
    sender
  rescue StandardError => e
    raise InvalidMessageError.new("recover sender error: #{e}")
  end
end