mirror of
https://github.com/aljazceru/lightning.git
synced 2026-02-06 06:34:22 +01:00
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:
194
common/onion.c
194
common/onion.c
@@ -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;
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
Reference in New Issue
Block a user