From 077ec99788447c2c234bd60ac017376d5ea422fe Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 17 Oct 2022 11:14:38 +1030 Subject: [PATCH] common/onion: blinded payment support. We make it look like a normal payment for the caller. Signed-off-by: Rusty Russell --- common/onion.c | 194 +++++++++++++++++++++++++++- common/onion.h | 10 +- common/test/run-onion-test-vector.c | 7 + common/test/run-sphinx.c | 16 ++- devtools/Makefile | 2 + lightningd/options.c | 5 +- lightningd/peer_htlcs.c | 21 ++- wallet/test/run-wallet.c | 3 + 8 files changed, 242 insertions(+), 16 deletions(-) diff --git a/common/onion.c b/common/onion.c index 1f1d4582f..7166901e3 100644 --- a/common/onion.c +++ b/common/onion.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -107,10 +108,131 @@ u8 *onion_final_hop(const tal_t *ctx, return make_tlv_hop(ctx, tlv); } +static u64 ceil_div(u64 a, u64 b) +{ + return (a + b - 1) / b; +} + +static bool handle_blinded_forward(struct onion_payload *p, + struct amount_msat amount_in, + u32 cltv_expiry, + const struct tlv_tlv_payload *tlv, + const struct tlv_encrypted_data_tlv *enc, + u64 *failtlvtype) +{ + u64 amt = amount_in.millisatoshis; /* Raw: allowed to wrap */ + + /* BOLT-route-blinding #4: + * - If it not the final node: + * - MUST return an error if fields other + * than `encrypted_recipient_data` or `blinding_point` are present. + */ + for (size_t i = 0; i < tal_count(tlv->fields); i++) { + if (tlv->fields[i].numtype != TLV_TLV_PAYLOAD_BLINDING_POINT + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA) { + *failtlvtype = tlv->fields[i].numtype; + return false; + } + } + + /* BOLT-route-blinding #4: + * - If it not the final node: + *... + * - MUST return an error if `encrypted_recipient_data` does not + * contain `short_channel_id` or `next_node_id`. + */ + if (!enc->short_channel_id && !enc->next_node_id) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + return false; + } + + /* FIXME: handle fwd-by-node-id */ + if (!enc->short_channel_id) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + return false; + } + + p->forward_channel = tal_dup(p, struct short_channel_id, + enc->short_channel_id); + p->total_msat = NULL; + + /* BOLT-route-blinding #4: + * - If it not the final node: + *... + * - MUST return an error if `encrypted_recipient_data` does not + * contain `payment_relay`. + */ + if (!enc->payment_relay) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + return false; + } + + /* FIXME: Put these formulae in BOLT 4! */ + /* amt_to_forward = ceil((amount_msat - fee_base_msat) * 1000000 / (1000000 + fee_proportional_millionths)) */ + /* If these values are crap, that's OK: the HTLC will fail. */ + p->amt_to_forward = amount_msat(ceil_div((amt - enc->payment_relay->fee_base_msat) * 1000000, + 1000000 + enc->payment_relay->fee_proportional_millionths)); + p->outgoing_cltv = cltv_expiry - enc->payment_relay->cltv_expiry_delta; + return true; +} + +static bool handle_blinded_terminal(struct onion_payload *p, + const struct tlv_tlv_payload *tlv, + const struct tlv_encrypted_data_tlv *enc, + u64 *failtlvtype) +{ + /* BOLT-route-blinding #4: + * - If it is the final node: + * - MUST return an error if fields other than + * `encrypted_recipient_data`, `blinding_point`, `amt_to_forward` + * or `outgoing_cltv_value` are present. + * - MUST return an error if the `path_id` in + * `encrypted_recipient_data` does not match the one it created. + * - MUST return an error if `amt_to_forward` or + * `outgoing_cltv_value` are not present. + * - MUST return an error if `amt_to_forward` is below what it expects + * for the payment. + */ + for (size_t i = 0; i < tal_count(tlv->fields); i++) { + if (tlv->fields[i].numtype != TLV_TLV_PAYLOAD_BLINDING_POINT + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_AMT_TO_FORWARD + && tlv->fields[i].numtype != TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE) { + *failtlvtype = tlv->fields[i].numtype; + return false; + } + } + + if (!tlv->amt_to_forward) { + *failtlvtype = TLV_TLV_PAYLOAD_AMT_TO_FORWARD; + return false; + } + + if (!tlv->outgoing_cltv_value) { + *failtlvtype = TLV_TLV_PAYLOAD_OUTGOING_CLTV_VALUE; + return false; + } + + p->amt_to_forward = amount_msat(*tlv->amt_to_forward); + p->outgoing_cltv = *tlv->outgoing_cltv_value; + + p->forward_channel = NULL; + /* BOLT #4: + * - if it is the final node: + * - MUST treat `total_msat` as if it were equal to + * `amt_to_forward` if it is not present. */ + p->total_msat = tal_dup(p, struct amount_msat, + &p->amt_to_forward); + return true; +} + struct onion_payload *onion_decode(const tal_t *ctx, + bool blinding_support, const struct route_step *rs, const struct pubkey *blinding, const u64 *accepted_extra_tlvs, + struct amount_msat amount_in, + u32 cltv_expiry, u64 *failtlvtype, size_t *failtlvpos) { @@ -142,6 +264,73 @@ struct onion_payload *onion_decode(const tal_t *ctx, goto fail; } + if (blinding || tlv->blinding_point) { + struct tlv_encrypted_data_tlv *enc; + + /* Only supported with --experimental-onion-messages! */ + if (!blinding_support) { + if (!blinding) + goto fail; + *failtlvtype = TLV_TLV_PAYLOAD_BLINDING_POINT; + goto field_bad; + } + + /* BOLT-route-blinding #4: + * The reader: + * - If `blinding_point` is set (either in the payload or the + * outer message): + * - MUST return an error if it is set in both the payload + * and the outer message + */ + if (blinding && tlv->blinding_point) { + *failtlvtype = TLV_TLV_PAYLOAD_BLINDING_POINT; + goto field_bad; + } + if (tlv->blinding_point) + p->blinding = tal_dup(p, struct pubkey, + tlv->blinding_point); + else + p->blinding = tal_dup(p, struct pubkey, + blinding); + + /* BOLT-route-blinding #4: + * The reader: + *... + * - MUST return an error if `encrypted_recipient_data` is not + * present. + */ + if (!tlv->encrypted_recipient_data) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + ecdh(p->blinding, &p->blinding_ss); + enc = decrypt_encrypted_data(tmpctx, p->blinding, &p->blinding_ss, + tlv->encrypted_recipient_data); + if (!enc) { + *failtlvtype = TLV_TLV_PAYLOAD_ENCRYPTED_RECIPIENT_DATA; + goto field_bad; + } + + p->payment_constraints = tal_steal(p, enc->payment_constraints); + if (rs->nextcase == ONION_FORWARD) { + if (!handle_blinded_forward(p, amount_in, cltv_expiry, + tlv, enc, failtlvtype)) + goto field_bad; + } else { + if (!handle_blinded_terminal(p, tlv, enc, failtlvtype)) + goto field_bad; + } + + /* Blinded paths have no payment secret or metadata: + * we use the path_id for that. */ + p->payment_secret = NULL; + p->payment_metadata = NULL; + + p->tlv = tal_steal(p, tlv); + return p; + } + /* BOLT #4: * * The reader: @@ -186,8 +375,6 @@ struct onion_payload *onion_decode(const tal_t *ctx, } p->payment_secret = NULL; - p->blinding = tal_dup_or_null(p, struct pubkey, blinding); - if (tlv->payment_data) { p->payment_secret = tal_dup(p, struct secret, &tlv->payment_data->payment_secret); @@ -202,6 +389,9 @@ struct onion_payload *onion_decode(const tal_t *ctx, else p->payment_metadata = NULL; + p->blinding = NULL; + p->payment_constraints = NULL; + p->tlv = tal_steal(p, tlv); return p; diff --git a/common/onion.h b/common/onion.h index 65d76dcfb..c49e30101 100644 --- a/common/onion.h +++ b/common/onion.h @@ -5,6 +5,7 @@ #include struct route_step; +struct tlv_encrypted_data_tlv_payment_relay; struct onion_payload { struct amount_msat amt_to_forward; @@ -13,6 +14,7 @@ struct onion_payload { struct short_channel_id *forward_channel; struct secret *payment_secret; u8 *payment_metadata; + struct tlv_encrypted_data_tlv_payment_constraints *payment_constraints; /* If blinding is set, blinding_ss is the shared secret.*/ struct pubkey *blinding; @@ -39,23 +41,29 @@ u8 *onion_final_hop(const tal_t *ctx, const struct secret *payment_secret, const u8 *payment_metadata); + /** * onion_decode: decode payload from a decrypted onion. * @ctx: context to allocate onion_contents off. + * @blinding_support: --experimental-route-blinding? * @rs: the route_step, whose raw_payload is of at least length * onion_payload_length(). * @blinding: the optional incoming blinding point. * @accepted_extra_tlvs: Allow these types to be in the TLV without failing + * @amount_in: Incoming HTLC amount + * @cltv_expiry: Incoming HTLC cltv_expiry * @failtlvtype: (out) the tlv type which failed to parse. * @failtlvpos: (out) the offset in the tlv which failed to parse. * * If the payload is not valid, returns NULL. */ struct onion_payload *onion_decode(const tal_t *ctx, + bool blinding_support, const struct route_step *rs, const struct pubkey *blinding, const u64 *accepted_extra_tlvs, + struct amount_msat amount_in, + u32 cltv_expiry, u64 *failtlvtype, size_t *failtlvpos); - #endif /* LIGHTNING_COMMON_ONION_H */ diff --git a/common/test/run-onion-test-vector.c b/common/test/run-onion-test-vector.c index 3effdb5b5..7f0d5f4c2 100644 --- a/common/test/run-onion-test-vector.c +++ b/common/test/run-onion-test-vector.c @@ -59,6 +59,13 @@ struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u /* 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 decrypt_encrypted_data */ +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx UNNEEDED, + const struct pubkey *blinding UNNEEDED, + const struct secret *ss UNNEEDED, + const u8 *enctlv) + +{ fprintf(stderr, "decrypt_encrypted_data called!\n"); abort(); } /* Generated stub for ecdh */ void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) { fprintf(stderr, "ecdh called!\n"); abort(); } diff --git a/common/test/run-sphinx.c b/common/test/run-sphinx.c index 8a1e07b44..96daa1a8b 100644 --- a/common/test/run-sphinx.c +++ b/common/test/run-sphinx.c @@ -51,6 +51,16 @@ size_t bigsize_get(const u8 *p UNNEEDED, size_t max UNNEEDED, bigsize_t *val UNN /* Generated stub for bigsize_put */ size_t bigsize_put(u8 buf[BIGSIZE_MAX_LEN] UNNEEDED, bigsize_t v UNNEEDED) { fprintf(stderr, "bigsize_put called!\n"); abort(); } +/* Generated stub for decrypt_encrypted_data */ +struct tlv_encrypted_data_tlv *decrypt_encrypted_data(const tal_t *ctx UNNEEDED, + const struct pubkey *blinding UNNEEDED, + const struct secret *ss UNNEEDED, + const u8 *enctlv) + +{ fprintf(stderr, "decrypt_encrypted_data 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(); } @@ -82,12 +92,6 @@ void towire_tlv(u8 **pptr UNNEEDED, { fprintf(stderr, "towire_tlv called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ -#if EXPERIMENTAL_FEATURES -/* Generated stub for ecdh */ -void ecdh(const struct pubkey *point UNNEEDED, struct secret *ss UNNEEDED) -{ fprintf(stderr, "ecdh called!\n"); abort(); } -#endif - extern secp256k1_context *secp256k1_ctx; static struct secret secret_from_hex(const char *hex) diff --git a/devtools/Makefile b/devtools/Makefile index a18548c66..9d23d644c 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -14,6 +14,8 @@ ALL_PROGRAMS += $(DEVTOOLS) DEVTOOLS_COMMON_OBJS := \ common/amount.o \ common/autodata.o \ + common/blinding.o \ + common/blindedpath.o \ common/coin_mvt.o \ common/base32.o \ common/bech32.o \ diff --git a/lightningd/options.c b/lightningd/options.c index 80f75f0b5..91f443643 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1007,6 +1007,9 @@ static char *opt_set_onion_messages(struct lightningd *ld) feature_set_or(ld->our_features, take(feature_set_for_feature(NULL, OPTIONAL_FEATURE(OPT_ONION_MESSAGES)))); + feature_set_or(ld->our_features, + take(feature_set_for_feature(NULL, + OPTIONAL_FEATURE(OPT_ROUTE_BLINDING)))); return NULL; } @@ -1081,7 +1084,7 @@ static void register_opts(struct lightningd *ld) opt_register_early_noarg("--experimental-onion-messages", opt_set_onion_messages, ld, "EXPERIMENTAL: enable send, receive and relay" - " of onion messages"); + " of onion messages and blinded payments"); opt_register_early_noarg("--experimental-offers", opt_set_offers, ld, "EXPERIMENTAL: enable send and receive of offers" diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index d9b31e86b..265146bb7 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -904,7 +904,7 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re struct lightningd *ld = request->ld; struct preimage payment_preimage; const jsmntok_t *resulttok, *paykeytok, *payloadtok, *fwdtok; - u8 *payload, *failonion; + u8 *failonion; if (!toks || !buffer) return true; @@ -920,7 +920,7 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re payloadtok = json_get_member(buffer, toks, "payload"); if (payloadtok) { - payload = json_tok_bin_from_hex(rs, buffer, payloadtok); + u8 *payload = json_tok_bin_from_hex(rs, buffer, payloadtok); if (!payload) fatal("Bad payload for htlc_accepted" " hook: %.*s", @@ -930,13 +930,17 @@ static bool htlc_accepted_hook_deserialize(struct htlc_accepted_hook_payload *re tal_free(rs->raw_payload); rs->raw_payload = prepend_length(rs, take(payload)); - request->payload = onion_decode(request, rs, + request->payload = onion_decode(request, + feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING), + rs, hin->blinding, ld->accept_extra_tlv_types, + hin->msat, + hin->cltv_expiry, &request->failtlvtype, &request->failtlvpos); - } else - payload = NULL; + } fwdtok = json_get_member(buffer, toks, "forward_to"); if (fwdtok) { @@ -1312,9 +1316,14 @@ static bool peer_accepted_htlc(const tal_t *ctx, hook_payload = tal(NULL, struct htlc_accepted_hook_payload); hook_payload->route_step = tal_steal(hook_payload, rs); - hook_payload->payload = onion_decode(hook_payload, rs, + hook_payload->payload = onion_decode(hook_payload, + feature_offered(ld->our_features->bits[INIT_FEATURE], + OPT_ROUTE_BLINDING), + rs, hin->blinding, ld->accept_extra_tlv_types, + hin->msat, + hin->cltv_expiry, &hook_payload->failtlvtype, &hook_payload->failtlvpos); hook_payload->ld = ld; diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index e2f7f324b..e6826e98f 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -548,9 +548,12 @@ enum watch_result onchaind_funding_spent(struct channel *channel UNNEEDED, { fprintf(stderr, "onchaind_funding_spent called!\n"); abort(); } /* Generated stub for onion_decode */ struct onion_payload *onion_decode(const tal_t *ctx UNNEEDED, + bool blinding_support UNNEEDED, const struct route_step *rs UNNEEDED, const struct pubkey *blinding UNNEEDED, const u64 *accepted_extra_tlvs UNNEEDED, + struct amount_msat amount_in UNNEEDED, + u32 cltv_expiry UNNEEDED, u64 *failtlvtype UNNEEDED, size_t *failtlvpos UNNEEDED) { fprintf(stderr, "onion_decode called!\n"); abort(); }