common/onion: blinded payment support.

We make it look like a normal payment for the caller.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2022-10-17 11:14:38 +10:30
parent c94c742e58
commit 077ec99788
8 changed files with 242 additions and 16 deletions

View File

@@ -2,6 +2,7 @@
#include <assert.h>
#include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h>
#include <common/blindedpath.h>
#include <common/ecdh.h>
#include <common/onion.h>
#include <common/sphinx.h>
@@ -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;

View File

@@ -5,6 +5,7 @@
#include <common/amount.h>
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 */

View File

@@ -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(); }

View File

@@ -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)

View File

@@ -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 \

View File

@@ -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"

View File

@@ -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;

View File

@@ -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(); }