diff --git a/common/blindedpath.c b/common/blindedpath.c index 687d2e21b..da51c6dfc 100644 --- a/common/blindedpath.c +++ b/common/blindedpath.c @@ -79,7 +79,6 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx, return NULL; ret = tal_dup_talarr(ctx, u8, raw_encmsg); - SUPERVERBOSE("\t\"encmsg_hex\": \"%s\",\n", tal_hex(tmpctx, ret)); /* BOLT-route-blinding #4: * - `rho(i) = HMAC256("rho", ss(i))` diff --git a/common/test/run-onion-message-test.c b/common/test/run-onion-message-test.c new file mode 100644 index 000000000..508ab6d97 --- /dev/null +++ b/common/test/run-onion-message-test.c @@ -0,0 +1,394 @@ +/* Creates test vector bolt04/blinded-onion-message-onion-test.json. Run output through jq! */ +static void maybe_print(const char *fmt, ...); +#define SUPERVERBOSE maybe_print +#include "config.h" +#include "../../wire/fromwire.c" +#include "../../wire/tlvstream.c" +#include "../../wire/towire.c" +#include "../amount.c" +#include "../bigsize.c" +#include "../blindedpath.c" +#include "../blindedpay.c" +#include "../blinding.c" +#include "../features.c" +#include "../hmac.c" +#include "../onion.c" +#include "../onion_message_parse.c" +#include "../sphinx.c" +#include "../type_to_string.c" +#if EXPERIMENTAL_FEATURES + #include "../../wire/onion_exp_wiregen.c" + #include "../../wire/peer_exp_wiregen.c" +#else + #include "../../wire/onion_wiregen.c" + #include "../../wire/peer_wiregen.c" +#endif +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for fromwire_channel_id */ +bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); } +/* Generated stub for fromwire_node_id */ +void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED) +{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); } +/* Generated stub for new_onionreply */ +struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED) +{ fprintf(stderr, "new_onionreply called!\n"); abort(); } +/* Generated stub for pubkey_from_node_id */ +bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); } +/* Generated stub for status_fmt */ +void status_fmt(enum log_level level UNNEEDED, + const struct node_id *peer UNNEEDED, + const char *fmt UNNEEDED, ...) + +{ fprintf(stderr, "status_fmt called!\n"); abort(); } +/* Generated stub for towire_channel_id */ +void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) +{ fprintf(stderr, "towire_channel_id called!\n"); abort(); } +/* Generated stub for towire_node_id */ +void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED) +{ fprintf(stderr, "towire_node_id called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static bool comma; + +static void flush_comma(void) +{ + if (comma) + printf(",\n"); + comma = false; +} + +static void maybe_comma(void) +{ + comma = true; +} + +static void json_start(const char *name, const char what) +{ + flush_comma(); + if (name) + printf("\"%s\":", name); + printf("%c\n", what); +} + +static void json_end(const char what) +{ + comma = false; + printf("%c\n", what); +} + +static void json_strfield(const char *name, const char *val) +{ + flush_comma(); + printf("\t\"%s\": \"%s\"", name, val); + maybe_comma(); +} + +static void json_hexfield(const char *name, const u8 *val, size_t len) +{ + json_strfield(name, tal_hexstr(tmpctx, val, len)); +} + +static void json_hex_talfield(const char *name, const u8 *val) +{ + json_strfield(name, tal_hexstr(tmpctx, val, tal_bytelen(val))); +} + +static void json_pubkey(const char *name, const struct pubkey *key) +{ + json_strfield(name, pubkey_to_hexstr(tmpctx, key)); +} + +static bool enable_superverbose; +static void maybe_print(const char *fmt, ...) +{ + if (enable_superverbose) { + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + } +} + +/* Updated each time, as we pretend to be Alice, Bob, Carol */ +static const struct privkey *mykey; + +void ecdh(const struct pubkey *point, struct secret *ss) +{ + if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey, + mykey->secret.data, NULL, NULL) != 1) + abort(); +} + +/* This established by trial and error! */ +#define LARGEST_TLV_SIZE 70 + +/* Generic, ugly, function to calc encrypted_recipient_data, + alias and next blinding, and print out JSON */ +static u8 *add_hop(const char *name, + const char *comment, + const struct pubkey *me, + const struct pubkey *next, + const struct privkey *override_blinding, + const char *additional_field_hexstr, + const char *path_id_hexstr, + struct privkey *blinding, /* in and out */ + struct pubkey *alias /* out */) +{ + struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx); + u8 *enctlv, *encrypted_recipient_data; + const u8 *additional; + + json_start(NULL, '{'); + json_strfield("alias", name); + json_strfield("comment", comment); + json_hexfield("blinding_secret", blinding->secret.data, sizeof(*blinding)); + + /* We have to calc first, to make padding correct */ + tlv->next_node_id = tal_dup_or_null(tlv, struct pubkey, next); + if (path_id_hexstr) + tlv->path_id = tal_hexdata(tlv, path_id_hexstr, + strlen(path_id_hexstr)); + + /* Normally we wouldn't know blinding privkey, and we'd just + * paste in the rest of the path as given, but here we're actually + * generating the lot. */ + if (override_blinding) { + tlv->next_blinding_override = tal(tlv, struct pubkey); + assert(pubkey_from_privkey(override_blinding, tlv->next_blinding_override)); + } + + /* This is assumed to be a valid TLV tuple, and greater than + * any previous, so we simply append. */ + if (additional_field_hexstr) + additional = tal_hexdata(tlv, additional_field_hexstr, + strlen(additional_field_hexstr)); + else + additional = NULL; + + enctlv = tal_arr(tmpctx, u8, 0); + towire_tlv_encrypted_data_tlv(&enctlv, tlv); + + /* Now create padding, and reencode */ + if (tal_bytelen(enctlv) + tal_bytelen(additional) != LARGEST_TLV_SIZE) + tlv->padding = tal_arrz(tlv, u8, + LARGEST_TLV_SIZE + - tal_bytelen(enctlv) + - tal_bytelen(additional) + - 2); + enctlv = tal_arr(tmpctx, u8, 0); + towire_tlv_encrypted_data_tlv(&enctlv, tlv); + towire(&enctlv, additional, tal_bytelen(additional)); + assert(tal_bytelen(enctlv) == LARGEST_TLV_SIZE); + + json_start("tlvs", '{'); + if (tlv->padding) + json_hex_talfield("padding", tlv->padding); + if (tlv->next_node_id) + json_pubkey("next_node_id", tlv->next_node_id); + if (tlv->path_id) + json_hex_talfield("path_id", tlv->path_id); + if (tlv->next_blinding_override) { + json_pubkey("next_blinding_override", + tlv->next_blinding_override); + json_hexfield("blinding_override_secret", + override_blinding->secret.data, + sizeof(*override_blinding)); + } + if (additional) { + /* Deconstruct into type, len, value */ + size_t totlen = tal_bytelen(additional); + u64 type = fromwire_bigsize(&additional, &totlen); + u64 len = fromwire_bigsize(&additional, &totlen); + assert(len == totlen); + json_hexfield(tal_fmt(tmpctx, "unknown_tag_%"PRIu64, type), + additional, len); + } + json_end('}'); + + maybe_comma(); + json_hex_talfield("encrypted_data_tlv", enctlv); + flush_comma(); + enable_superverbose = true; + encrypted_recipient_data = enctlv_from_encmsg_raw(tmpctx, + blinding, + me, + enctlv, + blinding, + alias); + enable_superverbose = false; + json_hex_talfield("encrypted_recipient_data", + encrypted_recipient_data); + if (override_blinding) + *blinding = *override_blinding; + + json_end('}'); + maybe_comma(); + return encrypted_recipient_data; +} + +int main(int argc, char *argv[]) +{ + const char *alias[] = {"Alice", "Bob", "Carol", "Dave"}; + struct privkey privkey[ARRAY_SIZE(alias)], blinding, override_blinding; + struct pubkey id[ARRAY_SIZE(alias)], blinding_pub; + struct secret session_key; + u8 *erd[ARRAY_SIZE(alias)]; /* encrypted_recipient_data */ + u8 *onion_message_packet, *onion_message; + struct pubkey blinded[ARRAY_SIZE(alias)]; + struct sphinx_path *sphinx_path; + struct onionpacket *op; + struct secret *path_secrets; + + common_setup(argv[0]); + + /* Alice is AAA... Bob is BBB... */ + for (size_t i = 0; i < ARRAY_SIZE(alias); i++) { + memset(&privkey[i], alias[i][0], sizeof(privkey[i])); + assert(pubkey_from_privkey(&privkey[i], &id[i])); + } + + memset(&session_key, 3, sizeof(session_key)); + + json_start(NULL, '{'); + json_strfield("comment", "Test vector creating an onionmessage, including joining an existing one"); + json_start("generate", '{'); + json_strfield("comment", "This sections contains test data for Dave's blinded path Bob->Dave; sender has to prepend a hop to Alice to reach Bob"); + json_hexfield("session_key", session_key.data, sizeof(session_key)); + json_start("hops", '['); + + memset(&blinding, 99, sizeof(blinding)); + memset(&override_blinding, 1, sizeof(override_blinding)); + erd[0] = add_hop("Alice", "Alice->Bob: note next_blinding_override to match that give by Dave for Bob", + &id[0], &id[1], + &override_blinding, NULL, NULL, + &blinding, &blinded[0]); + erd[1] = add_hop("Bob", "Bob->Carol", + &id[1], &id[2], + NULL, "fd023103123456", NULL, + &blinding, &blinded[1]); + erd[2] = add_hop("Carol", "Carol->Dave", + &id[2], &id[3], + NULL, NULL, NULL, + &blinding, &blinded[2]); + erd[3] = add_hop("Dave", "Dave is final node, hence path_id", + &id[3], NULL, + NULL, "fdffff0206c1", + "deadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0", + &blinding, &blinded[3]); + + json_end(']'); + json_end('}'); + maybe_comma(); + + memset(&blinding, 99, sizeof(blinding)); + assert(pubkey_from_privkey(&blinding, &blinding_pub)); + json_start("route", '{'); + json_strfield("comment", "The resulting blinded route Alice to Dave."); + json_pubkey("introduction_node_id", &id[0]); + json_pubkey("blinding", &blinding_pub); + + json_start("hops", '['); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + json_start(NULL, '{'); + if (i != 0) + json_pubkey("blinded_node_id", &blinded[i]); + json_hex_talfield("encrypted_recipient_data", erd[i]); + json_end('}'); + maybe_comma(); + } + json_end(']'); + json_end('}'); + maybe_comma(); + + json_start("onionmessage", '{'); + json_strfield("comment", "An onion message which sends a 'hello' to Dave"); + json_strfield("unknown_tag_1", "68656c6c6f"); + + /* Create the onionmessage */ + sphinx_path = sphinx_path_new(tmpctx, NULL); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + struct tlv_onionmsg_tlv *tlv = tlv_onionmsg_tlv_new(tmpctx); + u8 *onionmsg_tlv; + tlv->encrypted_recipient_data = erd[i]; + onionmsg_tlv = tal_arr(tmpctx, u8, 0); + /* For final hop, add unknown 'hello' field */ + if (i == ARRAY_SIZE(erd) - 1) { + towire_bigsize(&onionmsg_tlv, 1); /* type */ + towire_bigsize(&onionmsg_tlv, strlen("hello")); /* length */ + towire(&onionmsg_tlv, "hello", strlen("hello")); + } + towire_tlv_onionmsg_tlv(&onionmsg_tlv, tlv); + sphinx_add_hop(sphinx_path, &blinded[i], onionmsg_tlv); + } + + /* Make it use our session key! */ + sphinx_path->session_key = &session_key; + + /* BOLT-onion-message #4: + * - SHOULD set `len` to 1366 or 32834. + */ + op = create_onionpacket(tmpctx, sphinx_path, ROUTING_INFO_SIZE, + &path_secrets); + onion_message_packet = serialize_onionpacket(tmpctx, op); + json_hex_talfield("onion_message_packet", onion_message_packet); + json_end('}'); + maybe_comma(); + + json_start("decrypt", '{'); + json_strfield("comment", "This section contains the internal values generated by intermediate nodes when decrypting the onion."); + + onion_message = towire_onion_message(tmpctx, &blinding_pub, onion_message_packet); + + json_start("hops", '['); + for (size_t i = 0; i < ARRAY_SIZE(erd); i++) { + struct pubkey next_node_id; + struct tlv_onionmsg_tlv *final_om; + struct pubkey final_alias; + struct secret *final_path_id; + + json_start(NULL, '{'); + json_strfield("alias", alias[i]); + json_hexfield("privkey", privkey[i].secret.data, sizeof(privkey[i])); + json_hex_talfield("onion_message", onion_message); + + /* Now, do full decrypt code, to check */ + assert(fromwire_onion_message(tmpctx, onion_message, + &blinding_pub, &onion_message_packet)); + + /* For test_ecdh */ + mykey = &privkey[i]; + assert(onion_message_parse(tmpctx, onion_message_packet, &blinding_pub, NULL, + &id[i], + &onion_message, &next_node_id, + &final_om, + &final_alias, + &final_path_id)); + if (onion_message) { + json_pubkey("next_node_id", &next_node_id); + } else { + const struct tlv_field *hello; + json_start("tlvs", '{'); + json_hexfield("path_id", final_path_id->data, sizeof(*final_path_id)); + hello = &final_om->fields[0]; + json_hexfield(tal_fmt(tmpctx, + "unknown_tag_%"PRIu64, + hello->numtype), + hello->value, + hello->length); + json_end('}'); + } + json_end('}'); + maybe_comma(); + } + json_end(']'); + json_end('}'); + json_end('}'); + common_shutdown(); +}