@@ -32,6 +32,9 @@ within strings, so no confusion is possible there. However it does permit newli
3232extraneous whitespace between elements; client and server MUST NOT use newlines in such a
3333way.
3434
35+ Messages SHOULD be :ref: `padded <padding_messages >` to bucketed lengths,
36+ and buffered (to introduce delays) to protect against traffic analysis.
37+
3538If using JSON RPC 2.0's feature of parameter passing by name, the
3639names shown in the description of the method or notification in
3740question MUST be used.
@@ -211,3 +214,100 @@ and confirm the returned roots match.
211214 implementation would require hashing approximately 88MB of data to
212215 provide a single merkle proof. ElectrumX implements an optimization
213216 such that it hashes only approximately 180KB of data per proof.
217+
218+
219+ .. _padding_messages :
220+
221+ Traffic analysis
222+ ----------------
223+
224+ The goal is to defend against a passive network Man-in-the-Middle, such as an ISP
225+ or a Tor exit node, observing the encrypted TLS stream, and making educated guesses
226+ of the message contents based on TCP packet flow: timing, direction, and sizes of TCP packets.
227+
228+ .. note :: Raw cleartext TCP as transport for the JSON-RPC payloads is clearly out-of-scope here.
229+ Without encryption, a passive network observer could just see all the plaintext messages anyway.
230+
231+ As a generic mitigation, implementations (both client and server) SHOULD
232+
233+ - pad messages to bucketed lengths (e.g. powers of 2, with a min size), and
234+
235+ - introduce small timing delays, ideally by buffering messages.
236+
237+ We can fully backwards-compatibly add padding to the JSON-RPC messages by adding extra
238+ whitespaces inside the JSON objects in a way that parsers ignore.
239+ This can be done at any protocol version.
240+
241+ For example, instead of sending::
242+
243+ {"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"]}\n
244+
245+
246+ the client could send::
247+
248+ {"jsonrpc":"2.0","method":"server.version","id":0,"params":["electrum/4.5.8","1.4"] }\n
249+
250+
251+ For better results, both the client and the server SHOULD implement logic to pad the messages
252+ that they send. So that requests and responses (and notifications) SHOULD all be padded.
253+ This does not have to be rolled out simultaneously: it is ok for only a client to pad what
254+ they send and not the server (or the other way around),
255+ that just limits the effectiveness of the defense against traffic analysis.
256+
257+ Note when the JSON-RPC messages are sent in the TLS stream, they are sometimes batched together.
258+ That is, a single TCP packet might contain multiple small JSON-RPC messages,
259+ e.g. if the client tries to send multiple messages in a short burst.
260+ Also, many protocol requests are <100 bytes, so it would be wasteful to pad all to e.g. 1 kbyte.
261+
262+ To save bandwidth, instead of padding individual JSON-RPC messages,
263+ participants (the client and the server) COULD implement an application-level buffer,
264+ write the messages into that buffer, periodically empty the buffer into the TLS stream
265+ and only add the padding into e.g. the last JSON-RPC message when emptying the buffer.
266+
267+ .. note :: Example implementation
268+ in the `Electrum client <https://github.com/spesmilo/electrum/pull/9875 >`_
269+ and in the `electrumx server <https://github.com/spesmilo/electrumx/pull/301 >`_:
270+
271+ Both the client and the server writes raw JSON-RPC protocol messages into a buffer,
272+ which is then occasionally flushed to the wire. When it is flushed, padding is added
273+ to round up the total length to 1 KB, or to the next power of 2.
274+ The buffer is flushed if it reaches 1 KB, plus there is extra logic that periodically polls
275+ if the oldest message in the buffer is older than 1 second, in which case it is also flushed.
276+
277+ .. note :: Many protocol requests are <100 bytes. Contrast that with broadcasting a transaction,
278+ which could potentially be several megabytes of data.
279+ (max consensus-valid tx is 4 MB, times 2 for hex-encoding)
280+ Hence padding to a constant size is not practical.
281+ Instead it is recommended to pad to bucketed lengths, e.g. to powers of 2.
282+
283+ The specific details of the size of the buffer, how often it is flushed, and how the padding
284+ is done is not specified by the protocol at the moment.
285+
286+ .. note :: Some server implementations do not deal with TLS at all,
287+ they only implement the raw cleartext TCP protocol, and just recommend operators
288+ to put a reverse proxy in front that does TLS termination.
289+ Such protocol implementations (both client and server) are nevertheless still
290+ SHOULD implement all mentioned traffic analysis protections.
291+ That way, if the operator tunnels the traffic over TLS externally,
292+ the resulting stream meaningfully receives the protections.
293+
294+ .. note :: Buffering the messages to introduce timing delays
295+ and padding to ~bucketed sizes is a good baseline.
296+ However even approximate timing and direction of TCP packets
297+ can leak too much information in some scenarios.
298+
299+ To combat timing analysis, both the client and the server
300+ COULD send noise with a random timer, but more importantly at strategically selected events.
301+ For example, when the client receives a new block header notification,
302+ it COULD probabilistically send a random number of "server.ping" messages
303+ with small random sleeps in-between.
304+
305+ Protocol version 1.6 extends "server.ping" so that either party can send it, and
306+ that it can be sent either as a JSON-RPC "Request" or as a JSON-RPC "Notification".
307+ If sent as a notification, the receiver is expected not to respond.
308+
309+ When the server sends a block header notification to the client,
310+ it COULD also probabilistically send noise ("server.ping") notifications to the client,
311+ perhaps conditioned on whether it will also send
312+ :func: `blockchain.scriptpubkey.subscribe ` notifications.
313+ (so server could send noise if there are no status notifications to be sent)
0 commit comments