diff --git a/common/blindedpath.c b/common/blindedpath.c index 1976bca46..149a107a0 100644 --- a/common/blindedpath.c +++ b/common/blindedpath.c @@ -114,6 +114,19 @@ static u8 *enctlv_from_encmsg_raw(const tal_t *ctx, return ret; } +static u8 *enctlv_from_obs2_encmsg(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct tlv_obs2_encmsg_tlvs *encmsg, + struct privkey *next_blinding, + struct pubkey *node_alias) +{ + u8 *encmsg_raw = tal_arr(NULL, u8, 0); + towire_tlv_obs2_encmsg_tlvs(&encmsg_raw, encmsg); + return enctlv_from_encmsg_raw(ctx, blinding, node, take(encmsg_raw), + next_blinding, node_alias); +} + static u8 *enctlv_from_encmsg(const tal_t *ctx, const struct privkey *blinding, const struct pubkey *node, @@ -183,6 +196,22 @@ static u8 *decrypt_encmsg_raw(const tal_t *ctx, return dec; } +static struct tlv_obs2_encmsg_tlvs *decrypt_obs2_encmsg(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv) +{ + const u8 *cursor = decrypt_encmsg_raw(tmpctx, blinding, ss, enctlv); + size_t maxlen = tal_bytelen(cursor); + + /* BOLT-onion-message #4: + * + * - if the `enctlv` is not a valid TLV... + * - MUST drop the message. + */ + return fromwire_tlv_obs2_encmsg_tlvs(ctx, &cursor, &maxlen); +} + static struct tlv_encrypted_data_tlv *decrypt_encmsg(const tal_t *ctx, const struct pubkey *blinding, const struct secret *ss, @@ -325,3 +354,131 @@ u8 *create_final_enctlv(const tal_t *ctx, return enctlv_from_encmsg(ctx, blinding, final_node, encmsg, &unused_next_blinding, node_alias); } + +/* Obsolete variants */ +bool decrypt_obs2_enctlv(const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + struct pubkey *next_node, + struct pubkey *next_blinding) +{ + struct tlv_obs2_encmsg_tlvs *encmsg; + + encmsg = decrypt_obs2_encmsg(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + /* BOLT-onion-message #4: + * + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` ... does not contain + * `next_node_id`: + * - MUST drop the message. + */ + if (!encmsg->next_node_id) + return false; + + /* BOLT-onion-message #4: + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if the `enctlv` contains `self_id`: + * - MUST drop the message. + */ + if (encmsg->self_id) + return false; + + /* BOLT-onion-message #4: + * The reader: + * - if it is not the final node according to the onion encryption: + *... + * - if `blinding` is specified in the `enctlv`: + * - MUST pass that as `blinding` in the `onion_message` + * - otherwise: + * - MUST pass `blinding` derived as in + * [Route Blinding][route-blinding] (i.e. + * `E(i+1) = H(E(i) || ss(i)) * E(i)`). + */ + *next_node = *encmsg->next_node_id; + if (encmsg->next_blinding) + *next_blinding = *encmsg->next_blinding; + else { + /* E(i-1) = H(E(i) || ss(i)) * E(i) */ + struct sha256 h; + blinding_hash_e_and_ss(blinding, ss, &h); + blinding_next_pubkey(blinding, &h, next_blinding); + } + return true; +} + +bool decrypt_obs2_final_enctlv(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + const struct pubkey *my_id, + struct pubkey *alias, + struct secret **self_id) +{ + struct tlv_obs2_encmsg_tlvs *encmsg; + struct secret node_id_blinding; + + /* Repeat the tweak to get the alias it was using for us */ + subkey_from_hmac("blinded_node_id", ss, &node_id_blinding); + *alias = *my_id; + if (secp256k1_ec_pubkey_tweak_mul(secp256k1_ctx, + &alias->pubkey, + node_id_blinding.data) != 1) + return false; + + encmsg = decrypt_obs2_encmsg(tmpctx, blinding, ss, enctlv); + if (!encmsg) + return false; + + if (tal_bytelen(encmsg->self_id) == sizeof(**self_id)) { + *self_id = tal(ctx, struct secret); + memcpy(*self_id, encmsg->self_id, sizeof(**self_id)); + } else + *self_id = NULL; + + return true; +} + +u8 *create_obs2_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct pubkey *next_node, + size_t padlen, + const struct pubkey *override_blinding, + struct privkey *next_blinding, + struct pubkey *node_alias) +{ + struct tlv_obs2_encmsg_tlvs *encmsg = tlv_obs2_encmsg_tlvs_new(tmpctx); + if (padlen) + encmsg->padding = tal_arrz(encmsg, u8, padlen); + encmsg->next_node_id = cast_const(struct pubkey *, next_node); + encmsg->next_blinding = cast_const(struct pubkey *, override_blinding); + + return enctlv_from_obs2_encmsg(ctx, blinding, node, encmsg, + next_blinding, node_alias); +} + +u8 *create_obs2_final_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *final_node, + size_t padlen, + const struct secret *self_id, + struct pubkey *node_alias) +{ + struct tlv_obs2_encmsg_tlvs *encmsg = tlv_obs2_encmsg_tlvs_new(tmpctx); + struct privkey unused_next_blinding; + + if (padlen) + encmsg->padding = tal_arrz(encmsg, u8, padlen); + if (self_id) + encmsg->self_id = (u8 *)tal_dup(encmsg, struct secret, self_id); + + return enctlv_from_obs2_encmsg(ctx, blinding, final_node, encmsg, + &unused_next_blinding, node_alias); +} diff --git a/common/blindedpath.h b/common/blindedpath.h index 285ec189d..f34159395 100644 --- a/common/blindedpath.h +++ b/common/blindedpath.h @@ -105,4 +105,36 @@ bool decrypt_final_enctlv(const tal_t *ctx, struct secret **path_id) NON_NULL_ARGS(1, 2, 4, 5); +/* Obsolete variants */ +u8 *create_obs2_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *node, + const struct pubkey *next_node, + size_t padlen, + const struct pubkey *override_blinding, + struct privkey *next_blinding, + struct pubkey *node_alias) + NON_NULL_ARGS(2, 3, 4, 7, 8); +u8 *create_obs2_final_enctlv(const tal_t *ctx, + const struct privkey *blinding, + const struct pubkey *final_node, + size_t padlen, + const struct secret *self_id, + struct pubkey *node_alias) + NON_NULL_ARGS(2, 3, 6); +bool decrypt_obs2_enctlv(const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + struct pubkey *next_node, + struct pubkey *next_blinding) + NON_NULL_ARGS(1, 2, 4, 5); +bool decrypt_obs2_final_enctlv(const tal_t *ctx, + const struct pubkey *blinding, + const struct secret *ss, + const u8 *enctlv, + const struct pubkey *my_id, + struct pubkey *alias, + struct secret **self_id) + NON_NULL_ARGS(1, 2, 4, 5); + #endif /* LIGHTNING_COMMON_BLINDEDPATH_H */ diff --git a/common/json_helpers.c b/common/json_helpers.c index 90e570b5c..af06c2036 100644 --- a/common/json_helpers.c +++ b/common/json_helpers.c @@ -158,6 +158,42 @@ struct wally_psbt *json_to_psbt(const tal_t *ctx, const char *buffer, return psbt_from_b64(ctx, buffer + tok->start, tok->end - tok->start); } +struct tlv_obs2_onionmsg_payload_reply_path * +json_to_obs2_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok) +{ + struct tlv_obs2_onionmsg_payload_reply_path *rpath; + const jsmntok_t *hops, *t; + size_t i; + const char *err; + + rpath = tal(ctx, struct tlv_obs2_onionmsg_payload_reply_path); + err = json_scan(tmpctx, buffer, tok, "{blinding:%,first_node_id:%}", + JSON_SCAN(json_to_pubkey, &rpath->blinding), + JSON_SCAN(json_to_pubkey, &rpath->first_node_id), + NULL); + if (err) + return tal_free(rpath); + + hops = json_get_member(buffer, tok, "hops"); + if (!hops || hops->size < 1) + return tal_free(rpath); + + rpath->path = tal_arr(rpath, struct onionmsg_path *, hops->size); + json_for_each_arr(i, t, hops) { + rpath->path[i] = tal(rpath->path, struct onionmsg_path); + err = json_scan(tmpctx, buffer, t, "{id:%,encrypted_recipient_data:%}", + JSON_SCAN(json_to_pubkey, + &rpath->path[i]->node_id), + JSON_SCAN_TAL(rpath->path[i], + json_tok_bin_from_hex, + &rpath->path[i]->encrypted_recipient_data)); + if (err) + return tal_free(rpath); + } + + return rpath; +} + struct tlv_onionmsg_payload_reply_path * json_to_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok) { diff --git a/common/json_helpers.h b/common/json_helpers.h index d8edee866..1f5fe7fe3 100644 --- a/common/json_helpers.h +++ b/common/json_helpers.h @@ -88,6 +88,10 @@ bool split_tok(const char *buffer, const jsmntok_t *tok, struct tlv_onionmsg_payload_reply_path * json_to_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok); +/* Obsolete version! */ +struct tlv_obs2_onionmsg_payload_reply_path * +json_to_obs2_reply_path(const tal_t *ctx, const char *buffer, const jsmntok_t *tok); + /* Helpers for outputting JSON results */ /* '"fieldname" : "0289abcdef..."' or "0289abcdef..." if fieldname is NULL */ diff --git a/connectd/connectd_wire.csv b/connectd/connectd_wire.csv index b10e668a1..5b0950f93 100644 --- a/connectd/connectd_wire.csv +++ b/connectd/connectd_wire.csv @@ -122,6 +122,7 @@ msgdata,connectd_ping_reply,totlen,u16, # We tell lightningd we got an onionmsg msgtype,connectd_got_onionmsg_to_us,2145 +msgdata,connectd_got_onionmsg_to_us,obs2,bool, msgdata,connectd_got_onionmsg_to_us,node_alias,pubkey, msgdata,connectd_got_onionmsg_to_us,self_id,?secret, msgdata,connectd_got_onionmsg_to_us,reply_blinding,?pubkey, @@ -133,6 +134,7 @@ msgdata,connectd_got_onionmsg_to_us,rawmsg,u8,rawmsg_len # Lightningd tells us to send an onion message. msgtype,connectd_send_onionmsg,2041 +msgdata,connectd_send_onionmsg,obs2,bool, msgdata,connectd_send_onionmsg,id,node_id, msgdata,connectd_send_onionmsg,onion_len,u16, msgdata,connectd_send_onionmsg,onion,u8,onion_len diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 03af2a0fb..c39c35ea0 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -633,6 +633,9 @@ static bool handle_message_locally(struct peer *peer, const u8 *msg) } else if (type == WIRE_PONG) { handle_pong_in(peer, msg); return true; + } else if (type == WIRE_OBS2_ONION_MESSAGE) { + handle_obs2_onion_message(peer->daemon, peer, msg); + return true; } else if (type == WIRE_ONION_MESSAGE) { handle_onion_message(peer->daemon, peer, msg); return true; diff --git a/connectd/onion_message.c b/connectd/onion_message.c index d84349d53..cc8cce571 100644 --- a/connectd/onion_message.c +++ b/connectd/onion_message.c @@ -16,21 +16,178 @@ #include #include +/* Peer sends obsolete onion msg. */ +void handle_obs2_onion_message(struct daemon *daemon, + struct peer *peer, const u8 *msg) +{ + enum onion_wire badreason; + struct onionpacket *op; + struct pubkey blinding, ephemeral; + struct route_step *rs; + u8 *onion; + struct tlv_obs2_onionmsg_payload *om; + struct secret ss, onion_ss; + const u8 *cursor; + size_t max, maxlen; + + /* Ignore unless explicitly turned on. */ + if (!feature_offered(daemon->our_features->bits[NODE_ANNOUNCE_FEATURE], + OPT_ONION_MESSAGES)) + return; + + /* FIXME: ratelimit! */ + if (!fromwire_obs2_onion_message(msg, msg, &blinding, &onion)) { + inject_peer_msg(peer, + towire_warningfmt(NULL, NULL, + "Bad onion_message")); + return; + } + + /* We unwrap the onion now. */ + op = parse_onionpacket(tmpctx, onion, tal_bytelen(onion), &badreason); + if (!op) { + status_peer_debug(&peer->id, "onion msg: can't parse onionpacket: %s", + onion_wire_name(badreason)); + return; + } + + ephemeral = op->ephemeralkey; + if (!unblind_onion(&blinding, ecdh, &ephemeral, &ss)) { + status_peer_debug(&peer->id, "onion msg: can't unblind onionpacket"); + return; + } + + /* Now get onion shared secret and parse it. */ + ecdh(&ephemeral, &onion_ss); + rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); + if (!rs) { + status_peer_debug(&peer->id, + "onion msg: can't process onionpacket ss=%s", + type_to_string(tmpctx, struct secret, &onion_ss)); + return; + } + + /* The raw payload is prepended with length in the modern onion. */ + cursor = rs->raw_payload; + max = tal_bytelen(rs->raw_payload); + maxlen = fromwire_bigsize(&cursor, &max); + if (!cursor) { + status_peer_debug(&peer->id, "onion msg: Invalid hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + if (maxlen > max) { + status_peer_debug(&peer->id, "onion msg: overlong hop payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + + om = fromwire_tlv_obs2_onionmsg_payload(msg, &cursor, &maxlen); + if (!om) { + status_peer_debug(&peer->id, "onion msg: invalid onionmsg_payload %s", + tal_hex(tmpctx, rs->raw_payload)); + return; + } + + if (rs->nextcase == ONION_END) { + struct pubkey *reply_blinding, *first_node_id, me, alias; + const struct onionmsg_path **reply_path; + struct secret *self_id; + u8 *omsg; + + if (!pubkey_from_node_id(&me, &daemon->id)) { + status_broken("Failed to convert own id"); + return; + } + + /* Final enctlv is actually optional */ + if (!om->enctlv) { + alias = me; + self_id = NULL; + } else if (!decrypt_obs2_final_enctlv(tmpctx, &blinding, &ss, + om->enctlv, &me, &alias, + &self_id)) { + status_peer_debug(&peer->id, + "onion msg: failed to decrypt enctlv" + " %s", tal_hex(tmpctx, om->enctlv)); + return; + } + + if (om->reply_path) { + first_node_id = &om->reply_path->first_node_id; + reply_blinding = &om->reply_path->blinding; + reply_path = cast_const2(const struct onionmsg_path **, + om->reply_path->path); + } else { + first_node_id = NULL; + reply_blinding = NULL; + reply_path = NULL; + } + + /* We re-marshall here by policy, before handing to lightningd */ + omsg = tal_arr(tmpctx, u8, 0); + towire_tlvstream_raw(&omsg, om->fields); + daemon_conn_send(daemon->master, + take(towire_connectd_got_onionmsg_to_us(NULL, + true, /* obs2 */ + &alias, self_id, + reply_blinding, + first_node_id, + reply_path, + omsg))); + } else { + struct pubkey next_node, next_blinding; + struct peer *next_peer; + struct node_id next_node_id; + + /* This fails as expected if no enctlv. */ + if (!decrypt_obs2_enctlv(&blinding, &ss, om->enctlv, &next_node, + &next_blinding)) { + status_peer_debug(&peer->id, + "onion msg: invalid enctlv %s", + tal_hex(tmpctx, om->enctlv)); + return; + } + + /* Even though lightningd checks for valid ids, there's a race + * where it might vanish before we read this command. */ + node_id_from_pubkey(&next_node_id, &next_node); + next_peer = peer_htable_get(&daemon->peers, &next_node_id); + if (!next_peer) { + status_peer_debug(&peer->id, + "onion msg: unknown next peer %s", + type_to_string(tmpctx, + struct pubkey, + &next_node)); + return; + } + inject_peer_msg(next_peer, + take(towire_obs2_onion_message(NULL, + &next_blinding, + serialize_onionpacket(tmpctx, rs->next)))); + } +} + void onionmsg_req(struct daemon *daemon, const u8 *msg) { struct node_id id; u8 *onionmsg; struct pubkey blinding; struct peer *peer; + bool obs2; - if (!fromwire_connectd_send_onionmsg(msg, msg, &id, &onionmsg, &blinding)) + if (!fromwire_connectd_send_onionmsg(msg, msg, &obs2, &id, &onionmsg, &blinding)) master_badmsg(WIRE_CONNECTD_SEND_ONIONMSG, msg); /* Even though lightningd checks for valid ids, there's a race * where it might vanish before we read this command. */ peer = peer_htable_get(&daemon->peers, &id); if (peer) { - u8 *omsg = towire_onion_message(NULL, &blinding, onionmsg); + u8 *omsg; + if (obs2) + omsg = towire_obs2_onion_message(NULL, &blinding, onionmsg); + else + omsg = towire_onion_message(NULL, &blinding, onionmsg); inject_peer_msg(peer, take(omsg)); } } @@ -148,6 +305,7 @@ void handle_onion_message(struct daemon *daemon, towire_tlvstream_raw(&omsg, om->fields); daemon_conn_send(daemon->master, take(towire_connectd_got_onionmsg_to_us(NULL, + false, /* !obs2 */ &alias, self_id, reply_blinding, first_node_id, diff --git a/connectd/onion_message.h b/connectd/onion_message.h index 41b25982c..22fc9bb8b 100644 --- a/connectd/onion_message.h +++ b/connectd/onion_message.h @@ -3,7 +3,9 @@ #include "config.h" #include -/* Onion message comes in from peer */ +/* Various messages come in from peer */ +void handle_obs2_onion_message(struct daemon *daemon, + struct peer *peer, const u8 *msg); void handle_onion_message(struct daemon *daemon, struct peer *peer, const u8 *msg); diff --git a/connectd/test/run-onion_message.c b/connectd/test/run-onion_message.c new file mode 100644 index 000000000..4e9083318 --- /dev/null +++ b/connectd/test/run-onion_message.c @@ -0,0 +1,414 @@ +#include "config.h" +#include "../onion_message.c" +#include "common/blindedpath.c" +#include "common/blinding.c" +#include "common/bigsize.c" +#include "common/hmac.c" +#include "common/onion.c" +#include "common/sphinx.c" +#include "wire/fromwire.c" +#if EXPERIMENTAL_FEATURES +#include "wire/peer_exp_wiregen.c" +#include "wire/onion_exp_wiregen.c" +#else +#include "wire/peer_wiregen.c" +#include "wire/onion_wiregen.c" +#endif +#include "wire/tlvstream.c" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_msat */ +struct amount_msat amount_msat(u64 millisatoshis UNNEEDED) +{ fprintf(stderr, "amount_msat called!\n"); abort(); } +/* Generated stub for amount_msat_eq */ +bool amount_msat_eq(struct amount_msat a UNNEEDED, struct amount_msat b UNNEEDED) +{ fprintf(stderr, "amount_msat_eq called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for daemon_conn_send */ +void daemon_conn_send(struct daemon_conn *dc UNNEEDED, const u8 *msg UNNEEDED) +{ fprintf(stderr, "daemon_conn_send called!\n"); abort(); } +/* Generated stub for ecdh */ +void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) +{ fprintf(stderr, "ecdh called!\n"); abort(); } +/* Generated stub for fromwire_amount_msat */ +struct amount_msat fromwire_amount_msat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_msat called!\n"); abort(); } +/* Generated stub for fromwire_amount_sat */ +struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } +/* 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_connectd_send_onionmsg */ +bool fromwire_connectd_send_onionmsg(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, bool *obs2 UNNEEDED, struct node_id *id UNNEEDED, u8 **onion UNNEEDED, struct pubkey *blinding UNNEEDED) +{ fprintf(stderr, "fromwire_connectd_send_onionmsg 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 fromwire_wireaddr */ +bool fromwire_wireaddr(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct wireaddr *addr UNNEEDED) +{ fprintf(stderr, "fromwire_wireaddr called!\n"); abort(); } +/* Generated stub for inject_peer_msg */ +void inject_peer_msg(struct peer *peer UNNEEDED, const u8 *msg TAKES UNNEEDED) +{ fprintf(stderr, "inject_peer_msg called!\n"); abort(); } +/* Generated stub for master_badmsg */ +void master_badmsg(u32 type_expected UNNEEDED, const u8 *msg) +{ fprintf(stderr, "master_badmsg 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 node_id_from_pubkey */ +void node_id_from_pubkey(struct node_id *id UNNEEDED, const struct pubkey *key UNNEEDED) +{ fprintf(stderr, "node_id_from_pubkey 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 */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_amount_msat */ +void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED) +{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); } +/* Generated stub for towire_amount_sat */ +void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool 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_connectd_got_onionmsg_to_us */ +u8 *towire_connectd_got_onionmsg_to_us(const tal_t *ctx UNNEEDED, bool obs2 UNNEEDED, const struct pubkey *node_alias UNNEEDED, const struct secret *self_id UNNEEDED, const struct pubkey *reply_blinding UNNEEDED, const struct pubkey *reply_first_node UNNEEDED, const struct onionmsg_path **reply_path UNNEEDED, const u8 *rawmsg UNNEEDED) +{ fprintf(stderr, "towire_connectd_got_onionmsg_to_us 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(); } +/* Generated stub for towire_pad */ +void towire_pad(u8 **pptr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_pad called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_tu32 */ +void towire_tu32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_tu32 called!\n"); abort(); } +/* Generated stub for towire_tu64 */ +void towire_tu64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_tu64 called!\n"); abort(); } +/* Generated stub for towire_u16 */ +void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) +{ fprintf(stderr, "towire_u16 called!\n"); abort(); } +/* 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(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* Generated stub for towire_warningfmt */ +u8 *towire_warningfmt(const tal_t *ctx UNNEEDED, + const struct channel_id *channel UNNEEDED, + const char *fmt UNNEEDED, ...) +{ fprintf(stderr, "towire_warningfmt called!\n"); abort(); } +/* Generated stub for towire_wireaddr */ +void towire_wireaddr(u8 **pptr UNNEEDED, const struct wireaddr *addr UNNEEDED) +{ fprintf(stderr, "towire_wireaddr called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +/* Updated each time, as we pretend to be Alice, Bob, Carol */ +static const struct privkey *mykey; + +static void test_ecdh(const struct pubkey *point, struct secret *ss) +{ + if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey, + mykey->secret.data, NULL, NULL) != 1) + abort(); +} + +static void json_strfield(const char *name, const char *val) +{ + printf("\t\"%s\": \"%s\",\n", name, val); +} + +static void json_onionmsg_payload(const struct tlv_obs2_onionmsg_payload *om) +{ + if (om->reply_path) { + printf("\t\"reply_path\": {\n"); + json_strfield("first_node_id", + type_to_string(tmpctx, struct pubkey, + &om->reply_path->first_node_id)); + json_strfield("blinding", + type_to_string(tmpctx, struct pubkey, + &om->reply_path->blinding)); + printf("\t\"path\": [\n"); + for (size_t i = 0; i < tal_count(om->reply_path->path); i++) { + json_strfield("node_id", + type_to_string(tmpctx, struct pubkey, + &om->reply_path->path[i]->node_id)); + json_strfield("encrypted_recipient_data", + tal_hex(tmpctx, + om->reply_path->path[i]->encrypted_recipient_data)); + } + printf("]}\n"); + } + if (om->invoice) + json_strfield("invoice", tal_hex(tmpctx, om->invoice)); + if (om->invoice_request) + json_strfield("invoice_request", + tal_hex(tmpctx, om->invoice_request)); + if (om->invoice_error) + json_strfield("invoice_error", + tal_hex(tmpctx, om->invoice_error)); +} + +/* Return next onion (and updates blinding), or NULL */ +static u8 *json_test(const char *testname, + const u8 *data, + const struct privkey *me, + const struct privkey *blinding_priv, + struct pubkey *blinding) +{ + struct pubkey my_id, next_node; + struct secret ss, onion_ss; + struct pubkey ephemeral; + struct route_step *rs; + const u8 *cursor; + size_t max, maxlen; + struct onionpacket *op; + struct tlv_obs2_onionmsg_payload *om; + + op = parse_onionpacket(tmpctx, data, tal_bytelen(data), NULL); + assert(op); + + pubkey_from_privkey(me, &my_id); + printf("{"); + json_strfield("test name", testname); + json_strfield("reader_privkey", + type_to_string(tmpctx, struct privkey, me)); + json_strfield("reader_id", + type_to_string(tmpctx, struct pubkey, &my_id)); + + if (blinding_priv) + json_strfield("blinding_privkey", + type_to_string(tmpctx, struct privkey, + blinding_priv)); + json_strfield("blinding", + type_to_string(tmpctx, struct pubkey, blinding)); + printf("\"onionmsg\": {\n"); + json_strfield("raw", tal_hex(tmpctx, data)); + json_strfield("version", tal_fmt(tmpctx, "%i", op->version)); + json_strfield("public_key", + type_to_string(tmpctx, struct pubkey, &op->ephemeralkey)); + json_strfield("hop_payloads", + tal_hex(tmpctx, op->routinginfo)); + json_strfield("hmac", + tal_hexstr(tmpctx, &op->hmac, sizeof(op->hmac))); + printf("},\n"); + + ephemeral = op->ephemeralkey; + + /* Set this for test_ecdh */ + mykey = me; + assert(unblind_onion(blinding, test_ecdh, &ephemeral, &ss)); + json_strfield("ECDH shared secret", + type_to_string(tmpctx, struct secret, &ss)); + /* Reproduce internal calc from unblind_onion */ + { + struct secret hmac; + subkey_from_hmac("blinded_node_id", &ss, &hmac); + json_strfield("HMAC256(\\\"blinded_node_id\\\", ss(i)) * k(i)", + type_to_string(tmpctx, struct secret, &hmac)); + } + json_strfield("Tweaked onion pubkey", + type_to_string(tmpctx, struct pubkey, &ephemeral)); + + /* Now get onion shared secret and parse it. */ + test_ecdh(&ephemeral, &onion_ss); + json_strfield("onion shared secret", + type_to_string(tmpctx, struct secret, &onion_ss)); + rs = process_onionpacket(tmpctx, op, &onion_ss, NULL, 0, false); + assert(rs); + + printf("\"onion contents\": {\n"); + json_strfield("raw", tal_hex(tmpctx, rs->raw_payload)); + + cursor = rs->raw_payload; + max = tal_bytelen(rs->raw_payload); + maxlen = fromwire_bigsize(&cursor, &max); + json_strfield("length", tal_fmt(tmpctx, "%zu", maxlen)); + json_strfield("rawtlv", tal_hexstr(tmpctx, cursor, maxlen)); + json_strfield("hmac", tal_hexstr(tmpctx, rs->next->hmac.bytes, + sizeof(rs->next->hmac.bytes))); + om = fromwire_tlv_obs2_onionmsg_payload(tmpctx, &cursor, &maxlen); + assert(om); + + json_onionmsg_payload(om); + + /* We expect one of these. */ + assert(om->enctlv); + + printf("\t\"encrypted_data_tlv\": {\n"); + json_strfield("raw", tal_hex(tmpctx, om->enctlv)); + + if (rs->nextcase == ONION_END) { + struct secret *self_id; + struct pubkey alias; + assert(decrypt_obs2_final_enctlv(tmpctx, + blinding, &ss, + om->enctlv, + &my_id, &alias, &self_id)); + if (self_id) { + json_strfield("self_id", + type_to_string(tmpctx, struct secret, + self_id)); + } + printf("}\n"); + return NULL; + } else { + assert(decrypt_obs2_enctlv(blinding, &ss, om->enctlv, &next_node, + blinding)); + json_strfield("next_node", + type_to_string(tmpctx, struct pubkey, &next_node)); + json_strfield("next_blinding", + type_to_string(tmpctx, struct pubkey, + blinding)); + printf("}"); + printf("},\n"); + return serialize_onionpacket(tmpctx, rs->next); + } +} + +int main(int argc, char *argv[]) +{ + struct onionpacket *op; + u8 *data; + struct privkey alice, bob, carol, dave, blinding_priv; + struct pubkey alice_id, bob_id, carol_id, dave_id; + struct pubkey blinding; + + common_setup(argv[0]); + + memset(&alice, 'A', sizeof(alice)); + memset(&bob, 'B', sizeof(bob)); + memset(&carol, 'C', sizeof(carol)); + memset(&dave, 'D', sizeof(dave)); + pubkey_from_privkey(&alice, &alice_id); + pubkey_from_privkey(&bob, &bob_id); + pubkey_from_privkey(&carol, &carol_id); + pubkey_from_privkey(&dave, &dave_id); + + /* ThomasH sends via email: + * + * { + * "version":0, + * "public_key": + * "0256b328b30c8bf5839e24058747879408bdb36241dc9c2e7c619faa12b2920967", + * "hop_payloads": + * "37df67dcefdb678725cb8074d3224dfe235ba3f22f71ac8a2c9d1398b1175295b1dd3f14c02d698021e8a8856637306c6f195e01494eb8dc636b4462367533a84786b8592e580086cdf0f1c58b77eb68703a2fb82ecc2e91307a25b6d5e4045174551b1c867264d3905e4f05b2e5bcfed7e7276660bf7e956bce5afa395e7e4c15883b856bc93dd9d6a968838ef51314d38dd41e5ab84b8846dca3c61d87e55780e7a7da336a965a4652263413cdef41daa68f7bb7cd4d566c19a1c4eece369c47e604575f38e7a246a985c3441b60ae33c564395bb7a4bbe28325ccdb07503285dacf90b5e09f4e455fb42459741f9d497000298b99f1e70adc28f59a1be85a96952f27b6a6c5d6a08822b4f5cae05daa6c2ce2f8ca5fdd4e8f0df46b94791b3159fe8eace11bcf8d58be425b49ce2b47c007affefd5cea785c1996ad805f8c8c5ca79f15ab26e2bd4080b1d74328e7ce5bd2a579c71a6bd25f33f2ce475a2cfbe67ed1f4eb8fbd86920f41d573488abe059166aabbc3be187c435423ead6a5473994e0246efe76e419893aa2d7566b2645f3496d97585de9c92b8c5a5226398cc459ce84abc02fe2b45b5ecaf21961730d4a34bbe6fdfe720e71e3d81a494c01080d8039360d534c6ee5a3c47a1874e526969add9126b30d9192f85ba45bcfd7029cc7560f0e25e14b5deaa805360c4967705e85325ac055922863470f5397e8404022488caebf9204acd6cb02a11088aebf7e497b4ff1172f0a9c6bf980914cc4eb42fc78b457add549abf1134f84922b217502938b42d10b35079f44c5168d4c3e9fe7ca8094ef72ed73ef84f1d3530b6b3545f9f4f013e7e8cbcf2619f57754a7380ce6a9532ee14c55990faa43df6c09530a314b5f4ce597f5ec9b776e8597ce258ac47dac43bd3ac9e52788ff3a66b7dc07cd1bc3e6d197339d85fa8d3d6c3054dd1a5e416c714b544de6eb55209e40e3cac412a51748370160d2d73b6d97abd62f7bae70df27cd199c511fa693019c5717d471e934906b98cd974fda4dd1cb5e2d721044a0be2bdf24d0971e09f2f39488fe389fc5230699b4df7cec7447e5be4ea49bd7c3fe1a5ec7358510dc1dd9c1a8da68c0863188d80549e49f7c00f57d2009b2427b2aed1569603fc247734039469f9fdf3ddd3a22fa95c5d8066a468327a02b474c9915419af82c8edc67686984767fe7885207c6820f6c2e57cb8fd0bcb9981ebc8065c74e970a5d593c3b73ee25a0877ca096a9f7edfee6d43bd817c7d415fea9abb6f206c61aa36942df9318762a76b9da26d0d41a0ae9eee042a175f82dc134bf6f2d46a218db358d6852940e6e30df4a58ac6cb409e7ce99afe1e3f42768bd617af4d0a235d0ba0dd5075f9cc091784395d30e7e42d4e006db21bea9b45d1f122b75c051e84e2281573ef54ebad053218fff0cc28ea89a06adc218d4134f407654990592e75462f5ee4a463c1e46425222d48761162da8049613cafd7ecc52ff8024e9d58512b958e3a3d12dede84e1441247700bca0f992875349448b430683c756438fd4e91f3d44f3cf624ed21f3c63cf92615ecc201d0cd3159b1b3fccd8f29d2daba9ac5ba87b1dd2f83323a2b2d3176b803ce9c7bdc4bae615925eb22a213df1eeb2f8ff95586536caf042d565984aacf1425a120a5d8d7a9cbb70bf4852e116b89ff5b198d672220af2be4246372e7c3836cf50d732212a3e3346ff92873ace57fa687b2b1aab3e8dc6cb9f93f865d998cff0a1680d9012a9597c90a070e525f66226cc287814f4ac4157b15a0b25aa110946cd69fd404fafd5656669bfd1d9e509eabc004c5a", + * "hmac": "564bb85911bea8f90d306f4acdafa1c0887619ac72606b11e6b2765734d810ac" + * } + */ + op = tal(tmpctx, struct onionpacket); + op->version = 0; + assert(pubkey_from_hexstr("0256b328b30c8bf5839e24058747879408bdb36241dc9c2e7c619faa12b2920967", strlen("0256b328b30c8bf5839e24058747879408bdb36241dc9c2e7c619faa12b2920967"), &op->ephemeralkey)); + assert(hex_decode("564bb85911bea8f90d306f4acdafa1c0887619ac72606b11e6b2765734d810ac", + strlen("564bb85911bea8f90d306f4acdafa1c0887619ac72606b11e6b2765734d810ac"), + &op->hmac, sizeof(op->hmac))); + op->routinginfo = tal_hexdata(op, "37df67dcefdb678725cb8074d3224dfe235ba3f22f71ac8a2c9d1398b1175295b1dd3f14c02d698021e8a8856637306c6f195e01494eb8dc636b4462367533a84786b8592e580086cdf0f1c58b77eb68703a2fb82ecc2e91307a25b6d5e4045174551b1c867264d3905e4f05b2e5bcfed7e7276660bf7e956bce5afa395e7e4c15883b856bc93dd9d6a968838ef51314d38dd41e5ab84b8846dca3c61d87e55780e7a7da336a965a4652263413cdef41daa68f7bb7cd4d566c19a1c4eece369c47e604575f38e7a246a985c3441b60ae33c564395bb7a4bbe28325ccdb07503285dacf90b5e09f4e455fb42459741f9d497000298b99f1e70adc28f59a1be85a96952f27b6a6c5d6a08822b4f5cae05daa6c2ce2f8ca5fdd4e8f0df46b94791b3159fe8eace11bcf8d58be425b49ce2b47c007affefd5cea785c1996ad805f8c8c5ca79f15ab26e2bd4080b1d74328e7ce5bd2a579c71a6bd25f33f2ce475a2cfbe67ed1f4eb8fbd86920f41d573488abe059166aabbc3be187c435423ead6a5473994e0246efe76e419893aa2d7566b2645f3496d97585de9c92b8c5a5226398cc459ce84abc02fe2b45b5ecaf21961730d4a34bbe6fdfe720e71e3d81a494c01080d8039360d534c6ee5a3c47a1874e526969add9126b30d9192f85ba45bcfd7029cc7560f0e25e14b5deaa805360c4967705e85325ac055922863470f5397e8404022488caebf9204acd6cb02a11088aebf7e497b4ff1172f0a9c6bf980914cc4eb42fc78b457add549abf1134f84922b217502938b42d10b35079f44c5168d4c3e9fe7ca8094ef72ed73ef84f1d3530b6b3545f9f4f013e7e8cbcf2619f57754a7380ce6a9532ee14c55990faa43df6c09530a314b5f4ce597f5ec9b776e8597ce258ac47dac43bd3ac9e52788ff3a66b7dc07cd1bc3e6d197339d85fa8d3d6c3054dd1a5e416c714b544de6eb55209e40e3cac412a51748370160d2d73b6d97abd62f7bae70df27cd199c511fa693019c5717d471e934906b98cd974fda4dd1cb5e2d721044a0be2bdf24d0971e09f2f39488fe389fc5230699b4df7cec7447e5be4ea49bd7c3fe1a5ec7358510dc1dd9c1a8da68c0863188d80549e49f7c00f57d2009b2427b2aed1569603fc247734039469f9fdf3ddd3a22fa95c5d8066a468327a02b474c9915419af82c8edc67686984767fe7885207c6820f6c2e57cb8fd0bcb9981ebc8065c74e970a5d593c3b73ee25a0877ca096a9f7edfee6d43bd817c7d415fea9abb6f206c61aa36942df9318762a76b9da26d0d41a0ae9eee042a175f82dc134bf6f2d46a218db358d6852940e6e30df4a58ac6cb409e7ce99afe1e3f42768bd617af4d0a235d0ba0dd5075f9cc091784395d30e7e42d4e006db21bea9b45d1f122b75c051e84e2281573ef54ebad053218fff0cc28ea89a06adc218d4134f407654990592e75462f5ee4a463c1e46425222d48761162da8049613cafd7ecc52ff8024e9d58512b958e3a3d12dede84e1441247700bca0f992875349448b430683c756438fd4e91f3d44f3cf624ed21f3c63cf92615ecc201d0cd3159b1b3fccd8f29d2daba9ac5ba87b1dd2f83323a2b2d3176b803ce9c7bdc4bae615925eb22a213df1eeb2f8ff95586536caf042d565984aacf1425a120a5d8d7a9cbb70bf4852e116b89ff5b198d672220af2be4246372e7c3836cf50d732212a3e3346ff92873ace57fa687b2b1aab3e8dc6cb9f93f865d998cff0a1680d9012a9597c90a070e525f66226cc287814f4ac4157b15a0b25aa110946cd69fd404fafd5656669bfd1d9e509eabc004c5a", + strlen("37df67dcefdb678725cb8074d3224dfe235ba3f22f71ac8a2c9d1398b1175295b1dd3f14c02d698021e8a8856637306c6f195e01494eb8dc636b4462367533a84786b8592e580086cdf0f1c58b77eb68703a2fb82ecc2e91307a25b6d5e4045174551b1c867264d3905e4f05b2e5bcfed7e7276660bf7e956bce5afa395e7e4c15883b856bc93dd9d6a968838ef51314d38dd41e5ab84b8846dca3c61d87e55780e7a7da336a965a4652263413cdef41daa68f7bb7cd4d566c19a1c4eece369c47e604575f38e7a246a985c3441b60ae33c564395bb7a4bbe28325ccdb07503285dacf90b5e09f4e455fb42459741f9d497000298b99f1e70adc28f59a1be85a96952f27b6a6c5d6a08822b4f5cae05daa6c2ce2f8ca5fdd4e8f0df46b94791b3159fe8eace11bcf8d58be425b49ce2b47c007affefd5cea785c1996ad805f8c8c5ca79f15ab26e2bd4080b1d74328e7ce5bd2a579c71a6bd25f33f2ce475a2cfbe67ed1f4eb8fbd86920f41d573488abe059166aabbc3be187c435423ead6a5473994e0246efe76e419893aa2d7566b2645f3496d97585de9c92b8c5a5226398cc459ce84abc02fe2b45b5ecaf21961730d4a34bbe6fdfe720e71e3d81a494c01080d8039360d534c6ee5a3c47a1874e526969add9126b30d9192f85ba45bcfd7029cc7560f0e25e14b5deaa805360c4967705e85325ac055922863470f5397e8404022488caebf9204acd6cb02a11088aebf7e497b4ff1172f0a9c6bf980914cc4eb42fc78b457add549abf1134f84922b217502938b42d10b35079f44c5168d4c3e9fe7ca8094ef72ed73ef84f1d3530b6b3545f9f4f013e7e8cbcf2619f57754a7380ce6a9532ee14c55990faa43df6c09530a314b5f4ce597f5ec9b776e8597ce258ac47dac43bd3ac9e52788ff3a66b7dc07cd1bc3e6d197339d85fa8d3d6c3054dd1a5e416c714b544de6eb55209e40e3cac412a51748370160d2d73b6d97abd62f7bae70df27cd199c511fa693019c5717d471e934906b98cd974fda4dd1cb5e2d721044a0be2bdf24d0971e09f2f39488fe389fc5230699b4df7cec7447e5be4ea49bd7c3fe1a5ec7358510dc1dd9c1a8da68c0863188d80549e49f7c00f57d2009b2427b2aed1569603fc247734039469f9fdf3ddd3a22fa95c5d8066a468327a02b474c9915419af82c8edc67686984767fe7885207c6820f6c2e57cb8fd0bcb9981ebc8065c74e970a5d593c3b73ee25a0877ca096a9f7edfee6d43bd817c7d415fea9abb6f206c61aa36942df9318762a76b9da26d0d41a0ae9eee042a175f82dc134bf6f2d46a218db358d6852940e6e30df4a58ac6cb409e7ce99afe1e3f42768bd617af4d0a235d0ba0dd5075f9cc091784395d30e7e42d4e006db21bea9b45d1f122b75c051e84e2281573ef54ebad053218fff0cc28ea89a06adc218d4134f407654990592e75462f5ee4a463c1e46425222d48761162da8049613cafd7ecc52ff8024e9d58512b958e3a3d12dede84e1441247700bca0f992875349448b430683c756438fd4e91f3d44f3cf624ed21f3c63cf92615ecc201d0cd3159b1b3fccd8f29d2daba9ac5ba87b1dd2f83323a2b2d3176b803ce9c7bdc4bae615925eb22a213df1eeb2f8ff95586536caf042d565984aacf1425a120a5d8d7a9cbb70bf4852e116b89ff5b198d672220af2be4246372e7c3836cf50d732212a3e3346ff92873ace57fa687b2b1aab3e8dc6cb9f93f865d998cff0a1680d9012a9597c90a070e525f66226cc287814f4ac4157b15a0b25aa110946cd69fd404fafd5656669bfd1d9e509eabc004c5a")); + + data = serialize_onionpacket(tmpctx, op); + printf("[\n"); + + memset(&blinding_priv, 5, sizeof(blinding_priv)); + pubkey_from_privkey(&blinding_priv, &blinding); + + data = json_test("onion message for Alice", + data, + &alice, + &blinding_priv, + &blinding); + + data = json_test("onion message for Bob", + data, + &bob, + NULL, + &blinding); + + data = json_test("onion message for Carol", + data, + &carol, + NULL, + &blinding); + + data = json_test("onion message for Dave", + data, + &dave, + NULL, + &blinding); + + assert(!data); + printf("]\n"); + + common_shutdown(); + return 0; +} diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 2d7fab144..f7ffa99eb 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -134,6 +134,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->dev_no_version_checks = false; ld->dev_max_funding_unconfirmed = 2016; ld->dev_ignore_modern_onion = false; + ld->dev_ignore_obsolete_onion = false; ld->dev_disable_commit = -1; #endif diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 6025a31e2..973db2a7c 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -263,7 +263,7 @@ struct lightningd { u32 dev_max_funding_unconfirmed; /* Special switches to test onion compatibility */ - bool dev_ignore_modern_onion; + bool dev_ignore_modern_onion, dev_ignore_obsolete_onion; /* Tell channeld to disable commits after this many. */ int dev_disable_commit; diff --git a/lightningd/onion_message.c b/lightningd/onion_message.c index b940202ea..ae05c7c8f 100644 --- a/lightningd/onion_message.c +++ b/lightningd/onion_message.c @@ -1,7 +1,6 @@ #include "config.h" #include #include -#include #include #include #include @@ -22,7 +21,10 @@ struct onion_message_hook_payload { struct onionmsg_path **reply_path; struct pubkey *reply_first_node; struct pubkey *our_alias; + + /* Exactly one of these is set! */ struct tlv_onionmsg_payload *om; + struct tlv_obs2_onionmsg_payload *obs2_om; }; static void json_add_blindedpath(struct json_stream *stream, @@ -61,32 +63,56 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload, payload->reply_path); } - if (deprecated_apis) + /* Common convenience fields */ + if (payload->obs2_om) { + json_add_bool(stream, "obs2", true); + if (payload->obs2_om->invoice_request) + json_add_hex_talarr(stream, "invoice_request", + payload->obs2_om->invoice_request); + if (payload->obs2_om->invoice) + json_add_hex_talarr(stream, "invoice", payload->obs2_om->invoice); + + if (payload->obs2_om->invoice_error) + json_add_hex_talarr(stream, "invoice_error", + payload->obs2_om->invoice_error); + + json_array_start(stream, "unknown_fields"); + for (size_t i = 0; i < tal_count(payload->obs2_om->fields); i++) { + if (payload->obs2_om->fields[i].meta) + continue; + json_object_start(stream, NULL); + json_add_u64(stream, "number", payload->obs2_om->fields[i].numtype); + json_add_hex(stream, "value", + payload->obs2_om->fields[i].value, + payload->obs2_om->fields[i].length); + json_object_end(stream); + } + json_array_end(stream); + } else { json_add_bool(stream, "obs2", false); + if (payload->om->invoice_request) + json_add_hex_talarr(stream, "invoice_request", + payload->om->invoice_request); + if (payload->om->invoice) + json_add_hex_talarr(stream, "invoice", payload->om->invoice); - if (payload->om->invoice_request) - json_add_hex_talarr(stream, "invoice_request", - payload->om->invoice_request); - if (payload->om->invoice) - json_add_hex_talarr(stream, "invoice", payload->om->invoice); + if (payload->om->invoice_error) + json_add_hex_talarr(stream, "invoice_error", + payload->om->invoice_error); - if (payload->om->invoice_error) - json_add_hex_talarr(stream, "invoice_error", - payload->om->invoice_error); - - json_array_start(stream, "unknown_fields"); - for (size_t i = 0; i < tal_count(payload->om->fields); i++) { - if (payload->om->fields[i].meta) - continue; - json_object_start(stream, NULL); - json_add_u64(stream, "number", payload->om->fields[i].numtype); - json_add_hex(stream, "value", - payload->om->fields[i].value, - payload->om->fields[i].length); - json_object_end(stream); + json_array_start(stream, "unknown_fields"); + for (size_t i = 0; i < tal_count(payload->om->fields); i++) { + if (payload->om->fields[i].meta) + continue; + json_object_start(stream, NULL); + json_add_u64(stream, "number", payload->om->fields[i].numtype); + json_add_hex(stream, "value", + payload->om->fields[i].value, + payload->om->fields[i].length); + json_object_end(stream); + } + json_array_end(stream); } - json_array_end(stream); - json_object_end(stream); } @@ -117,6 +143,7 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) struct onion_message_hook_payload *payload; u8 *submsg; struct secret *self_id; + bool obs2; size_t submsglen; const u8 *subptr; @@ -124,6 +151,7 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) payload->our_alias = tal(payload, struct pubkey); if (!fromwire_connectd_got_onionmsg_to_us(payload, msg, + &obs2, payload->our_alias, &self_id, &payload->reply_blinding, @@ -136,7 +164,9 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) } #if DEVELOPER - if (ld->dev_ignore_modern_onion) + if (!obs2 && ld->dev_ignore_modern_onion) + return; + if (obs2 && ld->dev_ignore_obsolete_onion) return; #endif @@ -148,11 +178,22 @@ void handle_onionmsg_to_us(struct lightningd *ld, const u8 *msg) submsglen = tal_bytelen(submsg); subptr = submsg; - payload->om = fromwire_tlv_onionmsg_payload(payload, &subptr, &submsglen); - if (!payload->om) { - log_broken(ld->log, "bad got_onionmsg_tous om: %s", - tal_hex(tmpctx, msg)); - return; + if (obs2) { + payload->om = NULL; + payload->obs2_om = fromwire_tlv_obs2_onionmsg_payload(payload, &subptr, &submsglen); + if (!payload->obs2_om) { + log_broken(ld->log, "bad got_obs2_onionmsg_tous om: %s", + tal_hex(tmpctx, msg)); + return; + } + } else { + payload->obs2_om = NULL; + payload->om = fromwire_tlv_onionmsg_payload(payload, &subptr, &submsglen); + if (!payload->om) { + log_broken(ld->log, "bad got_onionmsg_tous om: %s", + tal_hex(tmpctx, msg)); + return; + } } tal_free(submsg); @@ -209,10 +250,11 @@ static struct command_result *param_onion_hops(struct command *cmd, return NULL; } -static struct command_result *json_sendonionmessage(struct command *cmd, - const char *buffer, - const jsmntok_t *obj UNNEEDED, - const jsmntok_t *params) +static struct command_result *json_sendonionmessage2(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params, + bool obs2) { struct onion_hop *hops; struct node_id *first_id; @@ -258,13 +300,29 @@ static struct command_result *json_sendonionmessage(struct command *cmd, "Creating onion failed (tlvs too long?)"); subd_send_msg(cmd->ld->connectd, - take(towire_connectd_send_onionmsg(NULL, first_id, + take(towire_connectd_send_onionmsg(NULL, obs2, first_id, serialize_onionpacket(tmpctx, op), blinding))); return command_success(cmd, json_stream_success(cmd)); } +static struct command_result *json_sendonionmessage(struct command *cmd, + const char *buffer, + const jsmntok_t *obj, + const jsmntok_t *params) +{ + return json_sendonionmessage2(cmd, buffer, obj, params, false); +} + +static struct command_result *json_sendobs2onionmessage(struct command *cmd, + const char *buffer, + const jsmntok_t *obj, + const jsmntok_t *params) +{ + return json_sendonionmessage2(cmd, buffer, obj, params, true); +} + static const struct json_command sendonionmessage_command = { "sendonionmessage", "utility", @@ -273,6 +331,14 @@ static const struct json_command sendonionmessage_command = { }; AUTODATA(json_command, &sendonionmessage_command); +static const struct json_command sendobs2onionmessage_command = { + "sendobs2onionmessage", + "utility", + json_sendobs2onionmessage, + "Send obsolete message to {first_id}, using {blinding}, encoded over {hops} (id, tlv)" +}; +AUTODATA(json_command, &sendobs2onionmessage_command); + static struct command_result *param_pubkeys(struct command *cmd, const char *name, const char *buffer, @@ -364,6 +430,32 @@ static struct command_result *json_blindedpath(struct command *cmd, json_add_blindedpath(response, "blindedpath", &first_blinding_pubkey, &first_node, path); + /* Now create obsolete one! */ + blinding_iter = first_blinding; + for (size_t i = 0; i < nhops - 1; i++) { + path[i] = tal(path, struct onionmsg_path); + path[i]->encrypted_recipient_data = create_obs2_enctlv(path[i], + &blinding_iter, + &ids[i], + &ids[i+1], + /* FIXME: Pad? */ + 0, + NULL, + &blinding_iter, + &path[i]->node_id); + } + + /* FIXME: Add padding! */ + path[nhops-1] = tal(path, struct onionmsg_path); + path[nhops-1]->encrypted_recipient_data = create_obs2_final_enctlv(path[nhops-1], + &blinding_iter, + &ids[nhops-1], + /* FIXME: Pad? */ + 0, + &cmd->ld->onion_reply_secret, + &path[nhops-1]->node_id); + json_add_blindedpath(response, "obs2blindedpath", + &first_blinding_pubkey, &first_node, path); return command_success(cmd, response); } diff --git a/lightningd/options.c b/lightningd/options.c index cb190223f..e75685492 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -733,6 +733,9 @@ static void dev_register_opts(struct lightningd *ld) opt_register_noarg("--dev-no-modern-onion", opt_set_bool, &ld->dev_ignore_modern_onion, "Ignore modern onion messages"); + opt_register_noarg("--dev-no-obsolete-onion", opt_set_bool, + &ld->dev_ignore_obsolete_onion, + "Ignore obsolete onion messages"); opt_register_arg("--dev-disable-commit-after", opt_set_intval, opt_show_intval, &ld->dev_disable_commit, diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 7a3c0b06c..6039ed6fd 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -639,13 +639,96 @@ static struct pubkey *path_to_node(const tal_t *ctx, /* Marshal arguments for sending onion messages */ struct sending { struct sent *sent; - struct tlv_onionmsg_payload *payload; + const char *msgfield; + const u8 *msgval; + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path; struct command_result *(*done)(struct command *cmd, const char *buf UNUSED, const jsmntok_t *result UNUSED, struct sent *sent); }; +static struct command_result * +send_obs2_message(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct sending *sending) +{ + struct sent *sent = sending->sent; + struct privkey blinding_iter; + struct pubkey fwd_blinding, *node_alias; + size_t nhops = tal_count(sent->path); + struct tlv_obs2_onionmsg_payload **payloads; + struct out_req *req; + + /* Now create enctlvs for *forward* path. */ + randombytes_buf(&blinding_iter, sizeof(blinding_iter)); + if (!pubkey_from_privkey(&blinding_iter, &fwd_blinding)) + return command_fail(cmd, LIGHTNINGD, + "Could not convert blinding %s to pubkey!", + type_to_string(tmpctx, struct privkey, + &blinding_iter)); + + /* We overallocate: this node (0) doesn't have payload or alias */ + payloads = tal_arr(cmd, struct tlv_obs2_onionmsg_payload *, nhops); + node_alias = tal_arr(cmd, struct pubkey, nhops); + + for (size_t i = 1; i < nhops - 1; i++) { + payloads[i] = tlv_obs2_onionmsg_payload_new(payloads); + payloads[i]->enctlv = create_obs2_enctlv(payloads[i], + &blinding_iter, + &sent->path[i], + &sent->path[i+1], + /* FIXME: Pad? */ + 0, + NULL, + &blinding_iter, + &node_alias[i]); + } + /* Final payload contains the actual data. */ + payloads[nhops-1] = tlv_obs2_onionmsg_payload_new(payloads); + + /* We don't include enctlv in final, but it gives us final alias */ + if (!create_obs2_final_enctlv(tmpctx, &blinding_iter, &sent->path[nhops-1], + /* FIXME: Pad? */ 0, + NULL, + &node_alias[nhops-1])) { + /* Should not happen! */ + return command_fail(cmd, LIGHTNINGD, + "Could create final enctlv"); + } + + /* FIXME: This interface is a string for sendobsonionmessage! */ + if (streq(sending->msgfield, "invoice_request")) { + payloads[nhops-1]->invoice_request + = cast_const(u8 *, sending->msgval); + } else { + assert(streq(sending->msgfield, "invoice")); + payloads[nhops-1]->invoice + = cast_const(u8 *, sending->msgval); + } + payloads[nhops-1]->reply_path = sending->obs2_reply_path; + + req = jsonrpc_request_start(cmd->plugin, cmd, "sendobs2onionmessage", + sending->done, + forward_error, + sending->sent); + json_add_pubkey(req->js, "first_id", &sent->path[1]); + json_add_pubkey(req->js, "blinding", &fwd_blinding); + json_array_start(req->js, "hops"); + for (size_t i = 1; i < nhops; i++) { + u8 *tlv; + json_object_start(req->js, NULL); + json_add_pubkey(req->js, "id", &node_alias[i]); + tlv = tal_arr(tmpctx, u8, 0); + towire_tlv_obs2_onionmsg_payload(&tlv, payloads[i]); + json_add_hex_talarr(req->js, "tlv", tlv); + json_object_end(req->js); + } + json_array_end(req->js); + return send_outreq(cmd->plugin, req); +} + static struct command_result * send_modern_message(struct command *cmd, struct tlv_onionmsg_payload_reply_path *reply_path, @@ -683,7 +766,7 @@ send_modern_message(struct command *cmd, &node_alias[i]); } /* Final payload contains the actual data. */ - payloads[nhops-1] = sending->payload; + payloads[nhops-1] = tlv_onionmsg_payload_new(payloads); /* We don't include enctlv in final, but it gives us final alias */ if (!create_final_enctlv(tmpctx, &blinding_iter, &sent->path[nhops-1], @@ -695,12 +778,22 @@ send_modern_message(struct command *cmd, "Could create final enctlv"); } + /* FIXME: This interface is a string for sendobsonionmessage! */ + if (streq(sending->msgfield, "invoice_request")) { + payloads[nhops-1]->invoice_request + = cast_const(u8 *, sending->msgval); + } else { + assert(streq(sending->msgfield, "invoice")); + payloads[nhops-1]->invoice + = cast_const(u8 *, sending->msgval); + } payloads[nhops-1]->reply_path = reply_path; req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage", - sending->done, + /* Try sending older version next */ + send_obs2_message, forward_error, - sending->sent); + sending); json_add_pubkey(req->js, "first_id", &sent->path[1]); json_add_pubkey(req->js, "blinding", &fwd_blinding); json_array_start(req->js, "hops"); @@ -734,6 +827,15 @@ static struct command_result *use_reply_path(struct command *cmd, json_tok_full_len(result), json_tok_full(buf, result)); + sending->obs2_reply_path = json_to_obs2_reply_path(cmd, buf, + json_get_member(buf, result, + "obs2blindedpath")); + if (!sending->obs2_reply_path) + plugin_err(cmd->plugin, + "could not parse obs2 reply path %.*s?", + json_tok_full_len(result), + json_tok_full(buf, result)); + /* Remember our alias we used so we can recognize reply */ sending->sent->reply_alias = tal_dup(sending->sent, struct pubkey, @@ -769,7 +871,8 @@ static struct command_result *make_reply_path(struct command *cmd, static struct command_result *send_message(struct command *cmd, struct sent *sent, - struct tlv_onionmsg_payload *payload STEALS, + const char *msgfield TAKES, + const u8 *msgval TAKES, struct command_result *(*done) (struct command *cmd, const char *buf UNUSED, @@ -778,7 +881,8 @@ static struct command_result *send_message(struct command *cmd, { struct sending *sending = tal(cmd, struct sending); sending->sent = sent; - sending->payload = tal_steal(sending, payload); + sending->msgfield = tal_strdup(sending, msgfield); + sending->msgval = tal_dup_talarr(sending, u8, msgval); sending->done = done; return make_reply_path(cmd, sending); @@ -817,12 +921,11 @@ sendinvreq_after_connect(struct command *cmd, const jsmntok_t *result UNUSED, struct sent *sent) { - struct tlv_onionmsg_payload *payload = tlv_onionmsg_payload_new(sent); + u8 *rawinvreq = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice_request(&rawinvreq, sent->invreq); - payload->invoice_request = tal_arr(payload, u8, 0); - towire_tlv_invoice_request(&payload->invoice_request, sent->invreq); - - return send_message(cmd, sent, payload, sendonionmsg_done); + return send_message(cmd, sent, "invoice_request", rawinvreq, + sendonionmsg_done); } struct connect_attempt { @@ -1367,12 +1470,9 @@ sendinvoice_after_connect(struct command *cmd, const jsmntok_t *result UNUSED, struct sent *sent) { - struct tlv_onionmsg_payload *payload = tlv_onionmsg_payload_new(sent); - - payload->invoice = tal_arr(payload, u8, 0); - towire_tlv_invoice(&payload->invoice, sent->inv); - - return send_message(cmd, sent, payload, prepare_inv_timeout); + u8 *rawinv = tal_arr(tmpctx, u8, 0); + towire_tlv_invoice(&rawinv, sent->inv); + return send_message(cmd, sent, "invoice", rawinv, prepare_inv_timeout); } static struct command_result *createinvoice_done(struct command *cmd, diff --git a/plugins/offers.c b/plugins/offers.c index 08b08d52e..bf37740a9 100644 --- a/plugins/offers.c +++ b/plugins/offers.c @@ -41,14 +41,65 @@ static struct command_result *sendonionmessage_error(struct command *cmd, return command_hook_success(cmd); } +/* FIXME: replyfield string interface is to accomodate obsolete API */ +static struct command_result * +send_obs2_onion_reply(struct command *cmd, + struct tlv_obs2_onionmsg_payload_reply_path *reply_path, + const char *replyfield, + const u8 *replydata) +{ + struct out_req *req; + size_t nhops = tal_count(reply_path->path); + + req = jsonrpc_request_start(cmd->plugin, cmd, "sendobs2onionmessage", + finished, sendonionmessage_error, NULL); + + json_add_pubkey(req->js, "first_id", &reply_path->first_node_id); + json_add_pubkey(req->js, "blinding", &reply_path->blinding); + json_array_start(req->js, "hops"); + for (size_t i = 0; i < nhops; i++) { + struct tlv_obs2_onionmsg_payload *omp; + u8 *tlv; + + json_object_start(req->js, NULL); + json_add_pubkey(req->js, "id", &reply_path->path[i]->node_id); + + omp = tlv_obs2_onionmsg_payload_new(tmpctx); + omp->enctlv = reply_path->path[i]->encrypted_recipient_data; + + /* Put payload in last hop. */ + if (i == nhops - 1) { + if (streq(replyfield, "invoice")) { + omp->invoice = cast_const(u8 *, replydata); + } else { + assert(streq(replyfield, "invoice_error")); + omp->invoice_error = cast_const(u8 *, replydata); + } + } + tlv = tal_arr(tmpctx, u8, 0); + towire_tlv_obs2_onionmsg_payload(&tlv, omp); + json_add_hex_talarr(req->js, "tlv", tlv); + json_object_end(req->js); + } + json_array_end(req->js); + return send_outreq(cmd->plugin, req); +} + struct command_result * send_onion_reply(struct command *cmd, struct tlv_onionmsg_payload_reply_path *reply_path, - struct tlv_onionmsg_payload *payload) + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path, + const char *replyfield, + const u8 *replydata) { struct out_req *req; size_t nhops; + /* Exactly one must be set! */ + assert(!reply_path != !obs2_reply_path); + if (obs2_reply_path) + return send_obs2_onion_reply(cmd, obs2_reply_path, replyfield, replydata); + req = jsonrpc_request_start(cmd->plugin, cmd, "sendonionmessage", finished, sendonionmessage_error, NULL); @@ -64,14 +115,18 @@ send_onion_reply(struct command *cmd, json_object_start(req->js, NULL); json_add_pubkey(req->js, "id", &reply_path->path[i]->node_id); - /* Put payload in last hop. */ - if (i == nhops - 1) - omp = payload; - else - omp = tlv_onionmsg_payload_new(tmpctx); - + omp = tlv_onionmsg_payload_new(tmpctx); omp->encrypted_data_tlv = reply_path->path[i]->encrypted_recipient_data; + /* Put payload in last hop. */ + if (i == nhops - 1) { + if (streq(replyfield, "invoice")) { + omp->invoice = cast_const(u8 *, replydata); + } else { + assert(streq(replyfield, "invoice_error")); + omp->invoice_error = cast_const(u8 *, replydata); + } + } tlv = tal_arr(tmpctx, u8, 0); towire_tlv_onionmsg_payload(&tlv, omp); json_add_hex_talarr(req->js, "tlv", tlv); @@ -86,6 +141,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd, const jsmntok_t *params) { const jsmntok_t *om, *replytok, *invreqtok, *invtok; + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path = NULL; struct tlv_onionmsg_payload_reply_path *reply_path = NULL; if (!offers_enabled) @@ -94,20 +150,31 @@ static struct command_result *onion_message_modern_call(struct command *cmd, om = json_get_member(buf, params, "onion_message"); replytok = json_get_member(buf, om, "reply_blindedpath"); if (replytok) { - reply_path = json_to_reply_path(cmd, buf, replytok); - if (!reply_path) - plugin_err(cmd->plugin, "Invalid reply path %.*s?", - json_tok_full_len(replytok), - json_tok_full(buf, replytok)); + bool obs2; + json_to_bool(buf, json_get_member(buf, om, "obs2"), &obs2); + if (obs2) { + obs2_reply_path = json_to_obs2_reply_path(cmd, buf, replytok); + if (!obs2_reply_path) + plugin_err(cmd->plugin, "Invalid obs2 reply path %.*s?", + json_tok_full_len(replytok), + json_tok_full(buf, replytok)); + } else { + reply_path = json_to_reply_path(cmd, buf, replytok); + if (!reply_path) + plugin_err(cmd->plugin, "Invalid reply path %.*s?", + json_tok_full_len(replytok), + json_tok_full(buf, replytok)); + } } invreqtok = json_get_member(buf, om, "invoice_request"); if (invreqtok) { const u8 *invreqbin = json_tok_bin_from_hex(tmpctx, buf, invreqtok); - if (reply_path) + if (reply_path || obs2_reply_path) return handle_invoice_request(cmd, invreqbin, - reply_path); + reply_path, + obs2_reply_path); else plugin_log(cmd->plugin, LOG_DBG, "invoice_request without reply_path"); @@ -117,7 +184,7 @@ static struct command_result *onion_message_modern_call(struct command *cmd, if (invtok) { const u8 *invbin = json_tok_bin_from_hex(tmpctx, buf, invtok); if (invbin) - return handle_invoice(cmd, invbin, reply_path); + return handle_invoice(cmd, invbin, reply_path, obs2_reply_path); } return command_hook_success(cmd); diff --git a/plugins/offers.h b/plugins/offers.h index 73baca4ee..68feefa12 100644 --- a/plugins/offers.h +++ b/plugins/offers.h @@ -10,5 +10,7 @@ struct command; struct command_result *WARN_UNUSED_RESULT send_onion_reply(struct command *cmd, struct tlv_onionmsg_payload_reply_path *reply_path, - struct tlv_onionmsg_payload *payload); + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path, + const char *replyfield, + const u8 *replydata); #endif /* LIGHTNING_PLUGINS_OFFERS_H */ diff --git a/plugins/offers_inv_hook.c b/plugins/offers_inv_hook.c index bd92c64b7..a15179465 100644 --- a/plugins/offers_inv_hook.c +++ b/plugins/offers_inv_hook.c @@ -12,6 +12,7 @@ struct inv { struct tlv_invoice *inv; /* May be NULL */ + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path; struct tlv_onionmsg_payload_reply_path *reply_path; /* The offer, once we've looked it up. */ @@ -25,8 +26,8 @@ fail_inv_level(struct command *cmd, const char *fmt, va_list ap) { char *full_fmt, *msg; - struct tlv_onionmsg_payload *payload; struct tlv_invoice_error *err; + u8 *errdata; full_fmt = tal_fmt(tmpctx, "Failed invoice"); if (inv->inv) { @@ -43,7 +44,7 @@ fail_inv_level(struct command *cmd, plugin_log(cmd->plugin, l, "%s", msg); /* Only reply if they gave us a path */ - if (!inv->reply_path) + if (!inv->reply_path && !inv->obs2_reply_path) return command_hook_success(cmd); /* Don't send back internal error details. */ @@ -55,10 +56,10 @@ fail_inv_level(struct command *cmd, err->error = tal_dup_arr(err, char, msg, strlen(msg), 0); /* FIXME: Add suggested_value / erroneous_field! */ - payload = tlv_onionmsg_payload_new(tmpctx); - payload->invoice_error = tal_arr(payload, u8, 0); - towire_tlv_invoice_error(&payload->invoice_error, err); - return send_onion_reply(cmd, inv->reply_path, payload); + errdata = tal_arr(cmd, u8, 0); + towire_tlv_invoice_error(&errdata, err); + return send_onion_reply(cmd, inv->reply_path, inv->obs2_reply_path, + "invoice_error", errdata); } static struct command_result *WARN_UNUSED_RESULT @@ -318,7 +319,8 @@ static struct command_result *listoffers_error(struct command *cmd, struct command_result *handle_invoice(struct command *cmd, const u8 *invbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS) + struct tlv_onionmsg_payload_reply_path *reply_path STEALS, + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path STEALS) { size_t len = tal_count(invbin); struct inv *inv = tal(cmd, struct inv); @@ -327,6 +329,7 @@ struct command_result *handle_invoice(struct command *cmd, int bad_feature; struct sha256 m, shash; + inv->obs2_reply_path = tal_steal(inv, obs2_reply_path); inv->reply_path = tal_steal(inv, reply_path); inv->inv = fromwire_tlv_invoice(cmd, &invbin, &len); diff --git a/plugins/offers_inv_hook.h b/plugins/offers_inv_hook.h index fbc12ace6..a733d6c46 100644 --- a/plugins/offers_inv_hook.h +++ b/plugins/offers_inv_hook.h @@ -6,5 +6,6 @@ /* We got an onionmessage with an invoice! reply_path could be NULL. */ struct command_result *handle_invoice(struct command *cmd, const u8 *invbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS); + struct tlv_onionmsg_payload_reply_path *reply_path STEALS, + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path STEALS); #endif /* LIGHTNING_PLUGINS_OFFERS_INV_HOOK_H */ diff --git a/plugins/offers_invreq_hook.c b/plugins/offers_invreq_hook.c index 851bc5569..7f586437f 100644 --- a/plugins/offers_invreq_hook.c +++ b/plugins/offers_invreq_hook.c @@ -17,6 +17,7 @@ struct invreq { struct tlv_invoice_request *invreq; struct tlv_onionmsg_payload_reply_path *reply_path; + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path; /* The offer, once we've looked it up. */ struct tlv_offer *offer; @@ -35,8 +36,8 @@ fail_invreq_level(struct command *cmd, const char *fmt, va_list ap) { char *full_fmt, *msg; - struct tlv_onionmsg_payload *payload; struct tlv_invoice_error *err; + u8 *errdata; full_fmt = tal_fmt(tmpctx, "Failed invoice_request"); if (invreq->invreq) { @@ -61,10 +62,10 @@ fail_invreq_level(struct command *cmd, err->error = tal_dup_arr(err, char, msg, strlen(msg), 0); /* FIXME: Add suggested_value / erroneous_field! */ - payload = tlv_onionmsg_payload_new(tmpctx); - payload->invoice_error = tal_arr(payload, u8, 0); - towire_tlv_invoice_error(&payload->invoice_error, err); - return send_onion_reply(cmd, invreq->reply_path, payload); + errdata = tal_arr(cmd, u8, 0); + towire_tlv_invoice_error(&errdata, err); + return send_onion_reply(cmd, invreq->reply_path, invreq->obs2_reply_path, + "invoice_error", errdata); } static struct command_result *WARN_UNUSED_RESULT PRINTF_FMT(3,4) @@ -171,7 +172,6 @@ static struct command_result *createinvoice_done(struct command *cmd, { char *hrp; u8 *rawinv; - struct tlv_onionmsg_payload *payload; const jsmntok_t *t; /* We have a signed invoice, use it as a reply. */ @@ -184,9 +184,8 @@ static struct command_result *createinvoice_done(struct command *cmd, json_tok_full(buf, t)); } - payload = tlv_onionmsg_payload_new(tmpctx); - payload->invoice = rawinv; - return send_onion_reply(cmd, ir->reply_path, payload); + return send_onion_reply(cmd, ir->reply_path, ir->obs2_reply_path, + "invoice", rawinv); } static struct command_result *createinvoice_error(struct command *cmd, @@ -848,13 +847,15 @@ static struct command_result *handle_offerless_request(struct command *cmd, struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, - struct tlv_onionmsg_payload_reply_path *reply_path) + struct tlv_onionmsg_payload_reply_path *reply_path, + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path) { size_t len = tal_count(invreqbin); struct invreq *ir = tal(cmd, struct invreq); struct out_req *req; int bad_feature; + ir->obs2_reply_path = tal_steal(ir, obs2_reply_path); ir->reply_path = tal_steal(ir, reply_path); ir->invreq = fromwire_tlv_invoice_request(cmd, &invreqbin, &len); diff --git a/plugins/offers_invreq_hook.h b/plugins/offers_invreq_hook.h index c9dc5f37c..da59e52b5 100644 --- a/plugins/offers_invreq_hook.h +++ b/plugins/offers_invreq_hook.h @@ -8,5 +8,6 @@ extern u16 cltv_final; /* We got an onionmessage with an invreq! */ struct command_result *handle_invoice_request(struct command *cmd, const u8 *invreqbin, - struct tlv_onionmsg_payload_reply_path *reply_path STEALS); + struct tlv_onionmsg_payload_reply_path *reply_path STEALS, + struct tlv_obs2_onionmsg_payload_reply_path *obs2_reply_path STEALS); #endif /* LIGHTNING_PLUGINS_OFFERS_INVREQ_HOOK_H */ diff --git a/tests/test_pay.py b/tests/test_pay.py index 63cf2331e..d55dade1d 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -4479,6 +4479,27 @@ def test_fetchinvoice_3hop(node_factory, bitcoind): l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) + # Make sure l4 handled both onions before shutting down + l1.daemon.wait_for_log(r'plugin-fetchinvoice: Received modern onion .*obs2\\":false') + l1.daemon.wait_for_log(r'plugin-fetchinvoice: No match for modern onion.*obs2\\":true') + + # Test with obsolete onion. + l4.stop() + l4.daemon.opts['dev-no-modern-onion'] = None + l4.start() + l4.rpc.connect(l3.info['id'], 'localhost', l3.port) + + l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) + + # Test with modern onion. + l4.stop() + del l4.daemon.opts['dev-no-modern-onion'] + l4.daemon.opts['dev-no-obsolete-onion'] = None + l4.start() + l4.rpc.connect(l3.info['id'], 'localhost', l3.port) + + l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']}) + def test_fetchinvoice(node_factory, bitcoind): # We remove the conversion plugin on l3, causing it to get upset. @@ -4775,8 +4796,10 @@ def test_dev_rawrequest(node_factory): assert 'invoice' in ret -def test_sendinvoice(node_factory, bitcoind): +def do_test_sendinvoice(node_factory, bitcoind, disable): l2opts = {'experimental-offers': None} + if disable: + l2opts[disable] = None l1, l2 = node_factory.line_graph(2, wait_for_announce=True, opts=[{'experimental-offers': None}, l2opts]) @@ -4858,6 +4881,20 @@ def test_sendinvoice(node_factory, bitcoind): assert out['amount_received_msat'] == Millisatoshi(10000000) +def test_sendinvoice(node_factory, bitcoind): + do_test_sendinvoice(node_factory, bitcoind, None) + + +@pytest.mark.developer("needs to --dev-no-obsolete-onion") +def test_sendinvoice_modern(node_factory, bitcoind): + do_test_sendinvoice(node_factory, bitcoind, 'dev-no-obsolete-onion') + + +@pytest.mark.developer("needs to --dev-no-modern-onion") +def test_sendinvoice_obsolete(node_factory, bitcoind): + do_test_sendinvoice(node_factory, bitcoind, 'dev-no-modern-onion') + + def test_self_pay(node_factory): """Repro test for issue 4345: pay ourselves via the pay plugin. diff --git a/wire/extracted_onion_01_offers.patch b/wire/extracted_onion_01_offers.patch index 263d9b736..90c0cb6c4 100644 --- a/wire/extracted_onion_01_offers.patch +++ b/wire/extracted_onion_01_offers.patch @@ -1,9 +1,29 @@ --- wire/extracted_onion_wire_csv 2020-03-25 10:24:12.861645774 +1030 +++ - 2020-03-26 13:47:13.498294435 +1030 -@@ -8,6 +8,10 @@ +@@ -8,6 +8,30 @@ tlvtype,tlv_payload,payment_data,8 tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,tlv_payload,payment_data,total_msat,tu64, ++tlvtype,obs2_onionmsg_payload,reply_path,2 ++tlvdata,obs2_onionmsg_payload,reply_path,first_node_id,point, ++tlvdata,obs2_onionmsg_payload,reply_path,blinding,point, ++tlvdata,obs2_onionmsg_payload,reply_path,path,onionmsg_path,... ++tlvtype,obs2_onionmsg_payload,enctlv,10 ++tlvdata,obs2_onionmsg_payload,enctlv,enctlv,byte,... ++tlvtype,obs2_onionmsg_payload,invoice_request,64 ++tlvdata,obs2_onionmsg_payload,invoice_request,invoice_request,byte,... ++tlvtype,obs2_onionmsg_payload,invoice,66 ++tlvdata,obs2_onionmsg_payload,invoice,invoice,byte,... ++tlvtype,obs2_onionmsg_payload,invoice_error,68 ++tlvdata,obs2_onionmsg_payload,invoice_error,invoice_error,byte,... ++tlvtype,obs2_encmsg_tlvs,padding,1 ++tlvdata,obs2_encmsg_tlvs,padding,pad,byte,... ++tlvtype,obs2_encmsg_tlvs,next_node_id,4 ++tlvdata,obs2_encmsg_tlvs,next_node_id,node_id,point, ++tlvtype,obs2_encmsg_tlvs,next_blinding,12 ++tlvdata,obs2_encmsg_tlvs,next_blinding,blinding,point, ++tlvtype,obs2_encmsg_tlvs,self_id,14 ++tlvdata,obs2_encmsg_tlvs,self_id,data,byte,... +subtype,onionmsg_path +subtypedata,onionmsg_path,node_id,point, +subtypedata,onionmsg_path,enclen,u16, diff --git a/wire/extracted_onion_02_modernonion.patch b/wire/extracted_onion_02_modernonion.patch index bed4ec9cc..4d1bc69ca 100644 --- a/wire/extracted_onion_02_modernonion.patch +++ b/wire/extracted_onion_02_modernonion.patch @@ -1,9 +1,9 @@ --- wire/onion_wire.csv 2021-11-16 15:17:39.446494580 +1030 +++ wire/onion_wire.csv.raw 2021-11-16 15:36:00.046441058 +1030 @@ -8,10 +8,36 @@ - tlvtype,tlv_payload,payment_data,8 - tlvdata,tlv_payload,payment_data,payment_secret,byte,32 - tlvdata,tlv_payload,payment_data,total_msat,tu64, + tlvdata,obs2_encmsg_tlvs,next_blinding,blinding,point, + tlvtype,obs2_encmsg_tlvs,self_id,14 + tlvdata,obs2_encmsg_tlvs,self_id,data,byte,... +tlvtype,tlv_payload,encrypted_recipient_data,10 +tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... +tlvtype,tlv_payload,blinding_point,12 diff --git a/wire/extracted_onion_exp_enctlv.patch b/wire/extracted_onion_exp_enctlv.patch index 458bc318e..1897193b8 100644 --- a/wire/extracted_onion_exp_enctlv.patch +++ b/wire/extracted_onion_exp_enctlv.patch @@ -10,7 +10,6 @@ +tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... +tlvtype,tlv_payload,blinding_point,12 +tlvdata,tlv_payload,blinding_point,blinding,point, - tlvtype,tlv_payload,encrypted_recipient_data,10 - tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... - tlvtype,tlv_payload,blinding_point,12 - + tlvtype,obs2_onionmsg_payload,reply_path,2 + tlvdata,obs2_onionmsg_payload,reply_path,first_node_id,point, + tlvdata,obs2_onionmsg_payload,reply_path,blinding,point, diff --git a/wire/onion_wire.csv b/wire/onion_wire.csv index 87c2cc8f0..2ac0c4cff 100644 --- a/wire/onion_wire.csv +++ b/wire/onion_wire.csv @@ -8,6 +8,26 @@ tlvdata,tlv_payload,short_channel_id,short_channel_id,short_channel_id, tlvtype,tlv_payload,payment_data,8 tlvdata,tlv_payload,payment_data,payment_secret,byte,32 tlvdata,tlv_payload,payment_data,total_msat,tu64, +tlvtype,obs2_onionmsg_payload,reply_path,2 +tlvdata,obs2_onionmsg_payload,reply_path,first_node_id,point, +tlvdata,obs2_onionmsg_payload,reply_path,blinding,point, +tlvdata,obs2_onionmsg_payload,reply_path,path,onionmsg_path,... +tlvtype,obs2_onionmsg_payload,enctlv,10 +tlvdata,obs2_onionmsg_payload,enctlv,enctlv,byte,... +tlvtype,obs2_onionmsg_payload,invoice_request,64 +tlvdata,obs2_onionmsg_payload,invoice_request,invoice_request,byte,... +tlvtype,obs2_onionmsg_payload,invoice,66 +tlvdata,obs2_onionmsg_payload,invoice,invoice,byte,... +tlvtype,obs2_onionmsg_payload,invoice_error,68 +tlvdata,obs2_onionmsg_payload,invoice_error,invoice_error,byte,... +tlvtype,obs2_encmsg_tlvs,padding,1 +tlvdata,obs2_encmsg_tlvs,padding,pad,byte,... +tlvtype,obs2_encmsg_tlvs,next_node_id,4 +tlvdata,obs2_encmsg_tlvs,next_node_id,node_id,point, +tlvtype,obs2_encmsg_tlvs,next_blinding,12 +tlvdata,obs2_encmsg_tlvs,next_blinding,blinding,point, +tlvtype,obs2_encmsg_tlvs,self_id,14 +tlvdata,obs2_encmsg_tlvs,self_id,data,byte,... tlvtype,tlv_payload,encrypted_recipient_data,10 tlvdata,tlv_payload,encrypted_recipient_data,encrypted_data,byte,... tlvtype,tlv_payload,blinding_point,12