The RLPx Transport Protocol, which adds some personal understanding, due to The limited level of cryptography, is not correct. In addition, the original article may have been updated, the latest content please directly read the original.

This document defines the RLPx transport protocol, a TCP-based transport protocol for communication between Ethereum nodes. This protocol is suitable for encrypted frames of any content, but it is commonly used to host devP2P application protocols.

Node id

All encryption operations are based on secP256K1 elliptic curve. Each node needs to maintain a static private key that is saved and restored between sessions. It is recommended that the private key be reset only manually, for example, by deleting a file or database entry.

ECIES encryption

Elliptic Curve Integrated Encryption Scheme (ECIES) is an asymmetric Encryption method used in RLPx handshake. The password system used by RLPx is:

  • secp256k1Elliptic curve, generator
  • :NIST SP 800-56Cascading key export function
  • :HMACuseSHA-256The hash function
  • :AES-256The encryption algorithmCTRmodel

(This is AES-128, but the Ethereum source code is AES-256, so this article changes to AES-256.)

Alice wants to send a static private key that can be used by BobDecrypted encrypted messages. Alice knows Bob’s static public key.

To encrypt the message, Alice generates a random numberAnd the corresponding elliptic curve public key is generatedAnd calculate the shared key.. And then,Export the encryption and authentication master key and generate an initial vector randomly. Alice sends an encrypted message to Bob, including..

Bob needs to decrypt the messageTo do this, you need to export the shared key, includingAnd export encryption and authentication keys. Bob goes through the equationVerify whether the message is valid after calculationGets the plaintext message.

Shake hands

The “handshake” process builds the master key used in the session phase. The handshake is done between the originator (the node that initiates the TCP connection request) and the receiver (the node that accepts the connection).

Handshake agreement:

E is the ECIES asymmetric encryption function defined above.

Note: E stands for encryption; S stands for signature; H stands for Hash

auth -> E(remote-pubk, S(ephemeral-privk, static-shared-secret ^ nonce) || H(ephemeral-pubk) || pubk || nonce || 0x0)   # send by the initiator of the handshake, send the local node (public key + temporary public key + random number) required for key negotiation to the other party.
auth-ack -> E(remote-pubk, remote-ephemeral-pubk || nonce || 0x0)   The receiver responds to the auth message by sending the local node (temporary public key + random number) needed for key negotiation to the other party. The public key of this node is known to the other party, so there is no need to send it here.

static-shared-secret = ecdh.agree(privkey, remote-pubk)
Copy the code

Calculation of value after handshake (steps are as follows) :

ephemeral-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk)
shared-secret = keccak256(ephemeral-shared-secret || keccak256(nonce || initiator-nonce))
aes-secret = keccak256(ephemeral-shared-secret || shared-secret)
# destroy shared-secret
mac-secret = keccak256(ephemeral-shared-secret || aes-secret)
# destroy ephemeral-shared-secret

Initiator:
egress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-sent-init)
# destroy nonce
ingress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-recvd-ack)
# destroy remote-nonce

Recipient:
egress-mac = keccak256.update(mac-secret ^ initiator-nonce || auth-sent-ack)
# destroy nonce
ingress-mac = keccak256.update(mac-secret ^ recipient-nonce || auth-recvd-init)
# destroy remote-nonce
Copy the code
  1. Forward security can be guaranteed to a certain extent with temporary keys. Forward security is described later.
  2. The most important part of the handshake is the negotiated key (aes-secret and MAC-secret).
  3. During key negotiation, you need to know the (public key + temporary public key + random number) of the local node and the peer node. This is not absolute, different key negotiation algorithms have different implementation methods, but basically both parties need to exchange some data, so as to deduce the same key respectively.

In addition, the core of the entire key negotiation process is ECDH key negotiation. However, the ECDH negotiation process requires that the peer party is authenticated and trusted. In fact, the ECIES encryption is equivalent to the authentication of the message receiver, because only the corresponding private key can decrypt the message.

