diff --git a/common/sphinx.c b/common/sphinx.c index 80ab1adae..9155dd247 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -57,6 +57,14 @@ struct sphinx_path { /* The individual hops on this route. */ struct sphinx_hop *hops; + + /* If this is a rendez-vous onion, then the following node_id tells us + * which node will be processing this onion and decompressing the + * onion. It is used to generate the prefill obfuscation stream to + * hide the fact that the onion was compressed from the next + * node. NULL if this is not a rendez-vous onion, and shouldn't be + * compressible. */ + struct pubkey *rendezvous_id; }; struct sphinx_path *sphinx_path_new(const tal_t *ctx, const u8 *associated_data) @@ -64,6 +72,7 @@ struct sphinx_path *sphinx_path_new(const tal_t *ctx, const u8 *associated_data) struct sphinx_path *sp = tal(ctx, struct sphinx_path); sp->associated_data = tal_dup_talarr(sp, u8, associated_data); sp->session_key = NULL; + sp->rendezvous_id = NULL; sp->hops = tal_arr(sp, struct sphinx_hop, 0); return sp; } @@ -259,6 +268,36 @@ static bool generate_header_padding(void *dst, size_t dstlen, return true; } +static bool generate_prefill(void *dst, size_t dstlen, + const struct sphinx_path *path, + struct hop_params *params) +{ + u8 stream[2 * ROUTING_INFO_SIZE]; + u8 key[KEY_LEN]; + size_t fillerStart, fillerSize; + + memset(dst, 0, dstlen); + for (int i = 0; i < tal_count(path->hops); i++) { + if (!generate_key(&key, RHO_KEYTYPE, strlen(RHO_KEYTYPE), + ¶ms[i].secret)) + return false; + + generate_cipher_stream(stream, key, sizeof(stream)); + + /* Sum up how many bytes have been used by previous hops, + * that gives us the start in the stream */ + fillerSize = 0; + for (int j = 0; j < i; j++) + fillerSize += sphinx_hop_size(&path->hops[j]); + fillerStart = ROUTING_INFO_SIZE - fillerSize - dstlen; + + /* Apply the cipher-stream to the part of the filler that'll + * be added by this hop */ + xorbytes(dst, dst, stream + fillerStart, dstlen); + } + return true; +} + static void compute_blinding_factor(const struct pubkey *key, const struct secret *sharedsecret, u8 res[BLINDING_FACTOR_SIZE]) @@ -386,6 +425,39 @@ static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) memcpy(dest + tal_bytelen(hop->raw_payload), hop->hmac, HMAC_SIZE); } +static void sphinx_prefill_stream_xor(u8 *dst, size_t dstlen, + const struct secret *shared_secret) +{ + u8 padkey[KEY_LEN]; + generate_key(padkey, "prefill", 7, shared_secret); + xor_cipher_stream(dst, padkey, dstlen); +} + +static void sphinx_prefill(u8 *routinginfo, const struct sphinx_path *sp, + size_t prefill_size, struct hop_params *params) +{ + int num_hops = tal_count(sp->hops); + size_t fillerSize = sphinx_path_payloads_size(sp) - + sphinx_hop_size(&sp->hops[num_hops - 1]); + size_t last_hop_size = sphinx_hop_size(&sp->hops[num_hops - 1]); + int prefill_offset = + ROUTING_INFO_SIZE - fillerSize - last_hop_size - prefill_size; + u8 prefill[prefill_size]; + struct secret shared_secret; + + /* Generate the prefill stream, which cancels out the layers of + * encryption that will be applied while wrapping the onion. This + * leaves the middle, unused, section with all 0x00 bytes after + * encrypting. */ + generate_prefill(prefill, prefill_size, sp, params); + memcpy(routinginfo + prefill_offset, prefill, prefill_size); + + /* Now fill in the obfuscation stream, which can be regenerated by the + * node processing this onion. */ + create_shared_secret(&shared_secret, sp->rendezvous_id, sp->session_key); + sphinx_prefill_stream_xor(routinginfo + prefill_offset, prefill_size, &shared_secret); +} + struct onionpacket *create_onionpacket( const tal_t *ctx, struct sphinx_path *sp, @@ -402,6 +474,8 @@ struct onionpacket *create_onionpacket( u8 nexthmac[HMAC_SIZE]; struct hop_params *params; struct secret *secrets = tal_arr(ctx, struct secret, num_hops); + size_t payloads_size = sphinx_path_payloads_size(sp); + size_t max_prefill = ROUTING_INFO_SIZE - payloads_size; if (sphinx_path_payloads_size(sp) > ROUTING_INFO_SIZE) { tal_free(packet); @@ -435,6 +509,11 @@ struct onionpacket *create_onionpacket( generate_header_padding(filler, sizeof(filler), sp, params); + if (sp->rendezvous_id != NULL) + /* FIXME: Fuzz this or expose to the caller to hide encoded + * route length. */ + sphinx_prefill(packet->routinginfo, sp, max_prefill, params); + for (i = num_hops - 1; i >= 0; i--) { memcpy(sp->hops[i].hmac, nexthmac, HMAC_SIZE); generate_key_set(¶ms[i].secret, &keys);