Skip to content

Encrypt the Renter-Host Protocol

Luke Champine requested to merge encrypt-rpcs into unstable

All RPC communication is now encrypted after the initial key exchange handshake.

Encryption uses ChaCha20-Poly1305. No other ciphers are currently supported. Each message consists of the message length, followed by a random nonce, followed by the encrypted payload. (Previously we did not require length-prefixes because the decoder had full type information, but that information is now obscured by encryption.) I also considered using an implicit incrementing nonce, which would eliminate the overhead of sending random nonces, but that seemed more error prone since you could potentially desynchronize. Since we already used the prepend-nonce pattern for our Twofish encryption, I added helper functions to the crypto package that are now used by both the Twofish encryption and the renter-host encryption.

Another consequence of encryption is that RPC IDs must be encrypted in a separate payload from the RPC request itself. Otherwise, you wouldn't know which RPC you were processing until you had decrypted the entire payload, which means the host would have to permit any payload to be as large as the maximum size of the largest potential payload, i.e. at least 4 MiB.

This means we now have three different types of objects sent in the RPC protocol: IDs, requests, and responses. I have added helper functions for each of these to the renter, host, and modules packages. I also added an rpcSession type to the host that tracks all of the session-specific data; this type is then passed to each RPC handler function instead of separately passing the conn, storageObligation, etc.

I also refactored the renter's FormContract and RenewContract functions to use the Session type. Those functions were already duplicating lots of Session code, and adding encryption would just make it even more of a headache, so I copied Chris' code from !3362 (merged) to allow the creation of sessions without an existing contract. This should prevent merge conflicts down the line.

There is definitely room for improvement here wrt performance. Specifically, we're re-allocating every time we encrypt or decrypt, which is gross for large payloads like upload/download. We're also double-encrypting sector data; as I recall, the jury is still out on whether that's something we're comfortable with. To that end, I added a ChaCha20-Poly1035 benchmark to see how fast it is. On my machine, it runs at about 1700 MB/s, so encrypting a whole 4 MiB sector only takes 2ms, as long as you do it without reallocating. (With reallocating, it takes 3ms, but that could just be a consequence of the benchmark generating tons of allocs in a short timeframe, which doesn't match real-world behavior.) For a normal-sized RPC request or response (est 1024 bytes), encryption adds about 700ns of overhead. My feeling is that this is fast enough that we don't need to worry about the overhead, although we will want to return at some point to eliminate the excess allocations.

Merge request reports