The process for creating an encrypted connection is as follows:

  1. The initiator sends a TCP connection to the receiverauthThe message
  2. The receiver accepts the connection, decrypts it, and authenticates itauthMessage (checkrecovery of signature == keccak256(ephemeral-pubk))
  3. Receiver generationauth-ackThe message
  4. The receiver exports the key and sends the first data frame
  5. The initiator received the packetauth-ackMessage to export the key
  6. The originator sends the first data frame (The code corresponds to Hello Packet)
  7. The receiver receives and validates the data frame
  8. The originator receives and validates the data frame
  9. ifMACIf both sides are authenticated, the encrypted handshake is complete.

Simple summary is: establish TCP connection -> key negotiation (Auth, auth-ACK) -> double send export the same key -> send Hello (protocol negotiation) -> create complete. See the Ethereum source code for the implementation.

framing

All packets after Auth are transmitted in frames. Either party can disconnect if the first frame packet validation fails.

The main purpose of frame transfer is to reliably support multiplexing protocols over a single connection. Secondly, because the packet is divided into frames, appropriate demarcation points are generated for the message authentication code, which makes the flow of encryption easy. The data frame is authenticated by the key generated by the handshake.

The data frame header provides data packet size and protocol information.

frame = header || header-mac || frame-data || frame-mac
header = frame-size || header-data || padding
frame-size = size of frame excluding padding, integer < 2**24, big endian
header-data = rlp.list(protocol-type[, context-id])
protocol-type = integer < 2**16, big endian
context-id = integer< 2 * * 16, big endian padding = zero-fill to 16-byte boundary frame-content = any binary data header-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ header-ciphertext).digest frame-mac = left16(egress-mac.update(aes(mac-secret,egress-mac)) ^ left16(egress-mac.update(frame-ciphertext).digest)) egress-mac = keccak256 state, continuously updated with egress bytes ingress-mac = keccak256 state, continuously updated with ingress bytes left16(x) is the first 16 bytes of x || is concatenate ^ is xorCopy the code

Update the egress-MAC or ingress-MAC of sent and received ciphertext data to implement message authentication. For header data, this is done by xOR operation between the header of the encrypted output data and the corresponding MAC (see header-MAC). This is done to ensure uniform operation for plaintext MAC and ciphertext. All Macs are sent in clear text.

Padding bytes are used to prevent cache starvation and align frame components to the specified block byte size.

Known problems

  • The RLPx handshake is considered easy to crack becauseaes-secretandmac-secretIs used repeatedly for reading and writing. Both ends of the RLPx connection generate two CTR streams from the same key, nonce and IV. If an attacker knows a plaintext, they can crack the unknown plaintext with a reused keystream.
  • The frame encoding provides a protocol type field for multiplexingprotocol-type, but DevP2P does not use this field.

Forward security of RLPx transport protocol

RLPx uses PerfectForwardSecrecy, in a nutshell. Both sides of the link generate random private keys from which the public key is obtained. The two parties then exchange their public keys so that each party can generate the same shared-secret from its own random private key and the other party’s public key. Subsequent communications use this shared key as the key to the symmetric encryption algorithm. Put it this way. If one party’s private key is leaked, it will only affect the security of the message after the leak, which is safe for the previous communication (because the communication key is randomly generated and disappears when used up).

Forward safety

ForwardSecrecy or ForwardSecrecy FS, sometimes referred to as PerfectForwardSecrecy PFS, is a security attribute of communication protocols in cryptography. It means that the leakage of a long-used master key will not lead to the leakage of a past session key. Forward security protects past communications from future exposure of passwords or keys. If the system has forward security, it can ensure that if the password or key is accidentally leaked at some point, the past communication is still safe and will not be affected, even if the system is actively attacked.

Finally, it should be noted that this document gives a brief description of the RLPx protocol, but there are still many details to be dealt with. Please look at the source code of Ethereum in depth.

If there is any question or wrong place in the article, please pay attention to the wechat public number and I exchange learning, welcome to comment!