From 3f7990d72b1da0c952b234a049972e71a8d39056 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 12 Feb 2019 21:17:09 +0100 Subject: [PATCH 01/14] sphinx: Consolidate some of the hard-coded parameter naming For the multi-frame support we need to introduce the FRAME_SIZE parameter and I took the opportunity to fix up some of the naming. Signed-off-by: Christian Decker --- common/sphinx.c | 45 ++++++++++++++++++++++----------------------- common/sphinx.h | 18 ++++++++++-------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index 40ed4c2a094d..0d687902a38a 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -16,10 +16,9 @@ #define BLINDING_FACTOR_SIZE 32 #define SHARED_SECRET_SIZE 32 -#define HMAC_SIZE 32 - -#define NUM_STREAM_BYTES ((NUM_MAX_HOPS + 1) * HOP_DATA_SIZE) #define KEY_LEN 32 + +#define NUM_STREAM_BYTES (2*ROUTING_INFO_SIZE) #define ONION_REPLY_SIZE 256 struct hop_params { @@ -98,7 +97,7 @@ struct onionpacket *parse_onionpacket(const tal_t *ctx, } read_buffer(&m->routinginfo, src, ROUTING_INFO_SIZE, &p); - read_buffer(&m->mac, src, SECURITY_PARAMETER, &p); + read_buffer(&m->mac, src, HMAC_SIZE, &p); return m; } @@ -148,7 +147,7 @@ static void compute_packet_hmac(const struct onionpacket *packet, write_buffer(mactemp, assocdata, assocdatalen, &pos); compute_hmac(mac, mactemp, sizeof(mactemp), mukey, KEY_LEN); - memcpy(hmac, mac, SECURITY_PARAMETER); + memcpy(hmac, mac, HMAC_SIZE); } static bool generate_key(void *k, const char *t, u8 tlen, const u8 *s) @@ -166,7 +165,7 @@ static bool generate_header_padding( ) { int i; - u8 cipher_stream[(NUM_MAX_HOPS + 1) * hopsize]; + u8 cipher_stream[(NUM_MAX_FRAMES + 1) * FRAME_SIZE]; u8 key[KEY_LEN]; memset(dst, 0, dstlen); @@ -175,7 +174,7 @@ static bool generate_header_padding( return false; generate_cipher_stream(cipher_stream, key, sizeof(cipher_stream)); - int pos = ((NUM_MAX_HOPS - i) + 1) * hopsize; + int pos = ((NUM_MAX_FRAMES - i) + 1) * hopsize; xorbytes(dst, dst, cipher_stream + pos, sizeof(cipher_stream) - pos); } return true; @@ -311,7 +310,7 @@ static void serialize_hop_data(tal_t *ctx, u8 *dst, const struct hop_data *data) towire_amount_msat(&buf, data->amt_forward); towire_u32(&buf, data->outgoing_cltv); towire_pad(&buf, 12); - towire(&buf, data->hmac, SECURITY_PARAMETER); + towire(&buf, data->hmac, HMAC_SIZE); memcpy(dst, buf, tal_count(buf)); tal_free(buf); } @@ -319,13 +318,13 @@ static void serialize_hop_data(tal_t *ctx, u8 *dst, const struct hop_data *data) static void deserialize_hop_data(struct hop_data *data, const u8 *src) { const u8 *cursor = src; - size_t max = HOP_DATA_SIZE; + size_t max = FRAME_SIZE; data->realm = fromwire_u8(&cursor, &max); fromwire_short_channel_id(&cursor, &max, &data->channel_id); data->amt_forward = fromwire_amount_msat(&cursor, &max); data->outgoing_cltv = fromwire_u32(&cursor, &max); fromwire_pad(&cursor, &max, 12); - fromwire(&cursor, &max, &data->hmac, SECURITY_PARAMETER); + fromwire(&cursor, &max, &data->hmac, HMAC_SIZE); } struct onionpacket *create_onionpacket( @@ -340,9 +339,9 @@ struct onionpacket *create_onionpacket( { struct onionpacket *packet = talz(ctx, struct onionpacket); int i, num_hops = tal_count(path); - u8 filler[(num_hops - 1) * HOP_DATA_SIZE]; + u8 filler[(num_hops - 1) * FRAME_SIZE]; struct keyset keys; - u8 nexthmac[SECURITY_PARAMETER]; + u8 nexthmac[HMAC_SIZE]; u8 stream[ROUTING_INFO_SIZE]; struct hop_params *params = generate_hop_params(ctx, sessionkey, path); struct secret *secrets = tal_arr(ctx, struct secret, num_hops); @@ -353,26 +352,26 @@ struct onionpacket *create_onionpacket( return NULL; } packet->version = 0; - memset(nexthmac, 0, SECURITY_PARAMETER); + memset(nexthmac, 0, HMAC_SIZE); memset(packet->routinginfo, 0, ROUTING_INFO_SIZE); - generate_header_padding(filler, sizeof(filler), HOP_DATA_SIZE, + generate_header_padding(filler, sizeof(filler), FRAME_SIZE, "rho", 3, num_hops, params); for (i = num_hops - 1; i >= 0; i--) { - memcpy(hops_data[i].hmac, nexthmac, SECURITY_PARAMETER); + memcpy(hops_data[i].hmac, nexthmac, HMAC_SIZE); hops_data[i].realm = 0; generate_key_set(params[i].secret, &keys); generate_cipher_stream(stream, keys.rho, ROUTING_INFO_SIZE); - /* Rightshift mix-header by 2*SECURITY_PARAMETER */ - memmove(packet->routinginfo + HOP_DATA_SIZE, packet->routinginfo, - ROUTING_INFO_SIZE - HOP_DATA_SIZE); + /* Rightshift mix-header by 2*HMAC_SIZE */ + memmove(packet->routinginfo + FRAME_SIZE, packet->routinginfo, + ROUTING_INFO_SIZE - FRAME_SIZE); serialize_hop_data(packet, packet->routinginfo, &hops_data[i]); xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); if (i == num_hops - 1) { - size_t len = (NUM_MAX_HOPS - num_hops + 1) * HOP_DATA_SIZE; + size_t len = (NUM_MAX_FRAMES - num_hops + 1) * FRAME_SIZE; memcpy(packet->routinginfo + len, filler, sizeof(filler)); } @@ -403,11 +402,11 @@ struct route_step *process_onionpacket( ) { struct route_step *step = talz(ctx, struct route_step); - u8 hmac[SECURITY_PARAMETER]; + u8 hmac[HMAC_SIZE]; struct keyset keys; u8 blind[BLINDING_FACTOR_SIZE]; u8 stream[NUM_STREAM_BYTES]; - u8 paddedheader[ROUTING_INFO_SIZE + HOP_DATA_SIZE]; + u8 paddedheader[ROUTING_INFO_SIZE + FRAME_SIZE]; step->next = talz(step, struct onionpacket); step->next->version = msg->version; @@ -433,9 +432,9 @@ struct route_step *process_onionpacket( deserialize_hop_data(&step->hop_data, paddedheader); - memcpy(&step->next->mac, step->hop_data.hmac, SECURITY_PARAMETER); + memcpy(&step->next->mac, step->hop_data.hmac, HMAC_SIZE); - memcpy(&step->next->routinginfo, paddedheader + HOP_DATA_SIZE, ROUTING_INFO_SIZE); + memcpy(&step->next->routinginfo, paddedheader + FRAME_SIZE, ROUTING_INFO_SIZE); if (memeqzero(step->next->mac, sizeof(step->next->mac))) { step->nextcase = ONION_END; diff --git a/common/sphinx.h b/common/sphinx.h index e1f6fb5a5178..ff6eb7172bd6 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -12,17 +12,19 @@ #include #include -#define SECURITY_PARAMETER 32 -#define NUM_MAX_HOPS 20 -#define PAYLOAD_SIZE 32 -#define HOP_DATA_SIZE (1 + SECURITY_PARAMETER + PAYLOAD_SIZE) -#define ROUTING_INFO_SIZE (HOP_DATA_SIZE * NUM_MAX_HOPS) -#define TOTAL_PACKET_SIZE (1 + 33 + SECURITY_PARAMETER + ROUTING_INFO_SIZE) +#define VERSION_SIZE 1 +#define REALM_SIZE 1 +#define HMAC_SIZE 32 +#define PUBKEY_SIZE 33 +#define FRAME_SIZE 65 +#define NUM_MAX_FRAMES 20 +#define ROUTING_INFO_SIZE (FRAME_SIZE * NUM_MAX_FRAMES) +#define TOTAL_PACKET_SIZE (VERSION_SIZE + PUBKEY_SIZE + HMAC_SIZE + ROUTING_INFO_SIZE) struct onionpacket { /* Cleartext information */ u8 version; - u8 mac[SECURITY_PARAMETER]; + u8 mac[HMAC_SIZE]; struct pubkey ephemeralkey; /* Encrypted information */ @@ -70,7 +72,7 @@ struct hop_data { struct amount_msat amt_forward; u32 outgoing_cltv; /* Padding omitted, will be zeroed */ - u8 hmac[SECURITY_PARAMETER]; + u8 hmac[HMAC_SIZE]; }; struct route_step { From 9ef489607bf7293d69eb1ea0bdd997fa597cb9cd Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 14 Feb 2019 14:54:17 +0100 Subject: [PATCH 02/14] sphinx: Add sphinx_path struct to encapsulate routing related info `struct sphinx_path` serves as a container for all the routing related information, with a couple of constructors that can be used for normal operation or testing (with pre-determined `session_key`). Signed-off-by: Christian Decker --- common/sphinx.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ common/sphinx.h | 31 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/common/sphinx.c b/common/sphinx.c index 0d687902a38a..8d9c1ebcca5a 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -34,6 +34,57 @@ struct keyset { u8 gamma[KEY_LEN]; }; +/* + * All the necessary information to generate a valid onion for this hop on a + * sphinx path. The payload is preserialized in order since the onion + * generation is payload agnostic. */ +struct sphinx_hop { + struct pubkey pubkey; + u8 realm; + const u8 *payload; + u8 hmac[HMAC_SIZE]; +}; + +/* Encapsulates the information about a given payment path for the the onion + * routing algorithm. + */ +struct sphinx_path { + /* The session_key used to generate the shared secrets along the + * path. This MUST be generated in a cryptographically secure manner, + * and is exposed solely for testing, i.e., it can be set to known + * values in unit tests. If unset it'll be generated during the packet + * generation. */ + struct secret *session_key; + + /* The associated data is appended to the packet when generating the + * HMAC, but is not passed along as part of the packet. It is used to + * ensure some external data (HTLC payment_hash) is not modified along + * the way. */ + u8 *associated_data; + + /* The individual hops on this route. */ + struct sphinx_hop *hops; +}; + +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_arr(sp, u8, associated_data, + tal_bytelen(associated_data), 0); + sp->session_key = NULL; + sp->hops = tal_arr(sp, struct sphinx_hop, 0); + return sp; +} + +struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, + const u8 *associated_data, + const struct secret *session_key) +{ + struct sphinx_path *sp = sphinx_path_new(ctx, associated_data); + sp->session_key = tal_dup(sp, struct secret, session_key); + return sp; +} + /* Small helper to append data to a buffer and update the position * into the buffer */ diff --git a/common/sphinx.h b/common/sphinx.h index ff6eb7172bd6..2fa0fc7ea82a 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -36,6 +36,18 @@ enum route_next_case { ONION_FORWARD = 1, }; +/** + * A sphinx payment path. + * + * This struct defines a path a payment is taking through the Lightning + * Network, including the session_key used to generate secrets, the associated + * data that'll be included in the HMACs and the payloads at each hop in the + * path. The struct is opaque since it should not be modified externally. Use + * `sphinx_path_new` or `sphinx_path_new_with_key` (testing only) to create a + * new instance. + */ +struct sphinx_path; + /* BOLT #4: * * The `hops_data` field is a structure that holds obfuscations of the @@ -199,4 +211,23 @@ struct onionreply *unwrap_onionreply(const tal_t *ctx, const struct secret *shared_secrets, const int numhops, const u8 *reply); +/** + * Create a new empty sphinx_path. + * + * The sphinx_path instance can then be decorated with other functions and + * passed to `create_onionpacket` to generate the packet. + */ +struct sphinx_path *sphinx_path_new(const tal_t *ctx, + const u8 *associated_data); + +/** + * Create a new empty sphinx_path with a given `session_key`. + * + * This MUST NOT be used outside of tests and tools as it may leak the path + * details if the `session_key` is not randomly generated. + */ +struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, + const u8 *associated_data, + const struct secret *session_key); + #endif /* LIGHTNING_COMMON_SPHINX_H */ From 4240cadf611af3a666ac3fc5dab8c3b06c50c963 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 14 Feb 2019 16:59:17 +0100 Subject: [PATCH 03/14] sphinx: Add function to add a new v0 hop to a sphinx_path This is just taking the existing serialization code and repackaging it in a more useful form. Signed-off-by: Christian Decker --- common/sphinx.c | 127 +++++++++++++++++++++++++-------------- common/sphinx.h | 12 ++-- common/test/run-sphinx.c | 3 + devtools/onion.c | 19 +++--- lightningd/pay.c | 35 ++++++----- 5 files changed, 123 insertions(+), 73 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index 8d9c1ebcca5a..09b555d799d6 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,47 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, return sp; } +static size_t sphinx_hop_count_frames(const struct sphinx_hop *hop) +{ + size_t size = REALM_SIZE + tal_bytelen(hop->payload) + HMAC_SIZE; + + /* Rounding up to the closest multiple of FRAME_SIZE) */ + return (size + FRAME_SIZE - 1) / FRAME_SIZE; +} + +static size_t sphinx_path_count_frames(const struct sphinx_path *path) +{ + size_t size = 0; + for (size_t i=0; ihops); i++) + size += sphinx_hop_count_frames(&path->hops[i]); + return size; +} + +/** + * Add a raw payload hop to the path. + */ +static void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey, u8 realm, + const u8 *payload) +{ + struct sphinx_hop sp; + sp.payload = payload; + sp.realm = realm; + sp.pubkey = *pubkey; + tal_arr_expand(&path->hops, sp); + assert(sphinx_path_count_frames(path) <= NUM_MAX_FRAMES); +} + +void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const struct short_channel_id *scid, + struct amount_msat forward, u32 outgoing_cltv) +{ + u8 *buf = tal_arr(path, u8, 0); + towire_short_channel_id(&buf, scid); + towire_u64(&buf, forward.millisatoshis); /* Raw: low-level serializer */ + towire_u32(&buf, outgoing_cltv); + sphinx_add_raw_hop(path, pubkey, 0, buf); +} + /* Small helper to append data to a buffer and update the position * into the buffer */ @@ -247,28 +289,24 @@ static void compute_blinding_factor(const struct pubkey *key, memcpy(res, &temp, 32); } -static bool blind_group_element( - struct pubkey *blindedelement, - const struct pubkey *pubkey, - const u8 blind[BLINDING_FACTOR_SIZE]) +static bool blind_group_element(struct pubkey *blindedelement, + const struct pubkey *pubkey, + const u8 blind[BLINDING_FACTOR_SIZE]) { /* tweak_mul is inplace so copy first. */ if (pubkey != blindedelement) *blindedelement = *pubkey; - if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, &blindedelement->pubkey, blind) != 1) + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &blindedelement->pubkey, blind) != 1) return false; return true; } -static bool create_shared_secret( - u8 *secret, - const struct pubkey *pubkey, - const u8 *sessionkey) +static bool create_shared_secret(u8 *secret, const struct pubkey *pubkey, + const struct secret *session_key) { - - if (secp256k1_ecdh(secp256k1_ctx, secret, &pubkey->pubkey, sessionkey, - NULL, NULL) - != 1) + if (secp256k1_ecdh(secp256k1_ctx, secret, &pubkey->pubkey, + session_key->data, NULL, NULL) != 1) return false; return true; } @@ -279,7 +317,7 @@ bool onion_shared_secret( const struct privkey *privkey) { return create_shared_secret(secret, &packet->ephemeralkey, - privkey->secret.data); + &privkey->secret); } static void generate_key_set(const u8 secret[SHARED_SECRET_SIZE], @@ -294,19 +332,21 @@ static void generate_key_set(const u8 secret[SHARED_SECRET_SIZE], static struct hop_params *generate_hop_params( const tal_t *ctx, const u8 *sessionkey, - struct pubkey path[]) + struct sphinx_path *path) { - int i, j, num_hops = tal_count(path); + int i, j, num_hops = tal_count(path->hops); struct pubkey temp; u8 blind[BLINDING_FACTOR_SIZE]; struct hop_params *params = tal_arr(ctx, struct hop_params, num_hops); /* Initialize the first hop with the raw information */ - if (secp256k1_ec_pubkey_create( - secp256k1_ctx, ¶ms[0].ephemeralkey.pubkey, sessionkey) != 1) + if (secp256k1_ec_pubkey_create(secp256k1_ctx, + ¶ms[0].ephemeralkey.pubkey, + path->session_key->data) != 1) return NULL; - if (!create_shared_secret(params[0].secret, &path[0], sessionkey)) + if (!create_shared_secret(params[0].secret, &path->hops[0].pubkey, + path->session_key)) return NULL; compute_blinding_factor( @@ -327,7 +367,7 @@ static struct hop_params *generate_hop_params( * Order is indifferent, multiplication is commutative. */ memcpy(&blind, sessionkey, 32); - temp = path[i]; + temp = path->hops[i].pubkey; if (!blind_group_element(&temp, &temp, blind)) return NULL; for (j = 0; j < i; j++) @@ -353,19 +393,6 @@ static struct hop_params *generate_hop_params( return params; } -static void serialize_hop_data(tal_t *ctx, u8 *dst, const struct hop_data *data) -{ - u8 *buf = tal_arr(ctx, u8, 0); - towire_u8(&buf, data->realm); - towire_short_channel_id(&buf, &data->channel_id); - towire_amount_msat(&buf, data->amt_forward); - towire_u32(&buf, data->outgoing_cltv); - towire_pad(&buf, 12); - towire(&buf, data->hmac, HMAC_SIZE); - memcpy(dst, buf, tal_count(buf)); - tal_free(buf); -} - static void deserialize_hop_data(struct hop_data *data, const u8 *src) { const u8 *cursor = src; @@ -378,25 +405,35 @@ static void deserialize_hop_data(struct hop_data *data, const u8 *src) fromwire(&cursor, &max, &data->hmac, HMAC_SIZE); } +static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) +{ + memset(dest, 0, FRAME_SIZE); + dest[0] = hop->realm; + memcpy(dest + 1, hop->payload, tal_bytelen(hop->payload)); + memcpy(dest + FRAME_SIZE - HMAC_SIZE, hop->hmac, HMAC_SIZE); +} + struct onionpacket *create_onionpacket( const tal_t *ctx, - struct pubkey *path, - struct hop_data hops_data[], - const u8 *sessionkey, - const u8 *assocdata, - const size_t assocdatalen, + struct sphinx_path *sp, struct secret **path_secrets ) { struct onionpacket *packet = talz(ctx, struct onionpacket); - int i, num_hops = tal_count(path); + int i, num_hops = tal_count(sp->hops); u8 filler[(num_hops - 1) * FRAME_SIZE]; struct keyset keys; u8 nexthmac[HMAC_SIZE]; u8 stream[ROUTING_INFO_SIZE]; - struct hop_params *params = generate_hop_params(ctx, sessionkey, path); + struct hop_params *params; struct secret *secrets = tal_arr(ctx, struct secret, num_hops); + if (sp->session_key == NULL) { + sp->session_key = tal(sp, struct secret); + randombytes_buf(sp->session_key, sizeof(struct secret)); + } + + params = generate_hop_params(ctx, sp->session_key->data, sp); if (!params) { tal_free(packet); tal_free(secrets); @@ -410,15 +447,15 @@ struct onionpacket *create_onionpacket( "rho", 3, num_hops, params); for (i = num_hops - 1; i >= 0; i--) { - memcpy(hops_data[i].hmac, nexthmac, HMAC_SIZE); - hops_data[i].realm = 0; + memcpy(sp->hops[i].hmac, nexthmac, HMAC_SIZE); generate_key_set(params[i].secret, &keys); generate_cipher_stream(stream, keys.rho, ROUTING_INFO_SIZE); - /* Rightshift mix-header by 2*HMAC_SIZE */ + /* Rightshift mix-header by FRAME_SIZE */ memmove(packet->routinginfo + FRAME_SIZE, packet->routinginfo, ROUTING_INFO_SIZE - FRAME_SIZE); - serialize_hop_data(packet, packet->routinginfo, &hops_data[i]); + sphinx_write_frame(packet->routinginfo, &sp->hops[i]); + xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); if (i == num_hops - 1) { @@ -426,7 +463,7 @@ struct onionpacket *create_onionpacket( memcpy(packet->routinginfo + len, filler, sizeof(filler)); } - compute_packet_hmac(packet, assocdata, assocdatalen, keys.mu, + compute_packet_hmac(packet, sp->associated_data, tal_bytelen(sp->associated_data), keys.mu, nexthmac); } memcpy(packet->mac, nexthmac, sizeof(nexthmac)); diff --git a/common/sphinx.h b/common/sphinx.h index 2fa0fc7ea82a..36ff11ddf70a 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -109,11 +109,7 @@ struct route_step { */ struct onionpacket *create_onionpacket( const tal_t * ctx, - struct pubkey path[], - struct hop_data hops_data[], - const u8 * sessionkey, - const u8 *assocdata, - const size_t assocdatalen, + struct sphinx_path *sp, struct secret **path_secrets ); @@ -230,4 +226,10 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, const u8 *associated_data, const struct secret *session_key); +/** + * Add a V0 (Realm 0) single frame hop to the path. + */ +void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey, + const struct short_channel_id *scid, + struct amount_msat forward, u32 outgoing_cltv); #endif /* LIGHTNING_COMMON_SPHINX_H */ diff --git a/common/test/run-sphinx.c b/common/test/run-sphinx.c index c4d6c2cd5cf4..8df58ac45dc0 100644 --- a/common/test/run-sphinx.c +++ b/common/test/run-sphinx.c @@ -53,6 +53,9 @@ void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) /* Generated stub for towire_u32 */ void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) { fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } /* Generated stub for towire_u8 */ void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) { fprintf(stderr, "towire_u8 called!\n"); abort(); } diff --git a/devtools/onion.c b/devtools/onion.c index 7c8cf08b80d0..0b2a4f6ce5ef 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -15,13 +16,17 @@ static void do_generate(int argc, char **argv) int num_hops = argc - 1; struct pubkey *path = tal_arr(ctx, struct pubkey, num_hops); u8 privkeys[argc - 1][32]; - u8 sessionkey[32]; + struct secret session_key; struct hop_data hops_data[num_hops]; struct secret *shared_secrets; - u8 assocdata[32]; + u8 *assocdata; + struct sphinx_path *sp; - memset(&sessionkey, 'A', sizeof(sessionkey)); - memset(&assocdata, 'B', sizeof(assocdata)); + assocdata = tal_arr(ctx, u8, 32); + memset(&session_key, 'A', sizeof(struct secret)); + memset(assocdata, 'B', tal_bytelen(assocdata)); + + sp = sphinx_path_new_with_key(ctx, assocdata, &session_key); for (int i = 0; i < num_hops; i++) { if (!hex_decode(argv[1 + i], 66, privkeys[i], 33)) { @@ -31,9 +36,7 @@ static void do_generate(int argc, char **argv) privkeys[i]) != 1) errx(1, "Could not decode pubkey"); fprintf(stderr, "Node %d pubkey %s\n", i, secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey)); - } - for (int i = 0; i < num_hops; i++) { memset(&hops_data[i], 0, sizeof(hops_data[i])); hops_data[i].realm = i; memset(&hops_data[i].channel_id, i, @@ -41,11 +44,11 @@ static void do_generate(int argc, char **argv) hops_data[i].amt_forward.millisatoshis = i; /* Raw: test code */ hops_data[i].outgoing_cltv = i; fprintf(stderr, "Hopdata %d: %s\n", i, tal_hexstr(NULL, &hops_data[i], sizeof(hops_data[i]))); + sphinx_add_v0_hop(sp, &path[i], &hops_data[i].channel_id, hops_data[i].amt_forward, i); } struct onionpacket *res = - create_onionpacket(ctx, path, hops_data, sessionkey, assocdata, - sizeof(assocdata), &shared_secrets); + create_onionpacket(ctx, sp, &shared_secrets); u8 *serialized = serialize_onionpacket(ctx, res); if (!serialized) diff --git a/lightningd/pay.c b/lightningd/pay.c index 13ac3fbfd849..bbc91916f04a 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -596,37 +596,36 @@ send_payment(struct lightningd *ld, enum onion_type failcode; size_t i, n_hops = tal_count(route); struct hop_data *hop_data = tal_arr(tmpctx, struct hop_data, n_hops); - struct pubkey *path = tal_arr(tmpctx, struct pubkey, n_hops); struct node_id *ids = tal_arr(tmpctx, struct node_id, n_hops); struct wallet_payment *payment = NULL; struct htlc_out *hout; struct short_channel_id *channels; struct routing_failure *fail; struct channel *channel; + struct sphinx_path *path; + struct short_channel_id finalscid; + struct pubkey pubkey; + bool ret; /* Expiry for HTLCs is absolute. And add one to give some margin. */ base_expiry = get_block_height(ld->topology) + 1; - /* Extract IDs for each hop: create_onionpacket wants array of *keys*, - * and wallet wants continuous array of node_ids */ - for (i = 0; i < n_hops; i++) { + path = sphinx_path_new(tmpctx, rhash->u.u8); + /* Extract IDs for each hop: create_onionpacket wants array. */ + for (i = 0; i < n_hops; i++) ids[i] = route[i].nodeid; - /* JSON parsing checked these were valid, so Shouldn't Happen */ - if (!pubkey_from_node_id(&path[i], &ids[i])) { - return command_fail(cmd, PAY_UNSPECIFIED_ERROR, - "Invalid nodeid %s", - type_to_string(tmpctx, - struct node_id, - &ids[i])); - } - } /* Copy hop_data[n] from route[n+1] (ie. where it goes next) */ for (i = 0; i < n_hops - 1; i++) { + ret = pubkey_from_node_id(&pubkey, &ids[i]); + assert(ret); hop_data[i].realm = 0; hop_data[i].channel_id = route[i+1].channel_id; hop_data[i].amt_forward = route[i+1].amount; hop_data[i].outgoing_cltv = base_expiry + route[i+1].delay; + sphinx_add_v0_hop(path, &pubkey, &route[i + 1].channel_id, + route[i + 1].amount, + base_expiry + route[i + 1].delay); } /* And finally set the final hop to the special values in @@ -636,6 +635,13 @@ send_payment(struct lightningd *ld, memset(&hop_data[i].channel_id, 0, sizeof(struct short_channel_id)); hop_data[i].amt_forward = route[i].amount; + memset(&finalscid, 0, sizeof(struct short_channel_id)); + ret = pubkey_from_node_id(&pubkey, &ids[i]); + assert(ret); + sphinx_add_v0_hop(path, &pubkey, &finalscid, + route[i].amount, + base_expiry + route[i].delay); + /* Now, do we already have a payment? */ payment = wallet_payment_by_hash(tmpctx, ld->wallet, rhash); if (payment) { @@ -684,8 +690,7 @@ send_payment(struct lightningd *ld, randombytes_buf(&sessionkey, sizeof(sessionkey)); /* Onion will carry us from first peer onwards. */ - packet = create_onionpacket(tmpctx, path, hop_data, sessionkey, rhash->u.u8, - sizeof(struct sha256), &path_secrets); + packet = create_onionpacket(tmpctx, path, &path_secrets); onion = serialize_onionpacket(tmpctx, packet); log_info(ld->log, "Sending %s over %zu hops to deliver %s", From ad636d1a39dbd3030aa271ee710f77eb1beec866 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Sat, 16 Feb 2019 18:04:02 +0100 Subject: [PATCH 04/14] sphinx: Fix the onion cli tool to take public keys This was a mismatch between the go tool and this test tool so far. Just aligning the tools to allows for easier testing. Signed-off-by: Christian Decker --- devtools/onion.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/devtools/onion.c b/devtools/onion.c index 0b2a4f6ce5ef..f5d6deef4a14 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -15,7 +15,7 @@ static void do_generate(int argc, char **argv) const tal_t *ctx = talz(NULL, tal_t); int num_hops = argc - 1; struct pubkey *path = tal_arr(ctx, struct pubkey, num_hops); - u8 privkeys[argc - 1][32]; + u8 rawpubkey[33]; struct secret session_key; struct hop_data hops_data[num_hops]; struct secret *shared_secrets; @@ -29,13 +29,16 @@ static void do_generate(int argc, char **argv) sp = sphinx_path_new_with_key(ctx, assocdata, &session_key); for (int i = 0; i < num_hops; i++) { - if (!hex_decode(argv[1 + i], 66, privkeys[i], 33)) { - errx(1, "Invalid private key hex '%s'", argv[1 + i]); + if (!hex_decode(argv[1 + i], 66, rawpubkey, 33)) { + errx(1, "Invalid public key hex '%s'", argv[1 + i]); } - if (secp256k1_ec_pubkey_create(secp256k1_ctx, &path[i].pubkey, - privkeys[i]) != 1) + + if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &path[i].pubkey, + rawpubkey, 33) != 1) errx(1, "Could not decode pubkey"); - fprintf(stderr, "Node %d pubkey %s\n", i, secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey)); + + fprintf(stderr, "Node %d pubkey %s\n", i, + secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey)); memset(&hops_data[i], 0, sizeof(hops_data[i])); hops_data[i].realm = i; @@ -43,7 +46,6 @@ static void do_generate(int argc, char **argv) sizeof(hops_data[i].channel_id)); hops_data[i].amt_forward.millisatoshis = i; /* Raw: test code */ hops_data[i].outgoing_cltv = i; - fprintf(stderr, "Hopdata %d: %s\n", i, tal_hexstr(NULL, &hops_data[i], sizeof(hops_data[i]))); sphinx_add_v0_hop(sp, &path[i], &hops_data[i].channel_id, hops_data[i].amt_forward, i); } From 2546a30bbc59a89a2816d9e56c762ee3663325ef Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Sat, 16 Feb 2019 18:41:04 +0100 Subject: [PATCH 05/14] sphinx: Clean up after migrating to the `sphinx_path` struct Signed-off-by: Christian Decker --- devtools/onion.c | 34 +++++++++------------------------- lightningd/pay.c | 7 ++----- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/devtools/onion.c b/devtools/onion.c index f5d6deef4a14..4129d15b29c7 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -14,49 +14,33 @@ static void do_generate(int argc, char **argv) { const tal_t *ctx = talz(NULL, tal_t); int num_hops = argc - 1; - struct pubkey *path = tal_arr(ctx, struct pubkey, num_hops); - u8 rawpubkey[33]; + struct pubkey pubkey; struct secret session_key; - struct hop_data hops_data[num_hops]; struct secret *shared_secrets; u8 *assocdata; struct sphinx_path *sp; + struct short_channel_id scid; + struct amount_msat amount; assocdata = tal_arr(ctx, u8, 32); memset(&session_key, 'A', sizeof(struct secret)); memset(assocdata, 'B', tal_bytelen(assocdata)); - sp = sphinx_path_new_with_key(ctx, assocdata, &session_key); for (int i = 0; i < num_hops; i++) { - if (!hex_decode(argv[1 + i], 66, rawpubkey, 33)) { + if (!pubkey_from_hexstr(argv[i+1], strlen(argv[i+1]), &pubkey)) errx(1, "Invalid public key hex '%s'", argv[1 + i]); - } - - if (secp256k1_ec_pubkey_parse(secp256k1_ctx, &path[i].pubkey, - rawpubkey, 33) != 1) - errx(1, "Could not decode pubkey"); - - fprintf(stderr, "Node %d pubkey %s\n", i, - secp256k1_pubkey_to_hexstr(ctx, &path[i].pubkey)); - - memset(&hops_data[i], 0, sizeof(hops_data[i])); - hops_data[i].realm = i; - memset(&hops_data[i].channel_id, i, - sizeof(hops_data[i].channel_id)); - hops_data[i].amt_forward.millisatoshis = i; /* Raw: test code */ - hops_data[i].outgoing_cltv = i; - sphinx_add_v0_hop(sp, &path[i], &hops_data[i].channel_id, hops_data[i].amt_forward, i); + amount.millisatoshis = i; /* Raw: test code */ + memset(&scid, i, sizeof(struct short_channel_id)); + sphinx_add_v0_hop(sp, &pubkey, &scid, amount, i); } - struct onionpacket *res = - create_onionpacket(ctx, sp, &shared_secrets); + struct onionpacket *res = create_onionpacket(ctx, sp, &shared_secrets); u8 *serialized = serialize_onionpacket(ctx, res); if (!serialized) errx(1, "Error serializing message."); - else - printf("%s\n", tal_hex(ctx, serialized)); + printf("%s\n", tal_hex(ctx, serialized)); tal_free(ctx); } diff --git a/lightningd/pay.c b/lightningd/pay.c index bbc91916f04a..c2ed9cd76a2d 100644 --- a/lightningd/pay.c +++ b/lightningd/pay.c @@ -609,6 +609,7 @@ send_payment(struct lightningd *ld, /* Expiry for HTLCs is absolute. And add one to give some margin. */ base_expiry = get_block_height(ld->topology) + 1; + memset(&finalscid, 0, sizeof(struct short_channel_id)); path = sphinx_path_new(tmpctx, rhash->u.u8); /* Extract IDs for each hop: create_onionpacket wants array. */ @@ -627,14 +628,10 @@ send_payment(struct lightningd *ld, route[i + 1].amount, base_expiry + route[i + 1].delay); } + for (i = 0; i < n_hops - 1; i++) /* And finally set the final hop to the special values in * BOLT04 */ - hop_data[i].realm = 0; - hop_data[i].outgoing_cltv = base_expiry + route[i].delay; - memset(&hop_data[i].channel_id, 0, sizeof(struct short_channel_id)); - hop_data[i].amt_forward = route[i].amount; - memset(&finalscid, 0, sizeof(struct short_channel_id)); ret = pubkey_from_node_id(&pubkey, &ids[i]); assert(ret); From abcf9a0275efda590bf082687bd17be0f4de1e74 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 18 Feb 2019 13:22:07 +0100 Subject: [PATCH 06/14] sphinx: Simplify the filler generation code Just some reorganizations and clarifications before we add the multi-frame support. Signed-off-by: Christian Decker --- common/sphinx.c | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index 09b555d799d6..bdfde390cff0 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -22,6 +22,8 @@ #define NUM_STREAM_BYTES (2*ROUTING_INFO_SIZE) #define ONION_REPLY_SIZE 256 +#define RHO_KEYTYPE "rho" + struct hop_params { u8 secret[SHARED_SECRET_SIZE]; u8 blind[BLINDING_FACTOR_SIZE]; @@ -248,27 +250,40 @@ static bool generate_key(void *k, const char *t, u8 tlen, const u8 *s) return compute_hmac(k, s, KEY_LEN, t, tlen); } -static bool generate_header_padding( - void *dst, size_t dstlen, - const size_t hopsize, - const char *keytype, - size_t keytypelen, - const u8 numhops, - struct hop_params *params - ) +static bool generate_header_padding(void *dst, size_t dstlen, + const struct sphinx_path *path, + struct hop_params *params) { - int i; - u8 cipher_stream[(NUM_MAX_FRAMES + 1) * FRAME_SIZE]; + u8 stream[2 * NUM_MAX_FRAMES * FRAME_SIZE]; u8 key[KEY_LEN]; + size_t fillerStart, fillerEnd, fillerFrames; memset(dst, 0, dstlen); - for (i = 1; i < numhops; i++) { - if (!generate_key(&key, keytype, keytypelen, params[i - 1].secret)) + for (int i = 0; i < tal_count(path->hops) - 1; i++) { + if (!generate_key(&key, RHO_KEYTYPE, strlen(RHO_KEYTYPE), + params[i].secret)) return false; - generate_cipher_stream(cipher_stream, key, sizeof(cipher_stream)); - int pos = ((NUM_MAX_FRAMES - i) + 1) * hopsize; - xorbytes(dst, dst, cipher_stream + pos, sizeof(cipher_stream) - pos); + generate_cipher_stream(stream, key, sizeof(stream)); + + /* Sum up how many frames have been used by previous hops, + * that gives us the start in the stream */ + fillerFrames = 0; + for (int j = 0; j < i; j++) + fillerFrames += 1; /* Currently constant, will change + with multi-frame hops */ + fillerStart = ROUTING_INFO_SIZE - (fillerFrames * FRAME_SIZE); + + /* The filler will dangle off of the end by the current + * hop-size, we'll make sure to copy it into the correct + * position in the next step. */ + /* TODO: This'll change with multi-frame hops */ + fillerEnd = ROUTING_INFO_SIZE + FRAME_SIZE; + + /* Apply the cipher-stream to the part of the filler that'll + * be added by this hop */ + xorbytes(dst, dst, stream + fillerStart, + fillerEnd - fillerStart); } return true; } @@ -443,8 +458,7 @@ struct onionpacket *create_onionpacket( memset(nexthmac, 0, HMAC_SIZE); memset(packet->routinginfo, 0, ROUTING_INFO_SIZE); - generate_header_padding(filler, sizeof(filler), FRAME_SIZE, - "rho", 3, num_hops, params); + generate_header_padding(filler, sizeof(filler), sp, params); for (i = num_hops - 1; i >= 0; i--) { memcpy(sp->hops[i].hmac, nexthmac, HMAC_SIZE); From 62f63d28ded043d3c74db9a5a1dcccb97a17a894 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 18 Feb 2019 14:02:25 +0100 Subject: [PATCH 07/14] sphinx: Expose sphinx_add_raw_hop for testing Shouldn't be used directly, but really useful for testing, since we can just cram a huge payload in without having to be valid. And we don't have a TLV spec yet. Signed-off-by: Christian Decker --- common/sphinx.c | 7 ++----- common/sphinx.h | 10 ++++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index bdfde390cff0..1e23302099e6 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -104,11 +104,8 @@ static size_t sphinx_path_count_frames(const struct sphinx_path *path) return size; } -/** - * Add a raw payload hop to the path. - */ -static void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey, u8 realm, - const u8 *payload) +void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey, + u8 realm, const u8 *payload) { struct sphinx_hop sp; sp.payload = payload; diff --git a/common/sphinx.h b/common/sphinx.h index 36ff11ddf70a..2ef7e1db9ebf 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -230,6 +230,12 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, * Add a V0 (Realm 0) single frame hop to the path. */ void sphinx_add_v0_hop(struct sphinx_path *path, const struct pubkey *pubkey, - const struct short_channel_id *scid, - struct amount_msat forward, u32 outgoing_cltv); + const struct short_channel_id *scid, struct amount_msat forward, + u32 outgoing_cltv); +/** + * Add a raw payload hop to the path. + */ +void sphinx_add_raw_hop(struct sphinx_path *path, const struct pubkey *pubkey, + u8 realm, const u8 *payload); + #endif /* LIGHTNING_COMMON_SPHINX_H */ From 6d91de67aa850654734cbc7bf276332de2f8c4e2 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 18 Feb 2019 15:14:02 +0100 Subject: [PATCH 08/14] sphinx: Implement multi-frame onion construction This is the actual change when constructing the multi-frame onion. It changes the rightshift, and how much filler we need to generate. It also has the realm stuffing with the number of additional frames. Signed-off-by: Christian Decker --- common/sphinx.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index 1e23302099e6..108d75a952bb 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -267,15 +267,15 @@ static bool generate_header_padding(void *dst, size_t dstlen, * that gives us the start in the stream */ fillerFrames = 0; for (int j = 0; j < i; j++) - fillerFrames += 1; /* Currently constant, will change - with multi-frame hops */ + fillerFrames += sphinx_hop_count_frames(&path->hops[j]); fillerStart = ROUTING_INFO_SIZE - (fillerFrames * FRAME_SIZE); /* The filler will dangle off of the end by the current * hop-size, we'll make sure to copy it into the correct * position in the next step. */ - /* TODO: This'll change with multi-frame hops */ - fillerEnd = ROUTING_INFO_SIZE + FRAME_SIZE; + fillerEnd = + ROUTING_INFO_SIZE + + (sphinx_hop_count_frames(&path->hops[i]) * FRAME_SIZE); /* Apply the cipher-stream to the part of the filler that'll * be added by this hop */ @@ -419,10 +419,12 @@ static void deserialize_hop_data(struct hop_data *data, const u8 *src) static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) { - memset(dest, 0, FRAME_SIZE); - dest[0] = hop->realm; - memcpy(dest + 1, hop->payload, tal_bytelen(hop->payload)); - memcpy(dest + FRAME_SIZE - HMAC_SIZE, hop->hmac, HMAC_SIZE); + u8 num_frames = sphinx_hop_count_frames(hop); + memset(dest, 0, num_frames * FRAME_SIZE); + dest[0] = hop->realm | (num_frames-1) << 4; + memcpy(dest + 1, hop->payload, tal_bytelen(hop->payload)); + memcpy(dest + (sphinx_hop_count_frames(hop) * FRAME_SIZE) - HMAC_SIZE, + hop->hmac, HMAC_SIZE); } struct onionpacket *create_onionpacket( @@ -433,7 +435,9 @@ struct onionpacket *create_onionpacket( { struct onionpacket *packet = talz(ctx, struct onionpacket); int i, num_hops = tal_count(sp->hops); - u8 filler[(num_hops - 1) * FRAME_SIZE]; + size_t fillerFrames = sphinx_path_count_frames(sp) - + sphinx_hop_count_frames(&sp->hops[num_hops - 1]); + u8 filler[fillerFrames * FRAME_SIZE]; struct keyset keys; u8 nexthmac[HMAC_SIZE]; u8 stream[ROUTING_INFO_SIZE]; @@ -463,15 +467,16 @@ struct onionpacket *create_onionpacket( generate_cipher_stream(stream, keys.rho, ROUTING_INFO_SIZE); /* Rightshift mix-header by FRAME_SIZE */ - memmove(packet->routinginfo + FRAME_SIZE, packet->routinginfo, - ROUTING_INFO_SIZE - FRAME_SIZE); + size_t shiftSize = sphinx_hop_count_frames(&sp->hops[i]) * FRAME_SIZE; + memmove(packet->routinginfo + shiftSize, packet->routinginfo, + ROUTING_INFO_SIZE-shiftSize); sphinx_write_frame(packet->routinginfo, &sp->hops[i]); xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); if (i == num_hops - 1) { - size_t len = (NUM_MAX_FRAMES - num_hops + 1) * FRAME_SIZE; - memcpy(packet->routinginfo + len, filler, sizeof(filler)); + size_t fillerSize = fillerFrames * FRAME_SIZE; + memcpy(packet->routinginfo + ROUTING_INFO_SIZE - fillerSize, filler, fillerSize); } compute_packet_hmac(packet, sp->associated_data, tal_bytelen(sp->associated_data), keys.mu, From acccab5d2c5855cb61f52c0de1a9efbe76916e55 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 Feb 2019 14:27:37 +0100 Subject: [PATCH 09/14] sphinx: Variable left-shift when unwrapping onion This is all it takes on the read side to use multiple frames. We are overshooting the padding a bit since we can at most use 16 additional frames, but ChaCha20 is cheap. Signed-off-by: Christian Decker --- common/sphinx.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index 108d75a952bb..bc27ece31f70 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -510,7 +510,8 @@ struct route_step *process_onionpacket( struct keyset keys; u8 blind[BLINDING_FACTOR_SIZE]; u8 stream[NUM_STREAM_BYTES]; - u8 paddedheader[ROUTING_INFO_SIZE + FRAME_SIZE]; + u8 paddedheader[2*ROUTING_INFO_SIZE]; + size_t shift_size; step->next = talz(step, struct onionpacket); step->next->version = msg->version; @@ -536,9 +537,14 @@ struct route_step *process_onionpacket( deserialize_hop_data(&step->hop_data, paddedheader); - memcpy(&step->next->mac, step->hop_data.hmac, HMAC_SIZE); + /* Extract how many frames we need to shift away */ + shift_size = ((paddedheader[0] >> 4) + 1) * FRAME_SIZE; - memcpy(&step->next->routinginfo, paddedheader + FRAME_SIZE, ROUTING_INFO_SIZE); + /* Copy the hmac from the last HMAC_SIZE bytes */ + memcpy(&step->next->mac, paddedheader + shift_size - HMAC_SIZE, HMAC_SIZE); + + /* Left shift the current payload out and make the remainder the new onion */ + memcpy(&step->next->routinginfo, paddedheader + shift_size, ROUTING_INFO_SIZE); if (memeqzero(step->next->mac, sizeof(step->next->mac))) { step->nextcase = ONION_END; From 75d10b5b7404e608b3425b0d235b5f938c451d1a Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 Feb 2019 15:30:16 +0100 Subject: [PATCH 10/14] sphinx: Add differentiation between payload versions This still only supports v0 payloads, but it is flexible enough to accomodate future payload formats as well, with the type-tag and the union holding the actual parsed data. It also better encapsulates the decoding of the former realm byte into payload type and frame count. Signed-off-by: Christian Decker --- common/sphinx.c | 29 ++++++++++++++++++++++++++--- common/sphinx.h | 14 ++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index bc27ece31f70..c1df257a7215 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -413,8 +413,6 @@ static void deserialize_hop_data(struct hop_data *data, const u8 *src) fromwire_short_channel_id(&cursor, &max, &data->channel_id); data->amt_forward = fromwire_amount_msat(&cursor, &max); data->outgoing_cltv = fromwire_u32(&cursor, &max); - fromwire_pad(&cursor, &max, 12); - fromwire(&cursor, &max, &data->hmac, HMAC_SIZE); } static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) @@ -427,6 +425,30 @@ static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) hop->hmac, HMAC_SIZE); } +static void sphinx_parse_payload(struct route_step *step, const u8 *src) +{ + size_t hop_size; + + /* Read the realm byte and extract the number of framse */ + step->realm = src[0] & 0x0F; + step->payload_frames = (src[0] >> 4) + 1; + hop_size = step->payload_frames * FRAME_SIZE; + + /* Copy common pieces over */ + step->raw_payload = + tal_dup_arr(step, u8, src + 1, hop_size - HMAC_SIZE - 1, 0); + memcpy(step->next->mac, src + hop_size - HMAC_SIZE, HMAC_SIZE); + + /* And now try to parse whatever the payload contains so we can use it + * later. */ + if (step->realm == SPHINX_V0_PAYLOAD) { + step->type = SPHINX_V0_PAYLOAD; + deserialize_hop_data(&step->payload.v0, src); + } else { + step->type = SPHINX_RAW_PAYLOAD; + } +} + struct onionpacket *create_onionpacket( const tal_t *ctx, struct sphinx_path *sp, @@ -536,9 +558,10 @@ struct route_step *process_onionpacket( return tal_free(step); deserialize_hop_data(&step->hop_data, paddedheader); + sphinx_parse_payload(step, paddedheader); /* Extract how many frames we need to shift away */ - shift_size = ((paddedheader[0] >> 4) + 1) * FRAME_SIZE; + shift_size = step->payload_frames * FRAME_SIZE; /* Copy the hmac from the last HMAC_SIZE bytes */ memcpy(&step->next->mac, paddedheader + shift_size - HMAC_SIZE, HMAC_SIZE); diff --git a/common/sphinx.h b/common/sphinx.h index 2ef7e1db9ebf..e1f69b9ed844 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -83,14 +83,24 @@ struct hop_data { struct short_channel_id channel_id; struct amount_msat amt_forward; u32 outgoing_cltv; - /* Padding omitted, will be zeroed */ - u8 hmac[HMAC_SIZE]; +}; + +enum sphinx_payload_type { + SPHINX_V0_PAYLOAD = 0, + SPHINX_RAW_PAYLOAD = 255, }; struct route_step { enum route_next_case nextcase; struct onionpacket *next; struct hop_data hop_data; + u8 realm; + enum sphinx_payload_type type; + union { + struct hop_data v0; + } payload; + u8 *raw_payload; + u8 payload_frames; }; /** From 38d29de91597a6a402125bba6f88b830bcdc82f2 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 20 Feb 2019 15:42:12 +0100 Subject: [PATCH 11/14] sphinx: Remove standalone v0 payload in favor of the unionized one Signed-off-by: Christian Decker --- common/sphinx.c | 1 - common/sphinx.h | 1 - lightningd/peer_htlcs.c | 14 ++++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index c1df257a7215..e4b1e245fa35 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -557,7 +557,6 @@ struct route_step *process_onionpacket( if (!blind_group_element(&step->next->ephemeralkey, &msg->ephemeralkey, blind)) return tal_free(step); - deserialize_hop_data(&step->hop_data, paddedheader); sphinx_parse_payload(step, paddedheader); /* Extract how many frames we need to shift away */ diff --git a/common/sphinx.h b/common/sphinx.h index e1f69b9ed844..2edb36e37dca 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -93,7 +93,6 @@ enum sphinx_payload_type { struct route_step { enum route_next_case nextcase; struct onionpacket *next; - struct hop_data hop_data; u8 realm; enum sphinx_payload_type type; union { diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index caec71e1782d..4dfa9a5b166b 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -622,6 +622,7 @@ static bool peer_accepted_htlc(struct channel *channel, struct route_step *rs; struct onionpacket *op; struct lightningd *ld = channel->peer->ld; + struct hop_data *hop_data; hin = find_htlc_in(&ld->htlcs_in, channel, id); if (!hin) { @@ -695,18 +696,19 @@ static bool peer_accepted_htlc(struct channel *channel, } /* Unknown realm isn't a bad onion, it's a normal failure. */ - if (rs->hop_data.realm != 0) { + if (rs->type != SPHINX_V0_PAYLOAD) { *failcode = WIRE_INVALID_REALM; goto out; } + hop_data = &rs->payload.v0; if (rs->nextcase == ONION_FORWARD) { struct gossip_resolve *gr = tal(ld, struct gossip_resolve); gr->next_onion = serialize_onionpacket(gr, rs->next); - gr->next_channel = rs->hop_data.channel_id; - gr->amt_to_forward = rs->hop_data.amt_forward; - gr->outgoing_cltv_value = rs->hop_data.outgoing_cltv; + gr->next_channel = hop_data->channel_id; + gr->amt_to_forward = hop_data->amt_forward; + gr->outgoing_cltv_value = hop_data->outgoing_cltv; gr->hin = hin; req = towire_gossip_get_channel_peer(tmpctx, &gr->next_channel); @@ -717,8 +719,8 @@ static bool peer_accepted_htlc(struct channel *channel, channel_resolve_reply, gr); } else handle_localpay(hin, hin->cltv_expiry, &hin->payment_hash, - rs->hop_data.amt_forward, - rs->hop_data.outgoing_cltv); + hop_data->amt_forward, + hop_data->outgoing_cltv); *failcode = 0; out: From c5e6080b79dc8354d0d937b6ce08322052591f86 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 21 Feb 2019 11:36:16 +0100 Subject: [PATCH 12/14] sphinx: Introduce a `runtest` command to the onion tool The `runtest` command takes a JSON onion spec, creates the onion and decodes it with the provided private keys. It is fully configurable and can be used for the test-vectors in the spec. Signed-off-by: Christian Decker --- common/test/onion-test-v0.json | 42 +++++++ devtools/Makefile | 2 + devtools/onion.c | 208 +++++++++++++++++++++++++++------ 3 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 common/test/onion-test-v0.json diff --git a/common/test/onion-test-v0.json b/common/test/onion-test-v0.json new file mode 100644 index 000000000000..5f3b34fa4d3a --- /dev/null +++ b/common/test/onion-test-v0.json @@ -0,0 +1,42 @@ +{ + "comment": "This is a simple testcase in which we only use v0 payloads, and all hops have single frame payloads", + "generate": { + "session_key": "4141414141414141414141414141414141414141414141414141414141414141", + "associated_data": "4242424242424242424242424242424242424242424242424242424242424242", + "hops": [ + { + "realm": 0, + "pubkey": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", + "payload": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "realm": 0, + "pubkey": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", + "payload": "0101010101010101000000000000000100000001000000000000000000000000" + }, + { + "realm": 0, + "pubkey": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", + "payload": "0202020202020202000000000000000200000002000000000000000000000000" + }, + { + "realm": 0, + "pubkey": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991", + "payload": "0303030303030303000000000000000300000003000000000000000000000000" + }, + { + "realm": 0, + "pubkey": "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145", + "payload": "0404040404040404000000000000000400000004000000000000000000000000" + } + ] + }, + "onion": "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf", + "decode": [ + "4141414141414141414141414141414141414141414141414141414141414141", + "4242424242424242424242424242424242424242424242424242424242424242", + "4343434343434343434343434343434343434343434343434343434343434343", + "4444444444444444444444444444444444444444444444444444444444444444", + "4545454545454545454545454545454545454545454545454545454545454545" + ] +} diff --git a/devtools/Makefile b/devtools/Makefile index d9a544ede004..ad92300f5429 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -13,6 +13,8 @@ DEVTOOLS_COMMON_OBJS := \ common/decode_short_channel_ids.o \ common/hash_u5.o \ common/node_id.o \ + common/json.o \ + common/json_helpers.o \ common/type_to_string.o \ common/utils.o \ common/version.o \ diff --git a/devtools/onion.c b/devtools/onion.c index 4129d15b29c7..fb097fc840e5 100644 --- a/devtools/onion.c +++ b/devtools/onion.c @@ -1,10 +1,16 @@ +#include #include #include #include #include +#include +#include #include +#include +#include #include #include +#include #include #include #include @@ -13,7 +19,7 @@ static void do_generate(int argc, char **argv) { const tal_t *ctx = talz(NULL, tal_t); - int num_hops = argc - 1; + int num_hops = argc - 2; struct pubkey pubkey; struct secret session_key; struct secret *shared_secrets; @@ -28,8 +34,9 @@ static void do_generate(int argc, char **argv) sp = sphinx_path_new_with_key(ctx, assocdata, &session_key); for (int i = 0; i < num_hops; i++) { - if (!pubkey_from_hexstr(argv[i+1], strlen(argv[i+1]), &pubkey)) - errx(1, "Invalid public key hex '%s'", argv[1 + i]); + if (!pubkey_from_hexstr(argv[i+2], strlen(argv[i+2]), &pubkey)) + errx(1, "Invalid public key hex '%s'", argv[i + 2]); + amount.millisatoshis = i; /* Raw: test code */ memset(&scid, i, sizeof(struct short_channel_id)); sphinx_add_v0_hop(sp, &pubkey, &scid, amount, i); @@ -44,27 +51,43 @@ static void do_generate(int argc, char **argv) tal_free(ctx); } -static void do_decode(int argc, char **argv) +static struct route_step *decode_with_privkey(const tal_t *ctx, const u8 *onion, char *hexprivkey, const u8 *assocdata) { - struct route_step *step; - struct onionpacket *msg; struct privkey seckey; + struct route_step *step; + struct onionpacket *packet; + enum onion_type why_bad; + u8 shared_secret[32]; + if (!hex_decode(hexprivkey, strlen(hexprivkey), &seckey, sizeof(seckey))) + errx(1, "Invalid private key hex '%s'", hexprivkey); + + packet = parse_onionpacket(ctx, onion, TOTAL_PACKET_SIZE, &why_bad); + + if (!packet) + errx(1, "Error parsing message: %s", onion_type_name(why_bad)); + + if (!onion_shared_secret(shared_secret, packet, &seckey)) + errx(1, "Error creating shared secret."); + + step = process_onionpacket(ctx, packet, shared_secret, assocdata, + tal_bytelen(assocdata)); + return step; + +} + +static void do_decode(int argc, char **argv) +{ const tal_t *ctx = talz(NULL, tal_t); u8 serialized[TOTAL_PACKET_SIZE]; char hextemp[2 * sizeof(serialized) + 1]; - memset(hextemp, 0, sizeof(hextemp)); - u8 shared_secret[32]; - u8 assocdata[32]; - enum onion_type why_bad; + u8 *assocdata = tal_arr(ctx, u8, 32); + struct route_step *step; - memset(&assocdata, 'B', sizeof(assocdata)); + memset(assocdata, 'B', tal_bytelen(assocdata)); - if (argc != 2) + if (argc != 3) opt_usage_exit_fail("Expect a privkey with --decode"); - if (!hex_decode(argv[1], strlen(argv[1]), &seckey, sizeof(seckey))) - errx(1, "Invalid private key hex '%s'", argv[1]); - if (!read_all(STDIN_FILENO, hextemp, sizeof(hextemp))) errx(1, "Reading in onion"); @@ -72,16 +95,7 @@ static void do_decode(int argc, char **argv) errx(1, "Invalid onion hex '%s'", hextemp); } - msg = parse_onionpacket(ctx, serialized, sizeof(serialized), &why_bad); - - if (!msg) - errx(1, "Error parsing message: %s", onion_type_name(why_bad)); - - if (!onion_shared_secret(shared_secret, msg, &seckey)) - errx(1, "Error creating shared secret."); - - step = process_onionpacket(ctx, msg, shared_secret, assocdata, - sizeof(assocdata)); + step = decode_with_privkey(ctx, serialized, tal_strdup(ctx, argv[2]), assocdata); if (!step || !step->next) errx(1, "Error processing message."); @@ -94,29 +108,151 @@ static void do_decode(int argc, char **argv) tal_free(ctx); } +/** + * Run an onion encoding/decoding unit-test from a file + */ +static void runtest(const char *filename) +{ + const tal_t *ctx = tal(NULL, u8); + bool valid; + char *buffer = grab_file(ctx, filename); + const jsmntok_t *toks, *session_key_tok, *associated_data_tok, *gentok, + *hopstok, *hop, *payloadtok, *pubkeytok, *realmtok, *oniontok, *decodetok; + const u8 *associated_data, *session_key_raw, *payload, *serialized, *onion; + struct secret session_key, *shared_secrets; + struct pubkey pubkey; + struct sphinx_path *path; + size_t i; + int realm; + struct onionpacket *res; + struct route_step *step; + char *hexprivkey; + + toks = json_parse_input(ctx, buffer, strlen(buffer), &valid); + if (!valid) + errx(1, "File is not a valid JSON file."); + + gentok = json_get_member(buffer, toks, "generate"); + if (!gentok) + errx(1, "JSON object does not contain a 'generate' key"); + + /* Unpack the common parts */ + associated_data_tok = json_get_member(buffer, gentok, "associated_data"); + session_key_tok = json_get_member(buffer, gentok, "session_key"); + associated_data = json_tok_bin_from_hex(ctx, buffer, associated_data_tok); + session_key_raw = json_tok_bin_from_hex(ctx, buffer, session_key_tok); + memcpy(&session_key, session_key_raw, sizeof(session_key)); + path = sphinx_path_new_with_key(ctx, associated_data, &session_key); + + /* Unpack the hops and build up the path */ + hopstok = json_get_member(buffer, gentok, "hops"); + json_for_each_arr(i, hop, hopstok) { + payloadtok = json_get_member(buffer, hop, "payload"); + realmtok = json_get_member(buffer, hop, "realm"); + pubkeytok = json_get_member(buffer, hop, "pubkey"); + payload = json_tok_bin_from_hex(ctx, buffer, payloadtok); + json_to_pubkey(buffer, pubkeytok, &pubkey); + json_to_int(buffer, realmtok, &realm); + sphinx_add_raw_hop(path, &pubkey, realm, payload); + } + res = create_onionpacket(ctx, path, &shared_secrets); + serialized = serialize_onionpacket(ctx, res); + + if (!serialized) + errx(1, "Error serializing message."); + + oniontok = json_get_member(buffer, toks, "onion"); + + if (oniontok) { + onion = json_tok_bin_from_hex(ctx, buffer, oniontok); + if (!memeq(onion, tal_bytelen(onion), serialized, + tal_bytelen(serialized))) + errx(1, + "Generated does not match the expected onion: \n" + "generated: %s\n" + "expected : %s\n", + tal_hex(ctx, serialized), tal_hex(ctx, onion)); + } + printf("Generated onion: %s\n", tal_hex(ctx, serialized)); + + hopstok = json_get_member(buffer, gentok, "hops"); + json_for_each_arr(i, hop, hopstok) { + payloadtok = json_get_member(buffer, hop, "payload"); + realmtok = json_get_member(buffer, hop, "realm"); + pubkeytok = json_get_member(buffer, hop, "pubkey"); + payload = json_tok_bin_from_hex(ctx, buffer, payloadtok); + json_to_pubkey(buffer, pubkeytok, &pubkey); + json_to_int(buffer, realmtok, &realm); + sphinx_add_raw_hop(path, &pubkey, realm, payload); + } + + decodetok = json_get_member(buffer, toks, "decode"); + + json_for_each_arr(i, hop, decodetok) { + hexprivkey = json_strdup(ctx, buffer, hop); + printf("Processing at hop %zu\n", i); + step = decode_with_privkey(ctx, serialized, hexprivkey, associated_data); + serialized = serialize_onionpacket(ctx, step->next); + if (!serialized) + errx(1, "Error serializing message."); + printf(" Realm: %d\n", step->realm); + printf(" Payload: %s\n", tal_hex(ctx, step->raw_payload)); + printf(" Next onion: %s\n", tal_hex(ctx, serialized)); + printf(" Next HMAC: %s\n", tal_hexstr(ctx, step->next->mac, HMAC_SIZE)); + } + + tal_free(ctx); +} + +/* Tal wrappers for opt. */ +static void *opt_allocfn(size_t size) +{ + return tal_arr_label(NULL, char, size, TAL_LABEL("opt_allocfn", "")); +} + +static void *tal_reallocfn(void *ptr, size_t size) +{ + if (!ptr) + return opt_allocfn(size); + tal_resize_(&ptr, 1, size, false); + return ptr; +} + +static void tal_freefn(void *ptr) +{ + tal_free(ptr); +} + int main(int argc, char **argv) { setup_locale(); + const char *method; - bool generate = false, decode = false; secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); opt_register_noarg("--help|-h", opt_usage_and_exit, - "--generate ... OR\n" - "--decode \n" - "Either create an onion message, or decode one step", - "Print this message."); - opt_register_noarg("--generate", opt_set_bool, &generate, - "Generate onion through the given hex pubkeys"); - opt_register_noarg("--decode", opt_set_bool, &decode, - "Decode onion from stdin given the private key"); + "decode \ngenerate ...\nruntest ", "Show this message"); + opt_register_version(); + opt_early_parse(argc, argv, opt_log_stderr_exit); opt_parse(&argc, argv, opt_log_stderr_exit); - if (generate) + if (argc < 2) + errx(1, "You must specify a method"); + method = argv[1]; + + if (streq(method, "runtest")) { + if (argc != 3) + errx(1, "'runtest' requires a filename argument"); + runtest(argv[2]); + } else if (streq(method, "generate")) { do_generate(argc, argv); - else if (decode) + } else if (streq(method, "decode")) { do_decode(argc, argv); + } else { + errx(1, "Unrecognized method '%s'", method); + } return 0; } From 3a6ed037b4af691ef02b270aba40464ae6925ab9 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 8 Apr 2019 13:11:31 +0930 Subject: [PATCH 13/14] sphinx: make multi-hop frame creation/decoding only for EXPERIMENTAL_FEATURES. This lets us merge it, since it has a nice refactor I would like to use for invoice hooks (raw payload access). Also checks that we don't give a payload too large to encode! Signed-off-by: Rusty Russell --- common/sphinx.c | 28 +++++++++++++++++++++++++--- common/sphinx.h | 6 ++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index e4b1e245fa35..e29c787827ab 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -90,10 +90,14 @@ struct sphinx_path *sphinx_path_new_with_key(const tal_t *ctx, static size_t sphinx_hop_count_frames(const struct sphinx_hop *hop) { +#if EXPERIMENTAL_FEATURES size_t size = REALM_SIZE + tal_bytelen(hop->payload) + HMAC_SIZE; /* Rounding up to the closest multiple of FRAME_SIZE) */ return (size + FRAME_SIZE - 1) / FRAME_SIZE; +#else + return 1; +#endif } static size_t sphinx_path_count_frames(const struct sphinx_path *path) @@ -415,23 +419,38 @@ static void deserialize_hop_data(struct hop_data *data, const u8 *src) data->outgoing_cltv = fromwire_u32(&cursor, &max); } -static void sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) +static bool sphinx_write_frame(u8 *dest, const struct sphinx_hop *hop) { u8 num_frames = sphinx_hop_count_frames(hop); + + if (num_frames > MAX_FRAMES_PER_HOP) + return false; + +#if !EXPERIMENTAL_FEATURES + if (REALM_SIZE + tal_bytelen(hop->payload) + HMAC_SIZE > FRAME_SIZE) + return false; +#endif + memset(dest, 0, num_frames * FRAME_SIZE); dest[0] = hop->realm | (num_frames-1) << 4; memcpy(dest + 1, hop->payload, tal_bytelen(hop->payload)); memcpy(dest + (sphinx_hop_count_frames(hop) * FRAME_SIZE) - HMAC_SIZE, hop->hmac, HMAC_SIZE); + return true; } static void sphinx_parse_payload(struct route_step *step, const u8 *src) { size_t hop_size; +#if EXPERIMENTAL_FEATURES /* Read the realm byte and extract the number of framse */ step->realm = src[0] & 0x0F; step->payload_frames = (src[0] >> 4) + 1; +#else + step->realm = src[0]; + step->payload_frames = 1; +#endif hop_size = step->payload_frames * FRAME_SIZE; /* Copy common pieces over */ @@ -492,8 +511,11 @@ struct onionpacket *create_onionpacket( size_t shiftSize = sphinx_hop_count_frames(&sp->hops[i]) * FRAME_SIZE; memmove(packet->routinginfo + shiftSize, packet->routinginfo, ROUTING_INFO_SIZE-shiftSize); - sphinx_write_frame(packet->routinginfo, &sp->hops[i]); - + if (!sphinx_write_frame(packet->routinginfo, &sp->hops[i])) { + tal_free(packet); + tal_free(secrets); + return NULL; + } xorbytes(packet->routinginfo, packet->routinginfo, stream, ROUTING_INFO_SIZE); if (i == num_hops - 1) { diff --git a/common/sphinx.h b/common/sphinx.h index 2edb36e37dca..6c896d10e26e 100644 --- a/common/sphinx.h +++ b/common/sphinx.h @@ -21,6 +21,12 @@ #define ROUTING_INFO_SIZE (FRAME_SIZE * NUM_MAX_FRAMES) #define TOTAL_PACKET_SIZE (VERSION_SIZE + PUBKEY_SIZE + HMAC_SIZE + ROUTING_INFO_SIZE) +#if EXPERIMENTAL_FEATURES +#define MAX_FRAMES_PER_HOP (1 << 4) +#else +#define MAX_FRAMES_PER_HOP 1 +#endif + struct onionpacket { /* Cleartext information */ u8 version; From 6c83600cfaceabcc3be3f20901c3f765411e1fb9 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 21 May 2019 21:47:01 +0200 Subject: [PATCH 14/14] fixup! sphinx: Add differentiation between payload versions --- common/sphinx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/sphinx.c b/common/sphinx.c index e29c787827ab..b4895b737c73 100644 --- a/common/sphinx.c +++ b/common/sphinx.c @@ -445,8 +445,8 @@ static void sphinx_parse_payload(struct route_step *step, const u8 *src) #if EXPERIMENTAL_FEATURES /* Read the realm byte and extract the number of framse */ - step->realm = src[0] & 0x0F; - step->payload_frames = (src[0] >> 4) + 1; + step->realm = src[0] & 0x07; + step->payload_frames = (src[0] >> 3) + 1; #else step->realm = src[0]; step->payload_frames = 1;